Find in Large File

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/kiss_xiaojie/article/details/78868136

Find in Large File

0. 来源

最近有学弟问我如何从一个大文件中查找,借自己的一些经验,立刻就认为有两种选择:

  • 文件稍微小一点的话,使用 Map 就可以了,把 ++label++ 放到 key 中,而 value 放的是 ++content++;
  • 文件大一点的话,直接使用数据库吧;

为什么会有这样的想法呢?因为我之前就是这样做的~可是,现在我会怎么做呢?

于是,我做了几个实验想把 大文件查询 做的更优雅些。故,总结于此~~

1. 问题描述

// size of file: 4.55 GB
// source of file: CSDN 用户画像
// one line format: label title content
// problem: find title and content by label given

// for example: D0663128STL中栈和队列的使用方法stl 中优先队列的使用方法...
//      label: D0663128
//      title: STL中栈和队列的使用方法
//      content: stl 中优先队列的使用方法...
//      problem: find 'STL中栈和队列的使用方法' and 'stl 中优先队列的使用方法...' by 'D0663128'

2. 想到的几种方法

2.1 顺序查找

顺序查找 是最无脑的方法: ++一个一个的查找,直到找到。并且,当查找下一个时必须要重写开始。++但是,这种方法,有唯一的好处就是不会:OutOfMemoryError。因为,如果编码正确的话,它会把读过的 Garbage collection

2.2 还是使用 Map

  • 初始想法

    首先进行预处理,读取所有的文件内容,并且把每个 label title&content putMapkey value 中。这样,查找的时候直接 get 就可以了。而且,java 还支持 序列化。也就是,我们可以把 Map 对象保存在文件中。++以后直接读取序列化得到的文件就可以了,而不需要重新进行预处理。++

  • 改进

    一开始,我没想到文件还挺大的。若 Map 保存所有的文件内容,那肯定会 OutOfMemoryError。于是,我做出如下改进:

    1) 还是使用 Map ,其中 key 依旧保存的为 label,但是 value 保存的是 索引。怎么理解这个索引?
    2) 其实 ++索引++ 想法来源于随机读取,即 Java 中的 RandomAccessFile 。这里索引由两部分组成:开始位置_结束位置

    • 开始位置:即 label 对应的内容的开始处相对于文件的偏移。
    • 结束位置:即 label 对应的内容的结束处相对于文件的偏移。
      3) 有了这两个 位置,使用 RandomAccessFile 可以很快的读取到所要获取的内容;

    value 保存的是 索引 后,Map 对象所占的内存空间会小很多,故,就不存在 OutOfMemoryError 问题了。经过实验,此时序列化的 Map 对象的文件大小为:32.9 MB
    此时,查询的时间瓶颈也就落在了:++读取 Map 对象序列化文件,恢复 Map 上了++

2.3 使用 B-Tree

  • 首先了解 B-Tree ,详见下面的博客(讲的不错)
    B-Tree 详解

  • 我的实现

    这学期,我在读 Introduction to Algorithms 并实现了上面的一些算法。(GitHub)。其中,也包括 B-Tree
    但是,我实现的版本没有考虑:++把结点保存到硬盘中++,也就是最后生成的 B-Tree 完全在内存中。现在看起来,真艹,这把 B-Tree 阉割的不像样,这完全丢掉了它的优点。

    原本,我打算完全重写,把它的每个结点操作都映射到硬盘上。但,由于时间因素,我只做了如下修改:

    • 生成 B-Tree 还是在内存中(没变);
    • B-Tree 构造完成后,我把它的每个结点都保存到硬盘中;
    • 重写 search 方法,使它从硬盘中的 B-Tree 中找;

    为什么这么修改?因为,++修改后我完全可以模拟真正 B-Tree 的查找过程++,这样就能分析它的性能了。

2.4 使用 数据库

