3.6 Node 数据存储
neo4j 中, Node 的存储是由 NodeStore 和 ArrayPropertyStore 2中类型配合来完成的. node 的label 内容是存在ArrayPropertyStore这样的DynamicStore 中,如果长度超过一个block ,则分block存储,并将其在ArrayPropertyStore中的第1个block 的 block_id 保存到 NodeStore类型文件相应record 的labels字段中。
下面是neo4j graph db 中,Node数据存储对应的文件:
neostore.nodestore.db
neostore.nodestore.db.id
neostore.nodestore.db.labels
neostore.nodestore.db.labels.id
ArrayPropertyStore的存储格式见< 3.3.2 DynamicStore 类型>,下面介绍一下 NodeStore 的文件存储格式。
3.6.1 NodeStore的主文件存储格式
NodeStore的主文件是neostore.nodestore.db, 其文件存储格式示意图如下,整个文件是有一个 RECORD_SIZE=15Bytes 的定长数组和一个字符串描述符“NodeStore v0.A.2”(文件类型描述TYPE_DESCRIPTOR和 neo4j 的 ALL_STORES_VERSION) 构成。访问时,可以通过 node_id 作为数组的下标进行访问。
1
2
3
4
5
|
< div >
// in_use(byte)+next_rel_id(int)+next_prop_id(int)+labels(5)+extra(byte) public static final int RECORD_SIZE = 15;
|
下面介绍一下 node record 中每个字段的含义:
- n inUse(1 Byte):第1字节,共分成3部分
// [pppp,rrrx]
// [ , x] in use bit
// [ ,rrr ] higher bits for rel id
// [pppp, ] higher bits for prop id
long inUseByte = buffer.get();
- 第1 bit 表示 record 是否在 use;
- 第2~4 bit 表示 node 的第1个 relationship_id 的 高3位;
- 第 5~8 bit表示 node 的第1个property_id 的 高4位
- next_rel_id(4 Bytes) : 第2~5字节是node 的第1个 relationship_id 的 低32位. 加上inUse 字节的第 2~4 bit作为高3位,构成一个完整的35位relationship_id。
- next_prop_id(4 Bytes) : 第6~9字节是node 的第1个 property_id 的 低32位. 加上inUse 字节的第 5~8 bit作为高4位,构成一个完整的36 位 property_id。
- labels(5 Bytes) : 第10~14字节是node 的label field。
- extra(1 Byte) : 第15字节是 extra , 目前只用到第 1 bit ,表示该node 是否 dense, 缺省的配置是 该 node 的 relationshiop 的数量超过 50 个,这表示是 dense.
3.3.2 NodeStore.java
neo4j 中与neostore.nodestore.db文件相对应的类是NodeStore,负责NodeRecord在neostore.nodestore.db文件中的读写。
下面看一下 NodeStore.java 中 getRecord 成员函数,可以帮助理解 Node Record 的存储格式。
1
|
<br />< div ><br /><br /> private NodeRecord getRecord( long id, PersistenceWindow window,<br /><br />RecordLoad load )<br /><br />{<br /><br />Buffer buffer = window.getOffsettedBuffer( id );<br /><br /> // [ , x] in use bit<br /><br />// [ ,xxx ] higher bits for rel id<br /><br />// [xxxx, ] higher bits for prop id<br /><br />long inUseByte = buffer.get();<br /><br />boolean inUse = (inUseByte & 0x1) == Record.IN_USE.intValue();<br /><br />if ( !inUse )<br /><br />{<br /><br />switch ( load )<br /><br />{<br /><br />case NORMAL:<br /><br />throw new InvalidRecordException( "NodeRecord[" + id + "] not in use" );<br /><br />case CHECK:<br /><br />return null;<br /><br />case FORCE:<br /><br />break;<br /><br />}<br /><br />}<br /><br />long nextRel = buffer.getUnsignedInt();<br /><br />long nextProp = buffer.getUnsignedInt();<br /><br />long relModifier = (inUseByte & 0xEL) << 31;<br /><br />long propModifier = (inUseByte & 0xF0L) << 28;<br /><br />long lsbLabels = buffer.getUnsignedInt();<br /><br />long hsbLabels = buffer.get() & 0xFF; // so that a negative byte won't fill the "extended" bits with ones.<br /><br />long labels = lsbLabels | (hsbLabels << 32);<br /><br />byte extra = buffer.get();<br /><br />boolean dense = (extra & 0x1) > 0;<br /><br />NodeRecord nodeRecord = new NodeRecord( id, dense, longFromIntAndMod( nextRel, relModifier ),<br /><br />longFromIntAndMod( nextProp, propModifier ) );<br /><br />nodeRecord.setInUse( inUse );<br /><br />nodeRecord.setLabelField( labels, Collections.<DynamicRecord>emptyList() );<br /><br />return nodeRecord;<br /><br />}<br /><br />
|