Lucene不是一个完整的全文索引应用,而是是一个用Java写的全文索引引擎工具包,它可以方便的嵌入到各种应用中实现针对应用的全文索引/检索功能。 已经有很多Java项目都使用了Lucene作为其后台的全文索引引擎,例如:Web论坛Jive,邮件列表HTML归档/浏览/查询系统Eyebrows,包括我们熟悉的Eclipse的全文帮助搜索功能。在实际开发中,因为数据库不是专为全文搜索设计的,所以对于全文搜索,特别是模糊查询类的全文搜索,用Lucene就比数据库的效率有优势的多。对于变化很少,但查询访问量大的数据,将数据库的数据以document形式存在本地,访问的时候不经过数据库,可以减少数据库压力。对于第一个Lucene程序,编写主要分以下几个步骤:
1.初始化Lucene的检索工具IndexSearcher
IndexSearcher是Lucene中最基本的检索工具,所有的检索都会用到IndexSearcher检索工具,但是在使用IndexSearcher之前,还要做一些准备工作,即对检索工具IndexSearcher进行初始化。
初始化IndexSearcher,需要设置索引存放的路径,这样才能让查询器定位索引,用于后面进行搜索。如以下为一个初始化IndexSearcher的过程:(4种方式)
public IndexSearcher(String path) throws IOException { this(IndexReader.open(path), true); } public IndexSearcher(Directory directory) throws IOException { this(IndexReader.open(directory), true); } public IndexSearcher(IndexReader r) { this(r, false); } private IndexSearcher(IndexReader r, boolean closeReader) { reader = r; this.closeReader = closeReader; }
返回的结果是IndexSearcher类的一个实例,indexDir表示索引文件的存放路径。
2.构建索引器和索引函数
实例化一个构造器:
IndexWriter writer = new IndexWriter("D:/index/", new PanGuAnalyzer(), true); //索引的存储位置这个函数有三个参数,分别是:path——索引文件存放路径,a——分词工具,create——true表示建立索引
3.建立索引
将要建立索引的文件构造成一个Document对象,并添加一个域,如:
Document doc = new Document(); doc.Add(new Field("id", item.id.ToString(), Field.Store.YES, Field.Index.UN_TOKENIZED));这里先解释下三个概念:
1)Field:可以理解成索引文件中一个个的字段块,占用空间按字段长度分配。
2)Store:一个内部类,它是static的,主要为了设置Field的存储属性.
主要有以下几种:
public static final Store COMPRESS = new Store("COMPRESS"); // 在索引中压缩存储Field的值
public static final Store YES = new Store("YES");//在索引中存储Field的值
public static final Store NO = new Store("NO"); // 在索引中不存储Field的值
3.)Index: 通过Index设置索引方式,有以下几种:
public static final Index TOKENIZED = new Index("TOKENIZED"); // 对Field进行索引,同时还要对其进行分词(由Analyzer来管理如何分词)
public static final Index UN_TOKENIZED = new Index("UN_TOKENIZED"); // 对Field进行索引,但不对其进行分词
public static final Index NO_NORMS = new Index("NO_NORMS"); // 对Field进行索引,但是不使用Analyzer
4.优化检索,关闭写入
writer.Optimize(); //添加完所有document,我们对索引进行优化,优化主要是将多个索引文件合并到一个,有利于提高索引速度。 writer.Close();//随后将writer关闭,这点很重要。5.开始检索
1)首先创建一个容器来存放你从索引文件中读取到的数据,这里我们使用Table
private DataTable dt() { DataTable mytab = new DataTable(); mytab.Columns.Add("ID"); mytab.Columns.Add("TRADENAME"); mytab.Columns.Add("AREANAME"); mytab.Columns.Add("COMPANYNAME"); mytab.Columns.Add("FHDES"); mytab.Clear(); return mytab; }2)读取索引文件中的数据
private IndexSearcher LuceneSource() { string INDEX_STORE_PATH = "D:/index/"; //INDEX_STORE_PATH 为索引存储目录 return new IndexSearcher(INDEX_STORE_PATH); }3)得到过滤后数据,即查询条件,你可以理解成SQL里的where条件
a. TermQuery
首先介绍最基本的查询,如果你想执行一个这样的查询:“在content域中包含‘lucene’的document”,那么你可以用TermQuery:
<span style="white-space:pre"> </span>Term t = new Term("content", " lucene");Query query = new TermQuery(t);b.BooleanQuery
如果你想让产品名称或者产品发货说明匹配关键字,那么你可以用:
<span style="white-space:pre"> </span>strkeyword = Common.ProductAbout.GetKeyWordsSplitBySpace(strkeyword, new PanGuTokenizer()); QueryParser companynameparser = new QueryParser("companyname", new PanGuAnalyzer(true)); Query companynamequery = companynameparser.Parse(strkeyword); QueryParser productnameparser = new QueryParser("productname", new PanGuAnalyzer(true)); Query productdesquery = productnameparser.Parse(strkeyword); bq.Add(productdesquery, BooleanClause.Occur.SHOULD);<span style="font-family: Arial, Helvetica, sans-serif;">//</span><span style="font-family: Arial, Helvetica, sans-serif;">此处的BooleanClause.Occur,此类有2个重要的属性,SHOULD和MUST,</span>
<span style="font-family: Arial, Helvetica, sans-serif;"><span style="white-space:pre"> </span>//SHOULD你就理解成SQL里’OR’,MUST理解成SQL里的’AND’,此处表示要同时满足productdesquery和idquery</span>
bq.Add(companynamequery, BooleanClause.Occur.SHOULD);c.WildcardQuery
可以使用通配符*,?等进行查询:
<span style="white-space:pre"> </span>Query query = new WildcardQuery(new Term("content", "use*");d.PrefixQuery
如果你想搜以‘淘’开头的词语,你可以用PrefixQuery:
<span style="white-space:pre"> </span>PrefixQuery query = new PrefixQuery(new Term("content ", "淘");e.FuzzyQuery
用于搜索相似的词,使用Levenshtein算法。如搜索"happy"类似的词:
<span style="white-space:pre"> </span>Query query = new FuzzyQuery(new Term("content", "wuzza");f. RangeQuery
允许搜索时间域从20150302到20150324之间的document
<span style="white-space:pre"> </span>RangeQuery query = new RangeQuery(new Term(“time”, “20150302”), new Term(“time”, “20150324”), true);//true表示含边界
6.构建Query
在使用Query之前,需要首先生成一个Query对象。Lucene既允许直接生成一个Query类型的对象,也允许使用QueryParser类的parse()方法来返回一个Query类型的对象。这两种方法在功能上是完全一样的,只是后者在使用时更方便一些,而前者则更为灵活。在API中的格式:
public static Query parse(String query, String field,Analyzer analyzer)
7.搜索并处理返回结果
在构建完Query对象后,就可以使用前面已经初始化好的IndexSearcher工具来进行检索了。IndexSearcher提供了良好的检索接口,用户只需简单地将Query对象传入,就可以得到一个返回结果。当然,这个过程看似简单,其中也有许多值得思考的问题,如检索结果的排序、过滤等。在Lucene中搜索结果的集合是用Hits类的实例来进行表示的。Hits对象中主要有以下几个经常使用的方法:
• length():返回搜索到结果的总数量。
• doc(int n):返回第n个文档。
• id(int n):返回第n个文档的内部ID号。
• score(n):返回第n个文档的得分。
在开发Web相关应用时,简便的方法是当某个用户检索完毕后,可直接将返回的Hits对象存入该用户的session中,然后根据用户的需要进行相关查询。不过这里读者要注意的一点,由于Hits对象被放入session中,并不适合存入大量文本。因为若是这样,对用户来说,可能导致浏览器的响应速度极慢,对服务器方来说,可能导致服务器的内容被大量Hits所占用,最终造成服务器的崩溃。比较好的一种方式,是将Lucene与数据库相结合,在索引中存入一些关键性的ID字段、路径字段或是简单的文本,而真正的数据提取则从数据库中得到。这样一来既可以发挥Lucene优势,也可以使服务器端的压力减轻。例子:
private Hits LuceneFilteridSource(BooleanQuery bq) { IndexSearcher mysearch = LuceneSource(); Sort sort = new Sort(new SortField("ID", SortField.DOC, false));//排序 return mysearch.Search(bq, sort); }这里的SortField("ID", SortField.DOC, false)指的是对id这个字段进行倒序。SortField主要有以下属性:
FIELD_DOC:按文档数字排序
FIELD_SCORE:按文档分值排序
MissingValue看了API但是没懂,就不提了。
最后的true表示反序,true为正序。
为了减少一次Hits所返回的数据量,可以用将数据放在多个Document里面,如:
Document doc1 = new Document(); doc1.add(Field.Text("contents", "word1 word")); doc1.add(Field.Keyword("path", "path\\document1.txt")); Document doc2 = new Document(); doc2.add(Field.Text("contents", "word2 word")); doc2.add(Field.Keyword("path", "path\\document2.txt")); Document doc3 = new Document(); doc3.add(Field.Text("contents", "word3 word")); doc3.add(Field.Keyword("path", "path\\document3.txt"));
writer.addDocument(doc1); writer.addDocument(doc2); writer.addDocument(doc3);
要想进一步改进可以研究下Hits的缓存机制。
注意,LUCENE不支持关键词为空的情况,所以如果你想把索引文件中所有的数据都调用出来,那可以用如下方法:
for (int i = 0; i < mysearch.MaxDoc(); i++) { Document doc = mysearch.Doc(i); FillingTable(mytab, doc); }下面是我写的一个用Lucene连接MySQL获取数据的入门例子:
环境:MyEclipse+MySQL
导入的包:
数据库表:Student
实现功能:输入名字,返回学生记录的id和地址:
package test; import java.awt.List; import java.io.BufferedInputStream; import java.io.BufferedReader; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStreamReader; import java.io.Reader; import java.io.StringReader; import java.sql.*; import org.apache.lucene.search.Hits; import org.apache.lucene.search.Query; import org.apache.lucene.analysis.TokenStream; import org.apache.lucene.analysis.standard.StandardAnalyzer; import org.apache.lucene.document.*; import org.apache.lucene.index.IndexReader; import org.apache.lucene.index.IndexWriter; import org.apache.lucene.index.TermPositionVector; import org.apache.lucene.queryParser.QueryParser; import org.apache.lucene.search.IndexSearcher; import org.apache.lucene.store.Directory; import org.apache.lucene.store.FSDirectory; import org.apache.lucene.store.RAMDirectory; import jeasy.analysis.MMAnalyzer; public class TestLucene { private static ResultSet recrd; static MMAnalyzer analyzer=new MMAnalyzer(); /** * @param args */ public static void main(String[] args) { // MMAnalyzer analyzer=new MMAnalyzer(); try { File indexpath= new File("F://index"); IndexWriter writer=new IndexWriter(indexpath, analyzer,true); recrd=getConn(); //获取数据库的记录集 // while (recrd.next()) { // System.out.println(recrd.getString(2)); // // } IndexBuilder(writer); //建立索引 BufferedReader bReader= new BufferedReader(new InputStreamReader(System.in)); String query=bReader.readLine().toString(); System.out.println(query); Hits hits= search(query); //输入查询内容后,查询 for(int i=0;i<hits.length();i++){ //返回查询后结果 Document document=hits.doc(i); System.out.println(document.get("user_id")); System.out.println(document.get("username")); System.out.println(document.get("address")); } } catch (Exception e) { // TODO: handle exception } } public static Hits search(String quString){ //搜索用户输入的字符 Hits hits=null; try { IndexSearcher iSearcher = new IndexSearcher(IndexReader .open("F://index")); QueryParser parser=new QueryParser("username",analyzer); Query query=parser.parse(quString); System.out.println(query.toString()); hits=iSearcher.search(query); return hits; } catch (Exception e) { // TODO Auto-generated catch block e.printStackTrace(); } return null; } public static void IndexBuilder(IndexWriter fWriter) throws Exception{ //建立索引 while(recrd.next()){ Directory ramDirectory=new RAMDirectory(); IndexWriter ramWriter=new IndexWriter(ramDirectory, new StandardAnalyzer(),true); Document document=new Document(); Field id= new Field("user_id",recrd.getString("user_id"),Field.Store.YES, Field.Index.TOKENIZED); Field name=new Field("username",recrd.getString("username"),Field.Store.YES, Field.Index.TOKENIZED); Field age=new Field("address",recrd.getString("address"),Field.Store.YES, Field.Index.TOKENIZED ); document.add(id); document.add(name); document.add(age); ramWriter.addDocument(document); ramWriter.close(); fWriter.addIndexes(new Directory[]{ramDirectory}); } } public static ResultSet getConn() { //建立数据库连接,并返回结果 try { Class.forName("com.mysql.jdbc.Driver"); String url="jdbc:mysql://localhost:3306/ss"; Connection conn = DriverManager.getConnection(url, "root", "123"); Statement stmt = conn.createStatement(ResultSet.TYPE_SCROLL_SENSITIVE,ResultSet.CONCUR_UPDATABLE); String sql = "select * from Student"; ResultSet rs = stmt.executeQuery(sql); return rs; } catch (Exception e) { // TODO Auto-generated catch block e.printStackTrace(); } return null; } }结果:
再次声明下,好像Lucene的版本更新变化很大,现在最新是Lucene5,据说已经不支持Lucene3以前的版本,Fields的Store和Index属性貌似被废除了,我在Lucene4的Api里面见到还有,但是用高版本的Lucene好像记得有报错,说没有这个方法。还有就是本来是连接SQLServer的,但是没连成,估计是编码问题。因为之前用Lucene来为本地文件建立检索和搜索时,文件源用记事本新建(编码默认为ANSI),就搜索不到结果,要另存为UTF-8才行,而从SQLServer搜索出来的query是没问题的。但是好像SQLServer不能改编码为UTF-8,只能GBK最多。这点有待探究。如果有幸你能看到我的博文,并有什么发现,欢迎大家交流赐教。
本文参考了一些百度上的资料,但是大部分系自己实践探索成果。转载请注明:http://blog.csdn.net/wws199304/