Lucene 详解

1.什么是全文检索
        对于搜索,按被搜索的资源类型,分为两种:可以转为文本的、多媒体类型。我们上一节提到的搜索功能都是搜索的可以转为文本的资源(第一种)。注意,百度或谷歌提供的音乐或视频搜索不是多媒体搜索,他们是按文件名搜索。在智能手机上有一款音乐搜索的软件,可以让他听10秒钟的音乐,然后他就能上网找出这段音乐的名称、演奏者等信息。这是多媒体搜索。
        按搜索的方式,上一节提到的搜索功能都是不处理语义,只是找出包含指定词的所有资源(只对词进行匹配)。下图就是显示“中国的首都是哪里”这个搜索要求对应的结果,可以看到,是没有“北京”这个结果的,结果页面都是出现了这些词的网页:
全文检索(Full-Text Retrieval)是指以文本作为检索对象,找出含有指定词汇的文本。全面、准确和快速是衡量全文检索系统的关键指标。
关于全文检索,我们要知道:1,只处理文本。2,不处理语义。3,搜索时英文不区分大小写。4,结果列表有相关度排序。
        在信息检索工具中,全文检索是最具通用性和实用性的。
全文检索不同于数据库的SQL查询。(他们所解决的问题不一样,解决的方案也不一样,所以不应进行对比)。在数据库中的搜索就是使用SQL,如:SELECT * FROM t WHERE content like ‘%ant%’。这样会有如下问题:
1.匹配效果:如搜索ant会搜索出planting。这样就会搜出很多无关的信息。
2.相关度排序:查出的结果没有相关度排序,不知道我想要的结果在哪一页。我们在使用百度搜索时,一般不需要翻页,为什么?因为百度做了相关度排序:为每一条结果打一个分数,这条结果越符合搜索条件,得分就越高,叫做相关度得分,结果列表会按照这个分数由高到低排列,所以第1页的结果就是我们最想要的结果。
3.全文检索的速度大大快于SQL的like搜索的速度。这是因为查询方式不同造成的,以查字典举例:数据库的like就是一页一页的翻,一行一行的找,而全文检索是先查目录,得到结果所在的页码,再直接翻到这一页。

2.使用Lucene的API操作索引库
        索引库是一个目录,里面是一些二进制文件,就如同数据库,所有的数据也是以文件的形式存在文件系统中的。我们不能直接操作这些二进制文件,而是使用Lucene提供的API完成相应的操作,就像操作数据库应使用SQL语句一样。
        对索引库的操作可以分为两种:管理与查询。管理索引库使用IndexWriter,从索引库中查询使用IndexSearcher。Lucene的数据结构为Document与Field。Document代表一条数据,Field代表数据中的一个属性。一个Document中有多个 Field,Field的值为String型,因为Lucene只处理文本。
        我们只需要把在我们的程序中的对象转成Document,就可以交给Lucene管理了,搜索的结果中的数据列表也是Document的集合。
        有了这些概念,可以写HelloWorld了,其他的概念可以在写完HelloWorld后再进行说明。

3.环境
要加入的jar包有:
lucene-core-3.0.1.jar(核心包)
contrib\analyzers\common\lucene-analyzers-3.0.1.jar(分词器)
contrib\highlighter\lucene-highlighter-3.0.1.jar(高亮)
contrib\memory\lucene-memory-3.0.1.jar(高亮)

4.核心类
索引核心类
1、IndexWriter(写索引)
2、Directory(索引存放位置)
3、Analyzer(分析器)
4、document(文档)
5、Field(域)

搜索核心类
1、IndexSearcher(搜索引)
2、Term(搜索功能基本单元)
3、Query(查询)
4、TermQuery(Query 子类 最基本查询类型)
5、TopDocs(指针容器)

IndexWriter
IndexWriter是在索引过程中的中心组件。这个类创建一个新的索引并且添加文档到一个已有的索引中。
它可以对索引进行添、删、更新操作,但是不能读取或搜索。

添加方法
addDocument (Document)加Document使用默认的分词器
addDocument (Document, Analyzer)加入的时候使用指定的分词器

