上一篇文章 :【信息检索】Java简易搜索引擎原理及实现(二)新增停用词表 + 查询处理,我们在建立好的倒排索引的结构中剔除了停用词,同时引入了AND、OR、ANDNOT操作符,支持三种查询方式。
这篇文章中,我们将在倒排索引的基础上,改进我们的字典结构,使用B+树索引来加快检索速度;同时引入轮排的索引方法,以支持通配符的模糊查询方式。

目标:支持通配符查询

在原有字典的基础上,扩展索引结构,实现支持通配符查询。
本文采用的方式是建立轮排索引(Permuterm Index)和 B+树索引结构

使用B+树的原因

首先我们说一下为什么要使用B+树来作为基础索引结构呢?
在这篇文章之前,我们已经建立好了倒排索引的结构,但各个item项之间,我们是用LinkedList来把它们串连在一起的,对于一个搜索的词汇,直接遍历搜索List明显效率感人。因此,我们考虑使用数据库索引这样的结构来存储item项,即B+树,能有效减少搜索次数。

那么为什么选用B+树而不是B树呢?
关于B+树和B树的对比,我们先来看一张图:

可以看到,B+树的非叶节点没有指针指向实际的数据,非叶节点只做导航作用,而B树在非叶节点上也有指向实际数据的指针。
同时,B+树的叶节点之间相邻节点使用链表相连,便于实现区间查找和遍历。而B树则需要进行每一层的递归遍历,相邻的元素可能在内存中不相邻,所以缓存命中性没有B+树好。

通配符查询原理

比如查询语句 mon*:找出所有以mon开头的单词。如果采用B+树结构的词典,我们可以很容易的解决,只需要查询范围在mon ≤ w < moo的所有单词就ok了。

但是查询语句 *mon:找出所有以mon结尾的单词就比较困难了。其中一种办法就是我们增加一个额外的B+树来存储所有单词,以从后向前的顺序,然后在这个树上查询范围在nom ≤ w < non的所有单词。

可是如何处理通配符在单词中间的查询呢?

比如query是co*tion的话。我们当然可以分别在B+树查询到co*和*tion的所有单词然后合并这些单词,但是这样开销太大了。

解决办法就是:轮排索引(Permuterm Index),我们把query的通配符转换到结尾处

设置一个标志$表示单词的结尾。

以hello举例,hello可以被转换成hello$, ello$h, llo$he, lo$hel, o$hell。$代表中hello的结束。现在,查询X等于查询X$,查询X等于查询X$,查询X等于查询X$,查询XY等于查询Y$X。对于hel*o来说,X等于hel,Y等于o。

既然我们已经把通配符都弄到了单词尾部,现在我们又可以通过B+树像以前那样查询了。

轮排索引的缺点在于,据统计,会使得词典的大小约变为原始的四倍。

因此,针对这个问题,又有k-gram索引结构,能解决词典扩大的问题。

k-gram:由k个字符组成的序列
2-gram:由2个字符组成的序列,又称二元组(bigram)

例如,对于文本 “April is the cruelest month”
它的2-gram(bigram)就是:
$a, ap, pr, ri, il, l$, $i, is, s$, $t, th, he, e$, $c, cr, ru, ue, el, le, es, st, t$, $m, mo, on, nt, h$
标志$表示单词的结尾

由bigram到词典项的倒排索引关系如图所示:

因此,查询mon*现在被转换为:$m AND mo AND on

具体实现思路

对于每个item项,将它的term值取出,用上面提到的轮排的方式,把该item项对应的所有字符串存为B+树的key,item项存为value,即原本的一个item项此时会对应有轮排出的多个key值。
举个例子来说,一个item项的term值是【互联网】,根据上述轮排的定义,它将对应于
互联网$、联网$互、网$互联
这三个字符串,将每个字符串和item当做一组key、value存入B+树结构中。

