背景
本文基于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类型且precision和scale都小于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值的存储

2564

被折叠的 条评论
为什么被折叠?



