Java Lucene中文分词

Lucene中文分词

基本概念

非结构化数据查询方法

(1)顺序扫描法(Serial Scanning)

所谓顺序扫描,比如要找内容包含某一个字符串的文件,就是一个文档一个文档的看,对于每一个文档,

从头看到尾,如果此文档包含此字符串,则此文档为我们要找的文件,接着看下一个文件,

直到扫描完所有的文件。如利用windows的搜索也可以搜索文件内容,只是相当的慢。

(2)全文检索(Full-text Search)

将非结构化数据中的一部分信息提取出来,重新组织,使其变得有一定结构,然后对此有一定结构的数据进行搜索,

从而达到搜索相对较快的目的。这部分从非结构化数据中提取出的然后重新组织的信息,我们称之索引。

例如:字典。字典的拼音表和部首检字表就相当于字典的索引,对每一个字的解释是非结构化的,

如果字典没有音节表和部首检字表,在茫茫辞海中找一个字只能顺序扫描。然而字的某些信息可以提取出来进行结构化处理,

比如读音,就比较结构化,分声母和韵母,分别只有几种可以一一列举,于是将读音拿出来按一定的顺序排列,

每一项读音都指向此字的详细解释的页数。我们搜索时按结构化的拼音搜到读音,然后按其指向的页数,便可找到我们的非结构化数据——也即对字的解释。

这种先建立索引,再对索引进行搜索的过程就叫全文检索(Full-text Search)。

虽然创建索引的过程也是非常耗时的,但是索引一旦创建就可以多次使用,全文检索主要处理的是查询,所以耗时间创建索引是值得的。

什么是Lucene

Lucene是apache下的一个开放源代码的全文检索引擎工具包。提供了完整的查询引擎和索引引擎,部分文本分析引擎(英文与德文两种西方语言)。

对于数据量大、数据结构不固定的数据可采用全文检索方式搜索,比如百度、Google等搜索引擎、论坛站内搜索、电商网站站内搜索等。

image.png

对搜索的原始内容进行索引构建一个索引库,索引过程包括:确定原始内容即要搜索的内容、采集文档、创建文档、分析文档、索引文档

从索引库中搜索内容,搜索过程包括:用户通过搜索界面、创建查询、执行搜索,从索引库搜索、渲染搜索结果

全文索引

搜索引擎通常检索的场景是:给定几个关键词,找出包含关键词的文档。怎么快速找到包含某个关键词的文档就成为搜索的关键。所以全文检索引擎一般使用倒排索引,如Lucene。

全文正排索引

一句话总结:通过文档找到所有的词,称为正排索引

正排表是以文档的ID为关键字,表中记录文档中每个字的位置信息,查找时扫描表中每个文档中字的信息直到找出所有包含查询关键字的文档。
正排表结构如图所示,这种组织方法在建立索引的时候结构比较简单,建立比较方便且易于维护;因为索引是基于文档建立的,若是有新的文档加入,直接为该文档建立一个新的索引块,挂接在原来索引文件的后面。若是有文档删除,则直接找到该文档号文档对应的索引信息,将其直接删除。但是在查询的时候需对所有的文档进行扫描以确保没有遗漏,这样就使得检索时间大大延长,检索效率低下。
尽管正排表的工作原理非常的简单,但是由于其检索效率太低,除非在特定情况下,否则实用性价值不大。

image.png

全文倒排索引

一句话总结:通过词找到所有文档,称为倒排索引

倒排表以字或词为关键字进行索引,表中关键字所对应的记录表项记录了出现这个字或词的所有文档,一个表项就是一个字表段,它记录该文档的ID和字符在该文档中出现的位置情况。
由于每个字或词对应的文档数量在动态变化,所以倒排表的建立和维护都较为复杂,但是在查询的时候由于可以一次得到查询关键字所对应的所有文档,所以效率高于正排表。在全文检索中,检索的快速响应是一个最为关键的性能,而索引建立由于在后台进行,尽管效率相对低一些,但不会影响整个搜索引擎的效率。