构建好B+树和轮排索引结构后,就是对搜索词的查询了。
根据上述在轮排原理处提到的多种不同的含通配符的搜索词格式,我们用不同的映射方式,将用户输入的搜索词映射为实际在B+树中查询的搜索词,然后直接到B+树中进行范围搜索即可,具体范围搜索的方法看下面的代码实现。

代码实现

每部分思路在代码中都有详细的注释,具体可参考代码理解。
B+树提供的功能主要就是数据的插入、精确查询、通配符查询。
用枚举类VagueMode来表示不同的通配符模式。

1.枚举类VagueMode

public enum VagueMode {X,   //X -> X$X_,  //X* -> X*$_X,  //*X -> X$*_X_, //*X* -> X*X_Y  //X*Y -> Y$X*
}

2.B+树构建

import java.util.List;public class BplusTree {/** 根节点 */protected Node root;/** 阶数,M值 */protected int order;/** 叶子节点的链表头*/protected Node head;public BplusTree(int order){if (order < 3) {System.out.print("order must be greater than 2");System.exit(0);}this.order = order;root = new Node(true, true);head = root;}public Node getHead() {return head;}public void setHead(Node head) {this.head = head;}public Node getRoot() {return root;}public void setRoot(Node root) {this.root = root;}public int getOrder() {return order;}public void setOrder(int order) {this.order = order;}public Item get(String key) {return root.get(key);}public List<Item> rangeFind(String key, VagueMode mode) {return root.rangeFind(key, mode);}public void insertOrUpdate(String key, Item obj) {root.insertOrUpdate(key, obj, this);}}

3.Node节点