针对这个方法,我没有做实验。因为,我发现,要把这个大家伙(文件)导入 MySQL 需要 1~2 小时。坑~

3. 比较

方法一、四就不用说了。这里主要是对方法二、三的对比。

Null Map B-Tree
预 处 理 58.213 s 88.834 s
加载时间 10.069 s 0 s
查询时间 10.082 s 0.202 s
  • 说明

    • 预处理时间:分别指的是 MapB-Tree 内存构造 + 保存到硬盘中的时间;

      这里 B-Tree 会生成 5720 个文件(因为有 5720 个结点),每个文件的大小平均为 5kb ,所以会慢一点;

    • 加载时间:Map 指的是从序列化文件恢复到内存的时间。而,B-Tree 为 0s。

      这里 B-Tree 设为 0 s,是因为它每次查询最多会读三个(因为有三层)大小约为 6kb 的文件(读的这些文件正是需要查询的结点),而我把这些时间都归为 查询时间 了。

    • 查询时间:这里是查询 4个 label 对应 title&content 的时间;

      需要说明的是,Map 的查询时间包含了它的 加载时间

  • 结论

    我们可以很容易得出,当只查询几个时,B-Tree 很快,只需要零点几秒。但是,当查询成千上万次时,Map 的效果会越来越好。因为,多次查询会把 Map 的加载时间平摊

4. source code

Find.java

/**
 * @author INotWant.
 */
public interface Find {

    /**
     * @param label 标号
     * @return 标号对应的内容
     */
    String findContent(String label);

}

FindInLargeFile.java

import java.io.*;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * @author INotWant.
 */
public class FindInLargeFile {

    public static String FILE_PATH = "data/largeFile.txt";


    /**
     * every find start again
     */
    public static class OrderFind implements Find {

        @Override
        public String findContent(String label) {
            String content = "";
            try (BufferedReader reader = new BufferedReader(new FileReader(FILE_PATH))) {
                String line = reader.readLine();
                while (line != null) {
                    String[] split = line.split("\u0001");
                    if (label.equals(split[0]))
                        return split[1];
                    line = reader.readLine();
                }
            } catch (Exception e) {
                throw new RuntimeException(e);
            }
            return content;
        }
    }

    public static class FindUseMap implements Find {

        private Map<String, String> map = new HashMap<>();
        private final static String OBJECT_FILE = "data/map";

