原创不易,转载请标明地址,或者直接附上我的博客首页https://georgedage.blog.csdn.net/
上篇博客我们说到,数据库为什么不适合搜索引擎的底层存储?,那么什么适合呢?
elasticsearch / solr
那么为什么搜索引擎适合呢?搜索引擎有什么优点呢?下面我们根据提出问题,由浅及深的进行探讨!!!
一、首先分析问题
我们查询时,输入的是苍老师,想要得到标题或内容中包含“苍老师”的新闻列表。怎么办?
有同学会提出,如果标题、内容列上都有一个这样的索引,里面能快速找到与苍老师关键字对应的文章id,再根据文章id就可以快速找到文章了。
二、那么你认为这个索引是什么样的结构呢
在这里,词到文章的索引,我们就称之为倒排索引!!! 也就是搜索引擎的精髓所在。
三、为什么称它为倒排索引?
其实说个秘密,哈哈,也不算秘密,倒排索引英文全名:Inverted Index,然后被国人翻译失败了,翻译成倒排索引,其实它真正的名字应该是反向索引。
那么反向索引还是索引吗?,从这个词上,你或许就能猜到。反向索引本质上其实还是索引,并无特别。
就上面两个图,我们能否将其合并。也就是说
四、上面的两个索引可以合并在一起吗
答案很显然,当然是可以的。
我们自己内心提出问题,为什么要这样做,为什么博主我会说到给两个索引合并在一起。这样做有什么好处?
这个做一个小问题,大家可以思考思考!
五、反向索引的记录数会不会很大
》如果是英文,最大是多少?
》如果是中文,最大是多少?
找了一份资料,资料显示:
所以,我们给出结论:量不会很大,100万以内;通过这个索引找文章会很快。
六、如何建立一个这样的索引
数据示例
新闻id:1
新闻标题:georgedage与乔治一起带球
新闻内容:2025年2月21日,georgedage来到了洛杉矶参加乔治的告别演出,并与乔治进行打球。
》怎样为上面的新闻文章建立反向索引? 一句话怎么分成多个词?人能分,计算机能不能分?
这里我们就引入第二件要说的分词器,也就是我们安装es的时候,提到会安装ik分词器。分词器的原理很简单,这里就是为了告诉大家,你也可以手写分词器!!!
七、分词器原理揭秘——如何建立一个这样的索引
》如果是英文文章,好不好分?
It’sone thing to find the 10 best documents to match your query
英文好分(有空格),中文则不好分。但一定得要分,否则无法建立反向索引。就必须写一套专门]的程序来做这个事情:分词器
八、分词器和自然语言之间的关系
一般来说,每门语言都有其对应的分词器,毕竟整个地球上,大家的语言并不相同。
九、如果要开发一个中文分词器,你觉得该怎么实现对一句话进行分词?
![](https://img-blog.csdnimg.cn/20200221163308884.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzQxOTQ2NTU3,size_16,color_FFFFFF,t_70)
》为什么我们不会分出:张三、说的、的确、确实、实在、在理?
因为我们的大脑可以进行歧义分析。
中文分词器原理:有个词的字典,对语句前后字进行组合,与字典匹配,歧义分析
十、接下来就是我们的重头戏,手写中文分词器
项目结构:
Tokenizer
package com.jd.search;
import java.io.BufferedReader;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.*;
public class Tokenizer {
private Map<Character,Object> dictionary;
public Tokenizer(String dictionaryFilePath) throws IOException{
dictionary = new TreeMap<Character, Object>();//红黑树的实现
//从文件加载字典到treeMap
this.loadDictionary(dictionaryFilePath);
}
private void loadDictionary(String dictionaryFilePath) throws IOException {
BufferedReader reader = new BufferedReader(new InputStreamReader(new FileInputStream(dictionaryFilePath)));
String line = null;
while((line = reader.readLine()) != null){
line = line.trim();
if (line.length() == 0){
continue;
}
char c;
Map<Character,Object> child = this.dictionary;
//组成以这个字符开头的词的树
for (int i = 0; i < line.length(); i++) {
c = line.charAt(i);
Map<Character,Object> ccMap = (Map<Character, Object>) child.get(c);
if (ccMap == null){
ccMap = new HashMap<Character, Object>();
child.put(c,ccMap);
}
child = ccMap;
}
child.put(' ',null);
}
}
public List<String> participle(String text) {
if (text == null){
return null;
}
text = text.trim();
if (text.length() == 0){
return null;
}
List<String> tokens = new ArrayList<String>();
char c;
for (int i = 0; i < text.length();) {
StringBuilder token = new StringBuilder();
Map<Character,Object> child = this.dictionary;
boolean matchToken = false;
for (int j = i; j < text.length(); j++) {
c = text.charAt(j);
Map<Character,Object> ccMap = (Map<Character, Object>) child.get(c);
if (ccMap == null){
if (child.containsKey(' ')){
matchToken = true;
i = j;
}
break;
}else {
token.append(c);
child = ccMap;
}
}
if (matchToken){
tokens.add(token.toString());
}else {
if (child.containsKey(' ')){
tokens.add(token.toString());
break;
}else {
tokens.add("" + text.charAt(i));
i++;
}
}
}
return tokens;
}
public static void main(String[] args) throws IOException {
Tokenizer tk = new Tokenizer(Tokenizer.class.getResource("/dictionary.txt").getPath());
List<String> tokens = tk.participle("乔治大哥是一个很优秀的博主");
for (String s:
tokens) {
System.out.println(s);
}
}
}
dictionary.txt
乔治大哥
是
一个
很
优秀
的
博主
结果展示:
最后
有什么想聊的,欢迎留言!欢迎吐槽!哈哈