import java.text.Collator;
import java.util.*;public class Node {/** 是否为叶子节点 */protected boolean isLeaf;/** 是否为根节点*/protected boolean isRoot;/** 父节点 */protected Node parent;/** 叶节点的前节点*/protected Node previous;/** 叶节点的后节点*/protected Node next;/** 节点的关键字 */protected List<Map.Entry<String, Item>> entries;/** 子节点 */protected List<Node> children;static Comparator<Object> comparator = Collator.getInstance(Locale.CHINA);public Node(boolean isLeaf) {this.isLeaf = isLeaf;entries = new ArrayList<>();if (!isLeaf) {children = new ArrayList<>();}}public Node(boolean isLeaf, boolean isRoot) {this(isLeaf);this.isRoot = isRoot;}//X*格式的通配符查询 -> X*$//key为已去除末尾的*的Xprivate List<Item> modeX_(String key) {//如果是叶子节点if (isLeaf) {Node node = this;int firstLargerPos = 0;int flag = 0; //用于控制循环的标志位,确保循环最多执行2次while (flag < 2) {firstLargerPos = 0;//找到第一个大于等于key的元素的下标for (; firstLargerPos < node.entries.size(); firstLargerPos++) {if (comparator.compare(node.entries.get(firstLargerPos).getKey(), key) >= 0) {flag = 2;break;}}node = node.next;flag++;}node = node.previous;if (firstLargerPos < node.entries.size()) {List<Item> list = new ArrayList<>();while (node.entries.get(firstLargerPos).getKey().startsWith(key)&& node.entries.get(firstLargerPos).getKey().endsWith("$")) {list.add(node.entries.get(firstLargerPos).getValue());firstLargerPos++;if (firstLargerPos >= node.entries.size()) {node = node.next;firstLargerPos = 0;}}return list;}//未找到所要查询的对象return null;//如果不是叶子节点} else {//如果key小于等于节点最左边的key,沿第一个子节点继续搜索if (comparator.compare(key, entries.get(0).getKey()) <= 0) {return children.get(0).modeX_(key);//如果key大于节点最右边的key,沿最后一个子节点继续搜索} else if (comparator.compare(key, entries.get(entries.size()-1).getKey()) >= 0) {return children.get(children.size()-1).modeX_(key);//否则沿比key大的前一个子节点继续搜索} else {for (int i = 0; i < entries.size(); i++) {if (comparator.compare(entries.get(i).getKey(), key) <= 0&& comparator.compare(entries.get(i+1).getKey(), key) > 0) {return children.get(i).modeX_(key);}}}}return null;}//*X格式的通配符查询 -> X$*//key = X$private List<Item> mode_X(String key) {//如果是叶子节点if (isLeaf) {Node node = this;int firstLargerPos = 0;int flag = 0; //用于控制循环的标志位,确保循环最多执行2次while (flag < 2) {firstLargerPos = 0;//找到第一个大于等于key的元素的下标for (; firstLargerPos < node.entries.size(); firstLargerPos++) {if (comparator.compare(node.entries.get(firstLargerPos).getKey(), key) >= 0) {flag = 2;break;}}node = node.next;flag++;}node = node.previous;if (firstLargerPos < node.entries.size()) {List<Item> list = new ArrayList<>();while (node.entries.get(firstLargerPos).getKey().startsWith(key)) {list.add(node.entries.get(firstLargerPos).getValue());firstLargerPos++;if (firstLargerPos >= node.entries.size()) {node = node.next;firstLargerPos = 0;}}return list;}//未找到所要查询的对象return null;//如果不是叶子节点} else {//如果key小于等于节点最左边的key,沿第一个子节点继续搜索if (comparator.compare(key, entries.get(0).getKey()) <= 0) {return children.get(0).mode_X(key);//如果key大于节点最右边的key,沿最后一个子节点继续搜索} else if (comparator.compare(key, entries.get(entries.size()-1).getKey()) >= 0) {return children.get(children.size()-1).mode_X(key);//否则沿比key大的前一个子节点继续搜索} else {for (int i = 0; i < entries.size(); i++) {if (comparator.compare(entries.get(i).getKey(), key) <= 0&& comparator.compare(entries.get(i+1).getKey(), key) > 0) {return children.get(i).mode_X(key);}}}}return null;}//*X*格式的通配符查询 -> X*private List<Item> mode_X_(String key) {//如果是叶子节点if (isLeaf) {Node node = this;int firstLargerPos = 0;int flag = 0; //用于控制循环的标志位,确保循环最多执行2次while (flag < 2) {firstLargerPos = 0;//找到第一个大于等于key的元素的下标for (; firstLargerPos < node.entries.size(); firstLargerPos++) {if (comparator.compare(node.entries.get(firstLargerPos).getKey(), key) >= 0) {flag = 2;break;}}node = node.next;flag++;}node = node.previous;if (firstLargerPos < node.entries.size()) {List<Item> list = new ArrayList<>();while (node.entries.get(firstLargerPos).getKey().startsWith(key)) {list.add(node.entries.get(firstLargerPos).getValue());firstLargerPos++;if (firstLargerPos >= node.entries.size()) {node = node.next;firstLargerPos = 0;}}return list;}//未找到所要查询的对象return null;//如果不是叶子节点} else {//如果key小于等于节点最左边的key,沿第一个子节点继续搜索if (comparator.compare(key, entries.get(0).getKey()) <= 0) {return children.get(0).mode_X_(key);//如果key大于节点最右边的key,沿最后一个子节点继续搜索} else if (comparator.compare(key, entries.get(entries.size()-1).getKey()) >= 0) {return children.get(children.size()-1).mode_X_(key);//否则沿比key大的前一个子节点继续搜索} else {for (int i = 0; i < entries.size(); i++) {if (comparator.compare(entries.get(i).getKey(), key) <= 0&& comparator.compare(entries.get(i+1).getKey(), key) > 0) {return children.get(i).mode_X_(key);}}}}return null;}//X*Y格式的通配符查询 -> Y$X*private List<Item> modeX_Y(String key) {//如果是叶子节点if (isLeaf) {Node node = this;int firstLargerPos = 0;int flag = 0; //用于控制循环的标志位,确保循环最多执行2次while (flag < 2) {firstLargerPos = 0;//找到第一个大于等于key的元素的下标for (; firstLargerPos < node.entries.size(); firstLargerPos++) {if (comparator.compare(node.entries.get(firstLargerPos).getKey(), key) >= 0) {flag = 2;break;}}node = node.next;flag++;}node = node.previous;if (firstLargerPos < node.entries.size()) {List<Item> list = new ArrayList<>();while (node.entries.get(firstLargerPos).getKey().startsWith(key)) {list.add(node.entries.get(firstLargerPos).getValue());firstLargerPos++;if (firstLargerPos >= node.entries.size()) {node = node.next;firstLargerPos = 0;}}return list;}//未找到所要查询的对象return null;//如果不是叶子节点} else {//如果key小于等于节点最左边的key,沿第一个子节点继续搜索if (comparator.compare(key, entries.get(0).getKey()) <= 0) {return children.get(0).modeX_Y(key);//如果key大于节点最右边的key,沿最后一个子节点继续搜索} else if (comparator.compare(key, entries.get(entries.size()-1).getKey()) >= 0) {return children.get(children.size()-1).modeX_Y(key);//否则沿比key大的前一个子节点继续搜索} else {for (int i = 0; i < entries.size(); i++) {if (comparator.compare(entries.get(i).getKey(), key) <= 0&& comparator.compare(entries.get(i+1).getKey(), key) > 0) {return children.get(i).modeX_Y(key);}}}}return null;}public List<Item> rangeFind(String key, VagueMode mode) {if (mode == VagueMode.X_) { //X*格式 -> X*$return modeX_(key.substring(0, key.lastIndexOf("*")));} else if (mode == VagueMode._X) { //*X格式 -> X$*return mode_X(key);} else if (mode == VagueMode._X_) { //*X*格式 -> X*return mode_X_(key);} else if (mode == VagueMode.X_Y) { //X*Y格式 -> Y$X*return modeX_Y(key);}return null;}public Item get(String key) {//如果是叶子节点if (isLeaf) {for (Map.Entry<String, Item> entry : entries) {if (comparator.compare(entry.getKey(), key) == 0) {//返回找到的对象return entry.getValue();}}//未找到所要查询的对象return null;//如果不是叶子节点} else {//如果key小于等于节点最左边的key,沿第一个子节点继续搜索if (comparator.compare(key, entries.get(0).getKey()) <= 0) {return children.get(0).get(key);//如果key大于节点最右边的key,沿最后一个子节点继续搜索} else if (comparator.compare(key, entries.get(entries.size()-1).getKey()) >= 0) {return children.get(children.size()-1).get(key);//否则沿比key大的前一个子节点继续搜索} else {for (int i = 0; i < entries.size(); i++) {if (comparator.compare(entries.get(i).getKey(), key) <= 0&& comparator.compare(entries.get(i+1).getKey(), key) > 0) {return children.get(i).get(key);}}}}return null;}public void insertOrUpdate(String key, Item item, BplusTree tree){//如果是叶子节点if (isLeaf){//不需要分裂,直接插入或更新if (contains(key) || entries.size() < tree.getOrder()){insertOrUpdate(key, item);if (parent != null) {//更新父节点parent.updateInsert(tree);}//需要分裂} else {//分裂成左右两个节点Node left = new Node(true);Node right = new Node(true);//设置链接if (previous != null){previous.setNext(left);left.setPrevious(previous);}if (next != null) {next.setPrevious(right);right.setNext(next);}if (previous == null){tree.setHead(left);}left.setNext(right);right.setPrevious(left);previous = null;next = null;//左右两个节点关键字长度int leftSize = (tree.getOrder() + 1) / 2 + (tree.getOrder() + 1) % 2;int rightSize = (tree.getOrder() + 1) / 2;//复制原节点关键字到分裂出来的新节点insertOrUpdate(key, item);for (int i = 0; i < leftSize; i++){left.getEntries().add(entries.get(i));}for (int i = 0; i < rightSize; i++){right.getEntries().add(entries.get(leftSize + i));}//如果不是根节点if (parent != null) {//调整父子节点关系int index = parent.getChildren().indexOf(this);parent.getChildren().remove(this);left.setParent(parent);right.setParent(parent);parent.getChildren().add(index,left);parent.getChildren().add(index + 1, right);setEntries(null);setChildren(null);//父节点插入或更新关键字parent.updateInsert(tree);setParent(null);//如果是根节点} else {isRoot = false;Node parent = new Node(false, true);tree.setRoot(parent);left.setParent(parent);right.setParent(parent);parent.getChildren().add(left);parent.getChildren().add(right);setEntries(null);setChildren(null);//更新根节点parent.updateInsert(tree);}}//如果不是叶子节点} else {//如果key小于等于节点最左边的key,沿第一个子节点继续搜索if (comparator.compare(key, entries.get(0).getKey()) <= 0) {children.get(0).insertOrUpdate(key, item, tree);//如果key大于节点最右边的key,沿最后一个子节点继续搜索} else if (comparator.compare(key, entries.get(entries.size()-1).getKey()) >= 0) {children.get(children.size()-1).insertOrUpdate(key, item, tree);//否则沿比key大的前一个子节点继续搜索} else {for (int i = 0; i < entries.size(); i++) {if (comparator.compare(entries.get(i).getKey(), key) <= 0&& comparator.compare(entries.get(i+1).getKey(), key) > 0) {children.get(i).insertOrUpdate(key, item, tree);break;}}}}}/** 插入节点后中间节点的更新 */protected void updateInsert(BplusTree tree){validate(this, tree);//如果子节点数超出阶数,则需要分裂该节点if (children.size() > tree.getOrder()) {//分裂成左右两个节点Node left = new Node(false);Node right = new Node(false);//左右两个节点关键字长度int leftSize = (tree.getOrder() + 1) / 2 + (tree.getOrder() + 1) % 2;int rightSize = (tree.getOrder() + 1) / 2;//复制子节点到分裂出来的新节点,并更新关键字for (int i = 0; i < leftSize; i++){left.getChildren().add(children.get(i));left.getEntries().add(new AbstractMap.SimpleEntry(children.get(i).getEntries().get(0).getKey(), null));children.get(i).setParent(left);}for (int i = 0; i < rightSize; i++){right.getChildren().add(children.get(leftSize + i));right.getEntries().add(new AbstractMap.SimpleEntry(children.get(leftSize + i).getEntries().get(0).getKey(), null));children.get(leftSize + i).setParent(right);}//如果不是根节点if (parent != null) {//调整父子节点关系int index = parent.getChildren().indexOf(this);parent.getChildren().remove(this);left.setParent(parent);right.setParent(parent);parent.getChildren().add(index,left);parent.getChildren().add(index + 1, right);setEntries(null);setChildren(null);//父节点更新关键字parent.updateInsert(tree);setParent(null);//如果是根节点}else {isRoot = false;Node parent = new Node(false, true);tree.setRoot(parent);left.setParent(parent);right.setParent(parent);parent.getChildren().add(left);parent.getChildren().add(right);setEntries(null);setChildren(null);//更新根节点parent.updateInsert(tree);}}}/** 调整节点关键字*/protected static void validate(Node node, BplusTree tree) {// 如果关键字个数与子节点个数相同if (node.getEntries().size() == node.getChildren().size()) {for (int i = 0; i < node.getEntries().size(); i++) {String key = node.getChildren().get(i).getEntries().get(0).getKey();if (comparator.compare(node.getEntries().get(i).getKey(), key) != 0) {node.getEntries().remove(i);node.getEntries().add(i, new AbstractMap.SimpleEntry(key, null));if(!node.isRoot()){validate(node.getParent(), tree);}}}// 如果子节点数不等于关键字个数但仍大于M / 2并且小于M,并且大于2} else if (node.isRoot() && node.getChildren().size() >= 2||node.getChildren().size() >= tree.getOrder() / 2&& node.getChildren().size() <= tree.getOrder()&& node.getChildren().size() >= 2) {node.getEntries().clear();for (int i = 0; i < node.getChildren().size(); i++) {String key = node.getChildren().get(i).getEntries().get(0).getKey();node.getEntries().add(new AbstractMap.SimpleEntry(key, null));if (!node.isRoot()) {validate(node.getParent(), tree);}}}}/** 判断当前节点是否包含该关键字*/protected boolean contains(String key) {for (Map.Entry<String, Item> entry : entries) {if (comparator.compare(entry.getKey(), key) == 0) {return true;}}return false;}/** 插入到当前节点的关键字中*/protected void insertOrUpdate(String key, Item obj){Map.Entry<String, Item> entry = new AbstractMap.SimpleEntry<>(key, obj);//如果关键字列表长度为0,则直接插入if (entries.size() == 0) {entries.add(entry);return;}//否则遍历列表for (int i = 0; i < entries.size(); i++) {//如果该关键字键值已存在,则更新if (comparator.compare(entries.get(i).getKey(), key) == 0) {entries.get(i).setValue(obj);return;//否则插入} else if (comparator.compare(entries.get(i).getKey(), key) > 0){//插入到链首if (i == 0) {entries.add(0, entry);return;//插入到中间} else {entries.add(i, entry);return;}}}//插入到末尾entries.add(entries.size(), entry);}public Node getPrevious() {return previous;}public void setPrevious(Node previous) {this.previous = previous;}public Node getNext() {return next;}public void setNext(Node next) {this.next = next;}public boolean isLeaf() {return isLeaf;}public void setLeaf(boolean isLeaf) {this.isLeaf = isLeaf;}public Node getParent() {return parent;}public void setParent(Node parent) {this.parent = parent;}public List<Map.Entry<String, Item>> getEntries() {return entries;}public void setEntries(List<Map.Entry<String, Item>> entries) {this.entries = entries;}public List<Node> getChildren() {return children;}public void setChildren(List<Node> children) {this.children = children;}public boolean isRoot() {return isRoot;}public void setRoot(boolean isRoot) {this.isRoot = isRoot;}
}