        @SuppressWarnings("unchecked")
        @Override
        public String findContent(String label) {
            // if size of map equal 0, initialize map
            // map save the start byte of line and the end byte of line for label
            File objectFile = new File(OBJECT_FILE);
            if (objectFile.exists() && map.size() == 0) {
                long startTime = System.currentTimeMillis();
                try (ObjectInputStream ois = new ObjectInputStream(new FileInputStream(objectFile))) {
                    map = (Map<String, String>) ois.readObject();
                } catch (Exception e) {
                    throw new RuntimeException(e);
                }
                long endTime = System.currentTimeMillis();
                System.out.println("load ::" + (endTime - startTime) / 1000.);
            }
            if (map.size() == 0) {
                try (BufferedReader reader = new BufferedReader(new FileReader(FILE_PATH));
                     ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(OBJECT_FILE))) {
                    String line = reader.readLine();
                    long lineStart = 0;
                    while (line != null) {
                        long lineEnd = lineStart + line.getBytes().length - 1;
                        String[] split = line.split("\u0001");
                        map.put(split[0], String.valueOf(lineStart) + "_" + String.valueOf(lineEnd));
                        lineStart = lineEnd + 3;
                        line = reader.readLine();
                    }
                    oos.writeObject(map);
                } catch (Exception e) {
                    throw new RuntimeException(e);
                }
            }
            // find label in map
            String[] split = map.get(label).split("_");
            long lineStart = Long.parseLong(split[0]);
            long lineEnd = Long.parseLong(split[1]);
            try (RandomAccessFile randomAccessFile = new RandomAccessFile(FILE_PATH, "r")) {
                randomAccessFile.seek(lineStart);
                byte[] bytes = new byte[(int) (lineEnd - lineStart + 1)];
                for (int i = 0; i < bytes.length; i++) {
                    bytes[i] = randomAccessFile.readByte();
                }
                return new String(bytes).split("\u0001")[1];
            } catch (Exception e) {
                throw new RuntimeException(e);
            }
        }
    }

    public static class FindUseBTree implements Find {

        // size of degree is determined by block of disk
        private BTree<String, String> bTree = new BTree<>(128);
        private final static String SAVE_PATH = "data/BTree/";

        public FindUseBTree() {
            File file = new File(SAVE_PATH);
            if (file.exists() && file.isDirectory() && file.listFiles() != null && file.listFiles().length > 0)
                return;
            createBTree();
            saveBTree();
            // for releasing memory
            bTree = null;
        }

        private void createBTree() {
            try (BufferedReader reader = new BufferedReader(new FileReader(FILE_PATH))) {
                String line;
                long lineStart = 0;
                while ((line = reader.readLine()) != null) {
                    long lineEnd = lineStart + line.getBytes().length - 1;
                    String[] split = line.split("\u0001");
                    bTree.insert(split[0], String.valueOf(lineStart) + "_" + String.valueOf(lineEnd));
                    lineStart = lineEnd + 3;
                }
            } catch (Exception e) {
                throw new RuntimeException(e);
            }
        }

        // file's name show layer & position in layer. number of layer or position start from 1.
        // for example, 1_1_1: root,
        //              3_1_2: 3nd layer and 3th position coming from 2th layer's 1th position
        //              x_y_z: 当前为 x 层的,且父结点为 x-1 层第 y 处结点的,第 z 个
        private void saveBTree() {
            /*
            int layerNum = 1;
            BTree.BNode<String, String> root = bTree.getRoot();
            List<BTree.BNode<String, String>> layerList = new ArrayList<>();
            layerList.add(root);
            while (layerList.size() > 0) {
                List<BTree.BNode<String, String>> newLayerList = new ArrayList<>();
                for (int i = 0; i < layerList.size(); i++) {
                    if (!layerList.get(i).isLeaf())
                        newLayerList.addAll(layerList.get(i).getChildren());
                    saveNode(layerNum, i + 1, layerList.get(i));
                }
                ++layerNum;
                layerList = newLayerList;
            }
            */
            int layerNum = 1;
            BTree.BNode<String, String> root = bTree.getRoot();
            List<List<BTree.BNode<String, String>>> layerListList = new ArrayList<>();
            List<BTree.BNode<String, String>> layerList = new ArrayList<>();
            layerList.add(root);
            layerListList.add(layerList);
            while (layerListList.size() > 0) {
                List<List<BTree.BNode<String, String>>> newLayerListList = new ArrayList<>();
                for (int i = 0; i < layerListList.size(); i++) {
                    layerList = layerListList.get(i);
                    for (int j = 0; j < layerList.size(); j++) {
                        if (!layerList.get(j).isLeaf())
                            newLayerListList.add(layerList.get(j).getChildren());
                        saveNode(layerNum, (i + 1) + "_" + (j + 1), layerList.get(j));
                    }
                }
                layerListList = newLayerListList;
                ++layerNum;
            }
        }

        private void saveNode(int layerNum, String posNum, BTree.BNode<String, String> node) {
            String fileName = SAVE_PATH + String.valueOf(layerNum) + "_" + posNum;
            try (ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(fileName))) {
                node.setChildren(null);
                oos.writeObject(node);
            } catch (Exception e) {
                throw new RuntimeException(e);
            }
        }

        @SuppressWarnings("unchecked")
        private BTree.BNode<String, String> readNode(String pLabel) {
            String fileName = SAVE_PATH + pLabel;
            BTree.BNode<String, String> bNode = null;
            try (ObjectInputStream ois = new ObjectInputStream(new FileInputStream(fileName))) {
                bNode = (BTree.BNode<String, String>) ois.readObject();
            } catch (Exception e) {
                e.printStackTrace();
            }
            return bNode;
        }

        @Override
        public String findContent(String label) {
            int layer = 1;
            String pLabel = "1_1_1";
            String pContent = null; // position of content in file
            while (pContent == null) {
                BTree.BNode<String, String> bNode = readNode(pLabel);
                List<String> keys = bNode.getKeys();
                int count = 1;
                for (int i = 0; i < keys.size(); i++) {
                    if (keys.get(i).equals(label)) {
                        pContent = bNode.getValues().get(i);
                        break;
                    }
                    if (label.compareTo(keys.get(i)) > 0)
                        ++count;
                    if (label.compareTo(keys.get(i)) < 0)
                        break;
                }
                ++layer;
                pLabel = layer + "_" + pLabel.split("_")[2] + "_" + count;
            }
            return getContent(pContent);
        }

        private String getContent(String pContent) {
            String[] split = pContent.split("_");
            long lineStart = Long.parseLong(split[0]);
            long lineEnd = Long.parseLong(split[1]);
            try (RandomAccessFile randomAccessFile = new RandomAccessFile(FILE_PATH, "r")) {
                randomAccessFile.seek(lineStart);
                byte[] bytes = new byte[(int) (lineEnd - lineStart + 1)];
                for (int i = 0; i < bytes.length; i++) {
                    bytes[i] = randomAccessFile.readByte();
                }
                return new String(bytes).split("\u0001")[1];
            } catch (Exception e) {
                throw new RuntimeException(e);
            }
        }
    }

    public static void main(String[] args) {
        FindInLargeFile.FILE_PATH = "data/blogContent.txt";

        // TEST 1
        /*
        Timing timing1 = new Timing(new OrderFind());
        Timing timing2 = new Timing(new FindUseMap());
        timing1.countTime("D0934038");
        timing2.countTime("D0934038");
        // */

        // TEST 2
        long startTime = System.currentTimeMillis();
        Timing timing = new Timing(new FindUseMap());
        String[] labels = {"D0934038", "D0339360", "D0379964", "D0776559"};
        timing.countTime(labels);
        long endTime = System.currentTimeMillis();
        System.out.println("预处理时间 :: " + (endTime - startTime) / 1000.);

//         TEST 3
//        long startTime = System.currentTimeMillis();
//        FindUseBTree findUseBTree = new FindUseBTree();
//        long endTime = System.currentTimeMillis();
//        System.out.println("预处理时间 :: " + (endTime - startTime) / 1000.);

        // TEST 4
//        Timing timing = new Timing(new FindUseBTree());
//        String[] labels = {"D0934038", "D0339360", "D0379964", "D0776559"};
//        timing.countTime(labels);

        // TEST 5
//        Timing timing1 = new Timing(new FindUseBTree());
//        String[] labels = {"D0934038", "D0339360", "D0379964", "D0776559"};
//        timing1.countTime(labels);
//        Timing timing2 = new Timing(new FindUseMap());
//        timing2.countTime(labels);

    }

}

