一个退役中校教你如何用go语言写一个基于B+树的json数据库(进阶篇)之BsTr tree(二)指针混写的实现1

3. BsTr tree指针混写的实现

内存块和文件页指针混写的实现依赖两个方面:

一是树节点内存块从文件读入的机制; 二是BsTr结构的文件读写功能。

  • (1)树节点内存块从文件读入的机制。有两种方法实现这个机制,一种是通过一个map保存offset与指针的映射关系来实现,另一种是通过判断节点指针的关系来实现。本文这两个方法都需要通过驻留协程来实现顺序执行,确保安全。

    • ❶ 第一种方法的代码如下(以int64类型的key建立的树为例):_groutine_i64keyCsWrFunc是一个驻留协程,用于处理节点如何获取,顺序执行。
  • 驻留协程
func _groutine_i64keyCsWrFunc() {
	b := true
	for b {
		select {
		case d, ok := <-__global_i64keyCsWr_chan__:
			if ok {
				switch d.wr {
				case P:
					if d.cs != nil && d.spr != nil {
                        //获取父节点,先查找是否存在映射关系,否则从磁盘读取
						p, selfi := d.cs.i64keyParent(d.spr, d.f)
                        //返回父节点以及最大key在父节点的索引
						__global_i64keyCsWr_Pre_chan__ <- &keyPre{p, selfi}
						continue
					}
					__global_i64keyCsWr_Pre_chan__ <- nil
				case L:
					if d.cs != nil && d.pa != nil && d.spr != nil {
                        //获取左兄弟节点,先查找是否存在映射关系,否则从磁盘读取
						__global_i64keyCsWr_LRIre_chan__ <- d.cs.i64keyLeft(d.pa, d.spr, d.f)
						continue
					}
					__global_i64keyCsWr_LRIre_chan__ <- nil
				case R:
					if d.cs != nil && d.pa != nil && d.spr != nil {                                                
                        //获取左兄弟节点,先查找是否存在映射关系,否则从磁盘读取
						__global_i64keyCsWr_LRIre_chan__ <- d.cs.i64keyRight(d.pa, d.spr, d.f)
						continue
					}
					__global_i64keyCsWr_LRIre_chan__ <- nil
				case I:
                    // 由于chunk比key多了父、左右兄弟,因此需要判断ci是否大于3
					if d.cs != nil && d.ci >= CHUNKINDEXIGNORE {                                                
                        //获取第ci个孩子节点,先查找是否存在映射关系,否则从磁盘读取
						__global_i64keyCsWr_LRIre_chan__ <- d.cs.cI(d.ci, d.spr, d.f)
						continue
					}
					__global_i64keyCsWr_LRIre_chan__ <- nil
				}
			}
		case <-__global_stop_i64keyCsWrFunc__:
			b = false
		}
	}
}
  • 获取父节点:
func (cs *BsTr) i64keyParent(spr *SpireReturn, f *os.File) (*BsTr, int) {
    // 首先直接通过指针获取父节点
	pa := cs.chunk[PARENT]
	flag := spr.flag
	if pa == nil { //如果父节点当前位空指针,则通过父节点页在文件的offset来获取
		po := cs.offs[PARENT]
		if po != -1 {
            // 如果父节点的offset不为-1,则先查找映射,映射的key为"offset@文件名"的字符串格式
			pointer_offset_map_parentid := strconv.FormatInt(po, 36) + "@" + f.Name()
			if v, ok := __global_pointer_offset_map__.Load(pointer_offset_map_parentid); ok {
                //如果存在映射关系,则获取父节点,并设置到当前节点的父节点位置上
				pa = v.(*BsTr)
				cs.chunk[PARENT] = pa
			}
		}
        // 如果在映射和内存中都没找到父节点,则从磁盘读入
		if pa == nil && po != -1 && f != nil {
			pa = cs.getPage(flag, po, f)
		}
	}
	iinpa := -1
	if pa != nil {
        // 父节点成功获取后,再获取当前节点最大key在父节点的位置。
		if flag&UNIQUE == UNIQUE {
			iinpa = i64keyChildIndexInParentWith(cs, pa, i64keyKvIsEmpty)
		} else {
			iinpa = i64keyChildIndexInParentNotUniqueWith(cs, pa, i64keyKvIsEmpty)
		}
        // 如果位置为chunk切片的长度,则表明是当前节点也是父节点的最大子节点,位置进行-1操作
		if iinpa == len(pa.chunk) {
			iinpa--
		}
        // 如果索引位置是有效的,则将父节点的相应孩子节点设置为当前节点
		if iinpa >= CHUNKINDEXIGNORE {
			pa.chunk[iinpa] = cs
		}
        // 如果父节点不是根节点,则父节点的读取时间戳更新为当前时间戳,以纳秒计
		if !pa.isSpire() {
			pa.ui64s[2] = uint64(time.Now().Nanosecond())
		}
	}
	return pa, iinpa
}
  • 获取左右兄弟节点:
func (cs *BsTr) i64keyRight(pa *BsTr, spr *SpireReturn, f *os.File) *BsTr {
	// 首先直接从chunk切片获取右兄弟节点
        right := cs.chunk[RIGHT]
	if right == nil { //如果chunk切片中的RIGHT索引元素为nil
		rio := cs.offs[RIGHT]  //则获取offset
		if rio != -1 { //如果offset不为-1,即存入过文件,则从映射获取右兄弟节点
			pointer_offset_map_rightid := strconv.FormatInt(rio, 36) + "@" + f.Name()
			if v, ok := __global_pointer_offset_map__.Load(pointer_offset_map_rightid); ok {
				right = v.(*BsTr)
			}
		}
		if right == nil { //如果chunk切片和全局映射中都不存在右兄弟节点的信息,则从文件页读入
			if rio != -1 && f != nil {
                // 从文件页读入
				right = cs.getPage(spr.flag, rio, f)
				if right == nil || right.IsEmptyFast() { //如果读入出错,则再次读取
					right = cs.getPage(spr.flag, rio, f) //如果再次读入出错,则panic
					panic("Error page.")
				}
			}
		}
		if right != nil {
            // 如果成功获取右兄弟节点,则设置好关系
			right.chunk[LEFT] = cs //将右兄弟节点的左兄弟节点设置为当前节点
			cs.chunk[RIGHT] = right //将当前节点的右兄弟节点设置为right
			var ni int
			right.chunk[PARENT], ni = right.i64keyParent(spr, f) //获取右兄弟节点在其父节点的位置
			right.chunk[PARENT].chunk[ni] = right //将右兄弟节点在其父节点设置相应孩子节点的位置上。
		}
	}
	if right != nil { //更新右兄弟节点的时间戳
		right.ui64s[2] = uint64(time.Now().Nanosecond())
	}
	return right
}
func (cs *BsTr) i64keyLeft(pa *BsTr, spr *SpireReturn, f *os.File) *BsTr {
	left := cs.chunk[LEFT]
	//when cs.offs[LEFT] == -1, certainly in Mem
	if left == nil {
		leo := cs.offs[LEFT]
		if leo != -1 {
			pointer_offset_map_leftid := strconv.FormatInt(leo, 36) + "@" + f.Name()
			if v, ok := __global_pointer_offset_map__.Load(pointer_offset_map_leftid); ok {
				left = v.(*BsTr)
			}
		}
		if left == nil {
			if leo != -1 && f != nil {
				left = cs.getPage(spr.flag, leo, f)
				if left == nil || left.IsEmptyFast() {
					left = cs.getPage(spr.flag, leo, f)
					panic("Error page.")
				}
			}
		}
		if left != nil {
			left.chunk[RIGHT] = cs
			cs.chunk[LEFT] = left
			var ni int
			left.chunk[PARENT], ni = left.i64keyParent(spr, f)
			left.chunk[PARENT].chunk[ni] = left
		}
	}
	if left != nil {
		left.ui64s[2] = uint64(time.Now().Nanosecond())
	}
	return left
}
  • 获取孩子节点:
// get the chunk i in mem
func (cs *BsTr) cI(i int, spr *SpireReturn, f *os.File) *BsTr {
	if i > len(cs.chunk) {
		return nil
	}
    //直接从chunk切片获取第i个孩子节点
	ci := cs.chunk[i]
	if ci == nil {
		if cs.offs[i] != -1 {
            //根据第i个孩子节点在文件中的offset从全局映射获取第i个孩子节点
			pointer_offset_map_id := strconv.FormatInt(cs.offs[i], 36) + "@" + f.Name()
			if v, ok := __global_pointer_offset_map__.Load(pointer_offset_map_id); ok {
				ci = v.(*BsTr)
				if ci.offs[PARENT] != cs.offset {
					pointer_offset_map_parentid := strconv.FormatInt(cs.offs[PARENT], 36) + "@" + f.Name()
                    //获取第i个孩子节点的旧父节点,可能发生了更新
					if v, ok = __global_pointer_offset_map__.Load(pointer_offset_map_parentid); ok {
						cp := v.(*BsTr)
                    // 如果旧的父节点被删除了,说明父节点发生了变化,则将其父节点设置为当前节点
						if cp.CheckDELETEFlag() {
							ci.offs[PARENT] = cs.offset
						}
					}
					ci.offs[PARENT] = cs.offset
				}
                //设置好父子节点关系
				ci.chunk[PARENT] = cs
				cs.chunk[i] = ci
			}
		}
		if ci == nil {
            //获取失败,则从磁盘读取第i个孩子节点
			ci = cs.heavyI(spr.flag, i, f)
		}
	}
	if ci != nil {
		ci.ui64s[2] = uint64(time.Now().Nanosecond())
	}
	return ci
}

