1. Lucene概述
什么是Lucene
- Lucene是一套用于全文检索和搜寻的开源程序库,由Apache软件基金会支持和提供
- Lucene提供了一个简单却强大的应用程序接口(API),能够做全文索引和搜寻,在Java开发环境里Lucene是一个成熟的免费开放源代码工具
- Lucene并不是现成的搜索引擎产品,但可以用来制作搜索引擎产品
- 官网 http://lucene.apache.org/
什么是全文检索
- 参考 参考百度百科
- Lucene下载及版本问题
- 目前最新的版本是6.x系列,但是大多数企业中依旧使用4.x版本,比较稳定
- Lucene与Solr的关系
- Lucene:一套实现了全文检索的底层API
- Solr:基于Lucene开发的企业级搜索应用服务器
2、Lucene的基本使用
- 2.1、准备工作
- 创建 Maven工程
- 引入Maven依赖
- 单元测试:junit
- 核心库:lucene-core
- 分词器:lucene-analyzers-common
- 查询解析器:lucene-queryparser
- 高亮显示:lucene-highlighter
<dependencies>
<!-- Junit单元测试 -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
</dependency>
<!-- lucene核心库 -->
<dependency>
<groupId>org.apache.lucene</groupId>
<artifactId>lucene-core</artifactId>
<version>4.10.2</version>
</dependency>
<!-- Lucene的查询解析器 -->
<dependency>
<groupId>org.apache.lucene</groupId>
<artifactId>lucene-queryparser</artifactId>
<version>4.10.2</version>
</dependency>
<!-- lucene的默认分词器库 -->
<dependency>
<groupId>org.apache.lucene</groupId>
<artifactId>lucene-analyzers-common</artifactId>
<version>4.10.2</version>
</dependency>
<!-- lucene的高亮显示 -->
<dependency>
<groupId>org.apache.lucene</groupId>
<artifactId>lucene-highlighter</artifactId>
<version>4.10.2</version>
</dependency>
<!-- IK分词器 -->
<dependency>
<groupId>com.janeluo</groupId>
<artifactId>ikanalyzer</artifactId>
<version>2012_u6</version>
</dependency>
</dependencies>
<build>
<plugins>
<!-- java编译插件 -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.2</version>
<configuration>
<source>1.7</source>
<target>1.7</target>
<encoding>UTF-8</encoding>
</configuration>
</plugin>
</plugins>
</build>
- 2.2 创建索引
- 创建索引的流程:
- 创建索引的流程:
- 2.3 代码实现
//索引的创建分三步
@Test
public void testCreate() throws Exception{
//1. 创建文档对象
Document document = new Document();
/*
* 1.1 创建并添加字段信息。
* 参数:字段的名称、字段的值、是否存储,
* 这里选Store.YES代表存储到文档列表。Store.NO代表不存储
*/
document.add(new StringField("id", "1", Store.YES));
/*
* 这里我们title字段需要用TextField,即创建索引又会被分词。
* StringField会创建索引,但是不会被分词
*/
document.add(new TextField("title", "2", Store.YES));
//2. 创建索引写入器
/*
* 2.1 索引目录类,指定索引在硬盘中的位置
*/
Directory d = FSDirectory.open(new File("luceneIndex"));
/*
* 2.2 创建分词器对象
*/
Analyzer analyzer = new StandardAnalyzer();
/*
* 2.3 索引写出工具的配置对象
*/
IndexWriterConfig conf = new IndexWriterConfig(Version.LATEST, analyzer);
/*
* 2.4 创建索引的写出工具类。参数:索引的目录和配置信息
*/
IndexWriter writer = new IndexWriter(d, conf);
//3. 把文档交给IndexWriter
writer.addDocument(document);
// 提交
writer.commit();
// 关闭
writer.close();
}
- 2.4 使用工具查看索引(Luke 查看工具 略)
2.5 创建索引的API详解
- Document(文档)
- 文档对象,是一条原始的数据
Field(字段类)
一个Document中可以有很多个不同的字段,每一个字段都是一个Field类的对象。
一个Document中的字段其类型是不确定的,因此Field类就提供了各种不同的子类,来对应这些不同类型的字段。
这些子类有一些不同的特性:
1)DoubleField、FloatField、IntField、LongField、StringField、TextField这些子类一定会被创建索引。但是不一定会被存储到文档列表。要通过构造函数中的参数Store来指定:如果Store.YES代表存储,Store.NO代表不存储
2)TextField即创建索引,又会被分词。StringField会创建索引,但是不会被分词。如果不分词,会造成整个字段作为一个词条,除非用户完全匹配,否则搜索不到
3)StoreField一定会被存储,但是一定不创建索引,StoredField可以创建各种数据类型的字段
.
问题1:如何确定一个字段是否需要存储?
如果一个字段要显示到最终的结果中,那么一定要存储,否则就不存储问题2:如何确定一个字段是否需要创建索引?
如果要根据这个字段进行搜索,那么这个字段就必须创建索引。问题3:如何确定一个字段是否需要分词?
前提是这个字段首先要创建索引。然后如果这个字段的值是不可分割的,那么就不需要分词。例如:IDDirectory( 目录类)– 指定索引要存储的位置
- FSDirectory:文件系统目录,会把索引库指向本地磁盘。
特点:速度略慢,但是比较安全 - RAMDirectory:内存目录,会把索引库保存在内存。
特点:速度快,但是不安全
- FSDirectory:文件系统目录,会把索引库指向本地磁盘。
Analyzer (分词器)
- IK分词器
- IndexWriterConfig
- 设置配置信息:Lucene的版本和分词器类型
- 设置是否清空索引库中的数据
- IndexWriter : 索引写出工具,作用就是 实现对索引的增(创建索引)、删(删除索引)、改(修改索引)
- Document(文档)
- 2.6 可以一次创建一个,也可以批量创建索引
//索引的创建分三步
@Test
public void testMultiCreate() throws Exception{
// 创建文档的集合
Collection<Document> docs = new ArrayList<>();
// 创建文档对象
Document document1 = new Document();
document1.add(new StringField("id", "1", Store.YES));
document1.add(new TextField("title", "谷歌地图之父跳槽facebook", Store.YES));
docs.add(document1);
// 创建文档对象
Document document2 = new Document();
document2.add(new StringField("id", "2", Store.YES));
document2.add(new TextField("title", "谷歌地图之父加盟FaceBook", Store.YES));
docs.add(document2);
// 创建文档对象
Document document3 = new Document();
document3.add(new StringField("id", "3", Store.YES));
document3.add(new TextField("title", "谷歌地图创始人拉斯离开谷歌加盟Facebook", Store.YES));
docs.add(document3);
// 创建文档对象
Document document4 = new Document();
document4.add(new StringField("id", "4", Store.YES));
document4.add(new TextField("title", "谷歌地图之父跳槽Facebook与Wave项目取消有关", Store.YES));
docs.add(document4);
// 创建文档对象
Document document5 = new Document();
document5.add(new StringField("id", "5", Store.YES));
document5.add(new TextField("title", "谷歌地图之父拉斯加盟社交网站Facebook", Store.YES));
docs.add(document5);
// 索引目录类,指定索引在硬盘中的位置
Directory directory = FSDirectory.open(new File("indexDir"));
// 引入IK分词器
Analyzer analyzer = new IKAnalyzer();
// 索引写出工具的配置对象
IndexWriterConfig conf = new IndexWriterConfig(Version.LATEST, analyzer);
// 设置打开方式:OpenMode.APPEND 会在索引库的基础上追加新索引。
//OpenMode.CREATE会先清空原来数据,再提交新的索引
conf.setOpenMode(OpenMode.CREATE);
// 创建索引的写出工具类。参数:索引的目录和配置信息
IndexWriter indexWriter = new IndexWriter(directory, conf);
// 把文档集合交给IndexWriter
indexWriter.addDocuments(docs);
// 提交
indexWriter.commit();
// 关闭
indexWriter.close();
}
- 2.7 查询索引
@Test
public void testQuery() throws IOException, ParseException{
/*
* 1. 索引目录对象
*/
Directory directory = FSDirectory.open(new File("indexDir"));
/*
* 2. 索引读取工具
*/
IndexReader reader = DirectoryReader.open(directory);
/*
* 3. 索引搜索工具
*/
IndexSearcher searcher = new IndexSearcher(reader);
/*
* 4. 创建查询解析器,两个参数:默认要查询的字段的名称,分词器
*/
QueryParser parser = new QueryParser("title", new IKAnalyzer());
/*
* 5. 创建查询对象
*/
Query query = parser.parse("谷歌地图之父拉斯");
/*
* 6. 搜索数据,两个参数:查询条件对象要查询的最大结果条数
* 返回的结果是 按照匹配度排名得分前N名的文档信息(包含查询到的总条数信息、所有符合条件的文档的编号信息)。
*/
TopDocs topDocs = searcher.search(query, 10);
/*
* 7. 解析结果 获取总条数
*/
System.out.println("本次搜索共找到" + topDocs.totalHits + "条数据");
// 获取得分文档对象(ScoreDoc)数组.SocreDoc中包含:文档的编号、文档的得分
ScoreDoc[] scoreDocs = topDocs.scoreDocs;
for (ScoreDoc scoreDoc : scoreDocs) {
// 取出文档编号
int docID = scoreDoc.doc;
// 根据编号去找文档
Document doc = reader.document(docID);
System.out.println("id: " + doc.get("id"));
System.out.println("title: " + doc.get("title"));
// 取出文档得分
System.out.println("得分: " + scoreDoc.score);
}
}