Timing.java

import java.util.ArrayList;
import java.util.List;

/**
 * @author INotWant.
 */
public class Timing {

    private Find findProcess;

    public Timing(Find findProcess) {
        this.findProcess = findProcess;
    }

    public void countTime(String label) {
        long start = System.currentTimeMillis();
        String content = findProcess.findContent(label);
        long endTime = System.currentTimeMillis();
        System.out.println(label + " :: " + content);
        System.out.println("spend time :: " + (endTime - start) / 1000.);
    }

    public void countTime(String... labels) {
        long start = System.currentTimeMillis();
        List<String> contents = new ArrayList<>();
        for (String label : labels)
            contents.add(findProcess.findContent(label));
        long endTime = System.currentTimeMillis();
        for (int i = 0; i < labels.length; i++) {
            System.out.println(labels[i] + " :: " + contents.get(i));
        }
        System.out.println("spend time :: " + (endTime - start) / 1000.);
    }
}

BTree.java

import java.io.Serializable;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;

/**
 * B 树
 * 要点 1):返回一个不可修改的集合;
 * 要点 2):规范小标的使用!
 *
 * @author INotWant.
 */
public class BTree<K extends Comparable<K>, V> {

    private BNode<K, V> root;
    private Integer degree; // 最小度数

    public BTree(Integer degree) {
        this.degree = degree;
    }