// parent get chunk i
// set parent left right
// only heavyI read child from file, and only set parent/child relation
// only getwrleft/getwrright read left/right from file
func (cs *BsTr) heavyI(flag uint32, i int, f *os.File) *BsTr {
	switch i {
	case 0, 1, 2: //parent/left/right not return
		return nil
	default:
		if i > len(cs.chunk)-1 {
			return nil
		}
		if cs.chunk[i] == nil {
			if cs.offs[i] != -1 && f != nil {
				cs.chunk[i] = cs.getPage(flag, cs.offs[i], f)
			}
		}
		cci := cs.chunk[i]
		if cci != nil && !cci.isSpire() {
			cci.chunk[PARENT] = cs
		}
		return cci
	}
}
  • 从磁盘页获取节点getPage函数:
// construct a chunk from disk page
// must guarantee cs in same-spire-chain:must be SingleStart-SingleEmBededStart MultiStart-MultiEmBededStart
func (cs *BsTr) getPage(flag uint32, offset int64, f *os.File) *BsTr {
	// 从文件的offset位置读取页面
        chunk := Fcr(offset, f)
	if chunk != nil { //如果读取成功
		//initialize chunkslice
		//leaf and node: offs--chunk
		if !chunk.isLeaf() { //非叶子节点,需要分配好子chunk切片
			chunk.chunk = make([]*BsTr, len(chunk.offs))
		} else {
			if flag&FILEBIG != FILEBIG { //如果是叶子节点,并且不是FILEBIG模式,需要分配好子chunk切片的空间
				chunk.chunk = make([]*BsTr, len(chunk.offs))
			} else { //如果是叶子节点,并且是FILEBIG模式,需要子chunk切片只需分配父节点、左右兄弟节点的空间
				chunk.chunk = make([]*BsTr, CHUNKINDEXIGNORE)
			}
		}
		//start from spire, cs.c4wr != nil will recurrence
		if cs.c4wr != nil { //说明是多线程执行环境下
			chunk.c4wr = sync.NewCond(&sync.Mutex{})
		}
        // 将offset与第i个节点指针加入映射
		pointer_offset_map_id := strconv.FormatInt(offset, 36) + "@" + f.Name()
		__global_pointer_offset_map__.Store(pointer_offset_map_id, chunk)
	}
	return chunk
}

// use channel, not use mutex
// ikf.go
// channel file
// Fcr gets a Bstr from offset by a channel request
func Fcr(offset int64, f *os.File) *BsTr {
	file_w := new(file_wr_insight)
	file_w.offset = offset
	file_w.wr = RR //读且返回数据
	file_w.f = f
    //向文件读写驻留协程发送指令数据
	__global_file_chan__ <- file_w
	d, ok := <-__global_readf_rechan__
	if ok && d.e == nil && d.wr == RRR {
		return d.c0
	} else {
		return nil
	}
}

// guarantee one file operation per request
// all file operations do in sequnce here
func _groutine_fFunc() { //文件顺序读写驻留协程
	bs := true
	for bs {
		select {
		case d, ok := <-__global_file_chan__:
			if ok {
				switch d.wr {
                //省略了其他代码
				case RR:  //从offset读入页面
					d.c0, _, d.e = GetFromFileOff(d.offset, d.f)
					d.wr = RRR
					__global_readf_rechan__ <- d
                    // 省略了其他代码
			}
		case <-__global_stop_f__:
			bs = false
			close(__global_stop_f_re__)
		}
	}
}

// GetFromFileOff gets a Bstr struct from offset
// 真正的BsTr结构读取函数
func GetFromFileOff(offset int64, f *os.File) (*BsTr, int64, error) {
        // 设置文件指针到offset
	if _, e := f.Seek(offset, io.SeekStart); e != nil {
		return nil, -1, e
	}
    // 从缓冲区获取读缓冲
	r := __global_read_pool__.Get().(*bufio.Reader)
    // 将reader重设为文件指针
	r.Reset(f)
    // 使用bufio读入页面
	cs, sz, e := GetFromBufio(r)
    // 将缓冲放回缓冲池
	__global_read_pool__.Put(r)
	return cs, sz, e
}

你可能感兴趣的:(数据库)