从01开始 从01开始
首页
  • 计算机科学导论
  • 数字电路
  • 计算机组成原理

    • 计算机组成原理-北大网课
  • 操作系统
  • Linux
  • Docker
  • 计算机网络
  • 计算机常识
  • Git
  • JavaSE
  • Java高级
  • JavaEE

    • Ant
    • Maven
    • Log4j
    • Junit
    • JDBC
    • XML-JSON
  • JavaWeb

    • 服务器软件
    • Servlet
  • Spring
  • 主流框架

    • Redis
    • Mybatis
    • Lucene
    • Elasticsearch
    • RabbitMQ
    • MyCat
    • Lombok
  • SpringMVC
  • SpringBoot
  • 学习网课的心得
  • 输入法
  • 节假日TodoList
  • 其他
  • 关于本站
  • 网站日记
  • 友人帐
  • 如何搭建一个博客
GitHub (opens new window)

peterjxl

人生如逆旅,我亦是行人
首页
  • 计算机科学导论
  • 数字电路
  • 计算机组成原理

    • 计算机组成原理-北大网课
  • 操作系统
  • Linux
  • Docker
  • 计算机网络
  • 计算机常识
  • Git
  • JavaSE
  • Java高级
  • JavaEE

    • Ant
    • Maven
    • Log4j
    • Junit
    • JDBC
    • XML-JSON
  • JavaWeb

    • 服务器软件
    • Servlet
  • Spring
  • 主流框架

    • Redis
    • Mybatis
    • Lucene
    • Elasticsearch
    • RabbitMQ
    • MyCat
    • Lombok
  • SpringMVC
  • SpringBoot
  • 学习网课的心得
  • 输入法
  • 节假日TodoList
  • 其他
  • 关于本站
  • 网站日记
  • 友人帐
  • 如何搭建一个博客
GitHub (opens new window)
  • JavaSE

  • JavaSenior

  • JavaEE

  • JavaWeb

  • Spring

  • 主流框架

    • Redis

    • Mybatis

    • Lucene

      • 全文检索的概念
      • Lucene概述
      • Lucene入门案例
      • 分析器
        • 什么是分析器
        • 使用分析器
        • 标准分析器和中文
        • 中文分析器IKAnalyzer
        • 自定义词典
        • 使用中文分析器创建索引
        • 源码
      • 常见的Field
      • 索引库的维护
      • Lucene索引库查询
      • Lucene
    • Elasticsearch

    • MQ

    • MyCat

    • Lombok

    • 主流框架
  • SpringMVC

  • SpringBoot

  • Java并发

  • Java源码

  • JVM

  • 韩顺平

  • Java
  • Java
  • 主流框架
  • Lucene
2023-05-16
目录

分析器

# 30.分析器

接下来我们讲一个我们略过的重要的对象:分析器   ‍

# 什么是分析器

作用:即分析文档,去除停用词和标点符号等。默认使用的是标准分析器StandardAnalyzer。

‍

我们之前构造索引的时候,使用的是默认配置IndexWriterConfig:

IndexWriter indexWriter = new IndexWriter(directory, new IndexWriterConfig());
1

‍

‍

查看IndexWriterConfig类​ 的源码,构造方法是这样的:默认使用StandardAnalyzer

public IndexWriterConfig() {
    this(new StandardAnalyzer());
}
1
2
3

‍

标准分析器,对英文的原始文档是没有问题的,怎么看有没问题呢?首先我们得会查看分析器的分析效果:

  • 首先StandardAnalyzer继承了Analyzer,抽象类。所有分析器的类都继承了它
  • 使用Analyzer对象的tokenStream方法返回一个Tokenstream对象。该对象中包含了最终分词结果

‍

# 使用分析器

所以我们想查看分析效果,直接使用并查看即可。实现步骤:

  1. 创建一个Analyzer对象,这里我们使用StandardAnalyzer对象
  2. 使用分析器对象的tokenStream方法获得一个TokenStream对象
  3. 向TokenStream对象中设置一个引用,相当于是一个指针。TokenStream对象包含了所有关键词,类似链表一样排列着,我们用一个指针指向关键词,访问指针就相当于访问关键词
  4. 调用TokenStream对象的rest方法,用处是将指针指向第一个数据(如果不调用会抛异常)
  5. 使用while循环遍历TokenStream对象
  6. 关闭TokenStream

‍

代码如下:这里我们直接指定一段文本给分析器分析。

