什么是lucene?
logo:
1)Lucene是一套用于全文检索和搜寻的开源程序库,由Apache软件基金会支持和提供
2)Lucene提供了一个简单却强大的应用程序接口(API),能够做全文索引和搜寻,在Java开发环境里Lucene是一个成熟的免费开放源代码工具
3)Lucene并不是现成的搜索引擎产品,但可以用来制作搜索引擎产品
4)官网:http://lucene.apache.org/
什么是全文检索?
lucene和solr的关系
实现搜索的方式:
方式1:数据库搜索
利用SQL语句进行模糊搜索:
select * from items where title like “%小米%”;
问题:
数据量很大的情况下,模糊搜索不一定走索引,因此效率就会很低。
方式2:Lucene技术
海量数据的情况下,利用倒排索引技术,实现快速的搜索、打分、排序等功能
倒排索引技术:
创建倒排索引,分为以下几步:
1)创建文档列表:
lucene首先对原始文档数据进行编号(DocID),形成列表,就是一个文档列表
2)创建倒排索引列表:
然后对文档中数据进行分词,得到词条。对词条进行编号,以词条创建索引。然后记录下包含该词条的所有文档编号(及其它信息)。
江湖悠悠 -->江湖、悠悠
倒排索引创建索引的流程:
1)首先把所有的原始数据进行编号,形成文档列表
2)把文档数据进行分词,得到很多的词条,以词条为索引。保存包含这些词条的文档的编号信息
搜索的过程:
当用户输入任意的词条时,首先对用户输入的数据进行分词,得到用户要搜索的所有词条,然后拿着这些词条去倒排索引列表中进行匹配。找到这些词条就能找到包含这些词条的所有文档的编号。然后根据这些编号去文档列表中找到文档
例如用户在百度中搜索"江湖"关键字,lucene根据这个单词,找到倒排列表中的文档id(0,1) ,然后根据文档id(0,1)查找出"j江湖悠悠,饮一壶浊酒"和“行走江湖,靠的是颜值”这两条数据展示给用户
Lucene的基本使用
使用Lucene的API来实现对索引的增(创建索引)、删(删除索引)、改(修改索引)、查(搜索数据)。
一:导入依赖
junit
junit
4.12
org.apache.lucene
lucene-core
4.10.2
org.apache.lucene
lucene-queryparser
4.10.2
org.apache.lucene
lucene-highlighter
4.10.2
com.janeluo
ikanalyzer
2012_u6
添加:
package LuceneTest;
import java.io.File;
import java.util.ArrayList;
import java.util.List;
import org.apache.lucene.document.Document;
import org.apache.lucene.document.Field.Store;
import org.apache.lucene.document.StringField;
import org.apache.lucene.document.TextField;
import org.apache.lucene.index.IndexWriter;
import org.apache.lucene.index.IndexWriterConfig;
import org.apache.lucene.index.IndexWriterConfig.OpenMode;
import org.apache.lucene.store.Directory;
import org.apache.lucene.store.FSDirectory;
import org.apache.lucene.util.Version;
import org.junit.Test;
import org.wltea.analyzer.lucene.IKAnalyzer;
public class LuceneCreate {
/*
* 创建文档(原始数据)索引
*/
@Test
public void createIndex() throws Exception {
// 创建文档对象
Document document = new Document();
// 创建并添加字段信息。参数:字段的名称、字段的值、是否存储,这里选Store.YES代表存储到文档列表。Store.NO代表不存储
document.add(new StringField("id", "1", Store.YES));
// 这里我们title字段需要用TextField,即创建索引又会被分词。StringField会创建索引,但是不会被分词
document.add(new TextField("title", "今夜漫长,江湖悠悠,孤饮一杯浊酒", Store.YES));
// 索引目录类,保存到项目下indexDir目录
Directory directory = FSDirectory.open(new File("indexDir"));
// 创建分词器对象
IKAnalyzer analyzer = new IKAnalyzer();
// 索引写出工具的配置对象
IndexWriterConfig conf = new IndexWriterConfig(Version.LATEST, analyzer);
// 创建索引的写出工具类。参数:索引的目录和配置信息
IndexWriter indexWriter = new IndexWriter(directory, conf);
// 把文档交给IndexWriter
indexWriter.addDocument(document);
// 提交
indexWriter.commit();
// 关闭
indexWriter.close();
}
/**
* 批量创建文档索引
*
* @param @throws Exception
* @throws
*/
@Test
public void createBatchIndex() throws Exception {
// 创建文档的集合
List docs = new ArrayList();
// 创建文档对象
Document document1 = new Document();
document1.add(new StringField("id", "1", Store.YES));
document1.add(new TextField("title", "今夜漫长,江湖悠悠,孤饮一杯浊酒", Store.YES));
docs.add(document1);
// 创建文档对象
Document document2 = new Document();
document2.add(new StringField("id", "2", Store.YES));
document2.add(new TextField("title", "行走江湖,靠的就是颜值", Store.YES));
docs.add(document2);
// 索引目录类,指定索引在硬盘中的位置
Directory directory = FSDirectory.open(new File("indexDir"));
// 引入IK分词器
IKAnalyzer 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();
}
}
修改:
package LuceneTest;
import java.io.File;
import org.apache.lucene.document.Document;
import org.apache.lucene.document.Field.Store;
import org.apache.lucene.document.StringField;
import org.apache.lucene.document.TextField;
import org.apache.lucene.index.IndexWriter;
import org.apache.lucene.index.IndexWriterConfig;
import org.apache.lucene.index.Term;
import org.apache.lucene.store.Directory;
import org.apache.lucene.store.FSDirectory;
import org.apache.lucene.util.Version;
import org.junit.Test;
import org.wltea.analyzer.lucene.IKAnalyzer;
public class LuceneUpdate {
/*
* 测试:修改索引 注意: A:Lucene修改功能底层会先删除,再把新的文档添加。
* B:修改功能会根据Term进行匹配,所有匹配到的都会被删除。这样不好 C:因此,一般我们修改时,都会根据一个唯一不重复字段进行匹配修改。例如ID
* D:但是词条搜索,要求ID必须是字符串。如果不是,这个方法就不能用。
* 如果ID是数值类型,我们不能直接去修改。可以先手动删除deleteDocuments(数值范围查询锁定ID),再添加。
*/
@Test
public void updateIndex() throws Exception {
// 创建目录对象
Directory directory = FSDirectory.open(new File("indexDir"));
// 创建配置对象
IndexWriterConfig conf = new IndexWriterConfig(Version.LATEST,
new IKAnalyzer());
// 创建索引写出工具
IndexWriter writer = new IndexWriter(directory, conf);
// 创建新的文档数据
Document doc = new Document();
doc.add(new StringField("id", "1", Store.YES));
doc.add(new TextField("title", "哈哈,追不上我吧,我就是这么强大",
Store.YES));
/*
* 修改索引。参数: 词条:根据这个词条匹配到的所有文档都会被修改 文档信息:要修改的新的文档数据
*/
writer.updateDocument(new Term("id", "1"), doc);
// 提交
writer.commit();
// 关闭
writer.close();
}
}
删除:
package LuceneTest;
import java.io.File;
import org.apache.lucene.index.IndexWriter;
import org.apache.lucene.index.IndexWriterConfig;
import org.apache.lucene.index.Term;
import org.apache.lucene.store.Directory;
import org.apache.lucene.store.FSDirectory;
import org.apache.lucene.util.Version;
import org.junit.Test;
import org.wltea.analyzer.lucene.IKAnalyzer;
public class LuceneDelete {
/*
* 演示:删除索引 注意: 一般,为了进行精确删除,我们会根据唯一字段来删除。比如ID 如果是用Term删除,要求ID也必须是字符串类型!
*/
@Test
public void deleteIndex() throws Exception {
// 创建目录对象
Directory directory = FSDirectory.open(new File("indexDir"));
// 创建配置对象
IndexWriterConfig conf = new IndexWriterConfig(Version.LATEST,
new IKAnalyzer());
// 创建索引写出工具
IndexWriter writer = new IndexWriter(directory, conf);
// 根据词条进行删除
writer.deleteDocuments(new Term("id", "1"));
// 删除所有
// writer.deleteAll();
// 提交
writer.commit();
// 关闭
writer.close();
}
}
查询:
package LuceneTest;
import java.io.File;
import org.apache.lucene.document.Document;
import org.apache.lucene.index.DirectoryReader;
import org.apache.lucene.index.IndexReader;
import org.apache.lucene.index.Term;
import org.apache.lucene.queryparser.classic.MultiFieldQueryParser;
import org.apache.lucene.search.FuzzyQuery;
import org.apache.lucene.search.IndexSearcher;
import org.apache.lucene.search.Query;
import org.apache.lucene.search.ScoreDoc;
import org.apache.lucene.search.TermQuery;
import org.apache.lucene.search.TopDocs;
import org.apache.lucene.search.WildcardQuery;
import org.apache.lucene.store.Directory;
import org.apache.lucene.store.FSDirectory;
import org.junit.Test;
import org.wltea.analyzer.lucene.IKAnalyzer;
public class LuceneSearch {
public void search(Query query) throws Exception {
// 索引目录对象
Directory directory = FSDirectory.open(new File("indexDir"));
// 索引读取工具
IndexReader reader = DirectoryReader.open(directory);
// 索引搜索工具
IndexSearcher searcher = new IndexSearcher(reader);
// 搜索数据,两个参数:查询条件对象要查询的最大结果条数
// 返回的结果是 按照匹配度排名得分前N名的文档信息(包含查询到的总条数信息、所有符合条件的文档的编号信息)。
TopDocs topDocs = searcher.search(query, 10);
// 获取总条数
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);
}
}
/**
* 根据多个字段查询
*
*/
@Test
public void search1() throws Exception {
MultiFieldQueryParser parser = new MultiFieldQueryParser(new String[] {
"id", "title" }, new IKAnalyzer());
//Query query = parser.parse("1");//自动匹配id进行查询
Query query = parser.parse("浊酒");//自动匹配title进行查询
search(query);
}
/**
* 普通词条查询
*
*/
@Test
public void search2() throws Exception {
// 创建词条查询对象
Query query = new TermQuery(new Term("title", "浊酒"));
search(query);
}
/**
* 模糊查询
* ? 可以代表任意一个字符
* * 可以任意多个任意字符
*/
@Test
public void search3() throws Exception {
// 创建查询对象
Query query = new WildcardQuery(new Term("title", "*酒*"));
search(query);
}
/**
* 测试相似度查询
*/
@Test
public void testFuzzyQuery() throws Exception {
//如果不采用FuzzyQuery,根据“烈酒”词条是查询不出结果的
Query query = new FuzzyQuery(new Term("title","烈酒"));
search(query);
}
}
高亮显示关键字:
原理:
1)给所有关键字加上一个HTML标签
2)给这个特殊的标签设置CSS样式
代码实现:
// 高亮显示
@Test
public void testHighlighter() throws Exception {
// 目录对象
Directory directory = FSDirectory.open(new File("indexDir"));
// 创建读取工具
IndexReader reader = DirectoryReader.open(directory);
// 创建搜索工具
IndexSearcher searcher = new IndexSearcher(reader);
QueryParser parser = new QueryParser("title", new IKAnalyzer());
Query query = parser.parse("江湖");
// 格式化器
Formatter formatter = new SimpleHTMLFormatter("", "");
Scorer 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 IKAnalyzer(), "title", title);
System.out.println("title: " + hTitle);
// 获取文档的得分
System.out.println("得分:" + scoreDoc.score);
}
}