第四节 分支节点页

分支节点主要用来构建索引,方便提升查询效率。下面我们来看看boltdb的分支节点的数据是如何存储的。

1. 分支节点页中元素定义:

分支节点在存储时,一个分支节点页上会存储多个分支页元素即branchPageElement。这个信息可以记做为分支页元素元信息。元信息中定义了具体该元素的页id(pgid)、该元素所指向的页中存储的最小key的值大小、最小key的值存储的位置距离当前的元信息的偏移量pos。下面是branchPageElement的详细定义:

  1. // branchPageElement represents a node on a branch page.
  2. type branchPageElement struct {
  3. pos uint32 //该元信息和真实key之间的偏移量
  4. ksize uint32
  5. pgid pgid
  6. }
  7. // key returns a byte slice of the node key.
  8. func (n *branchPageElement) key() []byte {
  9. buf := (*[maxAllocSize]byte)(unsafe.Pointer(n))
  10. // pos~ksize
  11. return (*[maxAllocSize]byte)(unsafe.Pointer(&buf[n.pos]))[:n.ksize]
  12. }

2. 分支节点页page中获取下标为index的某一个element的信息和获取全部的elements信息

  1. // branchPageElement retrieves the branch node by index
  2. func (p *page) branchPageElement(index uint16) *branchPageElement {
  3. return &((*[0x7FFFFFF]branchPageElement)(unsafe.Pointer(&p.ptr)))[index]
  4. }
  5. // branchPageElements retrieves a list of branch nodes.
  6. func (p *page) branchPageElements() []branchPageElement {
  7. if p.count == 0 {
  8. return nil
  9. }
  10. return ((*[0x7FFFFFF]branchPageElement)(unsafe.Pointer(&p.ptr)))[:]
  11. }

下图展现的是非叶子节点存储方式。

../imgs/非叶子节点存储.png

在内存中,分支节点页和叶子节点页都是通过node来表示,只不过的区别是通过其node中的isLeaf这个字段来区分。下面和大家分析分支节点页page和内存中的node的转换关系。

下面在介绍具体的转换关系前,我们介绍一下内存中的分支节点和叶子节点是如何描述的。

  1. // node represents an in-memory, deserialized page.
  2. type node struct {
  3. bucket *Bucket
  4. isLeaf bool
  5. unbalanced bool
  6. spilled bool
  7. key []byte
  8. pgid pgid
  9. parent *node
  10. children nodes
  11. inodes inodes
  12. }

在内存中,具体的一个分支节点或者叶子节点都被抽象为一个node对象,其中是分支节点还是叶子节点主要通通过其isLeaf字段来区分。下面对分支节点和叶子节点做两点说明:

  1. 对叶子节点而言,其没有children这个信息。同时也没有key信息。isLeaf字段为true,其上存储的key、value都保存在inodes中

  2. 对于分支节点而言,其具有key信息,同时children也不一定为空。isLeaf字段为false,同时该节点上的数据保存在inode中。

为了方便大家理解,node和page的转换,下面大概介绍下inode和nodes结构。我们在下一章会详细介绍node。

  1. const (
  2. bucketLeafFlag = 0x01
  3. )
  4. type nodes []*node
  5. func (s nodes) Len() int { return len(s) }
  6. func (s nodes) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
  7. func (s nodes) Less(i, j int) bool { return bytes.Compare(s[i].inodes[0].key, s[j].inodes[0].key) == -1 }
  8. // inode represents an internal node inside of a node.
  9. // It can be used to point to elements in a page or point
  10. // to an element which hasn't been added to a page yet.
  11. type inode struct {
  12. // 表示是否是子桶叶子节点还是普通叶子节点。如果flags值为1表示子桶叶子节点,否则为普通叶子节点
  13. flags uint32
  14. // 当inode为分支元素时,pgid才有值,为叶子元素时,则没值
  15. pgid pgid
  16. key []byte
  17. // 当inode为分支元素时,value为空,为叶子元素时,才有值
  18. value []byte
  19. }
  20. type inodes []inode

