Spark 4.0的VariantType 类型以及内部存储

背景

本文基于Spark 4.0

总结

Spark中的 VariantType 类型,用尽量少的字节来存储Json的格式化数据

分析

这里主要介绍 Variant 的存储,我们从VariantBuilder.buildJson方法(把对应的json数据存储为VariantType类型)开始:

public static Variant parseJson(JsonParser parser, boolean allowDuplicateKeys)
      throws IOException {
    VariantBuilder builder = new VariantBuilder(allowDuplicateKeys);
    builder.buildJson(parser);
    return builder.result();
  }

这个方法会调用buildJson这个方法:

JsonToken token = parser.currentToken();
    if (token == null) {
      throw new JsonParseException(parser, "Unexpected null token");
    }
    switch (token) {
      case START_OBJECT: {
        ArrayList<FieldEntry> fields = new ArrayList<>();
        int start = writePos;
        while (parser.nextToken() != JsonToken.END_OBJECT) {
          String key = parser.currentName();
          parser.nextToken();
          int id = addKey(key);
          fields.add(new FieldEntry(key, id, writePos - start));
          buildJson(parser);
        }
        finishWritingObject(start, fields);
        break;
      }
      case START_ARRAY: {
        ArrayList<Integer> offsets = new ArrayList<>();
        int start = writePos;
        while (parser.nextToken() != JsonToken.END_ARRAY) {
          offsets.add(writePos - start);
          buildJson(parser);
        }
        finishWritingArray(start, offsets);
        break;
      }
      case VALUE_STRING:
        appendString(parser.getText());
        break;
      case VALUE_NUMBER_INT:
        try {
          appendLong(parser.getLongValue());
        } catch (InputCoercionException ignored) {
          // If the value doesn't fit any integer type, parse it as decimal or floating instead.
          parseFloatingPoint(parser);
        }
        break;
      case VALUE_NUMBER_FLOAT:
        parseFloatingPoint(parser);
        break;
      case VALUE_TRUE:
        appendBoolean(true);
        break;
      case VALUE_FALSE:
        appendBoolean(false);
        break;
      case VALUE_NULL:
        appendNull();
        break;
      default:
        throw new JsonParseException(parser, "Unexpected token " + token);
    }

