原创不易,转载请标明地址,或者直接附上我的博客首页https://georgedage./
上篇博客我们说到,数据库为什么不适合搜索引擎的底层存储?,那么什么适合呢?
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
英文好分(有空格),中文则不好分。但一定得要分,否则无法建立反向索引。就必须写一套专门]的程序来做这个事情:分词器
八、分词器和自然语言之间的关系
一般来说,每门语言都有其对应的分词器,毕竟整个地球上,大家的语言并不相同。
九、如果要开发一个中文分词器,你觉得该怎么实现对一句话进行分词?
》为什么我们不会分出:张三、说的、的确、确实、实在、在理?
因为我们的大脑可以进行歧义分析。
中文分词器原理:有个词的字典,对语句前后字进行组合,与字典匹配,歧义分析
十、接下来就是我们的重头戏,手写中文分词器
项目结构:
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>();//红黑树的实现//从文件加载字典到treeMapthis.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
乔治大哥是一个很优秀的博主
结果展示:
最后
有什么想聊的,欢迎留言!欢迎吐槽!哈哈