B+树的算法(java实现)

2020-12-13 15:51

阅读:303

标签:数据   entry   优势   static   加载   父节点   try   除了   插入   

  • 定义

  一颗m阶B+树满足以下几个条件:

  1.除根节点外的节点的关键字个数最大为m-1,最小为m/2

  2.除叶节点外的每个节点的孩子节点的数目为该节点关键字个数加一,这些孩子节点的的关键字的范围与父节点关键字的大小对应(这个看图才看的清楚)

  3.叶子节点存放着所有的关键字,叶子节点间按关键字的大小用指针相互连接。内部节点以叶子节点的关键字的最小值作为索引

  • B+树的优势

  B+树相较于B树最大的优势在于数据全部都存在于叶子节点,叶子节点间以指针相互连接,这样在进行按照索引的范围查找的时候就只需要遍历前后指针就可以完成,而B树要一个一个索引去进行查找,效率差别很大。

  B+树相较于hash的优势在于B+树不用一次将数据全部加载到内存,而是先确定要查询索引的地址,将对应的地址的索引加载到内存。而hash需要将全部的数据一次性加载到内存才能完成查找。

  • B+树代码实现

  首先定义一个节点类

 1 import java.util.List;
 2 
 3 /*节点类*/
 4 public class Node {
 5 
 6     //节点的子节点
 7     private List nodes;
 8     //节点的键值对
 9     private List keyAndValue;
10     //节点的后节点
11     private Node nextNode;
12     //节点的前节点
13     private Node previousNode;
14     //节点的父节点
15     private Node parantNode;
16 
17     public Node( List nodes, List keyAndValue, Node nextNode,Node previousNode, Node parantNode) {
18         this.nodes = nodes;
19         this.keyAndValue = keyAndValue;
20         this.nextNode = nextNode;
21         this.parantNode = parantNode;
22         this.previousNode = previousNode;
23     }
24 
25      boolean isLeaf() {
26         return nodes==null;
27     }
28 
29      boolean isHead() {
30         return previousNode == null;
31     }
32 
33      boolean isTail() {
34         return nextNode == null;
35     }
36 
37      boolean isRoot() {
38         return parantNode == null;
39     }
40 
41 
42      List getNodes() {
43         return nodes;
44     }
45 
46      void setNodes(List nodes) {
47         this.nodes = nodes;
48     }
49 
50 
51     List getKeyAndValue() {
52         return keyAndValue;
53     }
54 
55 //    public void setKeyAndValue(List KeyAndValue) {
56 //        this.keyAndValue = KeyAndValue;
57 //    }
58 
59     Node getNextNode() {
60         return nextNode;
61     }
62 
63      void setNextNode(Node nextNode) {
64         this.nextNode = nextNode;
65     }
66 
67      Node getParantNode() {
68         return parantNode;
69     }
70 
71      void setParantNode(Node parantNode) {
72         this.parantNode = parantNode;
73     }
74 
75      Node getPreviousNode() {
76         return previousNode;
77     }
78 
79      void setPreviousNode(Node previousNode) {
80         this.previousNode = previousNode;
81     }
82 }

   定义一个存储关键字和数据的类

 1 public class KeyAndValue implements Comparable{
 2     /*存储索引关键字*/
 3     private int key;
 4     /*存储数据*/
 5     private Object value;
 6 
 7     @Override
 8     public int compareTo(KeyAndValue o) {
 9         //根据key的值升序排列
10         return this.key - o.key;
11     }
12 
13     public int getKey() {
14         return key;
15     }
16 
17     public void setKey(int key) {
18         this.key = key;
19     }
20 
21     public Object getValue() {
22         return value;
23     }
24 
25     public void setValue(Object value) {
26         this.value = value;
27     }
28 
29      KeyAndValue(int key, Object value) {
30         this.key = key;
31         this.value = value;
32     }
33 }

   最后是具体的实现代码

  1 import java.util.*;
  2 
  3 
  4 public class Btree {
  5     private static final String NODE = "NODE";
  6     static final String INT = "INT";
  7     private static final String PRENODE = "PRENODE";
  8     private static final String NEXTNODE = "NEXTNODE";
  9     //B+树的阶数
 10     private int rank;
 11     //根节点
 12     private Node root;
 13     //头结点
 14     private Node head;
 15 
 16     Btree(int rank) {
 17         this.rank = rank;
 18     }
 19 
 20     public Node getRoot() {
 21         return root;
 22     }
 23 
 24     public void insert(KeyAndValue entry) {
 25         List keyAndValues1 = new ArrayList();
 26         //插入第一个节点
 27         if (head == null) {
 28             keyAndValues1.add(entry);
 29             head = new Node(null, keyAndValues1, null, null, null);
 30             root = new Node(null, keyAndValues1, null, null, null);
 31         } else {
 32             Node node = head;
 33             //遍历链表,找到插入键值对对应的节点
 34             while (node != null) {
 35                 List keyAndValues = node.getKeyAndValue();
 36                 int exitFlag = 0;
 37                 //如果插入的键的值和当前节点键值对集合中的某个键的值相等,则直接替换value
 38                 for (KeyAndValue KV : keyAndValues) {
 39                     if (KV.getKey() == entry.getKey()) {
 40                         KV.setValue(entry.getValue());
 41                         exitFlag = 1;
 42                         break;
 43                     }
 44                 }
 45                 //如果插入的键已经有了,则退出循环
 46                 if (exitFlag == 1) {
 47                     break;
 48                 }
 49                 //如果当前节点是最后一个节点或者要插入的键值对的键的值小于下一个节点的键的最小值,则直接插入当前节点
 50                 if (node.getNextNode() == null || node.getNextNode().getKeyAndValue().get(0).getKey() >= entry.getKey()) {
 51                     splidNode(node, entry);
 52                     break;
 53                 }
 54                 //移动指针
 55                 node = node.getNextNode();
 56             }
 57         }
 58     }
 59 
 60 
 61     //判断是否需要拆分节点
 62     private void splidNode(Node node, KeyAndValue addkeyAndValue) {
 63         List keyAndValues = node.getKeyAndValue();
 64 
 65         if (keyAndValues.size() == rank - 1) {
 66             //先插入待添加的节点
 67             keyAndValues.add(addkeyAndValue);
 68             Collections.sort(keyAndValues);
 69             //取出当前节点的键值对集合
 70             //取出原来的key-value集合中间位置的下标
 71             int mid = keyAndValues.size() / 2;
 72             //取出原来的key-value集合中间位置的键
 73             int midKey = keyAndValues.get(mid).getKey();
 74             //构造一个新的键值对,不是叶子节点的节点不存储value的信息
 75             KeyAndValue midKeyAndValue = new KeyAndValue(midKey, "");
 76             //将中间位置左边的键值对封装成集合对象
 77             List leftKeyAndValues = new ArrayList();
 78             for (int i = 0; i ) {
 79                 leftKeyAndValues.add(keyAndValues.get(i));
 80             }
 81             //将中间位置右边边的键值对封装成集合对象
 82             List rightKeyAndValues = new ArrayList();
 83             //如果是叶子节点则在原节点中保留上移的key-value,否则原节点删除上移的key-value
 84             int k;
 85             if (node.isLeaf()) {
 86                 k = mid;
 87             } else {
 88                 k = mid + 1;
 89             }
 90             for (int i = k; i ) {
 91                 rightKeyAndValues.add(keyAndValues.get(i));
 92             }
 93             //对左右两边的元素重排序
 94             Collections.sort(leftKeyAndValues);
 95             Collections.sort(rightKeyAndValues);
 96             //以mid为界限将当前节点分列成两个节点,维护前指针和后指针
 97             Node rightNode;
 98             Node leftNode;
 99 //            if (node.isLeaf()) {
100             //如果是叶子节点维护前后指针
101             rightNode = new Node(null, rightKeyAndValues, node.getNextNode(), null, node.getParantNode());
102             leftNode = new Node(null, leftKeyAndValues, rightNode, node.getPreviousNode(), node.getParantNode());
103             rightNode.setPreviousNode(leftNode);
104 //            } else {
105 //                //如果不是叶子不维护前后指针
106 //                rightNode = new Node(null, rightKeyAndValues, null, null, node.getParantNode());
107 //                leftNode = new Node(null, leftKeyAndValues, null, null, node.getParantNode());
108 //            }
109             //如果当前分裂的节点有孩子节点,设置分裂后节点和孩子节点的关系
110             if (node.getNodes() != null) {
111                 //取得所有地孩子节点
112                 List nodes = node.getNodes();
113                 List leftNodes = new ArrayList();
114                 List rightNodes = new ArrayList();
115                 for (Node childNode : nodes) {
116                     //取得当前孩子节点的最大键值
117                     int max = childNode.getKeyAndValue().get(childNode.getKeyAndValue().size() - 1).getKey();
118                     if (max  midKeyAndValue.getKey()) {
119                         //小于mid处的键的数是左节点的子节点
120                         leftNodes.add(childNode);
121                         childNode.setParantNode(leftNode);
122                     } else {
123                         //大于mid处的键的数是右节点的子节点
124                         rightNodes.add(childNode);
125                         childNode.setParantNode(rightNode);
126                     }
127                 }
128                 leftNode.setNodes(leftNodes);
129                 rightNode.setNodes(rightNodes);
130             }
131 
132             //当前节点的前节点
133             Node preNode = node.getPreviousNode();
134             //分裂节点后将分裂节点的前节点的后节点设置为左节点
135             if (preNode != null) {
136                 preNode.setNextNode(leftNode);
137             }
138 
139             //当前节点的后节点
140             Node nextNode = node.getNextNode();
141             //分裂节点后将分裂节点的后节点的前节点设置为右节点
142             if (nextNode != null) {
143                 nextNode.setPreviousNode(rightNode);
144             }
145 
146             //如果由头结点分裂,则分裂后左边的节点为头节点
147             if (node == head) {
148                 head = leftNode;
149             }
150 
151             //父节点的子节点
152             List childNodes = new ArrayList();
153             childNodes.add(rightNode);
154             childNodes.add(leftNode);
155             //分裂
156             //当前节点无父节点
157             if (node.getParantNode() == null) {
158                 //父节点的键值对
159                 List parentKeyAndValues = new ArrayList();
160                 parentKeyAndValues.add(midKeyAndValue);
161                 //构造父节点
162                 Node parentNode = new Node(childNodes, parentKeyAndValues, null, null, null);
163                 //将子节点与父节点关联
164                 rightNode.setParantNode(parentNode);
165                 leftNode.setParantNode(parentNode);
166                 //当前节点为根节点
167                 root = parentNode;
168             } else {
169                 Node parentNode = node.getParantNode();
170                 //将原来的孩子节点(除了被拆分的节点)和新的孩子节点(左孩子和右孩子)合并之后与父节点关联
171                 childNodes.addAll(parentNode.getNodes());
172                 //移除正在被拆分的节点
173                 childNodes.remove(node);
174                 //将子节点与父节点关联
175                 parentNode.setNodes(childNodes);
176                 rightNode.setParantNode(parentNode);
177                 leftNode.setParantNode(parentNode);
178                 if (parentNode.getParantNode() == null) {
179                     root = parentNode;
180                 }
181                 //当前节点有父节点,递归调用拆分的方法,将父节点拆分
182                 splidNode(parentNode, midKeyAndValue);
183             }
184         } else {
185             keyAndValues.add(addkeyAndValue);
186             //排序
187             Collections.sort(keyAndValues);
188         }
189     }
190 
191 
192     //打印B+树
193     void printBtree(Node root) {
194         if (root == this.root) {
195             //打印根节点内的元素
196             printNode(root);
197             System.out.println();
198         }
199         if (root == null) {
200             return;
201         }
202 
203         //打印子节点的元素
204         if (root.getNodes() != null) {
205             //找到最左边的节点
206             Node leftNode = null;
207             Node tmpNode = null;
208             List childNodes = root.getNodes();
209             for (Node node : childNodes) {
210                 if (node.getPreviousNode() == null) {
211                     leftNode = node;
212                     tmpNode = node;
213                 }
214             }
215 
216             while (leftNode != null) {
217                 //从最左边的节点向右打印
218                 printNode(leftNode);
219                 System.out.print("|");
220                 leftNode = leftNode.getNextNode();
221             }
222             System.out.println();
223             printBtree(tmpNode);
224         }
225     }
226 
227     //打印一个节点内的元素
228     private void printNode(Node node) {
229         List keyAndValues = node.getKeyAndValue();
230         for (int i = 0; i ) {
231             if (i != (keyAndValues.size() - 1)) {
232                 System.out.print(keyAndValues.get(i).getKey() + ",");
233             } else {
234                 System.out.print(keyAndValues.get(i).getKey());
235             }
236         }
237     }
238 
239     public Object search(int key, Node node, String mode) {
240 
241         //如果是叶子节点则直接取值
242         if (node.isLeaf()) {
243             List keyAndValues = node.getKeyAndValue();
244             for (KeyAndValue keyAndValue : keyAndValues) {
245                 if (keyAndValue.getKey() == key) {
246                     switch (mode) {
247                         case NODE:
248                             return node;
249                         case INT:
250                             return keyAndValue.getValue();
251                     }
252                 }
253             }
254             return null;
255         }
256 
257 
258         List nodes = node.getNodes();
259         //如果寻找的key小于节点的键的最小值
260         int minKey = node.getKeyAndValue().get(0).getKey();
261         if (key  minKey) {
262             for (Node n : nodes) {
263                 List keyAndValues = n.getKeyAndValue();
264                 //找到子节点集合中最大键小于父节点最小键节点
265                 if (keyAndValues.get(keyAndValues.size() - 1).getKey()  minKey) {
266                     return search(key, n, mode);
267                 }
268             }
269         }
270         //如果寻找的key大于节点的键的最大值
271         int maxKey = getMaxKeyInNode(node);
272         if (key >= maxKey) {
273             for (Node n : nodes) {
274                 List keyAndValues = n.getKeyAndValue();
275                 //找到子节点集合中最小键大于等于父节点最小大键节点
276                 if (keyAndValues.get(0).getKey() >= maxKey) {
277                     return search(key, n, mode);
278                 }
279             }
280         }
281 
282         //如果寻找的key在最大值和最小值之间,首先定位到最窄的区间
283         int min = getLeftBoundOfKey(node, key);
284         int max = getRightBoundOfKey(node, key);
285 
286 
287         //去所有的子节点中找键的范围在min和max之间的节点
288         for (Node n : nodes) {
289             List kvs = n.getKeyAndValue();
290             //找到子节点集合中键的范围在min和max之间的节点
291             if (kvs.get(0).getKey() >= min && kvs.get(kvs.size() - 1).getKey()  max) {
292                 return search(key, n, mode);
293             }
294         }
295         return null;
296     }
297 
298 
299     public boolean delete(int key) {
300         System.out.println("delete:" + key);
301         System.out.println();
302 
303         //首先找到要删除的key所在的节点
304         Node deleteNode = (Node) search(key, root, NODE);
305         //如果没找到则删除失败
306         if (deleteNode == null) {
307             return false;
308         }
309 
310         if (deleteNode == root) {
311             delKeyAndValue(root.getKeyAndValue(), key);
312             return true;
313         }
314 
315         if (deleteNode == head && isNeedMerge(head)) {
316             head = head.getNextNode();
317         }
318 
319         return merge(deleteNode, key);
320     }
321 
322 
323     //平衡当前节点和前节点或者后节点的数量,使两者的数量都满足条件
324     private boolean balanceNode(Node node, Node bratherNode, String nodeType) {
325         if (bratherNode == null) {
326             return false;
327         }
328         List delKeyAndValues = node.getKeyAndValue();
329         if (isMoreElement(bratherNode)) {
330             List bratherKeyAndValues = bratherNode.getKeyAndValue();
331             int bratherSize = bratherKeyAndValues.size();
332             //兄弟节点删除挪走的键值对
333             KeyAndValue keyAndValue = null;
334             KeyAndValue keyAndValue1;
335             switch (nodeType) {
336                 case PRENODE:
337                     keyAndValue = bratherKeyAndValues.remove(bratherSize - 1);
338                     keyAndValue1 = getKeyAndValueinMinAndMax(node.getParantNode(), keyAndValue.getKey(), getMinKeyInNode(node));
339                     keyAndValue1.setKey(keyAndValue.getKey());
340                     break;
341                 case NEXTNODE:
342                     keyAndValue = bratherKeyAndValues.remove(0);
343                     keyAndValue1 = getKeyAndValueinMinAndMax(node.getParantNode(), getMaxKeyInNode(node), keyAndValue.getKey());
344                     keyAndValue1.setKey(bratherKeyAndValues.get(0).getKey());
345                     break;
346             }
347             //当前节点添加从前一个节点得来的键值对
348             delKeyAndValues.add(keyAndValue);
349 
350             //对键值对重排序
351             Collections.sort(delKeyAndValues);
352             return true;
353         }
354         return false;
355     }
356 
357     public boolean merge(Node node, int key) {
358         List delKeyAndValues = node.getKeyAndValue();
359         //首先删除该key-vaule
360         delKeyAndValue(delKeyAndValues, key);
361         //如果要删除的节点的键值对的数目小于节点最大键值对数目*填充因子
362         if (isNeedMerge(node)) {
363             Boolean isBalance;
364             //如果左节点有富余的键值对,则取一个到当前节点
365             Node preNode = getPreviousNode(node);
366             isBalance = balanceNode(node, preNode, PRENODE);
367             //如果此时已经平衡,则已经删除成功
368             if (isBalance) return true;
369 
370             //如果右兄弟节点有富余的键值对,则取一个到当前节点
371             Node nextNode = getNextNode(node);
372             isBalance = balanceNode(node, nextNode, NEXTNODE);
373 
374             return isBalance || mergeNode(node, key);
375         } else {
376             return true;
377         }
378     }
379 
380     //合并节点
381     //key 待删除的key
382     private boolean mergeNode(Node node, int key) {
383         if (node.isRoot()) {
384             return false;
385         }
386         Node preNode;
387         Node nextNode;
388         Node parentNode = node.getParantNode();
389         List childNodes = parentNode.getNodes();
390         List childNodes1 = node.getNodes();
391         List parentKeyAndValue = parentNode.getKeyAndValue();
392         List keyAndValues = node.getKeyAndValue();
393 
394         if (node.isLeaf()) {
395             if (parentKeyAndValue.size() == 1 && parentNode != root) {
396                 return true;
397             }
398             preNode = getPreviousNode(node);
399             nextNode = getNextNode(node);
400             if (preNode != null) {
401                 List preKeyAndValues = preNode.getKeyAndValue();
402                 keyAndValues.addAll(preKeyAndValues);
403                 if (preNode.isHead()) {
404                     head = node;
405                     node.setPreviousNode(null);
406                 } else {
407                     preNode.getPreviousNode().setNextNode(node);
408                     node.setPreviousNode(preNode.getPreviousNode());
409                 }
410                 //将合并后节点的后节点设置为当前节点的后节点
411                 preNode.setNextNode(node.getNextNode());
412                 KeyAndValue keyAndValue = getKeyAndValueinMinAndMax(parentNode, getMinKeyInNode(preNode), key);
413                 delKeyAndValue(parentKeyAndValue, keyAndValue.getKey());
414                 if (parentKeyAndValue.isEmpty()) {
415                     root = node;
416                 } else {
417                     //删除当前节点
418                     childNodes.remove(preNode);
419                 }
420                 Collections.sort(keyAndValues);
421                 merge(parentNode, key);
422                 return true;
423             }
424 
425             if (nextNode != null) {
426                 List nextKeyAndValues = nextNode.getKeyAndValue();
427                 keyAndValues.addAll(nextKeyAndValues);
428                 if (nextNode.isTail()) {
429                     node.setPreviousNode(null);
430                 } else {
431                     nextNode.getNextNode().setPreviousNode(node);
432                     node.setNextNode(nextNode.getNextNode());
433                 }
434 
435                 KeyAndValue keyAndValue = getKeyAndValueinMinAndMax(parentNode, key, getMinKeyInNode(nextNode));
436                 delKeyAndValue(parentKeyAndValue, keyAndValue.getKey());
437                 if (parentKeyAndValue.isEmpty()) {
438                     root = node;
439                     node.setParantNode(null);
440                 } else {
441                     //删除当前节点
442                     childNodes.remove(nextNode);
443                 }
444                 Collections.sort(keyAndValues);
445                 merge(parentNode, key);
446                 return true;
447             }
448             //前节点和后节点都等于null那么是root节点
449             return false;
450         } else {
451             preNode = getPreviousNode(node);
452             nextNode = getNextNode(node);
453             if (preNode != null) {
454                 //将前一个节点和当前节点还有父节点中的相应Key-value合并
455                 List preKeyAndValues = preNode.getKeyAndValue();
456                 preKeyAndValues.addAll(keyAndValues);
457                 int min = getMaxKeyInNode(preNode);
458                 int max = getMinKeyInNode(node);
459                 //父节点中移除这个key-value
460                 KeyAndValue keyAndValue = getKeyAndValueinMinAndMax(parentNode, min, max);
461                 parentKeyAndValue.remove(keyAndValue);
462                 if (parentKeyAndValue.isEmpty()) {
463                     root = preNode;
464                     node.setParantNode(null);
465                     preNode.setParantNode(null);
466                 } else {
467                     childNodes.remove(node);
468                 }
469                 assert nextNode != null;
470                 preNode.setNextNode(nextNode.getNextNode());
471                 //前节点加上一个当前节点的所有子节点中最小key的key-value
472                 KeyAndValue minKeyAndValue = getMinKeyAndValueInChildNode(node);
473                 assert minKeyAndValue != null;
474                 KeyAndValue keyAndValue1 = new KeyAndValue(minKeyAndValue.getKey(), minKeyAndValue.getValue());
475                 preKeyAndValues.add(keyAndValue1);
476                 List preChildNodes = preNode.getNodes();
477                 preChildNodes.addAll(node.getNodes());
478                 //将当前节点的孩子节点的父节点设为当前节点的后节点
479                 for (Node node1 : childNodes1) {
480                     node1.setParantNode(preNode);
481                 }
482                 Collections.sort(preKeyAndValues);
483                 merge(parentNode, key);


评论


亲,登录后才可以留言!