image.png

Lucene的评分机制

  • 结果:(TF-IDF,文档id)
  • 词频:Term Frequency,简称TF——词在文档中出现的频率
  • 逆文档频率:Inverse Document Frequency,简称IDF——词在所有文档中出现的频率越低,说明词越重要

中文分析器

Analyzer替换成能处理中文的分析器

  • 庖丁
  • 盘古
  • IK

实战

主要目标:针对txt小说文件进行关键词查询,关键词为泡沫 贷款

步骤分解

1.明确查询范围,准备基础数据,并整理到目录下

image.png

【注意】使用IK Analyzer要保证文档是UTF_8编码格式

2.创建索引生成器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
/**
* 创建索引生成器
*
* @return
* @throws IOException
*/
private IndexWriter createWriter() throws IOException {

// 指定索引生成目录并打开
var path = Paths.get(INDEX_DIR);
var dir = FSDirectory.open(path);

// 指定词法分析器
var analyzer = new IKAnalyzer();

// 建造者模式生成配置
var writerConfig = new IndexWriterConfig(analyzer);
return new IndexWriter(dir, writerConfig);
}

如果需要使用英文分析器,则new IKAnalyzer()改为new StandardAnalyzer()

3.读取小说数据,由Lucene引擎在指定目录创建索引

4.创建索引搜索器

5.使用索引搜索器进行搜索,注意搜索中的分析器必须与第二步中的分析器一致

Lucene的查询方式很 丰富,对于数值类型的数据,采取TermRangeQuery的方式,对于String类型的,就可以采取TermQuery等,查询方式了,可以通过采取合适的查询方式,检索到数据。Queryparser这个查询方式包含了其他几种查询方式。

【注意】几种查询方式的比较

TermQuery 精确查询
TermRangeQuery 查询一个范围
PrefixQuery 前缀匹配查询
WildcardQuery 通配符查询
BooleanQuery 多条件查询
PhraseQuery 短语查询
FuzzyQuery 模糊查询
Queryparser 万能查询(上面的都可以用这个来查询到)

代码

Lucene.java

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
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
package lucene;

import data.Book;
import org.apache.lucene.analysis.standard.StandardAnalyzer;
import org.apache.lucene.document.Document;
import org.apache.lucene.document.Field;
import org.apache.lucene.document.TextField;
import org.apache.lucene.index.DirectoryReader;
import org.apache.lucene.index.IndexWriter;
import org.apache.lucene.index.IndexWriterConfig;
import org.apache.lucene.queryparser.classic.ParseException;
import org.apache.lucene.queryparser.classic.QueryParser;
import org.apache.lucene.search.IndexSearcher;
import org.apache.lucene.store.FSDirectory;
import org.wltea.analyzer.lucene.IKAnalyzer;

import java.io.*;
import java.nio.file.Paths;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.Map;