    public static class BNode<K, V> implements Serializable {
        private List<K> keys = new LinkedList<>();   // 关键字集合
        private List<V> values = new LinkedList<>(); // 关键字对应的数据
        private List<BNode<K, V>> children = new LinkedList<>(); // 子孩子集合
        private boolean isLeaf; // 是否为叶节点
        private int n;  // 关键字的个数

        public int getN() {
            return n;
        }

        public List<K> getKeys() {
            // 返回一个不可以修改的 关键字集合
            return Collections.unmodifiableList(keys);
        }

        public List<V> getValues() {
            return Collections.unmodifiableList(values);
        }

        public List<BNode<K, V>> getChildren() {
            return Collections.unmodifiableList(this.children);
        }

        public boolean isLeaf() {
            return isLeaf;
        }

        public void setChildren(List<BNode<K, V>> children) {
            this.children = children;
        }
    }

    /**
     * @return 创建一个 空B树
     */
    public BNode<K, V> buildBTree() {
        this.root = new BNode<>();
        this.root.n = 0;
        this.root.isLeaf = true;
        return root;
    }

    /**
     * @param key   key
     * @param value value
     */
    public void insert(K key, V value) {
        this.root = this.root == null ? buildBTree() : this.root;
        if (this.root.n == 2 * degree - 1) {
            BNode<K, V> nRoot = new BNode<>();
            nRoot.n = 0;
            nRoot.isLeaf = false;
            nRoot.children.add(this.root);
            this.root = nRoot;
            split(nRoot, 0);
        }
        insertNoFull(this.root, key, value);
    }

    /**
     * 分裂结点,添加时不会造成结点关键字大于 2*degree-1
     * 【注】:分裂结点使得 B树 从根节点增高
     *
     * @param pNode 待“分裂”结点的父结点;
     * @param c     待“分裂”结点所在父结点的位置;
     */
    private void split(BNode<K, V> pNode, int c) {
        BNode<K, V> node = pNode.children.get(c);
        BNode<K, V> bNode = new BNode<>();
        // 新结点度数
        bNode.n = degree - 1;
        // 新结点关键字以及子孩子
        for (int i = 0; i < node.n; i++)
            if (i >= degree) {
                bNode.keys.add(node.keys.get(i));
                bNode.values.add(node.values.get(i));
                if (!node.isLeaf)
                    bNode.children.add(node.children.get(i));
            }
        if (!node.isLeaf)
            bNode.children.add(node.children.get(node.children.size() - 1));
        // 新结点是否为叶节点
        bNode.isLeaf = node.isLeaf;
        // 父结点修改
        pNode.keys.add(c, node.keys.get(degree - 1));
        pNode.values.add(c, node.values.get(degree - 1));
        pNode.children.add(c + 1, bNode);
        pNode.n += 1;
        // 旧结点修改
        for (int i = node.n - 1; i >= degree - 1; i--) {
            node.keys.remove(i);
            node.values.remove(i);
            if (!node.isLeaf)
                node.children.remove(i + 1);
        }
        node.n = degree - 1;
    }