删除方法
deleteDocuments (Term);
deleteDocuments (Term[]);
deleteDocuments (Query);
deleteDocuments (Query[]);
一般最好有个唯一索引,这样才好删,不然的话有可以会一删一大堆
如:writer.deleteDocument(new Term(“ID”, documentID));

更新方法
注意:更新索引也提供两个方法,其实Lucene是没有办法更新的,只有先删除了再更新,
updateDocument (Term, Document);
如:writer.updateDocument(new Term(“ID”, documenteId), newDocument);
updateDocument (Term, Document, Analyzer)

5.实例
import java.io.IOException;
import java.io.InputStreamReader;
import java.sql.*;
import java.util.Date;
import org.apache.lucene.analysis.Analyzer;
import org.apache.lucene.analysis.standard.StandardAnalyzer;
import org.apache.lucene.document.Document;
import org.apache.lucene.document.Field;
import org.apache.lucene.index.IndexWriter;
import org.apache.lucene.queryParser.ParseException;
import org.apache.lucene.queryParser.QueryParser;
import org.apache.lucene.search.Hits;
import org.apache.lucene.search.IndexSearcher;
import org.apache.lucene.search.Query;

public class SqlCon {
	public static void main(String[] args) {
		Date date1 = new Date();
		String connectionUrl = "jdbc:sqlserver://localhost:1433;"
				+ "databaseName=pubs;user=sa;password=";
		Connection con = null;
		Statement stmt = null;
		ResultSet rs = null;
		try {
			Class.forName("com.microsoft.sqlserver.jdbc.SQLServerDriver");
			con = DriverManager.getConnection(connectionUrl);
			String SQL = "SELECT TOP 10 * FROM users";
			stmt = con.createStatement();
			rs = stmt.executeQuery(SQL);
			IndexWriter writer = new IndexWrite("C://index//",
					new StandardAnalyzer(), true);
			while (rs.next()) {
				Document doc = new Document();
				doc.add(new Field("id", rs.getString("id"), Field.Store.YES,
						Field.Index.TOKENIZED));
				doc.add(new Field("name", rs.getString("name"),
						Field.Store.YES, Field.Index.TOKENIZED));
				doc.add(new Field("sex", rs.getString("sex"), Field.Store.YES,
						Field.Index.TOKENIZED));
				doc.add(new Field("age", rs.getString("age"), Field.Store.YES,
						Field.Index.TOKENIZED));
				writer.addDocument(doc);
			}
			writer.close();
			Date date2 = new Date();
			System.out.println("用时" + (date2.getTime() - date1.getTime())
					+ "毫秒索引已建立");
			DoSearch();
		} catch (Exception e) {
			e.printStackTrace();
		} finally {
			if (rs != null)
				try {
					rs.close();
				} catch (Exception e) {
				}
			if (stmt != null)
				try {
					stmt.close();
				} catch (Exception e) {
				}
			if (con != null)
				try {
					con.close();
				} catch (Exception e) {
				}
		}

	}

	public static void DoSearch() throws IOException, ParseException {
		IndexSearcher indexSearcher = new IndexSearcher("C://index//");// 和上面的IndexWriter一样是一个工具
		QueryParser queryParser = new QueryParser("name",
				new StandardAnalyzer());
		Query query = queryParser.parse("yxy");// 这个地方Query是抽象类大家也注意一下
		Hits hits = indexSearcher.search(query);
		// Document doc = null;
		System.out.print("正搜索................");
		System.out.println("找到了" + hits.length() + "个结果");
		for (int i = 0; i < hits.length(); i++) {
			Document doc = hits.doc(i);
			System.out.println("内容是:" + doc.get("name"));// 注意这里输出的是什么
		}
	}
}


6.高亮(Highlight)
需要的jar包为:
// 生成高亮器
Formatter formatter = newSimpleHTMLFormatter("<span class='kw'>", "</span>");
Scorer scorer = newQueryScorer(query);
Highlighter highlighter = newHighlighter(formatter, scorer);
highlighter.setTextFragmenter(newSimpleFragmenter(20));
// 使用高亮器:对content属性值进行摘要并高亮
Stringtext = highlighter.getBestFragment(LuceneUtils.getAnalyzer(),"content", doc.get("content"));
// 如果进行高亮的属性值中没有要搜索的关键字,则返回null
if (text != null) {
   doc.getField("content").setValue(text);
}

