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

原创不易,转载请标明地址,或者直接附上我的博客首页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


英文好分(有空格),中文则不好分。但一定得要分,否则无法建立反向索引。就必须写一套专门]的程序来做这个事情:分词器


八、分词器和自然语言之间的关系

一般来说,每门语言都有其对应的分词器,毕竟整个地球上,大家的语言并不相同。


九、如果要开发一个中文分词器,你觉得该怎么实现对一句话进行分词?

 

》为什么我们不会分出:张三、说的、的确、确实、实在、在理?


因为我们的大脑可以进行歧义分析。
中文分词器原理:有个词的字典,对语句前后字进行组合,与字典匹配,歧义分析
 


十、接下来就是我们的重头戏,手写中文分词器

项目结构:

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

乔治大哥
是
一个
很
优秀
的
博主

结果展示:

 

最后 

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

发布了777 篇原创文章 · 获赞 661 · 访问量 12万+

猜你喜欢

转载自blog.csdn.net/qq_41946557/article/details/104429259