• 【Lucene】实现全文索引


    2. Lucene 实现全文检索的流程
    2.1.索引和搜索流程图


    绿色表示索引过程,对要搜索的原始内容进行索引构建一个索引库,索引过程包括:确定原始内容即要搜索的内容 -> 采集文档 -> 创建文档 -> 分析文档 ->索引文档
    红色表示搜索过程,从索引库中搜索内容,搜索过程包括:用户通过搜索界面 -> 创建查询 -> 执行搜索,从索引库搜索 -> 渲染搜索结果
    2.2.创建索引
    步骤:

    获得文档

    原始文档:要基于那些数据来进行搜索,那么这些数据就是原始文档。
    搜索引擎:使用爬虫获得原始文档
    站内搜索:数据库中的数据。
    案例:直接使用 io 流读取磁盘上的文件。
    构建文档对象

    对应每个原始文档创建一个 Document 对象
    每个 document 对象中包含多个域(field)
    域中保存的就是原始文档的数据
    域的名称、域的值
    每个文档都有一个唯一的编号,就是文档id。
    注意:每个Document可以有多个Field,不同的Document可以有不同的Field,同一个Document可以有相同的Field(域名和域值都相同)

    分析文档

    就是分词的过程
    根据空格进行字符串拆分,得到一个单词列表
    把单词统一转换成小写。
    去除标点符号。
    去除停用词(无意义的词)
    每个关键词都封装成一个 Term 对象中
    Term 中包含两部分内容:
    关键词所在的域
    关键词本身
    不同的域中拆分出来的相同的关键词是不同的 Term 。
    创建索引

    基于关键词列表创建一个索引。保存到索引库中。
    索引库中:
    索引
    document 对象
    关键词和文档的对应关系
    通过词语找文档,这种索引的结构叫倒排索引结构。如下图:

    倒排索引结构也叫反向索引结构,包括索引和文档两部分,索引即词汇表,它的规模较小,而文档集合较大。
    2.3.查询索引
    用户查询接口

    用户输入查询条件的地方

    例如:百度的搜索框


    把关键词封装成一个查询对象(创建查询)

    要查询的域
    要搜索的关键词
    执行查询

    根据要查询的关键词到对应的域上进行搜索。
    找到关键词,根据关键词找到 对应的文档
    渲染结果

    根据文档的 id 找到文档的对象
    对关键词进行高亮显示
    分页处理
    最终展示给用户看。
    3.入门程序
    3.1.配置开发环境
    Lucene 下载

    Lucene是开发全文检索功能的工具包,从官方网站下载lucene-7.4.0,并解压。


    官方网站:http://lucene.apache.org/
    版本:lucene-7.4.0
    Jdk要求:1.8以上
    使用的jar包

    ​ lucene-core-7.4.0.jar

    ​ lucene-analyzers-common-7.4.0.jar

    3.2.需求
    实现一个文件的搜索功能,通过关键字搜索文件,凡是文件名或文件内容包括关键字的文件都需要找出来。还可以根据中文词语进行查询,并且需要支持多个条件查询。
    本案例中的原始内容就是磁盘上的文件,如下图:


    3.3.创建索引
    实现步骤:

    第一步:创建一个java工程,并导入jar包。
    第二步:创建一个indexwriter对象。
    ​ 1)指定索引库的存放位置Directory对象
    ​ 2)指定一个IndexWriterConfig对象。
    第二步:创建document对象。
    第三步:创建field对象,将field添加到document对象中。
    第四步:使用indexwriter对象将document对象写入索引库,此过程进行索引创建。并将索引和document对象写入索引库。
    第五步:关闭IndexWriter对象。

    代码实现:

    /**
    * @Auther: lss
    * @Date: 2019/5/7 17:27
    * @Description:
    */
    public class LuceneFirst {

    @Test
    public void createIndex() throws IOException {

    // 创建一个 Directory 对象,指定索引库保存的位置
    // 把索引库保存在内存中
    // Directory directory = new RAMDirectory();
    // 把索引库保存在磁盘中
    Directory directory = FSDirectory.open(new File("D:\IDEA1\lelucene\index").toPath());
    // 基于 Directory 对象创建一个 IndexWriter 对象
    IndexWriter writer = new IndexWriter(directory, new IndexWriterConfig());
    // 读取磁盘上的文件,对应每个文件创建一个文档对象。
    File dir = new File("D:\searchsource");
    File[] files = dir.listFiles();
    for (File file : files) {
    // 取文件名
    String fileName = file.getName();
    // 文件的路径
    String filePath = file.getPath();
    // 文件的路径
    String fileContent = FileUtils.readFileToString(file, "utf-8");
    // 文件的大小
    long fileSize = FileUtils.sizeOf(file);
    // 创建 Field

    Field fieldName = new TextField("name", fileName, Field.Store.YES);
    Field fieldPath = new TextField("path", filePath, Field.Store.YES);
    Field fieldContent = new TextField("content", fileContent, Field.Store.YES);
    Field fieldSize = new TextField("size", fileSize + "", Field.Store.YES);

    // 创建文档对象
    Document document = new Document();
    // 向文档对象中添加域
    document.add(fieldName);
    document.add(fieldPath);
    document.add(fieldContent);
    document.add(fieldSize);
    // 把文档对象写入索引库
    writer.addDocument(document);
    }
    // 关闭 indexWriter 对象
    writer.close();
    }
    }

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    执行测试: 生成的索引库

    这里生成的了一堆看不懂的文件,这些文件没办法用普通的文本编辑器查看,这该怎么办,有个小软件 luck 可以查看索引库。我会把文件上传到下载上。下载

    使用 luke 查看索引库中的内容

    我们使用的luke的版本是luke-7.4.0,跟lucene的版本对应的。可以打开7.4.0版本的lucene创建的索引库。需要注意的是此版本的Luke是jdk9编译的,所以要想运行此工具还需要jdk9才可以(PS:jdk 1.8貌似也是可以的)。

    3.4.查询索引
    实现步骤:

    第一步:创建一个Directory对象,也就是索引库存放的位置。
    第二步:创建一个indexReader对象,需要指定Directory对象。
    第三步:创建一个indexsearcher对象,需要指定IndexReader对象
    第四步:创建一个TermQuery对象,指定查询的域和查询的关键词。
    第五步:执行查询。
    第六步:返回查询结果。遍历查询结果并输出。
    第七步:关闭IndexReader对象

    代码实现:

    @Test
    public void searchIndex() throws Exception {
    // 创建一个 Directory 对象,指定索引库的位置
    Directory directory = FSDirectory.open(new File("D:\IDEA1\lelucene\index").toPath());
    // 创建一个 IndexReader 对象
    IndexReader indexReader = DirectoryReader.open(directory);
    // 创建一个Indexsearcher 对象,构造方法中的参数 indexReader 对象。
    IndexSearcher indexSearcher = new IndexSearcher(indexReader);
    // 创建一个 Query 对象,TermQuery
    Query query = new TermQuery(new Term("name", "spring"));
    // 执行查询,得到一个 TopDocs 对象
    // 参数1:查询对象 参数2:查询结果返回的最大记录数
    TopDocs topDocs = indexSearcher.search(query, 10);
    // 取查询结果的总记录数
    System.out.println("查询总记录数:" + topDocs.totalHits);
    // 取文档列表
    ScoreDoc[] scoreDocs = topDocs.scoreDocs;
    // 打印文档中的内容
    for (ScoreDoc doc : scoreDocs) {
    // 取文档 id
    int docId = doc.doc;
    // 根据 id 取文档对象
    Document document = indexSearcher.doc(docId);
    System.out.println(document.get("name"));
    System.out.println(document.get("path"));
    // System.out.println(document.get("content"));
    System.out.println(document.get("size"));
    System.out.println("------------------华丽的分割线");
    }
    // 关闭 IndexReader 对象
    indexReader.close();
    }
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    执行测试:

    这里没有打印内容,太多了不好截屏,同学们可以自行测试。

    我们使用 luck 查看分词的结果,发现对于中文是不友好的,英文以按照单词分,而中文只能是一个字一个字的,

    搜索英文是没有问题的,中文就不行了,单个字搜索还可以。这就有了问题,这就引入了一个分析器。


    4.分析器
    4.1.分析器的分词效果
    代码实现:

    @Test
    public void testTokenStream() throws Exception {
    // 创建一个标准分析器对象
    Analyzer analyzer = new StandardAnalyzer();
    // 获得tokenStream对象
    // 第一个参数:域名,可以随便给一个
    // 第二个参数:要分析的文本内容
    TokenStream tokenStream = analyzer.tokenStream("", "The Spring Framework provides a comprehensive programming and configuration model.");
    // TokenStream tokenStream = analyzer.tokenStream("", "单字分词:就是按照中文一个字一个字地进行分词。如:“我爱中国”");
    // 添加一个引用,可以获得每个关键词
    CharTermAttribute charTermAttribute = tokenStream.addAttribute(CharTermAttribute.class);
    // 添加一个偏移量的引用,记录了关键词的开始位置以及结束位置
    OffsetAttribute offsetAttribute = tokenStream.addAttribute(OffsetAttribute.class);
    // 将指针调整到列表的头部
    tokenStream.reset();
    // 遍历关键词列表,通过incrementToken方法判断列表是否结束
    while (tokenStream.incrementToken()) {
    // 关键词的起始位置
    // System.out.println("start->" + offsetAttribute.startOffset());
    // 取关键词
    System.out.println(charTermAttribute);
    // 结束位置
    // System.out.println("end->" + offsetAttribute.endOffset());
    }
    // 关闭 tokenStream 对象
    tokenStream.close();
    }
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    **执行测试:**英文

    中文:

    对中文不友好,这不行啊,我们查看源码发现 创建索引库我们没有指定分词器,使用的是默认的分词器 StandardAnalyzer

    下面介绍中文分析器

    4.2.中文分析器
    4.2.1.Lucene 自带中文分词器
    StandardAnalyzer:

    单字分词:就是按照中文一个字一个字地进行分词。如:“我爱中国”,
    效果:“我”、“爱”、“中”、“国”。

    SmartChineseAnalyzer:

    对中文支持较好,但扩展性差,扩展词库,禁用词库和同义词库等不好处理。

    4.2.2.IKAnalyzer

    使用方法:
    ​ 第一步:把jar包添加到工程中
    ​ 第二步:把配置文件和扩展词典和停用词词典添加到classpath下(hotword.dic 和 stopword.dic,配文件 IKAnalyzer.cfg.xml)

    注意:hotword.dic和ext_stopword.dic文件的格式为UTF-8,注意是无BOM 的UTF-8 编码也就是说禁止使用windows记事本编辑扩展词典文件

    使用EditPlus.exe保存为无BOM 的UTF-8 编码格式,如下图:

    -**扩展词典:**添加一些新词

    **停用词词典:**无意义的词或者是敏感词汇

    4.3.使用自定义分析器
    代码实现:

    @Test
    public void addDocument() throws Exception {
    // 创建一个 IndexWriter 对象,需要使用 IKAnalyzer 作为分析器
    Directory directory = FSDirectory.open(new File("D:\IDEA1\lelucene\index").toPath());
    // 创建一个 IndexWriter 对象,需要使用 IKAnalyzer 作为分析器
    IndexWriterConfig indexWriterConfig = new IndexWriterConfig(new IKAnalyzer());
    IndexWriter writer = new IndexWriter(directory, indexWriterConfig);
    // 创建一个 document 对象
    Document document = new Document();

    // 向 document 对象添加域
    document.add(new TextField("name", "新添加的文件", Field.Store.YES));
    document.add(new TextField("content", "新添加的文件内容", Field.Store.NO));
    document.add(new StoredField("path", "C:/temp/hello"));
    // 把文档添写入索引库
    writer.addDocument(document);
    // 关闭索引库
    writer.close();
    }
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19

    使用上面分词效果的代码测试:

    以后我们在创建索引库时,使用 IKAnalyzer 就可以了。
    --------------------- 

  • 相关阅读:
    Cf序列化器-Serializer解析
    yield和return
    pymongo的使用
    Homebrew介绍和使用
    TypeError: expected string or bytes-like object
    JavaScript读取文本,并渲染在html
    反序相等
    打印邮票的组合
    打印对称平方数
    字符串按照原意输出
  • 原文地址:https://www.cnblogs.com/hyhy904/p/10961716.html
Copyright © 2020-2023  润新知