全文检索概述
数据分类
结构化数据:具有固定格式或者长度有限的数据,例如数据库中的表。【SQL语句】
非结构化数据:与结构化数据对立,例如:邮件、网页、word文档。【数据扫描、全文检索】
半结构化数据:介于两者之间,例如xml或者json格式的数据。
全文检索过程
反向索引(倒排表):由字符串到文件的映射是文件到字符串映射的反向过程。
索引创建
索引检索
Lucene数学模型
文档、域、词元
文档是Lucene搜索和索引的原子单位,文档为包含一个或者多个域的容器,而域则是依次包含“真正的”被搜索的内容,域值通过分词技术处理,得到多个词元。
For Example,一篇小说(斗破苍穹)信息可以称为一个文档,小说信息又包含多个域,例如:标题(斗破苍穹)、作者、简介、最后更新时间等等,对标题这个域采用分词技术又可以得到一个或者多个词元(斗、破、苍、穹)。
词元权重计算
Term Frequency(tf):此term在文档中出现的次数,tf越大则该词元越重要。
Document Frequency(df):有多少文档包含此term,df越大该词元越不重要。
空间向量模型如下:
计算夹角的余弦值,夹角越小,余弦值越大,分值越大,从而相关性越大。
Lucene文件结构
层次结构
index:一个索引存放在一个目录中;
segment:一个索引中可以有多个段,段与段之间是独立的,添加新的文档可能产生新段,不同的段可以合并成一个新段;
document:文档是创建索引的基本单位,不同的文档保存在不同的段中,一个段可以包含多个文档;
field:域,一个文档包含不同类型的信息,可以拆分开索引;
term:词,索引的最小单位,是经过词法分析和语言处理后的数据。
正向信息
按照层次依次保存了从索引到词的包含关系:index-->segment-->document-->field-->term。
反向信息
反向信息保存了词典的倒排表映射:term-->document
配置Lucene开发环境
Lucene是ASF的开源项目,最新版本是5.2.1,但是鉴于网络上大多数教程使用的是Lucene 4,在本文中使用的版本是Lucene 4.3.1,下载解压,到对应目录找到以下的jar包并添加到构建路径中。
如果使用maven其maven依赖如下:
<dependency>
<groupId>org.apache.lucene</groupId>
<artifactId>lucene-core</artifactId>
<version>4.3.1</version>
</dependency>
<dependency>
<groupId>org.apache.lucene</groupId>
<artifactId>lucene-queryparser</artifactId>
<version>4.3.1</version>
</dependency>
<dependency>
<groupId>org.apache.lucene</groupId>
<artifactId>lucene-queries</artifactId>
<version>4.3.1</version>
</dependency>
<dependency>
<groupId>org.apache.lucene</groupId>
<artifactId>lucene-highlighter</artifactId>
<version>4.3.1</version>
</dependency>
<dependency>
<groupId>org.apache.lucene</groupId>
<artifactId>lucene-analyzers-smartcn</artifactId>
<version>4.3.1</version>
</dependency>
<dependency>
<groupId>org.apache.lucene</groupId>
<artifactId>lucene-analyzers-common</artifactId>
<version>4.3.1</version>
</dependency>
Lucene常用功能介绍
索引创建
创建索的关键类
创建索引的示例代码:
/**
* 索引创建
*/
@Test
public void createIndex() {
// 创建一个分词器(指定Lucene版本)
Analyzer analyzer = new StandardAnalyzer(Version.LUCENE_43);
// IndexWriter配置信息(指定Lucene版本和分词器)
IndexWriterConfig indexWriterConfig = new IndexWriterConfig(Version.LUCENE_43, analyzer);
// 设置索引的打开方式
indexWriterConfig.setOpenMode(OpenMode.CREATE_OR_APPEND);
// 创建Directory对象和IndexWriter对象
Directory directory = null;
IndexWriter indexWriter = null;
try {
directory = FSDirectory.open(new File("Lucene_index/test"));
// 检查Directory对象是否处于锁定状态(如果锁定则进行解锁)
if (IndexWriter.isLocked(directory)) {
IndexWriter.unlock(directory);
}
indexWriter = new IndexWriter(directory, indexWriterConfig);
} catch (IOException e) {
e.printStackTrace();
}
// 创建测试文档并为其添加域
Document doc1 = new Document();
doc1.add(new StringField("id", "abcde", Store.YES)); // 添加一个id域,域值为abcde
doc1.add(new TextField("content", "使用Lucene实现全文检索", Store.YES)); // 文本域
doc1.add(new IntField("num", 1, Store.YES)); // 添加数值域
// 将文档写入索引
try {
indexWriter.addDocument(doc1);
} catch (IOException e) {
e.printStackTrace();
}
Document doc2 = new Document();
doc2.add(new StringField("id", "yes", Store.YES));
doc2.add(new TextField("content", "Docker容器技术简介", Store.YES));
doc2.add(new IntField("num", 2, Store.YES));
try {
indexWriter.addDocument(doc2);
} catch (IOException e) {
e.printStackTrace();
}
// 将IndexWriter提交
try {
indexWriter.commit();
} catch (IOException e) {
e.printStackTrace();
} finally{
try {
indexWriter.close();
directory.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
生成的索引文件如下:
索引检索
索引检索关键类
利用以下的检索程序对前面创建的索引进行检索:
/**
* 索引检索
*/
@Test
public void searchIndex(){
Directory directory = null;
DirectoryReader dReader = null;
try {
directory = FSDirectory.open(new File("Lucene_index/test")); // 索引文件
dReader = DirectoryReader.open(directory); // 读取索引文件
IndexSearcher searcher = new IndexSearcher(dReader); // 创建IndexSearcher对象
Analyzer analyzer = new StandardAnalyzer(Version.LUCENE_43); // 指定分词技术(标准分词-与创建索引时使用的分词技术一致)
// 创建查询字符串(指定搜索域和采用的分词技术)
QueryParser parser = new QueryParser(Version.LUCENE_43, "content", analyzer);
Query query = parser.parse("Docker"); // 创建Query对象(指定搜索词)
// 检索索引(指定前10条)
TopDocs topDocs = searcher.search(query, 10);
if (topDocs != null) {
System.out.println("符合条件的文档总数为:" + topDocs.totalHits);
for (int i = 0; i < topDocs.scoreDocs.length; i++) {
Document doc = searcher.doc(topDocs.scoreDocs[i].doc);
System.out.println("id = " + doc.get("id") + ",content = " + doc.get("content") + ",num = " + doc.get("num"));
}
}
} catch (IOException e) {
e.printStackTrace();
} catch (ParseException e) {
e.printStackTrace();
} finally{
try {
dReader.close();
directory.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
运行结果:
Lucene分词器
常见分词器
其中IKAnalyzer需要下载专门的jar包
/**
* 常见分词器
*/
@Test
public void testAnalyzer(){
final String str = "利用Lucene 实现全文检索";
Analyzer analyzer = null;
analyzer = new StandardAnalyzer(Version.LUCENE_43); // 标准分词
print(analyzer, str);
analyzer = new IKAnalyzer(); // 第三方中文分词
print(analyzer, str);
analyzer = new WhitespaceAnalyzer(Version.LUCENE_43); // 空格分词
print(analyzer, str);
analyzer = new SimpleAnalyzer(Version.LUCENE_43); // 简单分词
print(analyzer, str);
analyzer = new CJKAnalyzer(Version.LUCENE_43); // 二分法分词
print(analyzer, str);
analyzer = new KeywordAnalyzer(); // 关键字分词
print(analyzer, str);
analyzer = new StopAnalyzer(Version.LUCENE_43); //被忽略词分词器
print(analyzer, str);
}
/**
* 该方法用于打印分词器及其分词结果
* @param analyzer 分词器
* @param str 需要分词的字符串
*/
public void print(Analyzer analyzer,String str){
StringReader stringReader = new StringReader(str);
try {
TokenStream tokenStream = analyzer.tokenStream("", stringReader); // 分词
tokenStream.reset();
CharTermAttribute term = tokenStream.getAttribute(CharTermAttribute.class); // 获取分词结果的CharTermAttribute
System.out.println("分词技术:" + analyzer.getClass());
while (tokenStream.incrementToken()) {
System.out.print(term.toString() + "|");
}
System.out.println();
} catch (IOException e) {
e.printStackTrace();
}
}
运行结果:
Query创建
/**
* 创建Query
*/
@Test
public void createQuery(){
String key = "JAVA EE Lucene案例开发";
String field = "name";
String[] fields = {"name","content"};
Analyzer analyzer = new StandardAnalyzer(Version.LUCENE_43); // 分词器
// 单域查询
QueryParser parser1 = new QueryParser(Version.LUCENE_43, field, analyzer);
Query query1 = null;
try {
query1 = parser1.parse(key); // 使用QueryParser创建Query
} catch (ParseException e) {
e.printStackTrace();
}
System.out.println(QueryParser.class + query1.toString());
// 多域查询
MultiFieldQueryParser parser2 = new MultiFieldQueryParser(Version.LUCENE_43, fields, analyzer);
try {
query1 = parser2.parse(key);
} catch (ParseException e) {
e.printStackTrace();
}
System.out.println(QueryParser.class + query1.toString());
// 短语查询
query1 = new TermQuery(new Term(field, key));
System.out.println(TermQuery.class + query1.toString());
// 前缀查询
query1 = new PrefixQuery(new Term(field, key));
System.out.println(PrefixQuery.class + query1.toString());
// 短语查询
PhraseQuery query2 = new PhraseQuery();
query2.setSlop(2); // 设置短语间的最大距离是2
query2.add(new Term(field, "Lucene"));
query2.add(new Term(field, "案例"));
System.out.println(PhraseQuery.class + query2.toString());
// 通配符查询
query1 = new WildcardQuery(new Term(field, "Lucene?"));
System.out.println(WildcardQuery.class + query1.toString());
// 字符串范围搜索
query1 = TermRangeQuery.newStringRange(field, "abc", "azz", false, false);
System.out.println(TermRangeQuery.class + query1.toString());
// 布尔条件查询
BooleanQuery query3 = new BooleanQuery();
query3.add(new TermQuery(new Term(field, "Lucene")),Occur.SHOULD); // 添加条件
query3.add(new TermQuery(new Term(field, "案例")),Occur.MUST);
query3.add(new TermQuery(new Term(field, "案例")),Occur.MUST_NOT);
System.out.println(BooleanQuery.class + query3.toString());
}
运行结果:
IndexSearcher常用方法
检索关键类
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。