4.构建B+树索引和轮排索引

public BplusTree exp3() {//获取到实验1得到的倒排索引字典Experiment1 exp1 = new Experiment1();LinkedList<Item> dictionary = exp1.exp1();//去除字典中的停用词Experiment2.delStopWord(dictionary);//构建轮排 + B+树索引结构long startTime = System.currentTimeMillis(); //获取开始时间BplusTree bPlusTree = new BplusTree(4);Iterator<Item> iterator = dictionary.iterator();while (iterator.hasNext()) {Item item = iterator.next();String cur_term = item.term + "$";for (int i = 0; i < cur_term.length() - 1; i++) {bPlusTree.insertOrUpdate(cur_term, item);cur_term = cur_term.substring(1) + cur_term.charAt(0);}}long endTime = System.currentTimeMillis(); //获取结束时间System.out.println("构建轮排 + B+树索引结构时间:" + (endTime - startTime) + "ms"); //输出构建轮排 + B+树索引结构时间return bPlusTree;
}

5.查询处理及调用过程

public class Experiment3 {private Scanner scanner;public static void main(String[] args) {//        String input = "竞*";
//        String input = "*赛";
//        String input = "*人*";
//        String input = "中华*共和国";
//        String input = "中*共*国";
//        String input = "社会*义";Experiment3 experiment3 = new Experiment3();BplusTree bPlusTree = experiment3.exp3(); //轮排 + B+树索引结构System.out.println("\n----------------------");System.out.println("请输入搜索词:(退出请输入-1)");//获取用户输入experiment3.scanner = new Scanner(System.in);String input = experiment3.scanner.nextLine().trim();experiment3.query(input, bPlusTree);}//支持通配符的查询public void query(String input, BplusTree bPlusTree) {while (!input.equals("-1")) {long startTime = System.nanoTime(); //获取开始时间List<Item> itemList;//含通配符的查询String key = input;if (!key.contains("*")) { //关键字中不含*,精确查询itemList = new ArrayList<>();Item item = bPlusTree.get(key + "$");itemList.add(item);} else { //关键字中含有*,通配符查询//判断关键字的格式if (key.startsWith("*")) {if (key.endsWith("*")) { //*X*格式itemList = bPlusTree.rangeFind(key.substring(1, key.length() - 1), VagueMode._X_);} else { //*X格式itemList = bPlusTree.rangeFind(key.substring(1) + "$", VagueMode._X);}} else if (key.endsWith("*")) { //X*格式itemList = bPlusTree.rangeFind(key, VagueMode.X_);} else { //X*Y格式String[] strings = key.split("\\*");key = strings[strings.length - 1] + "$" + strings[0]; //Y$XitemList = bPlusTree.rangeFind(key, VagueMode.X_Y);}}long endTime = System.nanoTime(); //获取结束时间System.out.println("查询[" + input + "]所用时间:" + (double)(endTime - startTime)/1000 + "us"); //输出查询时间if (itemList != null) {for (Item item1 : itemList) {System.out.print(item1.term + " : ");LinkedList<Item_ori> list = item1.ori_item_list;for (int i = 0; i < list.size(); i++) {if (i != list.size() - 1) {System.out.print(list.get(i).docId + " -> ");} else {System.out.print(list.get(i).docId);}}System.out.print("\n");}}System.out.println("----------------------");System.out.println("请输入搜索词:(退出请输入-1)");input = scanner.nextLine().trim();}}
}

这里只实现了B+树和轮排索引,有兴趣的同学还可以尝试B+树和k-gram索引的方式。

现在,我们可以通过搜索词拿到查询到的DocId了,但并未对查询结果进行一个排序展示(相关度高的排在前面)。
下一篇文章,【信息检索】Java简易搜索引擎原理及实现(四)利用布尔模型和向量模型计算权值
我们将对查询结果相关度进行计算,为后面查询的展示做好准备。

【信息检索】Java简易搜索引擎原理及实现(三)B+树索引和轮排索引结构相关推荐

