Lucene 全文检索
2018-10-07 本文已影响14人
爱撒谎的男孩
Lucene 全文检索
Field域
- Field是文档中的域,包括Field名和Field值两部分,一个文档可以包括多个Field,Document只是Field的一个承载体,Field值即为要索引的内容,也是要搜索的内容。
是否分词
- 分词就是对文件的内容或者其他的属性进行分割形成一个一个的语汇单元,分词的过程就是将一些动词,定冠词,不定冠词等内容去掉,保留名词。比如文件的内容,商品的介绍,这些内容都是需要用户输入关键词来查询的,因此这个必须分词
- 但是对于商品的
id
,订单号,身份证号这些是不用分词的,这个是必须全局匹配才会找到相关的内容
是否索引
-
索引的目的就是为了将来作为查询条件来搜索,比如商品的名称,商品的介绍,文章的内容,这些内容需要输入关键词搜索的,我们必须进行索引,如果不索引将会不能爱按照这些内容搜索。
-
不索引: 商品的
id
,图片的路径等这个是不需要作为查询条件的,因此不需要索引
是否存储
- 将Field值存储在文档中,存储在文档中的Field才可以从
Document
中获取。 - 比如:商品名称、订单号,凡是将来要从
Document
中获取的Field
都要存储。 - 否:不存储Field值,不存储的Field无法通过
Document
获取 - 比如:商品简介,内容较大不用存储。如果要向用户展示商品简介可以从系统的关系数据库中获取商品简介。
- 如果需要商品描述,则根据搜索出的商品ID去数据库中查询,然后显示出商品描述信息即可。
Field的常用类型
- 类型
Field改进
- 图书id
- 是否分词:不用分词,因为不会根据商品id来搜索商品
- 是否索引:不索引,因为不需要根据图书ID进行搜索
- 是否存储:要存储,因为查询结果页面需要使用id这个值。
- 图书名称:
- 是否分词:要分词,因为要将图书的名称内容分词索引,根据关键搜索图书名称抽取的词。
- 是否索引:要索引。
- 是否存储:要存储。
- 图书价格
- 是否分词:要分词,lucene对数字型的值只要有搜索需求的都要分词和索 引,因为lucene对数字型的内容要特殊分词处理,本例子可能要根据价格范 围搜索,需要分词和索引。
- 是否索引:要索引 是否存储:要存储
- 图书图片地址:
- 是否分词:不分词
- 是否索引:不索引
- 是否存储:要存储,因为只有根据图片地址才能找到对应的图片
- 图书描述:
- 是否分词:要分词
- 是否索引:要索引
- 是否存储:因为图书描述内容量大,不在查询结果页面直接显示,不存储。 不存储是来不在lucene的索引文件中记录,节省lucene的索引文件空间, 如果要在详情页面显示描述,思路: 从lucene中取出图书的id,根据图书的id查询关系数据库中book表 得到描述信息。
添加依赖
- 这里使用的
IKAnalyzer
这个中文分词器
<!-- 添加lucene支持 -->
<dependency>
<groupId>org.apache.lucene</groupId>
<artifactId>lucene-core</artifactId>
<version>4.10.3</version>
</dependency>
<dependency>
<groupId>org.apache.lucene</groupId>
<artifactId>lucene-queryparser</artifactId>
<version>4.10.3</version>
</dependency>
<dependency>
<groupId>org.apache.lucene</groupId>
<artifactId>lucene-analyzers-common</artifactId>
<version>4.10.3</version>
</dependency>
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>2.4</version>
</dependency>
<dependency>
<groupId>org.apache.lucene</groupId>
<artifactId>lucene-highlighter</artifactId>
<version>4.10.3</version>
</dependency>
<!-- IK分词器 -->
<dependency>
<groupId>com.janeluo</groupId>
<artifactId>ikanalyzer</artifactId>
<version>2012_u6</version>
</dependency>
添加IK中文分词器的扩展
- 只需要将这些文件下载下载出来,然后添加到
src/main/resource
路径下即可
Lucene的工具类
- 其中自己封装了一些方法
/**
* Lucene的工具类
* @author chenjiabing
*/
public class LuceneUtils {
/**
* 获取IndexWriter 用于创建索引库
* @return IndexWriter对象
* @throws Exception
*/
public static IndexWriter getIndexWriter() throws Exception{
//创建索引库存放的位置
Directory directory=FSDirectory.open(new File("/home/chenjiabing/Documents/Lucene"));
//使用IK中文分词器
IKAnalyzer ikAnalyzer=new IKAnalyzer(true);
//创建IndexWriteConfig对象,其中传入的是分析器对象
IndexWriterConfig indexWriterConfig=new IndexWriterConfig(Version.LATEST, ikAnalyzer);
//创建索引,其中的变量是索引库的位置,索引配置对象
IndexWriter indexWriter=new IndexWriter(directory, indexWriterConfig);
return indexWriter;
}
/**
* 获取IndexSearcher,用于查询
* @return IndexSearcher对象
* @throws Exception
*/
public static IndexSearcher getIndexSearcher()throws Exception{
//创建Directory对象,指定索引库的位置
Directory directory=FSDirectory.open(new File("/home/chenjiabing/Documents/Lucene"));
//创建IndexReader对象
IndexReader indexReader=DirectoryReader.open(directory);
//创建IndexSearcher对象
IndexSearcher indexSearcher=new IndexSearcher(indexReader);
return indexSearcher;
}
/**
* 根据查询语句,打印结果
* @param indexSearcher IndexSearch对象
* @param query 查询对象
* @param n 显示结果数量
* @throws IOException
*/
public static void doSearch(IndexSearcher indexSearcher,Query query,Integer n) throws IOException{
//执行查询
TopDocs topDocs=indexSearcher.search(query, n);
//返回查询结果
ScoreDoc[] scoreDocs=topDocs.scoreDocs;
//遍历查询结果
for (ScoreDoc scoreDoc : scoreDocs) {
int doc=scoreDoc.doc; //返回文档的编号
//根据编号查询文档
Document document=indexSearcher.doc(doc);
//输出文档中定义的域
System.out.println(document.get("fileName"));
/*System.out.println(document.get("fileSize"));
System.out.println(document.get("fileContent"));
System.out.println(document.get("filePath"));*/
}
}
}
创建索引库
// 创建索引
@Test
public void testIndex() throws Exception {
IndexWriter indexWriter=LuceneUtils.getIndexWriter();
//创建File对象,这里是需要索引的文件
File file=new File("/home/chenjiabing/Documents/Blog");
//获取文件夹下的所有文件
File[] files=file.listFiles();
//遍历所有的文件,取出相关 的内容
for (File f : files) {
Document document=new Document(); //创建文档对象
//获取文件名
String fileName=f.getName();
//创建域 文件名 分词,索引 存储
Field fieldName=new TextField("fileName", fileName, Store.YES);
//获取文件大小
Long fileSize=FileUtils.sizeOf(f);
//创建文件大小的域,分词,索引,存储
Field fieldSize=new LongField("fileSize", fileSize, Store.YES);
//获取文件路径
String filePath=f.getPath();
//创建文件路径的域 不分词,不索引 但是必须存储,用来找到指定的文件
Field fieldPath=new StoredField("filePath", filePath);
//获取文件内容
String fileContent=FileUtils.readFileToString(f);
//创建文件内容的域,分词,索引,存储
Field fieldContent=new TextField("fileContent", fileContent,Store.YES);
//将分出的这些域添加到文档中
document.add(fieldContent);
document.add(fieldName);
document.add(fieldPath);
document.add(fieldSize);
//将文档写入索引库
indexWriter.addDocument(document);
}
//关闭IndexWriter对象
indexWriter.close();
}
Query 搜索
TermQuery
- 这个是精确搜索
-
TermQuery
项查询,TermQuery不使用分析器
,搜索关键词作为整体来匹配Field域中的词进行查询,比如订单号、分类ID号等。- 这个查询的方式不会通过分词器进行分词查询,而是整个内容匹配。比如其中的查询条件为文件的名称:
springmvc拦截器
,那么只有当名称为...springmvc拦截器.....
这样整个词语连接在一起的时候才会查询到
- 这个查询的方式不会通过分词器进行分词查询,而是整个内容匹配。比如其中的查询条件为文件的名称:
- ``TermQuery(new Term("域名","搜索的词语"))
: 这里的域名是创建Field的时候指定的:
new Field("域名",值)`
@Test
public void testTermQuerySearch() throws Exception{
//获取IndexSearcher
IndexSearcher indexSearcher=LuceneUtils.getIndexSearcher();
//创建一个TermQuery对象,指定查询的域和需要查询的词
TermQuery query=new TermQuery(new Term("fileName","springmvc拦截器"));
System.out.println(query);
//打印查询结果
LuceneUtils.doSearch(indexSearcher, query, 10);
//关闭IndexReader
indexSearcher.getIndexReader().close();
}
NumericRangeQuery
- 指定数字范围查询.
- 适合查询价格等数字类型的
- 其中有许多创建查询范围的静态方法,适合多种数据类型的查询
@Test
public void testNumericRangeQuery() throws Exception{
IndexSearcher indexSearcher=LuceneUtils.getIndexSearcher();
/**
* 这里是查询文件大小的域:fileSize
* 第一个参数: 域名
* 第二个参数:最小值
* 第三个参数: 最大值
* 第四个参数: 是否包含最小值
* 第五个参数: 是否包含最大值
*/
Query query=NumericRangeQuery.newLongRange("fileSize", 1000L, 2000L, true, true);
//输出查询条件:fileSize:[1000 TO 2000]
System.out.println(query);
LuceneUtils.doSearch(indexSearcher, query, 10);
//关闭IndexReader
indexSearcher.getIndexReader().close();
}
BooleanQuery
-
BooleanQuery,布尔查询,实现组合条件查询。
-
Occur.MUST
: 当前的查询条件必须满足 -
Occur.SHOULD
: 当前的查询条件可满足可不满足,相当于or
-
MUST_NOT
:查询条件不能满足,相当于NOT
非+
@Test
public void testBooleanQuery() throws Exception{
IndexSearcher indexSearcher=LuceneUtils.getIndexSearcher();
//第一个查询条件 根据fileName域查询
Query query1=new TermQuery(new Term("fileName", "springmvc"));
//第二个查询条件,根据fileSize查询
Query query2=NumericRangeQuery.newLongRange("fileSize", 1000L, 2000L, true, true);
//创建BooleanQuery
BooleanQuery query=new BooleanQuery();
//添加查询条件,这个条件是必须满足的 :Occur.MUST
query.add(query1,Occur.MUST);
//添加第二个查询条件,这个条件可满足可不满足,相当于or
query.add(query2,Occur.SHOULD);
System.out.println(query);
//执行查询
LuceneUtils.doSearch(indexSearcher, query, 10);
indexSearcher.getIndexReader().close();
}
MatchAllDocsQuery
- 查询所有
- 返回的是索引库中所有的文件信息
//查询所有
@Test
public void testMatchAllDoc() throws Exception{
IndexSearcher indexSearcher=LuceneUtils.getIndexSearcher();
Query query=new MatchAllDocsQuery();
System.out.println(query);
LuceneUtils.doSearch(indexSearcher, query, 10);
indexSearcher.getIndexReader().close();
}
QueryParser [常用]
- 通过
QueryParser
也可以创建Query,QueryParser提供一个Parse
方法,此方法可以直接根据查询语法来查询
@Test
public void testQueryParser() throws Exception{
IndexSearcher indexSearcher=LuceneUtils.getIndexSearcher();
/**
* 第一个参数:指定了默认的查询的域名
* 第二个参数: 指定了分词器
*/
QueryParser parser=new QueryParser("fileName",new IKAnalyzer());
/**
* 其中的字符串的形式为: *:*
* 查询所有: *:*
* 根据默认域名查询直接写一个查询内容即可: "springmvc"
* 根据指定域名查询: "fileContent:springmvc"
*/
Query query=parser.parse("fileContent:拦截器");
System.out.println(query);
LuceneUtils.doSearch(indexSearcher, query, 50);
indexSearcher.getIndexReader().close();
}
MultiFieldQueryParser
- 通过MultiFieldQueryParser对多个域查询。
@Test
public void testMulitFiledQueryParser() throws Exception{
IndexSearcher indexSearcher=LuceneUtils.getIndexSearcher();
//指定默认查询的域名
String[] fields={"fileName","fileContent"};
/**
* 创建对象
* 第一个参数: 指定默认域名的数组
* 第二参数: 指定分词器,这里使用中文分词器
*/
MultiFieldQueryParser parser=new MultiFieldQueryParser(fields, new IKAnalyzer());
//这里没有指定域名,因此使用上面指定的两个默认的域名进行查询,这两个默认域名之间是or关系,只要满足就查询返回
//Query query=parser.parse("springmvc");
//指定filePath域名中搜索
Query query=parser.parse("filePath:/home/chenjiabing");
System.out.println(query);
LuceneUtils.doSearch(indexSearcher, query, 50);
indexSearcher.getIndexReader().close();
}
索引维护
- 每个document代表一个索引,维护索引其实就是对
document
的增删改查
添加索引
- 如果数据库系统做了变更,那么我们需要添加索引,那么此时就需要添加索引
indexWriter.addDocument(doc)
- 这个就像是创建索引库的时候,其实就是在添加索引
删除索引
指定条件删除
- 根据
Term
项删除索引,满足条件的将全部删除。 - Term是索引域中最小的单位。根据条件删除时,建议根据唯一键来进行删除。在solr中就是根据
ID
来进行删除和修改操作的。 writer.deleteDocuments(new Term("域名","值"));
@Test
public void deleteIndex() throws Exception {
// 创建分词器,标准分词器
Analyzer analyzer = new IKAnalyzer();
// 创建IndexWriter
IndexWriterConfig cfg = new IndexWriterConfig(Version.LUCENE_4_10_3,
analyzer);
Directory directory = FSDirectory
.open(new File("E:\\11-index\\hcx\\"));
// 创建IndexWriter
IndexWriter writer = new IndexWriter(directory, cfg);
// Terms
writer.deleteDocuments(new Term("id", "1"));
writer.close();
}
删除全部
- 将索引目录的索引信息全部删除,直接彻底删除,无法恢复。慎用!
// 删除索引
@Test
public void deleteIndex() throws Exception {
// 1、指定索引库目录
Directory directory = FSDirectory.open(new File("E:\\11-index\\0720"));
// 2、创建IndexWriterConfig
IndexWriterConfig cfg = new IndexWriterConfig(Version.LATEST,
new StandardAnalyzer());
// 3、 创建IndexWriter
IndexWriter writer = new IndexWriter(directory, cfg);
// 4、通过IndexWriter来删除索引
// a)、删除全部索引
writer.deleteAll();
// 5、关闭IndexWriter
writer.close();
}
修改索引
- 更新索引是先删除再添加,建议对更新需求采用此方法并且要保证对已存在的索引执行更新,可以先查询出来,确定更新记录存在执行更新操作。
@Test
public void updateIndex() throws Exception {
// 创建分词器,标准分词器
Analyzer analyzer = new StandardAnalyzer();
// 创建IndexWriter
IndexWriterConfig cfg = new IndexWriterConfig(Version.LUCENE_4_10_3,
analyzer);
Directory directory = FSDirectory
.open(new File("E:\\11-index\\hcx\\"));
// 创建IndexWriter
IndexWriter writer = new IndexWriter(directory, cfg);
// 第一个参数:指定查询条件
// 第二个参数:修改之后的对象
// 修改时如果根据查询条件,可以查询出结果,则将以前的删掉,然后覆盖新的Document对象,如果没有查询出结果,则新增一个Document
// 修改流程即:先查询,再删除,在添加
Document doc = new Document();
doc.add(new TextField("name", "lisi", Store.YES));
writer.updateDocument(new Term("name", "zhangsan"), doc);
writer.close();
}