B+树的序列化与反序列化,用json格式文件保存节点数据

引言

在前面的文章《B+树插入操作的图形化展示,python调用graphviz自动生成svg图形》中用图形化的方式展示了B+树在插入时图形的变化。

本文尝试将B+树数据保存到硬盘,并且可以根据需要随时加载到内存。前面做的图形化工作,正好可以用来直观地验证从文件加载B+树是否成功。

网上文献《关于 B+tree (附 python 模拟代码)》的作者用python实现了B+树的基本操作。但是他的输出结果是文本形式的,不方便直观地查看B+树的动态变化效果。本文在原作者python代码的基础上做了一些改进,实现了图形化展示的效果,添加了序列化为json格式文件的功能。对原作者的分享精神,再次表示感谢。

具体实现

下面是实现的效果图
在这里插入图片描述
这是序列化后的根节点#8号节点的内容

{"c": [4, 11, 7], "i": [17, 33], "M": 4, "par": 0, "bro": -1, "id": 8}

可以看出,json数据与图中数据是完全对应的,#8节点的id为8,孩子列表为[4, 11, 7],索引列表为[17, 33]

这是序列化后内部#4号节点的内容

{"c": [2, 10], "i": [9], "M": 4, "par": 8, "bro": -1, "id": 4}

这是序列化后叶子节点 #2号节点的内容

{"bro": 10, "par": 4, "id": 2, "L": 4, "v": ["2,e", "4,m", "6,c"]}

序列化中的关键技术点

1 .选用json格式保持节点数据,因为json格式容易理解,并且方便数据交换,而pickle格式只能在python中进行数据交换。

2.节点指针的序列化,为每个节点赋一个唯一性的整数,做为其id,在序列化节点指针时实际保存的是被指向的节点的id
下面是节点序列化的具体转换函数,节点分为内部节点与叶子节点

#内部节点转换为字典类型
def interNode2Dict(n):
    return {
        'M':n.M,
        'c':[i.id for i in n.childList],
        'i':[i for i in n.indexList],
        'bro':-1, #内部节点不需要brother指针,用-1表明这是一个内部节点
        'par':n._par.id if n._par else 0,
        'id':n.id
    }

#叶子节点转换为字典类型
def leafNode2Dict(n):
    return {
        'L':n.L ,
        'v':[str(i) for i in n.valueList],
        'bro':n._bro.id if n._bro else 0,
        'par':n._par.id if n._par else 0,
        'id':n.id
    }

在上述代码中,留意这一句

'c':[i.id for i in n.childList],

这就实现了将孩子对象指针转换成了孩子对象的id。
要注意这一个转换是有特别的考虑的,如果不做这个转换,而直接使用对象引用(指针),json在做序列化时会循着这个引用(指针)把整个对象链上的其它的对象也一并序列化。这样一来,要么是序列化出一堆不想要的东西;要么是发生循环引用;要么是发现某些对象不能json化。用整数id来代替指针就把这些问题都化解了。

3.为了保证节点可以json序列化,用json自带的常规类型的序列化功能不不够,还要自定义特别的转换类

class NodeEncoder(json.JSONEncoder):
    def default(self, n):
        if isinstance(n, Bptree_InterNode):
            return interNode2Dict(n)
        if isinstance(n, Bptree_Leaf):
            return leafNode2Dict(n)
        return json.JSONEncoder.default(self, n)

在做序列化时的调用格式如下:

json.dumps(n,cls=NodeEncoder)

与常规对象的序列化相比,多给了一个参数 cls=NodeEncoder,而这里的NodeEncoder就是刚才定义的转换类

4.每个节点保存为一个单独的json格式的文件,文件名的命令格式为 node+id+".json"
比如上文提到的根节点#8号节点的内容就保存为文件:node8.json。这样做的好处时,只要给出节点的id,就可以方便地找出节点被序列化后的内容。

5.在反序列化时实现按需加载,在B+树反序列化时,其实一开始只反序列化了根节点,只有当实际访问某个子节点时,子节点才被加载到内存。实现原理如以下代码所示

if type(self._childList[i])==type(0): #需要加载
            self._childList[i] = loadNode(self._childList[i])
        return self._childList[i]

