本文共 13502 字,大约阅读时间需要 45 分钟。
由于不同的项目使用不同的词语描述各种概念,所以这里有一个小小的术语表来帮助消除歧义。
默认情况下,Arrow格式是低位编址的(将低序字节存储在起始地址)。模式元数据有一个指明RecordBatches的字节顺序的字段。通常这是生成RecordBatch的系统的字节顺序。主要用例是在具有相同字节码的系统之间交换RecordBatches。首先,当尝试读取与底层系统不匹配的字节顺序的模式时,将会返回错误。参考实现集中在地位编址,并为此提供测试。最终我们可以通过字节交换来提供自动转换。
如上所述,所有缓冲区都旨在以64字节边界为准对齐内存,并且填充到64字节倍数的长度。对齐要求遵循优化内存访问的最佳做法:
任何数组具有已知且固定长度,存储为32位有符号整数,因此最多可以存储(2^31 - 1)个元素。我们选择一个有符号的int32有一下2个原因:
空值槽的数量是物理数组的属性,并被认为是数据结构的一部分。空值计数存储为32位有符号整数,因为它可能与数组长度一样大。
任何相对类型都可以有空值槽,不管是原始类型还是嵌套类型。
具有空值的数组必须具有连续的内存缓冲区,称为空(或有效)位图,其长度为64字节的倍数(如上所述),并且足够大,以至于每个数组槽至少有1位。 任何数组槽是否有效(非空)是在该位图的各个位中编码的。索引(设置位)j值为1表示该值不为空,而0(位未设置)表示该值为空。位图被初始化为在分配时间全部未设置(这包括填充)。is_valid[j] -> bitmap[j / 8] & (1 << (j % 8)) 我们使用最低有效位(LSB)编号(也称为位编址bit-endianness)。这意味着在一组8个位中,我们从右到左读:values = [0, 1, null, 2, null, 3]
bitmapj mod 8 7 6 5 4 3 2 1 0 0 0 1 0 1 0 1 1
具有0空值计数的数组可以选择不分配空值位图。实现为了方便可能会选择始终分配一个空值位图,但是在内存被共享时应该注意。
嵌套类型数组具有自己的空值位图和空值计数,而不管其子数组的空值和空位。基本类型值数组表示固定长度的数组,每个值都具有通常用字节测量的相同的物理槽宽度,尽管规范还提供了位打包类型(例如以位编码的布尔值)。
在内部,数组包含一个连续的内存缓冲区,其总大小等于槽宽乘以数组长度。对于打包类型,大小将舍入到最接近的字节。 关联的空值位图被连续分配(如上所述),但不需要在内存中与值缓冲器相邻。例如int32的原始数组:
[1,2,null,4,8]
会像:
* Length: 5, Null count: 1* Null bitmap buffer: |Byte 0 (validity bitmap) \| Bytes 1-63 | |-------------------------|-----------------------| |00011011 | 0 (padding) |* Value Buffer: |Bytes 0-3 | Bytes 4-7 | Bytes 8-11| Bytes 12-15| Bytes 16-19 | Bytes 20-63 | |----------|-----------|-----------|-----------|-------------|-------------| | 1 | 2 | unspecified| 4 | 8 | unspecified |
[1,2,3,4,8]有两种可能的布局:
* Length: 5, Null count: 0* Null bitmap buffer: | Byte 0 (validity bitmap) | Bytes 1-63 | |--------------------------|-----------------------| | 00011111 | 0 (padding) |* Value Buffer: |Bytes 0-3 | Bytes 4-7| Bytes 8-11| bytes 12-15 | bytes 16-19 | Bytes 20-63 | |---------|----------|------------|-------------|-------------|-------------| | 1 | 2 | 3 | 4 | 8 | unspecified |
或者位图消失:
* Length 5, Null count: 0* Null bitmap buffer: Not required* Value Buffer: |Bytes 0-3 | Bytes 4-7 | Bytes 8-11| bytes 12-15 | bytes 16-19| Bytes 20-63 | |---------|-----------|------------|-------------|------------|-------------| | 1 | 2 | 3 | 4 | 8 | unspecified |
列表是一种嵌套类型,其中每个数组槽都包含一个可变大小的值序列,它们都具有相同的相对类型(异质性可以通过联合实现,稍后描述)。
列表类型被指定为List<T>,这里的T是任何相对类型(原始或嵌套)。 列表数组由以下组合表示:slot_position = offsets[j]slot_length = offsets[j + 1] - offsets[j] // (for 0 <= j < length)
偏移数组中的第一个值为0,最后一个元素是值数组的长度。
我们来看一个例子,List<Char>类型:其中Char是一个1字节的逻辑类型。
对于具有相应值的长度为4的数组:[['j','o','e'],null,['m','a','r','k'],[]]
将具有以下表示:
* Length: 4, Null count: 1* Null bitmap buffer: | Byte 0 (validity bitmap) | Bytes 1-63 | |--------------------------|-----------------------| | 00001101 | 0 (padding) |* Offsets buffer (int32) | Bytes 0-3 | Bytes 4-7 | Bytes 8-11 | Bytes 12-15 | Bytes 16-19 | Bytes 20-63 | |------------|-------------|-------------|-------------|-------------|-------------| | 0 | 3 | 3 | 7 | 7 | unspecified |* Values array (char array): * Length: 7, Null count: 0 * Null bitmap buffer: Not required | Bytes 0-6 | Bytes 7-63 | |------------|-------------| | joemark | unspecified |
[[[1,2],[3,4]],[[5,6,7],null,[8]],[[9,10]]]
将被表示如下:
* Length 3* Nulls count: 0* Null bitmap buffer: Not required* Offsets buffer (int32) | Bytes 0-3 | Bytes 4-7 | Bytes 8-11 | Bytes 12-15 | Bytes 16-63 | |------------|------------|------------|-------------|-------------| | 0 | 2 | 5 | 6 | unspecified |* Values array (`List`) * Length: 6, Null count: 1 * Null bitmap buffer: | Byte 0 (validity bitmap) | Bytes 1-63 | |--------------------------|-------------| | 00110111 | 0 (padding) | * Offsets buffer (int32) | Bytes 0-27 | Bytes 28-63 | |----------------------|-------------| | 0, 2, 4, 7, 7, 8, 10 | unspecified | * Values array (bytes): * Length: 10, Null count: 0 * Null bitmap buffer: Not required | Bytes 0-9 | Bytes 10-63 | |-------------------------------|-------------| | 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 | unspecified |
一个struct是一个嵌套类型,它被一个有序序列的相对类型(可以都是不同的)参数化,相对类型称为它的字段。
通常,这些字段具有名称,但名称及其类型是元数据类型的一部分,而不是物理内存布局。 一个struct数组没有为它的值分配任何额外的物理存储。如果结构体数组有一个或多个空值,则它必须具有分配的空值位图。 在物理上,一个struct类型中每个字段都有一个子数组。例如,struct(这里显示为字符串的字段名称用于说明)Struct < name: String (= List), age: Int32>
有两个子数组,一个列表 数组(如上所示)和一个具有Int32逻辑类型的4字节的基本类型数组。
[{'joe',1},{null,2},null,{'mark',4}]的布局将是:
* Length: 4, Null count: 1* Null bitmap buffer: |Byte 0 (validity bitmap) | Bytes 1-63 | |-------------------------|-----------------------| | 00001011 | 0 (padding) |* Children arrays: * field-0 array (`List`): * Length: 4, Null count: 2 * Null bitmap buffer: | Byte 0 (validity bitmap) | Bytes 1-63 | |--------------------------|-----------------------| | 00001001 | 0 (padding) | * Offsets buffer: | Bytes 0-19 | |----------------| | 0, 3, 3, 3, 7 | * Values array: * Length: 7, Null count: 0 * Null bitmap buffer: Not required * Value buffer: | Bytes 0-6 | |----------------| | joemark | * field-1 array (int32 array): * Length: 4, Null count: 1 * Null bitmap buffer: | Byte 0 (validity bitmap) | Bytes 1-63 | |--------------------------|-----------------------| | 00001011 | 0 (padding) | * Value Buffer: |Bytes 0-3 | Bytes 4-7 | Bytes 8-11 | Bytes 12-15 | Bytes 16-63 | |------------|-------------|-------------|-------------|-------------| | 1 | 2 | unspecified | 4 | unspecified |
虽然结构体没有为每个语义槽(即每个与C语言样结构体相似的标量)提供物理存储,但是可以通过空值位图将整个结构化槽设置为空。任何子字段数组可以根据各自的独立空值位图拥有空值。这意味着对于特定的结构体槽,结构体数组的空值位图可能表示一个空槽,当其一个或多个子数组在其相应的槽中具有非空值时。读取结构体数组时,父空值位图是权威的。这在上面的示例中说明,子数组具有空值结构体的有效实体,但是由父数组的空值位图“隐藏”。但是,独立处理时,子数组的对应值将不为空。
密集的联合在语义上类似于一个结构体,并且包含相对类型的有序序列。当一个结构体包含多个数组时,一个联合语义上是一个单个数组,其中每个槽可以有一个不同的类型。
联合类型可以被命名,但是像结构体一样,这将是元数据的问题,并且不会影响物理内存布局。 我们定义了针对不同用例优化的两种不同的联合类型。首先,密集联合,表示每个值为5字节开销的混合型数组。其物理布局如下:每个相对类型一个子数组逻辑联合的示例布局: Union<f: float, i: int32>具有以下值:[{f = 1.2},null,{f = 3.4},{i = 5}]
* Length: 4, Null count: 1* Null bitmap buffer: |Byte 0 (validity bitmap) | Bytes 1-63 | |-------------------------|-----------------------| |00001101 | 0 (padding) |* Types buffer: |Byte 0 | Byte 1 | Byte 2 | Byte 3 | Bytes 4-63 | |---------|-------------|----------|----------|-------------| | 0 | unspecified | 0 | 1 | unspecified |
//存的是Union中的索引 f索引为0, i索引为1
* Offset buffer: |Byte 0-3 | Byte 4-7 | Byte 8-11 | Byte 12-15 | Bytes 16-63 | |---------|-------------|-----------|------------|-------------| | 0 | unspecified | 1 | 0 | unspecified |* Children arrays: * Field-0 array (f: float): * Length: 2, nulls: 0 * Null bitmap buffer: Not required * Value Buffer: | Bytes 0-7 | Bytes 8-63 | |-----------|-------------| | 1.2, 3.4 | unspecified | * Field-1 array (i: int32): * Length: 1, nulls: 0 * Null bitmap buffer: Not required * Value Buffer: | Bytes 0-3 | Bytes 4-63 | |-----------|-------------| | 5 | unspecified |
稀疏联合与密集联合具有相同的结构,省略了偏移数组。在这种情况下,子数组的长度与union的长度相等。
虽然与密集联合相比,稀疏联合可能使用明显更多的空间,但在某些确定的用例中可能拥有一些优点:对于联合数组:
[{u0 = 5},{u1 = 1.2},{u2 ='joe'},{u1 = 3.4},{u0 = 4},{u2 ='mark'}]将具有以下布局:* Length: 6, Null count: 0* Null bitmap buffer: Not required* Types buffer: | Byte 0 | Byte 1 | Byte 2 | Byte 3 | Byte 4 | Byte 5 | Bytes 6-63 | |------------|-------------|-------------|-------------|-------------|--------------|-----------------------| | 0 | 1 | 2 | 1 | 0 | 2 | unspecified (padding) |* Children arrays: * u0 (Int32): * Length: 6, Null count: 4 * Null bitmap buffer: |Byte 0 (validity bitmap) | Bytes 1-63 | |-------------------------|-----------------------| |00010001 | 0 (padding) | * Value buffer: |Bytes 0-3 | Bytes 4-7 | Bytes 8-11 | Bytes 12-15 | Bytes 16-19 | Bytes 20-23 | Bytes 24-63 | |------------|-------------|-------------|-------------|-------------|--------------|-----------------------| | 5 | unspecified | unspecified | unspecified | 4 | unspecified | unspecified (padding) | * u1 (float): * Length: 6, Null count: 4 * Null bitmap buffer: |Byte 0 (validity bitmap) | Bytes 1-63 | |-------------------------|-----------------------| | 00001010 | 0 (padding) | * Value buffer: |Bytes 0-3 | Bytes 4-7 | Bytes 8-11 | Bytes 12-15 | Bytes 16-19 | Bytes 20-23 | Bytes 24-63 | |-------------|-------------|-------------|-------------|-------------|--------------|-----------------------| | unspecified | 1.2 | unspecified | 3.4 | unspecified | unspecified | unspecified (padding) | * u2 (`List`) * Length: 6, Null count: 4 * Null bitmap buffer: | Byte 0 (validity bitmap) | Bytes 1-63 | |--------------------------|-----------------------| | 00100100 | 0 (padding) | * Offsets buffer (int32) | Bytes 0-3 | Bytes 4-7 | Bytes 8-11 | Bytes 12-15 | Bytes 16-19 | Bytes 20-23 | Bytes 24-27 | Bytes 28-63 | |------------|-------------|-------------|-------------|-------------|-------------|-------------|-------------| | 0 | 0 | 0 | 3 | 3 | 3 | 7 | unspecified | * Values array (char array): * Length: 7, Null count: 0 * Null bitmap buffer: Not required | Bytes 0-7 | Bytes 8-63 | |------------|-----------------------| | joemark | unspecified (padding) |
请注意,稀疏联合中的嵌套类型必须在内部一致(例如,见图中的列表),即任何子数组上任何索引j的随机访问都不会导致错误。换句话说,嵌套类型的数组如果被重新解释为非嵌套数组,则必须是有效的。
与结构类似,特定的子数组可能具有非空槽,即使父联合数组的空值位图表示槽为空。此外,即使类型数组指示槽在索引处包含不同类型,子数组也可能具有非空槽。当字段被字典编码时,这些值由表示字典中值的索引的Int32数组表示。字典被收录为DictionaryBatch,它的id由字段表中的元数据(Message.fbs)中定义的字典属性引用。字典具有与字段类型相同的布局。字典中的每个实体都可以通过其DictionaryBatch中的索引来访问。当Schema引用Dictionary id时,它必须在任何RecordBatch之前为此id发送DictionaryBatch。
例如,您可以获得以下数据:type: List[ ['a', 'b'], ['a', 'b'], ['a', 'b'], ['c', 'd', 'e'], ['c', 'd', 'e'], ['c', 'd', 'e'], ['c', 'd', 'e'], ['a', 'b']]
在字典编码的形式中,这可能显示为:
data List(dictionary-encoded, dictionary id i)indices: [0, 0, 0, 1, 1, 1, 0]//['a','b']为字典值,索引为0;['c', 'd', 'e']为字典值,索引为2dictionary itype: List [ ['a', 'b'], ['c', 'd', 'e'],]
转载于:https://blog.51cto.com/1196740/2160722