搜索引擎——反向索引原理揭秘及手寫ik分詞器

原創(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)呢

image
image.gif

?

image
image.gif

?

在這里,詞到文章的索引,我們就稱之為倒排索引?。?! 也就是搜索引擎的精髓所在。


三、為什么稱它為倒排索引?

其實說個秘密,哈哈,也不算秘密,倒排索引英文全名:Inverted Index,然后被國人翻譯失敗了,翻譯成倒排索引,其實它真正的名字應(yīng)該是反向索引。

那么反向索引還是索引嗎?,從這個詞上,你或許就能猜到。反向索引本質(zhì)上其實還是索引,并無特別。

image
image.gif

?

就上面兩個圖,我們能否將其合并。也就是說


四、上面的兩個索引可以合并在一起嗎

image
image.gif

?

答案很顯然,當(dāng)然是可以的。

我們自己內(nèi)心提出問題,為什么要這樣做,為什么博主我會說到給兩個索引合并在一起。這樣做有什么好處?

這個做一個小問題,大家可以思考思考!


五、反向索引的記錄數(shù)會不會很大

》如果是英文,最大是多少?

》如果是中文,最大是多少?

找了一份資料,資料顯示:

image
image.gif

?

所以,我們給出結(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)對一句話進行分詞?

image
image.gif

?

》為什么我們不會分出:張三、說的、的確、確實、實在、在理?

因為我們的大腦可以進行歧義分析。
中文分詞器原理:有個詞的字典,對語句前后字進行組合,與字典匹配,歧義分析


十、接下來就是我們的重頭戲,手寫中文分詞器

項目結(jié)構(gòu):

image
image.gif

?

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);
        }

    }

}

image.gif

dictionary.txt

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

結(jié)果展示:

image
image.gif

?

最后

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

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容