刚从json串反序列化后,_childList[i]中存放的其实是一个整数id值,当实际访问这个孩子对象时,才由loadNode函数进行加载。这是一种典型的懒加载策略。loadNode函数的核心代码如下:

    if str(id) in nodePool :
        return nodePool[str(id)]
    rawNode=None
    with open('nodes/node'+str(id)+'.json', 'r') as f:
        rawNode = json.load(f)
    #debug(rawNode)
    if rawNode :        
        if rawNode['bro'] < 0 :#加载内部节点
            n=Bptree_InterNode(rawNode['M'],rawNode['id'])           
            n._bro=-1            
            n._par= rawNode['par']
            n.childList= ChildList(rawNode['c'])
            #debug(n.childList._childList,'childList._childList')
            n.indexList= rawNode['i']
            nodePool[str(id)]=n
        else:#加载叶子节点
            n=Bptree_Leaf(rawNode['L'],rawNode['id'])           
            n._bro= rawNode['bro']
            n._par= rawNode['par']
            n.valueList= [loadKV(s) for s in rawNode['v']]
            nodePool[str(id)]=n
        return n

这里用到了一个节点池,如果节点已经加载过了,就不会重复加载,如果是第一次加载,就要从json文件中读取。

6.为了实现按需加载,不用普通的列表来保存孩子节点的指针,而用是特定化的类对象,看一下这个类对象的代码

class ChildList(object):
    def __init__(self,listVal=[]): 
        self._childList = listVal

    def __getitem__(self,i):          
        if type(self._childList[i])==type(0): #需要加载
            self._childList[i] = loadNode(self._childList[i])
        return self._childList[i]
       
    def __setitem__(self,i,value): 
        self._childList[i] = value

    def index(self,value): 
        return self._childList.index(value)

    def insert(self,i,value): 
        return self._childList.insert(i,value)

特别留意一下其中的方法:getitem(self,i)

    def __getitem__(self,i):          
        if type(self._childList[i])==type(0): #需要加载
            self._childList[i] = loadNode(self._childList[i])
        return self._childList[i]   

实现了这个方法之后,这个类的对象就可以象数组一样进行访问,比如n.childList是ChildList类的一个对象,就可以这样来访问
n.childList[0]。这样看起来childList仿佛还是一个普通的列表。但与普通列表不同的是,在它的方法体中有这样一句

        if type(self._childList[i])==type(0): #需要加载
            self._childList[i] = loadNode(self._childList[i])         

当发现内部列表(数组)的一项存放的是整数,调用了加载函数从json文件加载对象。而这一点,是普通的列表对象做不到的。所以在这里要做特别的处理。

7 其它节点对象的指针(引用)属性,采用@property修饰的方法,也是为了在访问时可以按需加载,比如节点的父指针。另外还使用了@par.setter,这样可以在父指针被修改时做一些额外的处理,比如标记节点“脏”了。

    @property
    def par(self):
        if type(self._par)==type(0): #需要加载
            self._par = loadNode(self._par)
        return self._par

    @par.setter
    def par(self,value):
        #可在此处做一些额外的处理
        self._par=value

8 用一个固定的文件node1.json存放B+树根节点的id。因为在B+树的动态变化时,其根节点很可能会因为分裂或合并而发生变化,但不管如何变化,node1.json中总是存放实际的B+树根节点的id。这样在反序列化时总有一个固定的入口点node1.js

完整的python代码

BplusTree.py

# -*- coding:utf-8 -*- 
# B+树的序列化
# 原作者:thursdayhawk http://wiki.jikexueyuan.com/project/python-actual-combat/tutorial-11.html
# 修改成用graphviz图形化显示,添加了序列化为json格式文件的功能,修改者:littleZhuHui

import os
import json
from random import randint,choice
from bisect import bisect_right,bisect_left
from collections import deque

#显示信息的级别,控制不同等级的信息的可见性
infoLevel = 3

# id值为1的节点是 superRoot,是所有操作的起点,
#因此固定为1,而0表示找不到有效的节点
idSeed = 1

#存放已加载节点的hash表,由节点id映射到节点对象
nodePool ={}

#生成一个全局ID                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                          
def  getGID():
    global idSeed
    idSeed+=1
    return idSeed
节
#------------------ node 定义开始 ----------------------------------------------

class InitError(Exception):
    pass

class LoadNodeError(Exception):
    pass

class ParaError(Exception):
    pass

