由于不同的项目使用不同的词语描述各种概念,所以这里有一个小小的术语表来帮助消除歧义。
默认情况下,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]
bitmap
j 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
列表数组由以下组合表示:
slot_position = offsets[j]
slot_length = offsets[j + 1] - offsets[j] // (for 0 <= j < length)
偏移数组中的第一个值为0,最后一个元素是值数组的长度。
我们来看一个例子,List
对于具有相应值的长度为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
* 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']为字典值,索引为2
dictionary i
type: List
[
['a', 'b'],
['c', 'd', 'e'],
]
转载于:https://blog.51cto.com/1196740/2160722