利用Lucene实现全文检索
全文检索:
将非结构化数据中的一部分信息提取出来,重新组织,使其变得有一定结构,然后对此有一定结构的数据进行搜索,从而到达搜索相对较快的目的。这部分从非结构化数据中提取出来然后重新组织的信息,称之为索引。
Lucene创建索引的过程
- 获得文档
- 创建文档对象
- 分析文档(分词)
- 创建索引
Field
- 每个文档对象可以有多个Field,不同的文档对象可以有不同的域,同一个文档对象可以有相同的Field(域值和域名都相同)
- 每个文档都有唯一的编号,就是文档id
分析文档
- 将原始文档创建为包含域(Field)的文档(document),需要再对域中的内容进行分析。分析的过程是经过对原始文档提取单词,将字母转为小写,去除表点符号,取出停用词等过程生成最终的语汇单元,可以将语汇单元理解为一个一个的单词。
- 每个单词叫做一个Term,不同域中拆分出来的相同的单词是不同的term。term中包含两部分,一是文档的域名,另一部分是单词的内容。
创建索引
- 索引库中有两部分,一是文档,二是生成的索引
- 创建索引是对语汇单元索引,通过词语找文档,这种索引的结构叫倒排索引结构,也叫反向索引结构,包括索引和文档两部分,索引即词汇表,它的规模较小,而文档集合较大。
/**
* 创建索引
* @throws IOException
*/
@Test
public void test() throws IOException {
//创建一个indexwrite对象
//指定索引存放位置
//指定一个分词器,对文档内容进行分析
Directory directory = FSDirectory.open(Paths.get("D:\\Apicture\\index"));
Analyzer analyzer = new StandardAnalyzer();
IndexWriterConfig config = new IndexWriterConfig(analyzer);
IndexWriter indexWriter = new IndexWriter(directory,config);
//创建Field对象,将Field添加到document对象中
File f = new File("D:\\Apicture\\source");
File[] listFiles = f.listFiles();
for(File file : listFiles) {
Document document = new Document();
//4个域
//文件名称
String fileName = file.getName();
Field nameField = new TextField("fileName",fileName, Field.Store.YES);
//文件大小
long fileSize = FileUtils.sizeOf(file);
Field sizeField = new LongPoint("sizeField",fileSize);
//文件路径
String filePath = file.getPath();
Field pathField = new StoredField("filePath",filePath);
//文件内容
String fileContent = FileUtils.readFileToString(file);
Field contentField = new TextField("fileContent",fileContent, Field.Store.NO);
document.add(nameField);
document.add(sizeField);
document.add(pathField);
document.add(contentField);
//使用indexwriter将document对象写入索引库.此过程创建索引,并将document对象写入索引库
indexWriter.addDocument(document);
}
//关闭IndexWriter对象
indexWriter.close();
}
查询索引
用户输入查询关键字执行搜索之前需要先构建一个查询对象,查询对象中可以指定要搜索的Field文档域,查询关键字等,查询对象会生成具体的查询语法。例如:搜索语法为"fileName:lucene"表示在索引上查找域为fileName,并且关键字为lucene的term,并根据trem找到文档id列表。
/**
* 执行查询
* @throws IOException
*/
@Test
public void test() throws IOException {
//创建一个Directory对象,,也就是索引库存放的位置
Directory directory = FSDirectory.open(Paths.get("D:\\Apicture\\index"));
//创建一个IndexReader对象,需要指定Directory
IndexReader indexReader = DirectoryReader.open(directory);
//创建一个indexSearcher对象
IndexSearcher indexSearcher = new IndexSearcher(indexReader);
//创建一个TermQuery对象,指定查询的域和查询的关键词
Query query = new TermQuery(new Term("fileName","hello.txt"));
//执行查询
TopDocs topDocs = indexSearcher.search(query,2);
//遍历查询结果
ScoreDoc[] scoreDocs = topDocs.scoreDocs;
for(ScoreDoc scoreDoc : scoreDocs) {
//文件id
int doc = scoreDoc.doc;
Document document = indexReader.document(doc);
//文件名称
String fileName = document.get("fileName");
System.out.println(fileName);
//文件路径
String filePath = document.get("filePath");
System.out.println(filePath);
//文件大小,(没有存储,会打印null)
String fileSize = document.get("fileSize");
System.out.println(fileSize);
}
//关流
indexReader.close();
}
支持中文分词
- Lucene自带的标准分词器StandardAnalyzer对中文一个字一个字地进行分词。
- 第三方分词器:IK分词器
- 注意点: 高版本的Lucene使用IK Analyzer时会出现冲突,解决办法是重新写IKAnalyzer的jar包下org.wltea.analyzer.lucene下的IKANalyzer.java和IKTokenizer.java
import org.apache.lucene.analysis.Analyzer;
import org.apache.lucene.analysis.Tokenizer;
/**
* 重写IKAnalyzer
*/
public final class IKAnalyzer extends Analyzer {
private boolean useSmart;
public boolean useSmart() {
return useSmart;
}
public void setUseSmart(boolean useSmart) {
this.useSmart = useSmart;
}
public IKAnalyzer() {
this(false);
}
public IKAnalyzer(boolean useSmart) {
super();
this.useSmart = useSmart;
}
@Override
protected TokenStreamComponents createComponents(String fieldName) {
Tokenizer _IKTokenizer = new IKTokenizer(this.useSmart());
return new TokenStreamComponents(_IKTokenizer);
}
}
import java.io.IOException;
import org.apache.lucene.analysis.Tokenizer;
import org.apache.lucene.analysis.tokenattributes.CharTermAttribute;
import org.apache.lucene.analysis.tokenattributes.OffsetAttribute;
import org.apache.lucene.analysis.tokenattributes.TypeAttribute;
import org.wltea.analyzer.core.IKSegmenter;
import org.wltea.analyzer.core.Lexeme;
/**
* 重写IKTokenizer
*/
public final class IKTokenizer extends Tokenizer {
private IKSegmenter _IKImplement;
private final CharTermAttribute termAtt;
private final OffsetAttribute offsetAtt;
private final TypeAttribute typeAtt;
private int endPosition;
public IKTokenizer(boolean useSmart) {
super();
offsetAtt = addAttribute(OffsetAttribute.class);
termAtt = addAttribute(CharTermAttribute.class);
typeAtt = addAttribute(TypeAttribute.class);
_IKImplement = new IKSegmenter(input, useSmart);
}
@Override
public boolean incrementToken() throws IOException {
clearAttributes();
Lexeme nextLexeme = _IKImplement.next();
if (nextLexeme != null) {
termAtt.append(nextLexeme.getLexemeText());
termAtt.setLength(nextLexeme.getLength());
offsetAtt.setOffset(nextLexeme.getBeginPosition(), nextLexeme.getEndPosition());
endPosition = nextLexeme.getEndPosition();
typeAtt.setType(nextLexeme.getLexemeTypeString());
return true;
}
return false;
}
@Override
public void reset() throws IOException {
super.reset();
_IKImplement.reset(input);
}
@Override
public final void end() {
int finalOffset = correctOffset(this.endPosition);
offsetAtt.setOffset(finalOffset, finalOffset);
}
}
在创建索引的时候只需要将StandardAnalyzer更改为自己写的IKAnalyzer