# 定义键值对
class KeyValue(object):
    __slots__=('key','value')
    def __init__(self,key,value):
        self.key=key
        self.value=value
    def __str__(self):
        return '%s,%s'%(self.key,self.value)
    def __cmp__(self,key):
        if self.key>key:
            return 1
        elif self.key==key:
            return 0
        else:
            return -1

# 定义可按下标进行控制访问的childList类
# 不直接使用列表而用自定义对象表示childList ,主要原因有两点
# 按需加载,孩子节点序列化时存放的节点Id值,当实际访问时加载为对象
# 访问控制,当修改了孩子节点指针的值之后,可以有机会做更多的处理(可以实现事件钩子功能)
class ChildList(object):
    def __init__(self,listVal=[]): 
        self._childList = listVal

    def __getitem__(self,i):          
        if type(self._childList[i])==type(0): #需要加载
            self._childList[i] = loadNode(self._childList[i])
        return self._childList[i]
       
    def __setitem__(self,i,value): 
        self._childList[i] = value

    def index(self,value): 
        return self._childList.index(value)

    def insert(self,i,value): 
        return self._childList.insert(i,value)
       
# 内部节点
class Bptree_InterNode(object):

    def __init__(self,M,id=-1):
        if not isinstance(M,int):
            raise InitError,'M must be int'
        if M<=3:
            raise InitError,'M must be greater then 3'
        else:
            self.M=M
            self.childList=ChildList()
            self.indexList=[]
            self._bro=-1
            self._par=None
            #每个节点有一个唯一的整数值做为id,方便用graphviz绘图
            if id < 0:
                self.id = getGID()
            else:
                self.id = id

    def isleaf(self):
        return False

    def isfull(self):
        return len(self.indexList)>=self.M-1

    def isempty(self):
        return len(self.indexList)<=(self.M+1)/2-1

    @property
    def bro(self):
        if type(self._bro)==type(0): #需要加载
            self._bro = loadNode(self._bro)
        return self._bro

    @bro.setter
    def bro(self,value):
        #可在此处做一些额外的处理
        self._bro=value

    @property
    def par(self):
        if type(self._par)==type(0): #需要加载
            self._par = loadNode(self._par)
        return self._par

    @par.setter
    def par(self,value):
        #可在此处做一些额外的处理
        self._par=value

#叶子节点       
class Bptree_Leaf(object):

    def __init__(self,L,id=-1):
        if not isinstance(L,int):
            raise InitError,'L must be int'
        else:
            self.L=L
            self.childList='<br>\nleaf has no child list<br>\n'
            self.valueList=[]
            self._bro=0
            self._par=None
            #每个节点有一个唯一的整数值做为id,方便用graphviz绘图
            if id < 0:
                self.id = getGID()
            else:
                self.id = id

    def isleaf(self):
        return True

    def isfull(self):
        return len(self.valueList)>self.L

    def isempty(self):
        return len(self.valueList)<=(self.L+1)/2

    @property
    def bro(self):
        if type(self._bro)==type(0): #需要加载
            self._bro = loadNode(self._bro)
        return self._bro

    @bro.setter
    def bro(self,value):
        #可在此处做一些额外的处理
        self._bro=value

    @property
    def par(self):
        if type(self._par)==type(0): #需要加载
            self._par = loadNode(self._par)
        return self._par

    @par.setter
    def par(self,value):
        #可在此处做一些额外的处理
        self._par=value

#------------------ node 定义结束 ----------------------------------------------

#------------------    B+ 树 定义开始   ----------------------------------------