    // 插入帮助方法,已确定 node 不会满
    // 只在叶节点插入
    private void insertNoFull(BNode<K, V> node, K key, V value) {
        if (node.isLeaf) {
            int i = 0;
            for (; i < node.n; i++)
                if (node.keys.get(i).compareTo(key) >= 0)
                    break;
            node.keys.add(i, key);
            node.values.add(i, value);
            node.n++;
        } else {
            int i = 0;
            for (; i < node.n; i++)
                if (node.keys.get(i).compareTo(key) >= 0)
                    break;
            BNode<K, V> cNode = node.children.get(i);
            if (cNode.n == 2 * degree - 1) {
                split(node, i);
                if (node.keys.get(i).compareTo(key) < 0)
                    cNode = node.children.get(i + 1);
            }
            insertNoFull(cNode, key, value);
        }
    }

    /**
     * @param key 关键字
     * @return 关键字对应的值
     */
    public V search(K key) {
        BNode<K, V> cNode = this.root;
        while (cNode != null) {
            int i = 0;
            for (; i < cNode.n; i++) {
                if (cNode.keys.get(i).compareTo(key) == 0)
                    return cNode.values.get(i);
                else if (cNode.keys.get(i).compareTo(key) > 0)
                    break;
            }
            if (!cNode.isLeaf)
                cNode = cNode.children.get(i);
            else
                cNode = null;
        }
        return null;
    }

    /**
     * @param key 待删除的关键字
     * @return 删除成功时,返回关键字对应的“数据”,否则返回 null
     */
    public V delete(K key) {
        return deleteHelp(this.root, key);
    }

    // 删除帮组类
    private V deleteHelp(BNode<K, V> cNode, K key) {
        while (cNode != null) {
            int i = 0;
            for (; i < cNode.n; i++) {
                if (cNode.isLeaf && cNode.keys.get(i).compareTo(key) == 0) {
                    // 要删除的关键字在叶节点的情况
                    --cNode.n;
                    cNode.keys.remove(i);
                    return cNode.values.remove(i);
                } else if (cNode.keys.get(i).compareTo(key) == 0) {
                    // 要删除的关键字在内部结点的情况
                    int b1 = i;
                    int b2 = i + 1;
                    if (cNode.children.get(b1).n >= degree) {
                        // 左兄弟结点关键字数大于等于 t 的情况
                        BNode<K, V> bNode = cNode.children.get(b1);
                        K rKey = bNode.keys.get(bNode.n - 1);
                        V rValue = bNode.values.get(bNode.n - 1);
                        deleteHelp(bNode, rKey);
                        V resultValue = cNode.values.get(i);
                        cNode.keys.set(i, rKey);
                        cNode.values.set(i, rValue);
                        return resultValue;
                    } else if (cNode.children.get(b2).n >= degree) {
                        // 右兄弟结点关键字数大于等于 t 的情况
                        BNode<K, V> bNode = cNode.children.get(b2);
                        K rKey = bNode.keys.get(0);
                        V rValue = bNode.values.get(0);
                        deleteHelp(bNode, rKey);
                        V resultValue = cNode.values.get(i);
                        cNode.keys.set(i, rKey);
                        cNode.values.set(i, rValue);
                        return resultValue;
                    } else {
                        // 相邻结点都不大于的情况,需要合并
                        if (cNode.n - 1 == 0)
                            this.root = cNode.children.get(0);
                        union(cNode, i);
                        return deleteHelp(cNode, key);
                    }
                } else if (cNode.keys.get(i).compareTo(key) > 0)
                    break;
            }
            if (cNode.isLeaf)
                return null;
            else {
                // 要下降,此时要判断下降至结点的关键字的个数
                if (cNode.children.get(i).n >= degree)
                    cNode = cNode.children.get(i);
                else {
                    // 所至结点关键字数 t-1 的情况
                    int b1 = i - 1;
                    int b2 = i + 1;
                    if (b1 >= 0 && cNode.children.get(b1).n >= degree) {
                        // 由其左兄弟输送
                        BNode<K, V> bNode = cNode.children.get(b1);
                        K rKey = bNode.keys.remove(bNode.n - 1);
                        V rValue = bNode.values.remove(bNode.n - 1);
                        BNode<K, V> rChild = null;
                        if (!bNode.isLeaf)
                            rChild = bNode.children.remove(bNode.n);
                        --bNode.n;
                        K moveKey = cNode.keys.get(i - 1);
                        V moveValue = cNode.values.get(i - 1);
                        cNode.keys.set(i - 1, rKey);
                        cNode.values.set(i - 1, rValue);
                        cNode = cNode.children.get(i);
                        ((LinkedList<K>) cNode.keys).addFirst(moveKey);
                        ((LinkedList<V>) cNode.values).addFirst(moveValue);
                        if (!bNode.isLeaf)
                            ((LinkedList<BNode<K, V>>) cNode.children).addFirst(rChild);
                        ++cNode.n;
                    } else if (b2 < cNode.children.size() && cNode.children.get(b2).n >= degree) {
                        // 由其右兄弟输送
                        BNode<K, V> bNode = cNode.children.get(b2);
                        K rKey = bNode.keys.remove(0);
                        V rValue = bNode.values.remove(0);
                        BNode<K, V> rChild = null;
                        if (!bNode.isLeaf)
                            rChild = bNode.children.remove(0);
                        --bNode.n;
                        K moveKey = cNode.keys.get(i);
                        V moveValue = cNode.values.get(i);
                        cNode.keys.set(i, rKey);
                        cNode.values.set(i, rValue);
                        cNode = cNode.children.get(i);
                        ((LinkedList<K>) cNode.keys).addLast(moveKey);
                        ((LinkedList<V>) cNode.values).addLast(moveValue);
                        if (!bNode.isLeaf)
                            ((LinkedList<BNode<K, V>>) cNode.children).addLast(rChild);
                        ++cNode.n;
                    } else {
                        int b = b1 >= 0 ? b1 : b2;
                        if (b == b1)
                            union(cNode, i - 1);
                        else
                            union(cNode, i);
                    }
                }
            }
        }
        return null;
    }

