内存块和文件页指针混写的实现依赖两个方面:
一是树节点内存块从文件读入的机制; 二是BsTr结构的文件读写功能。
(1)树节点内存块从文件读入的机制。有两种方法实现这个机制,一种是通过一个map保存offset与指针的映射关系来实现,另一种是通过判断节点指针的关系来实现。本文这两个方法都需要通过驻留协程来实现顺序执行,确保安全。
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
}
}
// 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
}