#B+树类        
class Bptree(object):
    def __init__(self,M,L):
        if L>M:
            raise InitError,'L must be less or equal then M'
        else:
            self.M=M
            self.L=L
            self.__root=Bptree_Leaf(L)
            self.__leaf=self.__root

    #在树上查找
    def search(self,mi=None,ma=None):
        result=[]
        node=self.__root
        leaf=self.__leaf
        if mi is None and ma is None:
            raise ParaError,'you need to setup searching range'
        elif mi is not None and ma is not None and mi>ma:
            raise ParaError,'upper bound must be greater or equal than lower bound'
        def search_key(n,k):
            if n.isleaf():
                p=bisect_left(n.valueList,k)
                return (p,n)
            else:
                p=bisect_right(n.indexList,k)
                return search_key(n.childList[p],k)
        if mi is None:
            while True:
                for kv in leaf.valueList:
                    if kv<=ma:
                        result.append(kv)
                    else:
                        return result
                if leaf.bro==None:
                    return result
                else:
                    leaf=leaf.bro
        elif ma is None:
            index,leaf=search_key(node,mi)
            result.extend(leaf.valueList[index:])
            while True:
                if leaf.bro==None:
                    return result
                else:
                    leaf=leaf.bro
                    result.extend(leaf.valueList)
        else:
            if mi==ma:
                i,l=search_key(node,mi)
                try:
                    if l.valueList[i]==mi:
                        result.append(l.valueList[i])
                        return result
                    else:
                        return result
                except IndexError:
                    return result
            else:
                i1,l1=search_key(node,mi)
                i2,l2=search_key(node,ma)
                if l1 is l2:
                    if i1==i2:
                        return result
                    else:
                        result.extend(l.valueList[i1:i2])
                        return result
                else:
                    result.extend(l1.valueList[i1:])
                    l=l1
                    while True:
                        if l.bro==l2:
                            result.extend(l2.valueList[:i2+1])
                            return result
                        else:
                            result.extend(l.bro.valueList)
                            l=l.bro
    #遍历B+树的所有叶子节点
    def traversal(self):
        result=[]
        l=self.__leaf
        while True:
            result.extend(l.valueList)
            if l.bro==None:
                return result
            else:
                l=l.bro
    
    #显示B+树
    def show(self):

        def dotShow(tree):        
            q=deque()
            h=0
            q.append([self.__root,h])

            #生成childList对应的dot格式的文本串
            def childListDotStr(n):
                dotStr ='{'
                if n.childList==[]:
                    return '{}'
                else:
                    for i,k in enumerate(n.indexList):
                        dotStr +='<f%s>#%s|'%(n.childList[i].id,n.childList[i].id)
                    #childList比indexList多一个,要处理一下最右孩子指针               
                    dotStr +='<f%s>#%s}'%(n.childList[-1].id,n.childList[-1].id)
                return dotStr

            #生成childList对应的dot格式的文本串
            def childListEdgeStr(n):
                dotStr =''
                if n.childList==[]:
                    return ''
                else:
                    for i,k in enumerate(n.indexList):
                        dotStr +='node%s:f%s:s--node%s:e:n;\n'% (n.id,n.childList[i].id,n.childList[i].id)
                    #childList比indexList多一个,要处理一下最右孩子指针               
                    dotStr +='node%s:f%s:s--node%s:e:n;\n'% (n.id,n.childList[-1].id,n.childList[-1].id)
                return dotStr

            while True:
                try:
                    node,height=q.popleft()     
                except IndexError:
                    return
                else:
                    if not node.isleaf(): #内部节点
                        #print node.indexList,'the height is',height
                        nodeText = str([k for k in node.indexList])
                        tree.dotStr += 'node%s [label = "{<e> #%s|%s| %s}" ];\n'% (node.id,node.id,nodeText,childListDotStr(node))
                        tree.dotStr += childListEdgeStr(node)
                        if height==h:
                            h+=1
                        q.extend([[n,h] for n in node.childList])
                    else: #叶节点
                        #print [v.key for v in node.valueList],'the leaf is,',height
                        nodeText = str([k.key for k in node.valueList])
                        tree.dotStr += 'node%s [label = "{<e> #%s|%s}" ];\n'% (node.id,node.id,nodeText)
        self.dotStr=''
        dotShow(self)
        print(self.svgStr())

    #导出B+树
    def dump(self):
        def doDump():        
            q=deque()
            h=0
            q.append([self.__root,h])  
            while True:
                try:
                    node,height=q.popleft()                    
                    dumpNode(node)
                except IndexError:
                    return
                else:
                    if not node.isleaf(): #内部节点                        
                        if height==h:
                            h+=1
                        q.extend([[n,h] for n in node.childList])
                    else: #叶节点
                        pass
        doDump()
        #保存根节点的id信息到node1.json
        with open('nodes/node1.json', 'w') as f:
            f.write(json.dumps({'id':self.__root.id}))

    #加载根节点,而子节点是在真实访问时,发现如果存放的是整数id值,才真正加载进来(按需加载)
    def loadRoot(self,nodeId):
        self.__root = loadNode(nodeId)
        self.__leaf=self.__root
        #debug(self.__root,'__root')

    #加载B+树,假定根节点信息总是存话在node1.json文件中
    def load(self):
        rawNode=None
        with open('nodes/node1.json', 'r') as f:
            rawNode = json.load(f)
        #debug(rawNode)
        if rawNode :        
            nodeId = rawNode['id']
            self.loadRoot(nodeId)
        else:
            raise LoadNodeError,'can not load node1.json '


    # 生成svg图形对应的文本串
    def svgStr(self):
        dotHeader ='''
        graph G
{       
        rankdir = TB;
        node [shape=record];
        '''
        dotStr = dotHeader + self.dotStr +'}'
        dotFile =open('BplusTree.dot','w')
        dotFile.write(dotStr)
        dotFile.close()
        #调用dot命令生成svg文件
        os.system('dot -Tsvg BplusTree.dot -o BplusTree.html')
        #取出svg图形文件的内容
        svgFile =open('BplusTree.html','r')
        svgStr = svgFile.read()
        svgFile.close()        
        return svgStr
    

    #插入操作
    def insert(self,key_value):
        
        def split_node(n1):

            mid=self.M/2 #分裂点为度数的中点
            newnode=Bptree_InterNode(self.M)
            #新节点的数据是原节点的后半部分
            newnode.indexList=n1.indexList[mid:]
            newnode.childList=ChildList(n1.childList[mid:])
            newnode.par=n1.par  

            for c in newnode.childList:                
                c.par=newnode

            if n1.par is None: #如果当前节点是根节点,则创建一个新的根节点
                newroot=Bptree_InterNode(self.M)
                tipInfo(' #%s 号内部节点分裂,键 %s 将复制(上升)到新的根节点 #%s 中'%(n1.id,n1.indexList[mid-1],newroot.id))
                newroot.indexList=[n1.indexList[mid-1]]
                newroot.childList=ChildList([n1,newnode])
                n1.par=newnode.par=newroot
                self.__root=newroot
            else: #如果当前节点不是根节点
                tipInfo(' #%s 号内部节点分裂,键 %s 将复制(上升)到父节点 #%s 中'%(n1.id,n1.indexList[mid-1],n1.par.id))
                i=n1.par.childList.index(n1)
                n1.par.indexList.insert(i,n1.indexList[mid-1])
                n1.par.childList.insert(i+1,newnode)
            n1.indexList=n1.indexList[:mid-1]
            n1.childList=ChildList(n1.childList[:mid])

            return n1.par

        #叶子节点分裂
        def split_leaf(n2):
            mid=(self.L+1)/2 #分裂点为叶子节点度数+1的中点
            newleaf=Bptree_Leaf(self.L)
            newleaf.valueList=n2.valueList[mid:]
            if n2.par==None: #如果当前节点是既是叶子节点又是根节点,则创建一个新的内部节点
                newroot=Bptree_InterNode(self.M)                
                tipInfo(' #%s 号叶子节点分裂,键 %s 将复制(上升)到新的根节点 #%s 中'%(n2.id,n2.valueList[mid].key,newroot.id))
                newroot.indexList=[n2.valueList[mid].key]
                newroot.childList=ChildList([n2,newleaf])
                n2.par=newroot
                newleaf.par=newroot
                self.__root=newroot
            else:
                tipInfo(' #%s 号叶子节点分裂,键 %s 将复制(上升)到父节点 #%s 中'%(n2.id,n2.valueList[mid].key,n2.par.id))
                i=n2.par.childList.index(n2)
                n2.par.indexList.insert(i,n2.valueList[mid].key)
                n2.par.childList.insert(i+1,newleaf)
                newleaf.par=n2.par
            n2.valueList=n2.valueList[:mid]
            n2.bro=newleaf

        #插入节点
        def insert_node(n):
            tipInfo('对 #%s 号节点进行检查 '%(n.id))

            if not n.isleaf():
                tipInfo(' #%s 号节点是内部节点 '%(n.id))
                if n.isfull():
                    tipInfo(' #%s 号节点已满,分裂后再做插入操作 '%(n.id))
                    insert_node(split_node(n))
                else:                    
                    p=bisect_right(n.indexList,key_value)
                    #tipInfo(' 插入位置:%s '%p)
                    pp = 0 if p == 0  else p - 1
                    if p > 0:
                        tipInfo(' #%s 号节点未满,找到稍小于 %s 的键值 %s ,在 %s 的右孩子 #%s 号节点上执行插入操作'%(n.id,key_value.key,n.indexList[pp],n.indexList[pp],n.childList[p].id))
                    else:
                        tipInfo(' #%s 号节点未满,只能找到比 %s 稍大的键值 %s ,在 %s 的左孩子 #%s 号节点上执行插入操作'%(n.id,key_value.key,n.indexList[pp],n.indexList[pp],n.childList[p].id))
                    insert_node(n.childList[p])
            else:
                tipInfo(' #%s 号节点是叶子节点, 实际插入键值与卫星数据 '%(n.id))
                p=bisect_right(n.valueList,key_value)
                n.valueList.insert(p,key_value)
                if n.isfull():
                    tipInfo(' #%s 号叶子节点已满, 分裂该节点 '%(n.id))
                    split_leaf(n)
                else:
                    return

        insert_node(self.__root)    

