理解mysql索引背后的数据结构B~Tree(B-Tree/B+Tree)
来源:互联网 发布:mac充电灯不亮但能充电 编辑:程序博客网 时间:2024/04/30 13:25
前言
B-Tree在不同的文献中的定义略显不同,所以在我初学B-Tree的时候非常困惑,知乎的一篇回答解答了我的困惑为什么 B-tree 在不同著作中度的定义有一定差别? - oldsharp的回答 - 知乎 ,本文将以算法导论中的定义来对B-Tree展开讨论。
正文
在《算法导论》和《计算机程序设计艺术》一书中中对B-Tree的度的定义有略微的不同,在《算法导论》中定义了一个「最小度数t」表示「半满」状态,即最小孩子数,而2t则表示「全满」状态,即最大孩子数。
在《计算机程序设计艺术》一书中定义了一个「度m」表示「全满」状态,即最大孩子节点数,而ceil(m/2)则表示「半满」状态,即最小孩子数(ceil表示向上取整),相比于算法导论中定义的「度t」,《计算机程序设计艺术》一书中定义的「度m」更像是b树的阶,m代表m叉树。从定义可以看出,此种定义的「半满」状态(最小孩子节点数)是不稳定的,这取决于m的奇偶性。
关于这两种不同的定义,实现的最简的b树也是不同的,对于《算法导论》中的定义,一颗最简b树是一颗2-3-4树,而对于《计算机程序设计艺术》一书中的定义,一颗最简的b树是一颗2-3树。
推荐文章
在我学习B~Tree的时候,在网络上看到了一些不错的文章,接下来一一分享之,首先要理解mysql索引的本质是一种数据结构,而mysql的索引包括BTree索引、hash索引、全文索引等,本文主要讨论BTree索引,BTree索引是通过B+树实现的,所以要学习BTree索引第一步首先是要学习B~Tree,B-tree/B+tree/B*tree这篇文章非常简洁、明了、易懂的描述了B~Tree的结构及原理,在学习了B~Tree之后,MySQL索引背后的数据结构及算法原理这篇文章很好的分析了mysql的BTree索引实现以及一些mysql索引优化思路,mysql索引的实现是基于存储引擎的,这就意味着,对于不同的存储引擎,mysql的索引实现方式不同,在《MySQL索引背后的数据结构及算法原理》这篇文章中也分析了MyISAM 存储引擎中索引的实现以及InnoDB 存储引擎中索引实现的不同,有关mysql各类存储引擎的分析与比较,可以参看这篇文章Mysql 存储引擎。
使用java实现B-Tree
上面推荐的文章已经很好的解释了mysql索引及b~tree,在下也就不重复造轮子,本文主要分享一下我通过java语言实现的B-Tree,该实现是参照算法导论中的定义,但是在分裂时我选择在「全满」时就开始分裂,而不是等到溢出时再分裂,所以我虽然是参考算法导论中的定义,但是我实现的B-Tree的最简树是一颗2-3树(才疏学浅,实现略显拙劣,望大神指点),由于时间关系,我没有写删除操作,以下为代码:
/** * Created by 落叶刻痕 on 2017/11/29. * 本实现是基于算法导论中的定义: * d是B-Tree的度,表示半满状态下孩子节点的个数,即非根内节点的最少孩子数量,所以d-1表示最少的key个数 * 2d表示 全满状态下孩子节点的个数,即非根内节点的最多孩子数量,所以2d-1表示最多的key个数 * put操作时,在全满时就开始分裂而不是等到溢出时再分裂 */public class BTree<K extends Comparable<K>, V> implements Map<K, V> { private static final int DEFAULT_D = 2; //d为大于1的一个正整数,称为B-Tree的度,d表示半满状态,d必须大于等于2 private final int d; private final int minKeys; private final int maxKeys; //h为一个正整数,称为B-Tree的高度。 private int h; //B-Tree的根节点 private Node<K, V> root; //B-Tree中元素个数 private int size; public BTree(int d) { if (d<2) throw new IllegalArgumentException("Illegal Capacity: "+ d); this.d = d; this.size = 0; this.h = 1; this.minKeys = d - 1; this.maxKeys = 2 * d - 1; this.root = new Node<>(maxKeys, null); } public BTree() { this(DEFAULT_D); } @Override public int size() { return this.size; } @Override public boolean isEmpty() { return size() == 0; } @Override public boolean containsKey(Object key) { boolean hasKey = false; Iterator<Map.Entry<K, V>> iterator = entrySet().iterator(); while (iterator.hasNext()){ Map.Entry<K,V> entry = iterator.next(); if (entry.getKey().equals(key)) { hasKey = true; break; } } return hasKey; } @Override public boolean containsValue(Object value) { boolean hasValue = false; Iterator<Map.Entry<K, V>> iterator = entrySet().iterator(); while (iterator.hasNext()){ Map.Entry<K,V> entry = iterator.next(); if (entry.getValue().equals(value)) { hasValue = true; break; } } return hasValue; } @Override public V get(Object key) { assert key!=null; if (!(key instanceof Comparable)){ throw new ClassCastException("key need to implements Comparable"); } return find(key, root); } public V find(Object key, Node<K, V> node) { Entry<K, V>[] entry = node.data; for (int i = 0; i < node.getCount(); i++) { if (((Comparable) key).compareTo(entry[i].getKey()) == 0){ return entry[i].getValue(); } else if (i == 0 && ((Comparable) key).compareTo(entry[0].getKey()) < 0 && node.children[0]!=null) { return find(key, node.children[0]); } else if (i < node.getCount() - 1 && ((Comparable) key).compareTo(entry[i].getKey()) > 0 && ((Comparable) key).compareTo(entry[i+1].getKey()) < 0) { if (node.children[0]==null) return null; else return find(key, node.children[i+1]); } else if (i == node.getCount() - 1 && ((Comparable) key).compareTo(entry[i].getKey()) > 0 && node.children[0]!=null) { return find(key, node.children[i+1]); }else { System.out.println("---"); continue; } } return null; } public V put(K key, V value) { if (key == null) throw new NullPointerException("key can not be null!"); //插入元素 Node<K, V> node = insert(this.root, key, value, 1); //判断插入的节点是否需要分裂,当插入之后节点达到全满状态,立即分裂节点而不是等到溢出时再分裂 trySplit(node); return value; } @Override public V remove(Object key) { //TODO remove(Object key) return null; } @Override public void putAll(Map<? extends K, ? extends V> m) { //TODO putAll(Map<? extends K, ? extends V> m) } @Override public void clear() { //TODO clear() } @Override public Set<K> keySet() { Set<K> keySet = new TreeSet<>(); entrySet().forEach(entry -> keySet.add(entry.getKey())); return keySet; } @Override public Collection<V> values() { //TODO values() return null; } @Override public Set<Map.Entry<K, V>> entrySet() { Set<Map.Entry<K, V>> es = new TreeSet<>(); Node<K, V> node = this.root; addToSet(node, es); return es; } public void addToSet(Node<K, V> node, Set<Map.Entry<K, V>> es){ if (node.isLeaf()){ for (int i = 0; i < node.count; i++) { es.add(node.data[i]); } }else { //先添加最左边子节点的元素到set addToSet(node.children[0], es); for (int i = 0; i < node.count; i++) { //然后依次交替添加根节点、根节点右边子节点。 es.add(node.data[i]); addToSet(node.children[i+1], es); } } } private void trySplit(Node<K, V> node) { if (node.count < node.data.length) { return; } Node<K, V> root = null; if (node.parent == null) { //由于没有父节点,所以重新生成一个父节点 root = new Node<>(maxKeys, null); root.setLeaf(false); //根节点变为新增的父节点 this.root = root; //树的高度加一 this.h++; } else { root = node.parent; } //将node的parent指向父节点。 node.parent = root; //取node节点的中间值 int index = (int) Math.ceil(node.count / 2.0) - 1; //取出要放入父节点的值 Entry<K, V> entry = node.data[index]; //清空node节点中的该值 node.data[index] = null; node.count--; //分裂之后的兄弟节点 Node<K, V> brother = new Node<>(maxKeys, root); //将node节点中index后面的元素放入兄弟节点brother中 for (int i = index + 1; i <= node.data.length; i++) { if (i < node.data.length) { brother.data[i - index - 1] = node.data[i]; brother.count++; node.data[i] = null; node.count--; } //将node节点index后的元素的孩子节点放入兄弟节点中 brother.children[i - index - 1] = node.children[i]; node.children[i] = null; } if (brother.children[0] != null) { brother.setLeaf(false); } //将node节点中index位置的元素entry放入父节点root中 int parentIndex = addToParent(root, entry); root.children[parentIndex] = node; root.children[parentIndex + 1] = brother; //如果分分裂后父节点也达到了分裂条件,则让父节点也分裂 trySplit(root); } int addToParent(Node<K, V> node, Entry<K, V> entry) { //当为根节点时 if (node.count == 0) { node.data[0] = entry; node.count++; return 0; } int index = -1; for (int i = 0; i < node.count; i++) { if (entry.getKey().compareTo(node.data[i].getKey()) <= 0) { index = i; break; } } index = index == -1 ? node.count : index; //将插入点后面的元素都后移一位 for (int i = node.count; i > index; i--) { node.data[i] = node.data[i - 1]; node.children[i + 1] = node.children[i]; } node.children[index + 1] = null; //插入数据 node.data[index] = entry; node.count++; return index; } public Node<K, V> insert(Node<K, V> node, K key, V value, int ht) { //当插入第一个值到根节点时 if (node.count == 0) { Entry<K, V> entry = new Entry<>(key, value); node.data[0] = entry; node.count++; this.size++; return node; } //在节点中寻找key应该放在哪个位置 int index = -1; for (int i = 0; i < node.count; i++) { if (key.compareTo(node.data[i].getKey()) <= 0) { index = i; break; } } index = index == -1 ? node.count : index; //找到了应该插入的位置,先判断是否到达最后一层,如果没有到达最后一层,即当前不是叶子节点,则递归到下一层 if (ht < this.h) { Node<K, V> child = node.children[index]; return insert(child, key, value, ht + 1); } else if (ht == this.h) { //到达最底层即到达了叶子节点,将数据插入叶子节点 Entry<K, V> entry = new Entry<>(key, value); //将插入点后面的元素都后移一位 for (int i = node.count; i > index; i--) { node.data[i] = node.data[i - 1];// node.children[i+1] = node.children[i]; } //插入数据 node.data[index] = entry; node.count++; this.size++; return node; } return node; } static class Node<K extends Comparable<K>, V> { Entry<K, V>[] data; Node<K, V>[] children; Node<K, V> parent; //该节点key的个数 int count; //是否为叶子节点,默认为true boolean leaf = true; public int getCount() { return count; } public boolean isLeaf() { return leaf; } public void setLeaf(boolean leaf) { this.leaf = leaf; } Node(int maxKeys, Node<K, V> parent) { this.data = (Entry<K, V>[]) new Entry[maxKeys]; this.children = (Node<K, V>[]) new Node[maxKeys + 1]; this.parent = parent; count = 0; } } static class Entry<K extends Comparable<K>, V> implements Map.Entry, Comparable<Entry<K, V>> { K key; V value; Entry(K key, V value) { this.key = key; this.value = value; } public K getKey() { return key; } public void setKey(K key) { this.key = key; } public V getValue() { return value; } @Override public Object setValue(Object value) { this.value = (V) value; return value; } /** * 方便在遍历的时候将元素加入TreeSet集合中,其实根据中序遍历, * 得到的元素已经根据key按照从小到大的顺序排好序了,所以完全可以用一个list或者数组来装 * @param o * @return */ @Override public int compareTo(Entry o) { return o.getKey().compareTo(key)*(-1); } } public int getHeight() { return h; }}
- 理解mysql索引背后的数据结构B~Tree(B-Tree/B+Tree)
- MySQL索引使用的数据结构:B-Tree和B+Tree
- MySQL索引使用的数据结构:B-Tree和B+Tree
- MySQL索引使用的数据结构:B-Tree和B+Tree
- Mysql B-Tree 索引
- mysql b-tree索引
- mysql b-tree索引
- BTree,B-Tree,B+Tree,B*Tree的数据结构
- MySQL索引背后的数据结构及BTree B+Tree算法原理
- oracle B*Tree索引的理解
- oracle B*Tree索引的理解 续
- Mysql InnoDB B+Tree索引
- 关于索引的B tree B-tree B+tree B*tree 详解结构图
- 关于索引的B tree B-tree B+tree B*tree 详解结构图( 二)
- 关于索引的B tree B-tree B+tree B*tree 详解结构图
- 关于索引的B tree B-tree B+tree B*tree 详解结构图( 二)
- 关于索引的B tree B-tree B+tree B*tree 详解结构图
- 关于索引的B tree B-tree B+tree B*tree 详解结构图
- css样式中 border的应用
- Spring-framwork-core-1.1-1.2
- 详解YUV数据格式
- PHP AES 128位加密算法
- 制作圆形图片
- 理解mysql索引背后的数据结构B~Tree(B-Tree/B+Tree)
- json基本使用
- web前端案例-js开发智能鼠标感知遮罩层
- postgres访问认证配置文件pg_hba.conf
- makefile多目录的.c 格式.cpp混合编译
- Java_GC(三) ----不同gc的使用命令
- Nginx监听非80端口反向代理80端口出现跳转错误
- CodeForces-489f
- BIGEMAP地图下载器对比优势(水经注)