3. page->node

通过分支节点页来构建node节点

  1. // 根据page来初始化node
  2. // read initializes the node from a page.
  3. func (n *node) read(p *page) {
  4. n.pgid = p.id
  5. n.isLeaf = ((p.flags & leafPageFlag) != 0)
  6. n.inodes = make(inodes, int(p.count))
  7. for i := 0; i < int(p.count); i++ {
  8. inode := &n.inodes[i]
  9. if n.isLeaf {
  10. // 获取第i个叶子节点
  11. elem := p.leafPageElement(uint16(i))
  12. inode.flags = elem.flags
  13. inode.key = elem.key()
  14. inode.value = elem.value()
  15. } else {
  16. // 树枝节点
  17. elem := p.branchPageElement(uint16(i))
  18. inode.pgid = elem.pgid
  19. inode.key = elem.key()
  20. }
  21. _assert(len(inode.key) > 0, "read: zero-length inode key")
  22. }
  23. // Save first key so we can find the node in the parent when we spill.
  24. if len(n.inodes) > 0 {
  25. // 保存第1个元素的值
  26. n.key = n.inodes[0].key
  27. _assert(len(n.key) > 0, "read: zero-length node key")
  28. } else {
  29. n.key = nil
  30. }
  31. }

4. node->page

将node中的数据写入到page中

  1. // write writes the items onto one or more pages.
  2. // 将node转为page
  3. func (n *node) write(p *page) {
  4. // Initialize page.
  5. // 判断是否是叶子节点还是非叶子节点
  6. if n.isLeaf {
  7. p.flags |= leafPageFlag
  8. } else {
  9. p.flags |= branchPageFlag
  10. }
  11. // 这儿叶子节点不可能溢出,因为溢出时,会分裂
  12. if len(n.inodes) >= 0xFFFF {
  13. panic(fmt.Sprintf("inode overflow: %d (pgid=%d)", len(n.inodes), p.id))
  14. }
  15. p.count = uint16(len(n.inodes))
  16. // Stop here if there are no items to write.
  17. if p.count == 0 {
  18. return
  19. }
  20. // Loop over each item and write it to the page.
  21. // b指向的指针为提逃过所有item头部的位置
  22. b := (*[maxAllocSize]byte)(unsafe.Pointer(&p.ptr))[n.pageElementSize()*len(n.inodes):]
  23. for i, item := range n.inodes {
  24. _assert(len(item.key) > 0, "write: zero-length inode key")
  25. // Write the page element.
  26. // 写入叶子节点数据
  27. if n.isLeaf {
  28. elem := p.leafPageElement(uint16(i))
  29. elem.pos = uint32(uintptr(unsafe.Pointer(&b[0])) - uintptr(unsafe.Pointer(elem)))
  30. elem.flags = item.flags
  31. elem.ksize = uint32(len(item.key))
  32. elem.vsize = uint32(len(item.value))
  33. } else {
  34. // 写入分支节点数据
  35. elem := p.branchPageElement(uint16(i))
  36. elem.pos = uint32(uintptr(unsafe.Pointer(&b[0])) - uintptr(unsafe.Pointer(elem)))
  37. elem.ksize = uint32(len(item.key))
  38. elem.pgid = item.pgid
  39. _assert(elem.pgid != p.id, "write: circular dependency occurred")
  40. }
  41. // If the length of key+value is larger than the max allocation size
  42. // then we need to reallocate the byte array pointer.
  43. //
  44. // See: https://github.com/boltdb/bolt/pull/335
  45. klen, vlen := len(item.key), len(item.value)
  46. if len(b) < klen+vlen {
  47. b = (*[maxAllocSize]byte)(unsafe.Pointer(&b[0]))[:]
  48. }
  49. // Write data for the element to the end of the page.
  50. copy(b[0:], item.key)
  51. b = b[klen:]
  52. copy(b[0:], item.value)
  53. b = b[vlen:]
  54. }
  55. // DEBUG ONLY: n.dump()
  56. }