/**
* Lucene中文分词
*/
public class LuceneCN {

static final String INDEX_DIR = "/Users/kalosora/Desktop/novel/index_data";
static final String DATA_DIR = "/Users/kalosora/Desktop/novel/book";

/**
* 创建索引生成器
*
* @return
* @throws IOException
*/
private IndexWriter createWriter() throws IOException {

// 指定索引生成目录并打开
var path = Paths.get(INDEX_DIR);
var dir = FSDirectory.open(path);

// 指定词法分析器
var analyzer = new IKAnalyzer();

// 建造者模式生成配置
var writerConfig = new IndexWriterConfig(analyzer);
return new IndexWriter(dir, writerConfig);
}

// 创建索引
public void createIndex() throws IOException {

// 获取索引生成器
var writer = this.createWriter();

// 采集数据
Map<Integer, Book> bookMap = readFromBooks(DATA_DIR);

// TODO 按章节分割文档

// TODO 多线程处理以提高效率

// 创建文档对象
var documents = new LinkedList<Document>();
for (Map.Entry<Integer, Book> entry : bookMap.entrySet()) {
Integer key = entry.getKey();
Book value = entry.getValue();

var document = new Document();
document.add(new TextField("Name",
value.getName(),
Field.Store.YES));
// document.add(new TextField("Author",
// value.getAuthor(),
// Field.Store.YES));
document.add(new TextField("Sentence",
value.getSentence(),
Field.Store.YES));

documents.add(document);
}

// 对文档对象创建索引
for (var document : documents) {
writer.addDocument(document);
}

writer.close();
System.out.println("全文索引生成完毕!");
}

/**
* 从指定目录读取数据
*
* @param bookPath
* @return
*/
private Map<Integer, Book> readFromBooks(String bookPath) throws IOException {
Map<Integer, Book> bookMap = new HashMap<>();

// 获取文件列表
File booksPath = new File(bookPath);
var books = booksPath.listFiles();

// 遍历文件
int count = 0;
for (var book : books) {

// 获取书名和作者名
String fileName = book.getName();

FileInputStream fis = new FileInputStream(book);
BufferedReader reader = new BufferedReader(new InputStreamReader(fis, "UTF-8"));

String context = reader.readLine();
while (context != null) {
// 组装书本信息
Book bookInfo = new Book();
bookInfo.setName(fileName);
//bookInfo.setAuthor(bookAuthor);
bookInfo.setSentence(context);

// 入队并读取下一个句子
bookMap.put(count, bookInfo);
context = reader.readLine();

count++;
}
}

return bookMap;
}

/**
* 创建索引搜索器
*
* @return
* @throws IOException
*/
private IndexSearcher createSearcher() throws IOException {
// 指定索引生成目录并打开
var path = Paths.get(INDEX_DIR);
var dir = FSDirectory.open(path);
var reader = DirectoryReader.open(dir);

return new IndexSearcher(reader);
}

/**
* 从索引搜索
*
* @param keyWord
*/
public void queryIndex(String keyWord) throws ParseException, IOException {
var query = new QueryParser("Sentence", new IKAnalyzer()).parse(keyWord);

var searcher = this.createSearcher();

// 仅查询前10条
var docs = searcher.search(query, 10);

// 输出结果
for (var scoreDoc : docs.scoreDocs) {
var docID = scoreDoc.doc;
var doc = searcher.doc(docID);
// System.out.format("%s %s %s %s\n", scoreDoc.score,
// doc.get("Author"),
// doc.get("Name"),
// doc.get("Sentence"));

System.out.format("%s %s %s\n", scoreDoc.score,
doc.get("Name"),
doc.get("Sentence"));
}
}
}

main.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
/**
* 中文分词入口
*/
public class main {

public static void main(String[] args) throws IOException, ParseException {

LuceneCN lucene = new LuceneCN();

// 创建索引
//lucene.createIndex();

// 查询关键词
String keyword = "贷款 泡沫";
lucene.queryIndex(keyword);
}
}

pom.xml

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
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>

<groupId>org.example</groupId>
<artifactId>LuceneX</artifactId>
<version>1.0-SNAPSHOT</version>

<dependencies>
<!-- Lucene Lib -->
<dependency>
<groupId>org.apache.lucene</groupId>
<artifactId>lucene-memory</artifactId>
<version>7.1.0</version>
</dependency>
<dependency>
<groupId>org.apache.lucene</groupId>
<artifactId>lucene-core</artifactId>
<version>7.1.0</version>
</dependency>
<dependency>
<groupId>org.apache.lucene</groupId>
<artifactId>lucene-queryparser</artifactId>
<version>7.1.0</version>
</dependency>

<!-- IK 中文分词 -->
<dependency>
<groupId>com.github.magese</groupId>
<artifactId>ik-analyzer</artifactId>
<version>7.4.0</version>
</dependency>
</dependencies>

<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>12</source>
<target>12</target>
</configuration>
</plugin>
</plugins>
</build>
</project>

结果

输出目录

image.png

运行结果

image.png

参考

Java 全文检索引擎工具包 Lucene 原理解析

0%