#------------------    B+ 树 定义结束   ----------------------------------------

#------------------ 序列化相关函数 开始 ----------------------------------------

#加载kv
def loadKV(str):
    #debug(str,'loadKV')
    t=str.split(',')
    k=int(t[0])
    v=t[1]
    return KeyValue(k,v)

#加载节点
def loadNode(id):
    global nodePool
    if str(id) in nodePool :
        return nodePool[str(id)]
    rawNode=None
    with open('nodes/node'+str(id)+'.json', 'r') as f:
        rawNode = json.load(f)
    #debug(rawNode)
    if rawNode :        
        if rawNode['bro'] < 0 :#加载内部节点
            n=Bptree_InterNode(rawNode['M'],rawNode['id'])           
            n._bro=-1            
            n._par= rawNode['par']
            n.childList= ChildList(rawNode['c'])
            #debug(n.childList._childList,'childList._childList')
            n.indexList= rawNode['i']
            nodePool[str(id)]=n
        else:#加载叶子节点
            n=Bptree_Leaf(rawNode['L'],rawNode['id'])           
            n._bro= rawNode['bro']
            n._par= rawNode['par']
            n.valueList= [loadKV(s) for s in rawNode['v']]
            nodePool[str(id)]=n
        return n
    else:
        raise LoadNodeError,'can not load node :%s'%id
           