7.复合查询(多种查询条件的综合查询)
(1)联合两个索引查询,已解决:
IndexSearcher[] searchers = new IndexSearcher[2];  
searchers[0] = new IndexSearcher(m_indexpath); 
searchers[1] = new IndexSearcher(m_outindexpath); 
MultiSearcher multiSearcher = new MultiSearcher(searchers); 

(2)还有个进行多条件搜索 and 与 or 的操作————
用 MultiFieldQueryParser 建议重新封装
MultiFieldQueryParser.Parser(p[],d[],f[],analyer)
  成or 与 and操作合一 或者
BooleanQuery m_BooleanQuery = new BooleanQuery(); 
Query query = QueryParser.Parse(m_SearchText, "INSTRUMENT_NAME", analyzer); 
Query query2 = QueryParser.Parse(m_SearchText2, "INSTRUMENT_NAME2", analyzer); 
m_BooleanQuery.Add(query, true, false); 
m_BooleanQuery.Add(query2, true, false); 
(3)复合查询(多种查询条件的综合查询)
Query query=MultiFieldQueryParser.parse("索引”,new String[] {"title","content"},analyzer); 
Searcher searcher=new IndexSearcher(indexFilePath); 
Hits hits=searcher.search(query); 
for (int i = 0; i < hits.length(); i++)  { 
            System.out.println(hits.doc(i).get("name")); 
} 

8.为查询优化索引(index)
        Indexwriter.optimize()方法可以为查询优化索引(index),之前提到的参数调优是为indexing过程本身优化,而这里是为查询优化,优化主要是减少index文件数,这样让查询的时候少打开文件,优化过程中,lucene会拷贝旧的index再合并,合并完成以后删除旧的index,所以在此期间,磁盘占用增加, IO符合也会增加,在优化完成瞬间,磁盘占用会是优化前的2倍,在optimize过程中可以同时作search。

9.Lucene 的检索结果排序
        Lucene的排序主要是对org.apache.lucene.search.Sort的使用。Sort可以直接根据字段Field生成,也可以根据标准的SortField生成,但是作为Sort的字段,必须符合以下的条件:唯一值以及Indexed。可以对Integers, Floats, Strings三种类型排序。
        对整数型的ID检索结果排序只要进行以下的简单操作:
Sort sort = new Sort("id");
Hits hits = searcher.search(query, sort);
用户还可以根据自己定义更加复杂的排序,详细请参考API。

10.需要注意的问题
(1)IndexWriter在添加新的document后,需要重新建立Index,则需要调用writer.optimize();方法
(2)Lucene没有update索引的方法,需要删除后重新建立,参考remove方法
(3)用IndexReader删除Document后,需要重新用IndexWriter进行整理,否则无法在进行搜索(不知道是不是我设置问题)
(4)Lucene先在内存中进行索引操作,并根据一定的批量进行文件的写入。这个批次的间隔越大,文件的写入次数越少,但占用内存会很多。反之占用内存少,但文件IO操作频繁,索引速度会很慢。在IndexWriter中有一个MERGE_FACTOR参数可以帮助你在构造索引器后根据应用环境的情况充分利用内存减少文件的操作。根据我的使用经验:缺省Indexer是每20条记录索引后写入一次,每将MERGE_FACTOR增加50倍,索引速度可以提高1倍左右。
(5)并发操作Lucene
所有只读操作都可以并发
在index被修改期间,所有只读操作都可以并发
对index修改操作不能并发,一个index只能被一个线程占用
index的优化,合并,添加都是修改操作
(6)Locking机制
lucence内部使用文件来locking,默认的locking文件放在java.io.tmpdir,可以通过-Dorg.apache.lucene.lockDir=xxx指定新的dir,有write.lock commit.lock两个文件,lock文件用来防止并行操作index,如果并行操作, lucene会抛出异常,可以通过设置-DdisableLuceneLocks=true来禁止locking,这样做一般来说很危险,除非你有操作系统或者物理级别的只读保证,比如把index文件刻盘到CDROM上。

参考: http://www.iteye.com/topic/582236

猜你喜欢

转载自mengqingyu.iteye.com/blog/1819698