可以看到这里用到了JsonParser 来精细控制json的解析

  • 对于String类型VALUE_STRING

    • 如果String长度超过了63字节,则按照LONG_STR类型存储(其中1个字节用来存储类型,4个字节按照小端模式存储表示String的长度,后续再加上该String的内容),
      这里的1个字节存储类型分为前6位和后2位,前6位表示type info,后两位表示basic type,对应到这里就是 (byte)((16 << 2)|0)
      其中 16表示LONG_STR,0表示PRIMITIVE基本类型.图例如下:
      在这里插入图片描述

    • 如果String长度没有超过63字节,则按照SHORT_STR类型存储(其中1个字节用来存储数据类型,后续再加上string的内容)
      这里的1个字节存储的前6位存储该String的长度,后两位为1,这里的1表示basic type,也就是SHORT_STR类型,图例如下:
      在这里插入图片描述

  • 对于数字类型VALUE_NUMBER_INT
    根据整数的范围,来确定是存储的大小

    • 如果整数范围在 byte 范围内,则按照 INT1 类型存储(其中1个字节用来存储类型,1个字节来表示实际的整数数值),
      1个字节的存储类型为 (byte)((3 << 2)|0),其中 3表示INT1,0表示PRIMITIVE基本类型

    • 如果整数范围在 short 范围内,则按照 INT2 类型存储(其中1个字节用来存储类型,2个字节来表示实际的整数数值),
      1个字节的存储类型为 (byte)((4 << 2)|0),其中 4表示INT2,0表示PRIMITIVE基本类型
      2个字节的存储类型也是按照小端存储的方式存储

    • 如果整数范围在 int 范围内,则按照 INT4 类型存储(其中1个字节用来存储类型,4个字节来表示实际的整数数值),
      1个字节的存储类型为 (byte)((5 << 2)|0),其中 5表示INT4,0表示PRIMITIVE基本类型
      4个字节的存储类型也是按照小端存储的方式存储

    • 其他的,则按照 INT8 类型存储(其中1个字节用来存储类型,8个字节来表示实际的整数数值),
      1个字节的存储类型为 (byte)((6 << 2)|0),其中 6表示INT4,0表示PRIMITIVE基本类型
      8个字节的存储类型也是按照小端存储的方式存储

  • 对于浮点类型VALUE_NUMBER_FLOAT
    这种会先试着解析为decimal类型

    • 如果是decimal类型且precisionscale都小于38,则进一步处理

      • 如果是recision 和 scale都小于9,则按照DECIMAL4类型存储(其中1个字节用来存储类型,1个字节来存储小数点位数,4个字节来表示实际的int整数数值)
        1个字节的存储类型为 (byte)((8 << 2)|0),其中 8表示DECIMAL4,0表示PRIMITIVE基本类型,布局如下:
        在这里插入图片描述

      • 如果是recision 和 scale都小于18,则按照DECIMAL8类型存储(其中1个字节用来存储类型,1个字节来存储小数点位数,8个字节来表示实际的long整数数值)
        1个字节的存储类型为 (byte)((9 << 2)|0),其中 9表示DECIMAL8,0表示PRIMITIVE基本类型,布局和上面的一样

      • 如果是recision 和 scale都小于38,则按照DECIMAL16类型存储(其中1个字节用来存储类型,1个字节来存储小数点位数,字节数组(小端存储)来表示实际的long整数数值)
        1个字节的存储类型为 (byte)((10 << 2)|0),其中 10表示DECIMAL8,0表示PRIMITIVE基本类型,
        布局和上面的一样,只不过用字节数组来存储对应的decimal的值了,且会加上的的符号位以补全为16个字节,也就是说,这里面的数据数值加上符号,一共占据16字节

    • 否则,则按照IEEE DOUBLE类型存储(其中1个字节用来存储类型,8个字节用来存储实际的数据)
      1个字节的存储类型为 (byte)((7 << 2)|0),其中 7表示DOUBLE,0表示PRIMITIVE基本类型,具体的数值则调用Double.doubleToLongBit方法,且以小端的方式存储在
      字节数组中

  • 对于VALUE_TRUE类型,直接按照TRUE类型存储(其中1个字节用来存储类型,不额外存储内容)
    1个字节的存储类型为 (byte)((1 << 2)|0),其中 1表示TRUE,0表示PRIMITIVE基本类型

  • 对于VALUE_TRUE类型,直接按照FALSE类型存储(其中1个字节用来存储类型,不额外存储内容)
    1个字节的存储类型为 (byte)((2 << 2)|0),其中 2表示FALSE,0表示PRIMITIVE基本类型

  • 对于VALUE_NULL类型,直接按照NULL类型存储(其中1个字节用来存储类型,不额外存储内容)
    1个字节的存储类型为 (byte)((0 << 2)|0),其中 0表示NULL,0表示PRIMITIVE基本类型

  • 对于START_OBJECT类型, 也就是json对象
    这个会遍历所有的key和value,根据不同的类型,按照上面的方式进行处理, 之后会在所有的字节数组前面插入一个对象属性
    其中包括 header byte + object size + id list + offset list
    这里的 header 用1个字节表明了这是对象OBJECT类型,其中这1个字节(1个字节由以下构成:2位表明是否大对象,
    2位存储表明key个数的字节大小,2位表明存储offset偏移大小的字节大小,2个字节表示OBJECT类型(用10表示))
    布局如下 :
    在这里插入图片描述

    object size 根据具体该json数组所占用的字节大小来,如果太大就用4字节,否则用1个字节表示
    id list 根据对象中key的个数来确定,
    占据的大小做如下判断:如果小于0xFF,则是1,如果小于0xFFFF,则是2,如果小于0xFFFFFF,则是3,否额是4
    offset list 根据对象中所有的value所占用的字节数来确定,和id list中的逻辑一样
    最后插入一个总的该json数组value所占用的总的字节数,占用的字节数和offset list的一样
    再最后就是紧跟具体的object真实数据

  • 对于START_ARRAY类型,也就是Array对象
    这个和json对象的处理一样,header byte, object size, and offset list
    只不过 header byte ,这1个字节组成(4位表明是否大对象,2位表明存储offset偏移大小的字节大小,2个字节表示Array类型(用11表示))
    在这里插入图片描述

    object size 根据具体该数组所占用的字节大小来,如果太大就用4字节,否则用1个字节表示
    offset list 根据对象中所有的value所占用的字节数来确定,和object size中的逻辑一样
    最后插入一个总的该json数组value所占用的总的字节数,占用的字节数和offset list的一样
    再最后就是紧跟具体的array真实数据

最后还会调用builder.result这个方法,这里主要构造meta元数据(在json中我们并没有存储key的值,这里会进行存储)
这里会定义byte类型的数组 metadata
第一部分存放着header,占用1个字节(2字节表示存储key的offset的字节数,6个字节表示version信息,当前值为1),如下所示:
在这里插入图片描述

第二部分存放着该json涉及的key的个数,
占据的大小做如下判断:如果小于0xFF,则是1,如果小于0xFFFF,则是2,如果小于0xFFFFFF,则是3,否则是4
第三部分涉及具体的keyoffset 的连续存储
第四部分就是总的key的大小
第五部分就是具体的key值的存储

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值