#导出节点
def dumpNode(n):
    with open('nodes/node'+str(n.id)+'.json', 'w') as f:
        f.write(json.dumps(n,cls=NodeEncoder))

#节点编码为可以json化的类型时需要用到的转换类    
class NodeEncoder(json.JSONEncoder):
    def default(self, n):
        if isinstance(n, Bptree_InterNode):
            return interNode2Dict(n)
        if isinstance(n, Bptree_Leaf):
            return leafNode2Dict(n)
        return json.JSONEncoder.default(self, n)

#内部节点转换为字典类型
def interNode2Dict(n):
    return {
        'M':n.M,
        'c':[i.id for i in n.childList],
        'i':[i for i in n.indexList],
        'bro':-1, #内部节点不需要brother指针,用-1表明这是一个内部节点
        'par':n._par.id if n._par else 0,
        'id':n.id
    }

#叶子节点转换为字典类型
def leafNode2Dict(n):
    return {
        'L':n.L ,
        'v':[str(i) for i in n.valueList],
        'bro':n._bro.id if n._bro else 0,
        'par':n._par.id if n._par else 0,
        'id':n.id
    }

#------------------ 序列化相关函数 结束 ----------------------------------------

def tipInfo(str):
    global infoLevel
    if infoLevel and infoLevel < 2:
        println(str)

def debug(value,tip=''):
    global infoLevel
    if infoLevel and infoLevel < 1:
        print('\n<br>****** debug ****** %s *******'%tip)
        print(value)
        print('\n<br>')

def println(str):
    print('\n<br>')
    print(str)
    print('\n<br>')

#固定序列
def fixTestList():
    testlist=[]
    keyList =[10, 17, 9, 33, 33, 50, 36, 41, 31, 30, 13, 6, 37, 45, 20, 4, 35, 11, 2, 40]

    #通常情况下树高为3,下面这个序列会生成树高为4的B+树,可以分析一下为什么会这样?
    #keyList =[3, 33, 25, 30, 15, 27, 16, 35, 28, 39, 44, 2, 47, 45, 14, 42, 18, 3, 9, 18, 34, 19, 33, 46, 24, 45, 48, 20, 10, 8, 35, 3, 49, 48, 50, 9, 46, 1, 31, 6, 37, 34, 33, 37, 6, 48, 39, 24, 17] 
    for key in keyList:        
        value=choice('abcdefghijklmn')
        testlist.append(KeyValue(key,value))   
    tipInfo(str([k.key for k in testlist]))
    return testlist;

