原創(chuàng)不易,轉(zhuǎn)載請標(biāo)明地址,或者直接附上我的博客首頁https://georgedage.blog.csdn.net/
上篇博客我們說到,數(shù)據(jù)庫為什么不適合搜索引擎的底層存儲?,那么什么適合呢?
elasticsearch / solr
那么為什么搜索引擎適合呢?搜索引擎有什么優(yōu)點呢?下面我們根據(jù)提出問題,由淺及深的進行探討?。?!
一、首先分析問題
我們查詢時,輸入的是蒼老師,想要得到標(biāo)題或內(nèi)容中包含“蒼老師”的新聞列表。怎么辦?
有同學(xué)會提出,如果標(biāo)題、內(nèi)容列上都有一個這樣的索引,里面能快速找到與蒼老師關(guān)鍵字對應(yīng)的文章id,再根據(jù)文章id就可以快速找到文章了。
二、那么你認(rèn)為這個索引是什么樣的結(jié)構(gòu)呢

?

?
在這里,詞到文章的索引,我們就稱之為倒排索引?。?! 也就是搜索引擎的精髓所在。
三、為什么稱它為倒排索引?
其實說個秘密,哈哈,也不算秘密,倒排索引英文全名:Inverted Index,然后被國人翻譯失敗了,翻譯成倒排索引,其實它真正的名字應(yīng)該是反向索引。
那么反向索引還是索引嗎?,從這個詞上,你或許就能猜到。反向索引本質(zhì)上其實還是索引,并無特別。

?
就上面兩個圖,我們能否將其合并。也就是說
四、上面的兩個索引可以合并在一起嗎


?
答案很顯然,當(dāng)然是可以的。
我們自己內(nèi)心提出問題,為什么要這樣做,為什么博主我會說到給兩個索引合并在一起。這樣做有什么好處?
這個做一個小問題,大家可以思考思考!
五、反向索引的記錄數(shù)會不會很大
》如果是英文,最大是多少?
》如果是中文,最大是多少?
找了一份資料,資料顯示:


?
所以,我們給出結(jié)論:量不會很大,100萬以內(nèi);通過這個索引找文章會很快。
六、如何建立一個這樣的索引
數(shù)據(jù)示例
新聞id:1
新聞標(biāo)題:georgedage與喬治一起帶球
新聞內(nèi)容:2025年2月21日,georgedage來到了洛杉磯參加喬治的告別演出,并與喬治進行打球。
》怎樣為上面的新聞文章建立反向索引? 一句話怎么分成多個詞?人能分,計算機能不能分?
這里我們就引入第二件要說的分詞器,也就是我們安裝es的時候,提到會安裝ik分詞器。分詞器的原理很簡單,這里就是為了告訴大家,你也可以手寫分詞器?。?!
七、分詞器原理揭秘——如何建立一個這樣的索引
》如果是英文文章,好不好分?
It’sone thing to find the 10 best documents to match your query
英文好分(有空格),中文則不好分。但一定得要分,否則無法建立反向索引。就必須寫一套專門]的程序來做這個事情:分詞器
八、分詞器和自然語言之間的關(guān)系
一般來說,每門語言都有其對應(yīng)的分詞器,畢竟整個地球上,大家的語言并不相同。
九、如果要開發(fā)一個中文分詞器,你覺得該怎么實現(xiàn)對一句話進行分詞?

?
》為什么我們不會分出:張三、說的、的確、確實、實在、在理?
因為我們的大腦可以進行歧義分析。
中文分詞器原理:有個詞的字典,對語句前后字進行組合,與字典匹配,歧義分析
十、接下來就是我們的重頭戲,手寫中文分詞器
項目結(jié)構(gòu):

?
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>();//紅黑樹的實現(xiàn)
//從文件加載字典到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("喬治大哥是一個很優(yōu)秀的博主");
for (String s:
tokens) {
System.out.println(s);
}
}
}

dictionary.txt
喬治大哥
是
一個
很
優(yōu)秀
的
博主

結(jié)果展示:

?
最后
有什么想聊的,歡迎留言!歡迎吐槽!哈哈