    // 合并 cNode 结点中 i 和 i+1 子结点至 i 结点上
    private void union(BNode<K, V> cNode, int i) {
        K moveKey = cNode.keys.remove(i);
        V moveValue = cNode.values.remove(i);
        BNode<K, V> rChild = cNode.children.remove(i + 1);
        --cNode.n;
        cNode = cNode.children.get(i);
        cNode.keys.add(moveKey);
        cNode.keys.addAll(rChild.getKeys());
        cNode.values.add(moveValue);
        cNode.values.addAll(rChild.getValues());
        if (!rChild.isLeaf)
            cNode.children.addAll(rChild.getChildren());
        cNode.n += 1 + rChild.n;
    }

    public BNode<K, V> getRoot() {
        return root;
    }

    public static void main(String[] args) {
        System.out.println("--------- BEGIN ---------");
        BTree<Character, String> bTree = new BTree<>(2);
        bTree.insert('M', "Computer");
        bTree.insert('D', "Hello");
        bTree.insert('H', "Java");
        bTree.insert('Q', "C++");
        bTree.insert('C', "C");
        bTree.insert('I', "SSH");
        bTree.insert('A', "Compile");
        bTree.insert('B', "Linux");
        bTree.insert('T', "GitHub");
        bTree.insert('E', "World");
        bTree.insert('F', "Python");
        bTree.insert('J', "OA");
        bTree.insert('K', "CRM");
        System.out.println(bTree.search('A'));
        System.out.println(bTree.delete('Z'));
        System.out.println("---------- END ----------");
    }

}

【NOTE】 水平有限,不确保 BTree.java 不存在 BUG !!

猜你喜欢

转载自blog.csdn.net/kiss_xiaojie/article/details/78868136