#随机序列,用50个数的随机序列生成B+树时,观察生成的图形,大部分时候树高都是3,
#但偶尔出现树高为4,是因为B+树在面对特定数据时树高会高一些吗?
def randTestList():
    testlist=[]
    for i in range(1,100):
        key=randint(1,100)
        value=choice('abcdefghijklmn')
        testlist.append(KeyValue(key,value))   
    tipInfo(str([k.key for k in testlist]))
    return testlist;

#测试插入操作                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                           
def testInsert(): 
    M=4
    L=4
    #构造一个空的B+树
    mybptree=Bptree(M,L)

    println('B+树的插入过程, 内部%s阶,叶子%s阶 '%(M,L))

    tipInfo('插入序列')
    testlist = fixTestList()  
   
    #testlist = randTestList()    

    for kv in testlist:
        tipInfo('<br>------------ 准备插入 %s : -------------------------------'%kv.key)
        mybptree.insert(kv)
        tipInfo('插入 %s 后的B+树'%kv.key)
        mybptree.show()

    #导出B+树
    mybptree.dump()

#测试加载操作                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                           
def testLoad():  
    M=4
    L=4
    # M=6
    # L=6
    #构造一个空的B+树
    mybptree=Bptree(M,L)

    println('插入B+树, 内部%s阶,叶子%s阶 '%(M,L))

    tipInfo('插入序列')
    testlist = fixTestList()  
    #testlist = randTestList()    

    for kv in testlist:       
        mybptree.insert(kv)
       
    mybptree.show()

    #导出B+树
    mybptree.dump()

    println('<br>加载B+树, 内部%s阶,叶子%s阶 '%(M,L))
    mybptree.load()

    mybptree.show()
    
if __name__=='__main__':
    #testInsert()
    testLoad()

补充说明

图形化显示环境的搭建细节,还请参看以前的文章《平衡二叉树插入及删除操作的图形化展示,python调用graphviz自动生成svg图形
对svg图形做美化处理的细节,还请参看以前的文章《用jquery对graphviz生成的svg图形做后处理,改变字体,颜色,连线形状等

  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
下面是一个 Java 递归序列JSON 数据的示例代码: ```java import java.io.IOException; import java.util.ArrayList; import java.util.List; import com.fasterxml.jackson.core.JsonParseException; import com.fasterxml.jackson.databind.JsonMappingException; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; public class JsonTreeDeserializer { private static ObjectMapper mapper = new ObjectMapper(); public static void main(String[] args) { String json = "{\"id\":1,\"name\":\"root\",\"children\":[{\"id\":2,\"name\":\"child1\",\"children\":[{\"id\":3,\"name\":\"grandchild1\"},{\"id\":4,\"name\":\"grandchild2\"}]},{\"id\":5,\"name\":\"child2\",\"children\":[{\"id\":6,\"name\":\"grandchild3\"},{\"id\":7,\"name\":\"grandchild4\"}]}]}"; try { JsonNode rootNode = mapper.readTree(json); Node tree = deserialize(rootNode); System.out.println(tree); } catch (JsonParseException e) { e.printStackTrace(); } catch (JsonMappingException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } } public static Node deserialize(JsonNode node) { int id = node.get("id").asInt(); String name = node.get("name").asText(); Node tree = new Node(id, name); JsonNode childrenNode = node.get("children"); if (childrenNode != null && childrenNode.isArray()) { for (JsonNode childNode : childrenNode) { Node child = deserialize(childNode); tree.addChild(child); } } return tree; } } class Node { private int id; private String name; private List<Node> children; public Node(int id, String name) { this.id = id; this.name = name; this.children = new ArrayList<Node>(); } public void addChild(Node node) { this.children.add(node); } @Override public String toString() { return "Node [id=" + id + ", name=" + name + ", children=" + children + "]"; } } ``` 这个示例代码使用了 Jackson 库来进行 JSON 序列。首先读取 JSON 数据,然后递归地序列每个节点,并构建形结构。最后输出形结构。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值