Lucene学习笔记

562 查看

全文检索概述

数据分类

  • 结构化数据:具有固定格式或者长度有限的数据,例如数据库中的表。【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常用方法

检索关键类