一、介绍
Lucene是一款高性能的、可扩展的信息检索(IR)工具库。信息检索是指文档搜索、文档内信息搜索或者文档相关的元数据搜索等操作。
简单的说,搜索就是搜寻、查找,在IT行业中就是指用户输入关键字,通过相应的算法,查询并返回用户所需要的信息。
传统的模糊查询方法在处理大数据的时候效率低、耗时长(select * from 表名 where 字段名 like ‘%关键字%’),所以lucene应运而生,也有了倒排索引的概念
二、倒排索引:
又称为反向索引: 以字或者词,甚至是一句话一段话作为一个关键字进行索引, 每一个关键字都会对应着一个记录项, 记录项中记录了这个关键字出现在那些文档中, 在此文档的什么位置上
在实际的运用中,我们可以对数据库中原始的数据结构(左图),在业务空闲时事先根据左图内容,创建新的倒排索引结构的数据区域(右图)。用户有查询需求时,先访问倒排索引数据区域(右图),得出文档id后,通过文档id即可快速,准确的通过左图找到具体的文档内容。
Lucene、Solr、Elasticsearch关系
Lucene:底层的API,工具包
Solr:基于Lucene开发的企业级的搜索引擎产品
Elasticsearch:基于Lucene开发的企业级的搜索引擎产品
创建索引流程:
文档Document:数据库中一条具体的记录
字段Field:数据库中的每个字段
目录对象Directory:物理存储位置
写出器的配置对象:需要分词器和lucene的版本
三、Java API实现
API来实现对索引的增(创建索引)、删(删除索引)、改(修改索引)、查(搜索数据)。
1、IndexWriter:索引写入器对象,添加索引, 修改索引和删除索引。
需要传入Directory和indexWriterConfig对象 ,即索引的目录和配置
2、FSDirectory:用来指定文件系统的目录, 将索引信息保存到磁盘上
RAMDriectory: 内存目录, 将索引库信息存放到内存中
3、IndexWriterConfig: 索引写入器的配置类,需要传递Lucene的版本和分词器4、QueryParser(查询解析器)
5、Query(查询对象,包含要查询的关键词信息)
6、IndexSearch(索引搜索对象,执行搜索功能)
7、TopDocs(查询结果对象)
8、 ScoreDoc(得分文档对象)
代码实现:
依赖:
<properties>
<lunece.version>4.10.2</lunece.version>
</properties>
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
</dependency>
<!-- lucene核心库 -->
<dependency>
<groupId>org.apache.lucene</groupId>
<artifactId>lucene-core</artifactId>
<version>${lunece.version}</version>
</dependency>
<!-- Lucene的查询解析器 -->
<dependency>
<groupId>org.apache.lucene</groupId>
<artifactId>lucene-queryparser</artifactId>
<version>${lunece.version}</version>
</dependency>
<!-- lucene的默认分词器库 -->
<dependency>
<groupId>org.apache.lucene</groupId>
<artifactId>lucene-analyzers-common</artifactId>
<version>4.10.1</version>
</dependency>
<!-- lucene的高亮显示 -->
<dependency>
<groupId>org.apache.lucene</groupId>
<artifactId>lucene-highlighter</artifactId>
<version>${lunece.version}</version>
</dependency>
<!-- IK分词器 中文 -->
<dependency>
<groupId>com.janeluo</groupId>
<artifactId>ikanalyzer</artifactId>
<version>2012_u6</version>
</dependency>
</dependencies>
1、创建倒排索引
//创建只有一条文档的文件
@Test
public void simpleCreate() {
try {
//1 创建文档对象
Document ducument = new Document();
// 添加字段信息。参数:字段的名称、字段的值、是否存储,yes储存,no不储存
ducument.add(new StringField("id", "1", Field.Store.YES));
// title字段用TextField,创建索引被分词。StringField创建索引,但不会被分词
ducument.add(new TextField("title", "谷歌地图之父跳槽facebook", Field.Store.YES));
//2 创建存储目录,本地
FSDirectory directory = FSDirectory.open(new File("e:/hadoop/hdpdata/lucene"));
//3 创建分词器
StandardAnalyzer analyzer = new StandardAnalyzer();
//4 创建索引写入器的配置对象
IndexWriterConfig config = new IndexWriterConfig(Version.LATEST, analyzer);
//5 创建索引写入器对象
IndexWriter indexWriter = new IndexWriter(directory, config);
//6 将文档交给索引写入器
indexWriter.addDocument(ducument);
//7 提交
indexWriter.commit();
//8 关闭
indexWriter.close();
}catch (Exception e){
e.printStackTrace();
}
}
//创建多个文档的文件
@Test
public void manyCreate() {
try {
//1 创建文档对象集合
Collection<Document> documents = new ArrayList<Document>();
Document ducument = new Document();
// 添加字段信息。参数:字段的名称、字段的值、是否存储,yes储存,no不储存
ducument.add(new StringField("id", "1", Field.Store.YES));
// title字段用TextField,创建索引被分词。StringField创建索引,但不会被分词
ducument.add(new TextField("title", "谷歌地图之父跳槽facebook", Field.Store.YES));
documents.add(ducument);
Document ducument2 = new Document();
// 添加字段信息。参数:字段的名称、字段的值、是否存储,yes储存,no不储存
ducument2.add(new StringField("id", "1", Field.Store.YES));
// title字段用TextField,创建索引被分词。StringField创建索引,但不会被分词
ducument2.add(new TextField("title", "谷歌地图之父加盟FaceBook", Field.Store.YES));
documents.add(ducument2);
Document ducument3 = new Document();
// 添加字段信息。参数:字段的名称、字段的值、是否存储,yes储存,no不储存
ducument3.add(new StringField("id", "1", Field.Store.YES));
// title字段用TextField,创建索引被分词。StringField创建索引,但不会被分词
ducument3.add(new TextField("title", "谷歌地图创始人拉斯离开谷歌加盟Facebook", Field.Store.YES));
documents.add(ducument3);
Document ducument4 = new Document();
// 添加字段信息。参数:字段的名称、字段的值、是否存储,yes储存,no不储存
ducument4.add(new StringField("id", "1", Field.Store.YES));
// title字段用TextField,创建索引被分词。StringField创建索引,但不会被分词
ducument4.add(new TextField("title", "谷歌地图之父跳槽Facebook与Wave项目取消有关", Field.Store.YES));
documents.add(ducument4);
Document ducument5 = new Document();
// 添加字段信息。参数:字段的名称、字段的值、是否存储,yes储存,no不储存
ducument5.add(new StringField("id", "1", Field.Store.YES));
// title字段用TextField,创建索引被分词。StringField创建索引,但不会被分词
ducument5.add(new TextField("title", "谷歌地图之父拉斯加盟社交网站Facebook", Field.Store.YES));
documents.add(ducument5);
//2 创建存储目录,本地
FSDirectory directory = FSDirectory.open(new File("e:/hadoop/hdpdata/lucene"));
//3 创建分词器
StandardAnalyzer analyzer = new StandardAnalyzer();
// IKAnalyzer ikAnalyzer = new IKAnalyzer();
//4 创建索引写入器的配置对象
IndexWriterConfig config = new IndexWriterConfig(Version.LATEST, analyzer);
//如果有数据,覆盖
config.setOpenMode(IndexWriterConfig.OpenMode.CREATE);
//5 创建索引写入器对象
IndexWriter indexWriter = new IndexWriter(directory, config);
//6 将文档交给索引写入器
indexWriter.addDocuments(documents);
//7 提交
indexWriter.commit();
//8 关闭
indexWriter.close();
}catch (Exception e){
e.printStackTrace();
}
}
2、查询索引
//索引查询代码
@Test
public void search() {
try {
//1 创建查询文件
Directory dir = FSDirectory.open(new File("e:/hadoop/hdpdata/lucene"));
//2 索引读取工具
DirectoryReader reader = DirectoryReader.open(dir);
//搜索工具
IndexSearcher indexSearcher = new IndexSearcher(reader);
// 创建查询解析器,参数:查询的字段的名称,分词器
QueryParser queryParser = new QueryParser("title", new IKAnalyzer()); //new StandardAnalyzer()
//查询对象
Query query = queryParser.parse("谷歌");
//搜索工具查询,参数:查询对象,最大结果条数
//结果topdoc:按照匹配度排名得分前N名的文档信息(包含查询到的总条数信息、所有符合条件的文档的编号信息)
TopDocs search = indexSearcher.search(query, 10);
//获取总条数
System.out.println("一共有:"+search.totalHits+"条数据");
// 获取得分文档对象(ScoreDoc)数组.SocreDoc中包含:文档的编号、文档的得分
ScoreDoc[] scoreDocs = search.scoreDocs;
for(ScoreDoc scoreDoc : scoreDocs){
//文档编号
int id=scoreDoc.doc;
//文档编号对应文档
Document doc = reader.document(id);
System.out.println("id: "+doc.get("id")+" title: "+doc.get("title"));
// 取出文档得分
System.out.println("得分: " + scoreDoc.score);
}
}catch (Exception e){
e.printStackTrace();
}
}
3高亮显示
// 高亮显示
@Test
public void testHighlighter() throws Exception {
// 目录对象
Directory directory = FSDirectory.open(new File("e:/hadoop/hdpdata/lucene"));
// 创建读取工具
IndexReader reader = DirectoryReader.open(directory);
// 创建搜索工具
IndexSearcher searcher = new IndexSearcher(reader);
QueryParser parser = new QueryParser("title", new StandardAnalyzer());
Query query = parser.parse("谷歌");
// 格式化器
Formatter formatter = new SimpleHTMLFormatter("<body>", "</body>");
QueryScorer scorer = new QueryScorer(query);
// 准备高亮工具
Highlighter highlighter = new Highlighter(formatter, scorer);
// 搜索
TopDocs topDocs = searcher.search(query, 10);
System.out.println("本次搜索共" + topDocs.totalHits + "条数据");
ScoreDoc[] scoreDocs = topDocs.scoreDocs;
for (ScoreDoc scoreDoc : scoreDocs) {
// 获取文档编号
int docID = scoreDoc.doc;
Document doc = reader.document(docID);
System.out.println("id: " + doc.get("id"));
String title = doc.get("title");
// 用高亮工具处理普通的查询结果,参数:分词器,要高亮的字段的名称,高亮字段的原始值
String hTitle = highlighter.getBestFragment(new StandardAnalyzer(), "title", title);
System.out.println("title: " + hTitle);
// 获取文档的得分
System.out.println("得分:" + scoreDoc.score);
}
}
4、排序
// 排序
@Test
public void testSortQuery() throws Exception {
// 目录对象
Directory directory = FSDirectory.open(new File("e:/hadoop/hdpdata/lucene"));
// 创建读取工具
IndexReader reader = DirectoryReader.open(directory);
// 创建搜索工具
IndexSearcher searcher = new IndexSearcher(reader);
QueryParser parser = new QueryParser("title", new StandardAnalyzer());
Query query = parser.parse("谷歌");
// 创建排序对象,需要排序字段SortField,参数:字段的名称、字段的类型、是否反转如果是false,升序。true降序
Sort sort = new Sort(new SortField("id", SortField.Type.LONG, true));
// 搜索
TopDocs topDocs = searcher.search(query, 10,sort);
System.out.println("本次搜索共" + topDocs.totalHits + "条数据");
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"));
}
}
5、分页
// 分页
@Test
public void testPageQuery() throws Exception {
// 实际上Lucene本身不支持分页。因此我们需要自己进行逻辑分页。我们要准备分页参数:
int pageSize = 2;// 每页条数
int pageNum = 3;// 当前页码
int start = (pageNum - 1) * pageSize;// 当前页的起始条数
int end = start + pageSize;// 当前页的结束条数(不能包含)
// 目录对象
Directory directory = FSDirectory.open(new File("e:/hadoop/hdpdata/lucene" ));
// 创建读取工具
IndexReader reader = DirectoryReader.open(directory);
// 创建搜索工具
IndexSearcher searcher = new IndexSearcher(reader);
QueryParser parser = new QueryParser("title", new StandardAnalyzer());
Query query = parser.parse("谷歌");
// 创建排序对象,需要排序字段SortField,参数:字段的名称、字段的类型、是否反转如果是false,升序。true降序
Sort sort = new Sort(new SortField("id", SortField.Type.LONG, false));
// 搜索数据,查询0~end条
TopDocs topDocs = searcher.search(query, end,sort);
int count = topDocs.totalHits;
System.out.println("本次搜索共" + count + "条数据"+"当前页: "+pageNum);
ScoreDoc[] scoreDocs = topDocs.scoreDocs;
for (int i = start; i < end; i++) {
if(i == count ){
return;
}
ScoreDoc scoreDoc = scoreDocs[i];
// 获取文档编号
int docID = scoreDoc.doc;
Document doc = reader.document(docID);
System.out.println("id: " + doc.get("id"));
System.out.println("title: " + doc.get("title"));
}
}