理解mysql索引背后的数据结构B~Tree(B-Tree/B+Tree)

前言

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 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> iterator = entrySet().iterator();
        while (iterator.hasNext()){
            Map.Entry entry = iterator.next();
            if (entry.getKey().equals(key)) {
                hasKey = true;
                break;
            }
        }
        return hasKey;
    }

    @Override
    public boolean containsValue(Object value) {
        boolean hasValue = false;
        Iterator> iterator = entrySet().iterator();
        while (iterator.hasNext()){
            Map.Entry 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 node) {
        Entry[] 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 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 m) {
        //TODO putAll(Map m)
    }

    @Override
    public void clear() {
        //TODO clear()
    }

    @Override
    public Set keySet() {
        Set keySet = new TreeSet<>();
        entrySet().forEach(entry -> keySet.add(entry.getKey()));
        return keySet;
    }

    @Override
    public Collection values() {
        //TODO values()
        return null;
    }

    @Override
    public Set> entrySet() {
        Set> es = new TreeSet<>();
        Node node = this.root;
        addToSet(node, es);
        return es;
    }

    public void addToSet(Node node, Set> 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 node) {
        if (node.count < node.data.length) {
            return;
        }
        Node 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 entry = node.data[index];
        //清空node节点中的该值
        node.data[index] = null;
        node.count--;
        //分裂之后的兄弟节点
        Node 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 node, Entry 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 insert(Node node, K key, V value, int ht) {
        //当插入第一个值到根节点时
        if (node.count == 0) {
            Entry 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 child = node.children[index];
            return insert(child, key, value, ht + 1);
        } else if (ht == this.h) { //到达最底层即到达了叶子节点,将数据插入叶子节点
            Entry 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[] data;
        Node[] children;
        Node 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 parent) {
            this.data = (Entry[]) new Entry[maxKeys];
            this.children = (Node[]) 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;
    }
}

你可能感兴趣的:(数据结构-算法)