@Test
public void testTokenSteam() throws Exception {
    // 1. 创建一个Analyzer对象,这里我们使用StandardAnalyzer对象
    Analyzer analyzer = new StandardAnalyzer();

    // 2. 使用分析器对象的tokenStream方法获得一个TokenStream对象
    TokenStream tokenStream = analyzer.tokenStream("", "The Spring Framework provides a comprehensive programming and configuration model.");

    // 3. 向TokenStream对象中设置一个引用,相当于是一个指针。TokenStream对象包含了所有关键词,类似链表一样排列着,我们用一个指针指向关键词,访问指针就相当于访问关键词
    CharTermAttribute charTermAttribute = tokenStream.addAttribute(CharTermAttribute.class);

    // 4. 调用TokenStream对象的rest方法,用处是将指针指向第一个数据(如果不调用会抛异常)
    tokenStream.reset();

    // 5. 使用while循环遍历TokenStream对象
    while (tokenStream.incrementToken()) {  //返回true说明有下一个元素
        System.out.println(charTermAttribute.toString());
    }

    // 6. 关闭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

‍

​analyzer.tokenStream()​方法的第一个参数是Field,由于我们是直接给定文本,这里就留空。运行结果:可以看到去除了 the,a等单词和标点符号

spring
framework
provides
comprehensive
programming
configuration
model
1
2
3
4
5
6
7

‍

‍

# 标准分析器和中文

使用标准分析器,分析英文是没什么问题的;那如果是中文呢?我们可以测试下:

TokenStream tokenStream = analyzer.tokenStream("name", "鲁镇的酒店的格局,是和别处不同的:都是当街一个曲尺形的大柜台,柜里面预备着热水,可以随时温酒。做工的人,傍午傍晚散了工,每每花四文铜钱,买一碗酒,——这是二十多年前的事,现在每碗要涨到十文,——靠柜外站着,热热的喝了休息;倘肯多花一文,便可以买一碟盐煮笋,或者茴香豆,做下酒物了,如果出到十几文,那就能买一样荤菜,但这些顾客,多是短衣帮⑴,大抵没有这样阔绰⑵。只有穿长衫的,才踱进店面隔壁的房子里,要酒要菜,慢慢地坐喝。");
1

‍

运行结果:一个汉字一行,说明一个关键字就是一行,这肯定是不行的。搜索一个汉字可以搜索得到,但搜索词语的话肯定是不能成功的。

​

‍

我们可以验证下,比如我们的原始文档“全文检索.txt”中,有“全文”这个词语,试试能不能搜索成功。我们修改searchIndex​方法中的关键词:

    Query query = new TermQuery(new Term("content", "全文"));
1

‍

运行结果:这是因为一个汉字一个关键字

查询出来的总记录数:0
1

‍

所以标准分析器,在分词中文的时候,是不准确的,我们搜索一般都是输入关键词来搜索。为此我们使用中文分析器。

‍

‍

‍

# 中文分析器IKAnalyzer

虽然Lucene自带了一个中文分析器SmartChineseAnalyzer,对中文支持较好,但扩展性差,扩展词库,禁用词库和同义词库等不好处理。

IK-Analyzer不是官方提供的,而是第三方提供的。我们配置3个文件:

  • IKAnalyzer.cfg.xml:配置文件
  • hotword.dic:扩展词典,里面就是一些中文词语。可以添加一些新词,例如新出现的网络新词,新出现的公司名等
  • stopword.dic:停用词词典,无意义的词,或者敏感词

配置文件可以去我的Gitee (opens new window)或GitHub (opens new window)上下载。

‍

使用方法:

  • 添加依赖

    <dependency>
      <groupId>com.jianggujin</groupId>
      <artifactId>IKAnalyzer-lucene</artifactId>
      <version>7.0.0</version>
    </dependency>
    
    1
    2
    3
    4
    5
  • 添加配置文件、扩展词典和停用词词典,到resources目录下

  • 注意:hotword.dic和ext_stopword.dic文件的格式为UTF-8,注意是无BOM 的UTF-8 编码,否则报错。也就是说禁止使用windows记事本编辑扩展词典文件,该记事本默认是会保存为UTF-8 BOM

  • IKAnalyzer.cfg.xml :配置文件,可以配置多个词典(用分号分割)

    <?xml version="1.0" encoding="UTF-8"?>
    <!DOCTYPE properties SYSTEM "http://java.sun.com/dtd/properties.dtd">  
    <properties>  
    	<comment>IK Analyzer 扩展配置</comment>
    	<!--用户可以在这里配置自己的扩展字典 -->
    	<entry key="ext_dict">hotword.dic;</entry>
    
    	<!--用户可以在这里配置自己的扩展停止词字典-->
    	<entry key="ext_stopwords">stopword.dic;</entry> 
    
    </properties>
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11

‍

‍

测试:关键是第4行,创建的是IKAnalyzer

@Test
public void testIKAnalyzer() throws Exception{
    // 1. 创建一个Analyzer对象,这里我们使用StandardAnalyzer对象
    Analyzer analyzer = new IKAnalyzer();

    // 2. 使用分析器对象的tokenStream方法获得一个TokenStream对象
    TokenStream tokenStream = analyzer.tokenStream("", "鲁镇的酒店的格局,是和别处不同的:都是当街一个曲尺形的大柜台,柜里面预备着热水,可以随时温酒。做工的人,傍午傍晚散了工,每每花四文铜钱,买一碗酒,——这是二十多年前的事,现在每碗要涨到十文,——靠柜外站着,热热的喝了休息;倘肯多花一文,便可以买一碟盐煮笋,或者茴香豆,做下酒物了,如果出到十几文,那就能买一样荤菜,但这些顾客,多是短衣帮⑴,大抵没有这样阔绰⑵。只有穿长衫的,才踱进店面隔壁的房子里,要酒要菜,慢慢地坐喝。");

    // 3. 向TokenStream对象中设置一个引用,相当于是一个指针。TokenStream对象包含了所有关键词,类似链表一样排列着,我们用一个指针指向关键词,访问指针就相当于访问关键词
    CharTermAttribute charTermAttribute = tokenStream.addAttribute(CharTermAttribute.class);

    // 4. 调用TokenStream对象的rest方法,用处是将指针指向第一个数据(如果不调用会抛异常)
    tokenStream.reset();

    // 5. 使用while循环遍历TokenStream对象
    while (tokenStream.incrementToken()) {  //返回true说明有下一个元素
        System.out.println(charTermAttribute.toString());
    }

    // 6. 关闭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

‍

部分运行结果:可以看到是有一些词语的,并且显示加载了两个词典。

​

‍

IKAnalyzer是一个开源的,基于 java 语言开发的轻量级的中文分词工具包,由国人林良益 (opens new window)先生开发,从 2006 年 12 月推出 1.0 版开始。由于林良益先生在2012之后未对IKAnalyzer进行更新,后续版本的Lucene分词接口发生变化,导致不能在Lucene中使用了。

因此我们用的是这个分析器:IKAnalyzer-lucene (opens new window),是基于IKAnalyzer做了兼容性处理的。

‍

# 自定义词典

有时候我们需要添加一些流行语,此时词典是没有的,那么分词就会失败。例如:

​

‍

‍

我们加上了一个流行语“鸡你太美”,但是分词后将其分成了3部分“鸡”,“你”,“太美”,我们可以在hotword.dic中添加这个流行词,然后就能看到正常分词了:

​

‍

停用词同理,这里就不演示了。

‍

‍

# 使用中文分析器创建索引

之前我们说过,创建索引时默认使用的是标准分析器,我们可以自定义使用什么分析器,只需在构造函数里指定即可,代码如下:

@Test
    public void createIndexWithIK() throws Exception {
        //  1. 创建一个Director对象,指定索引库保存的位置。
        Directory directory = FSDirectory.open(new File("D:\\temp\\index").toPath());

        IndexWriterConfig config = new IndexWriterConfig(new IKAnalyzer());
        //  2. 基于Directory对象创建一个IndexWriter对象(用来写索引)
        IndexWriter indexWriter = new IndexWriter(directory, config);

        //  3. 读取磁盘上的文件,对应每个文件创建一个文档对象。
        File dir = new File("D:\\temp\\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);

            // 4. 创建Field
            // 参数1:域的名称, 参数2:域的内容, 参数3:是否存储
            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", String.valueOf(fileSize), Field.Store.YES);

            // 4. 将field添加到document对象中。
            Document document = new Document();
            document.add(fieldName);
            document.add(fieldPath);
            document.add(fieldContent);
            document.add(fieldSize);

            //  5. 使用IndexWriter对象将document对象写入索引库,此过程进行索引创建,并将索引和document对象写入索引库。
            indexWriter.addDocument(document);
        }

        //  6. 关闭IndexWriter对象。
        indexWriter.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

‍

‍

我们先将之前的索引库删掉,然后运行该测试方法,并用Luke重新打开:可以看到有中文的词语了。而使用标准分析器,中文都是一个一个字的

​

‍

# 源码

已将源码上传到Gitee (opens new window)或GitHub (opens new window)上。并且创建了分支demo2IK,读者可以通过切换分支来查看本文的示例代码

‍

‍

在GitHub上编辑此页 (opens new window)
上次更新: 2023/5/16 10:03:58
Lucene入门案例
常见的Field

← Lucene入门案例 常见的Field→

Theme by Vdoing | Copyright © 2022-2023 粤ICP备2022067627号-1 粤公网安备 44011302003646号
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式