  1. 【信息检索】Java简易搜索引擎原理及实现(一)建立倒排索引

    先放一张最终实现的效果图吧,免得没人看哈哈. 最终做的是学院网站的一个搜索引擎,支持精确查询和通配符查询.同时,提供了分页功能,每页展示15条数据. 对于每条查询结果,支持查询相似文档(相似度> ...

  2. java树算法_Java数据结构算法(三)树

    本文旨作于收集整理使用!! 导航 一.树 树(Tree)是n(n≥0)个结点的有限集,n=0称之为空树.在非空树种:当有且仅有一个特定的称为根(Root)的结点: 其余结点可以划分为m(m>0) ...

  3. Java项目---搜索引擎

    搜索引擎网页url : 链接 项目已上传gitee : 链接 一 : 项目相关背景 当我们输入搜索内容后,会展示出搜索结果页面: 搜索引擎获取页面的方式主要涉及"爬虫"这样的程序. ...

  4. 用 Golang 写一个搜索引擎(0x07)--- 正排索引

    最近各种技术盛会太多,朋友圈各种刷屏,有厂商发的各种广告,有讲师发的各种自拍,各种参会的朋友们各种自拍,好不热闹,不知道你的朋友圈是不是也是这样啊,去年还没这么多技术会议,今年感觉爆发了,呵呵,真是一 ...

  5. 用Golang写一个搜索引擎(0x07)--- 正排索引

    最近各种技术盛会太多,朋友圈各种刷屏,有厂商发的各种广告,有讲师发的各种自拍,各种参会的朋友们各种自拍,好不热闹,不知道你的朋友圈是不是也是这样啊,去年还没这么多技术会议,今年感觉爆发了,呵呵,真是一 ...

  6. java反射原理三种,java反射的原理、作用

    1.什么是反射,反射原理java反射的原理:java类的执行需要经历以下过程,编译:.java文件编译后生成.class字节码文件 加载:类加载器负责根据一个类的全限定名来读取此类的二进制字节流到JV ...

  7. 学习笔记之搜索引擎—原理、技术与系统

    搜索引擎 - 原理.技术与系统 Search Engine: Principle, Technology and Systems  李晓明 闫宏飞 王继民 著 by Li Xiaoming, Yan ...

  8. Java简易实现凯撒密码——英文句子加密

    Java简易实现凯撒密码--英文句子加密 今天要讲的是凯撒密码,它是一种替换加密的技术,明文中的所有字母都在字母表上向后(或向前)按照一个固定数目进行偏移后被替换成密文.例如,当偏移量是3的时候,所有 ...

  9. 基于java的搜索引擎系统设计与实现(项目报告+开题报告+答辩PPT+源代码+数据库+部署视频)

    项目报告 基于Java的搜索引擎的设计与实现 我们处在一个大数据的时代,伴随着网络信息资源的庞大,人们越来越多地注重怎样才能快速有效地从海量的网络信息中,检索出自己需要的.潜在的.有价值的信息,从而可 ...

最新文章

  1. linux php curl 安装包下载,linux中php如何安装CURL扩展方法
  2. 设计模式6——创建型模式之原型模式
  3. git-fork下来的项目(拷贝到本地 根据原来的库更新)
  4. 【ARM】Tiny4412裸板编程之按键(C语言)
  5. java 复制剪贴板_java_swing复制粘贴、剪贴板
  6. ArrayBlockingQueue原理分析-take方法
  7. program collections
  8. 吴恩达深度学习4.2练习_Convolutional Neural Networks_Residual Networks
  9. AngularJS 的异步服务测试与Mocking
  10. (四)洞悉linux下的Netfilteramp;iptables:包过滤子系统iptable_filter
  11. iOS navigationBar导航栏底部与self.view的分界线的隐藏
  12. 怎样的学术导师是好导师(Nature)
  13. Windows下sqlmap安装方法
  14. 普林斯顿微积分读本(修订版)
  15. 远程读取mysql_远程获取数据库和文件的方法
  16. 新生儿婴幼儿宝宝护理知识学习
  17. 打印机设置纵向打印,打印出来确实横向
  18. 视频压缩后大小没变怎么办?视频压缩后大小没变是为什么?
  19. 面试 以及面试中对公司更好的了解
  20. Android开发:微信平台应用申请

热门文章

  1. Gitlab集成Sonarqube实现自动检测代码并发送报告给提交者
  2. OS_PV操作_4.过独木桥问题
  3. 怎么将计算机的触摸鼠标锁定,戴尔笔记本触摸鼠标怎么锁定
  4. html5源码笔记(四)【爱创课堂专业前端培训】
  5. 开源电子商城系统:罗列几个电子商城系统,和一个不错的开源电子商城项目:mall,先做技术调研,主要还是学习代码。
  6. 参加项目管理培训的一些体会
  7. 土方量方lisp_时隔3年,再做双倍超立方数的题目,这次用Lisp
  8. 几则小故事(网上收集)
  9. 宝付正式执行“适当降低小微企业支付手续费”工作部署任务
  10. oeasy教您玩转linux 010211 牛说 cowsay