直观感受一下,如何使用Java代码,利用Lucene创建索引并实现全文搜索的功能。
基础知识
(1)分词器,有很多种,根据业务需要自己设定。常见的IK分词器、jieba分词器、PanGu分词器等等。
可以参考Apache Lucene(全文检索引擎)—分词器;11大Java开源中文分词器的使用方法和分词效果对比做简单了解。
(2)Lucene的基本知识
- Document:文档是建索引的基本单位,不同的文档是保存在不同的段中的,包含一个或者多个Field;
- Field:一篇文档包含不同类型的信息,可以分开索引,比如标题,时间,正文,作者等,都可以保存在不同的Field里;
- Term:不可分割的单词,搜索最小单元,是经过词法分析和语言处理后的字符串;
- Token:一个Term呈现方式,包含这个Term的内容、在文档中的起始位置、类型;
先把简答功能搭建起来,其次熟练使用工具,然后深入理解原理。
创建索引
/* INDEX_PATH指创建索引后,索引的存放位置*/
Directory directory = FSDirectory.open(FileSystems.getDefault().getPath(INDEX_PATH));
/*分词器Analyzer analyzer = new StandardAnalyzer();*/
Analyzer analyzer = new IKAnalyzer(true);
IndexWriterConfig indexWriterConfig = new IndexWriterConfig(analyzer);
IndexWriter indexWriter = new IndexWriter(directory, indexWriterConfig);
/* 把需要创建索引文件放入FILE_PATH*/
List<File> files = listFiles(FILE_PATH);
for (int i = 0; i < files.size(); i++) {
Document document = new Document();
File file = files.get(i);
document.add(new Field("content", readFile(file.getAbsolutePath()), TextField.TYPE_STORED));
document.add(new Field("fileName", file.getName(), TextField.TYPE_STORED));
document.add(new Field("updateTime", file.lastModified() + "", TextField.TYPE_STORED));
indexWriter.addDocument(document);
}
new Field需要加入到document中,存储选项和索引选项的枚举值如下:
Field.Store.YES或者NO(存储域选项)
设置为YES表示或把这个域中的内容完全存储到文件中,方便进行文本的还原;
设置为NO表示把这个域的内容不存储到文件中,但是可以被索引,此时内容无法完全还原;
Field.Index(索引选项)
Index.ANALYZED:进行分词和索引,适用于标题、内容等;
Index.NOT_ANALYZED:进行索引,但是不进行分词,如果身份证号、姓名、ID等,适用于精确搜索;
Index.ANALYZED_NOT_NORMS:进行分词但是不存储norms信息,这个norms中包括了创建索引的时间和权值等信息;
Index.NOT_ANALYZED_NOT_NORMS:即不进行分词也不存储norms信息;
Index.NO:不进行索引;
搜索索引
/*1、创建Directory*/
/*INDEX_PATH 索引存放的路径*/
Directory directory = FSDirectory.open(FileSystems.getDefault().getPath(INDEX_PATH));
/*2、创建IndexReade*/
DirectoryReader directoryReader = DirectoryReader.open(directory);
/* 3、根据IndexReader创建IndexSearch*/
IndexSearcher indexSearcher = new IndexSearcher(directoryReader);
/*4、创建搜索的Query*/
/*使用IK分词*/
Analyzer analyzer = new IKAnalyzer(true);
/*要搜索的字段,一般搜索时都不会只搜索一个字段*/
String[] fields = {"fileName", "content"};
BooleanClause.Occur[] clauses = {BooleanClause.Occur.SHOULD, BooleanClause.Occur.SHOULD};
/*MultiFieldQueryParser表示多个域解析*/
/*keyword 表示:用户搜索内容经过分词去掉停用词后的词语*/
Query multiFieldQuery = MultiFieldQueryParser.parse(keyWord, fields, clauses, analyzer);
/*5、根据searcher搜索并且返回TopDocs 搜索前10条结果*/
TopDocs topDocs = indexSearcher.search(multiFieldQuery, 10);
/*6、根据TopDocs获取ScoreDoc对象,即匹配文档的个数*/
ScoreDoc[] scoreDocs = topDocs.scoreDocs;
QueryScorer scorer = new QueryScorer(multiFieldQuery, "content");
for (ScoreDoc scoreDoc : scoreDocs) {
/*7、根据searcher和ScoreDoc对象获取具体的Document对象*/
Document document = indexSearcher.doc(scoreDoc.doc);
System.out.println("-----------------------------------------");
System.out.println(document.get("fileName") + ":" + document.get("updateTime"));
}
搜索还可以用其他的搜索方式:分组搜索、模糊搜索等等
布尔搜索:字段之间的与或非关系,MUST表示and,MUST_NOT表示not,SHOULD表示or
注意:
搜索时容易出现以下异常,造成这个异常的主要原因是,搜索的内容包含了Lucene的关键字
org.apache.lucene.queryParser.ParseException: Encountered "<EOF>" at line 1, column 0.
Was expecting one of:
<NOT> ...
"+" ...
"-" ...
"(" ...
<QUOTED> ...
<TERM> ...
<PREFIXTERM> ...
<WILDTERM> ...
"[" ...
"{" ...
<NUMBER> ...
出现特殊字符或者Lucene关键字,可以使用 QueryParser的静态方法escape(string s),进行自动转义特殊字符后再进行关键字的查询 ;或者使用反斜杠(\\)进行转义,如搜索中包含“+”,使用“\\+”进行转义
lucene的特殊字符保护了以下的字符:
+, -, &&, || , ! , (,) , { } , [ ] , ^, " , ~, *, ?, : , \, / 。