http://akululu.iteye.com/blog/314130
lucene多索引文件并行查询
博客分类: LuceneLucene并行索引多目录
1、多字段搜索就是同时要一个以上的字段中的内容进行比较搜索,类似概念在SQL中就是select * from Table where a like '%query%' or b like '%query%'。
Lucene.net中的单个字段查询大家都比较熟悉,这里对字段content进行搜索
Query query = QueryParser.Parse(querystr,"content",new ChineseAnalyzer());
Hits hits = searcher.Search(query);
对多个字段查询用到一个MultiFieldQueryParser对象,该对象继承自Query,我们要对字段title,content进行搜索。
string[] fields = {"content","title"};
Query multiquery = MultiFieldQueryParser.Parse(querystr,fields,new ChineseAnalyzer());
Hits hits = searcher.Search(multiquery);
2、多索引目录就是要在多个索引目录的中进行比较搜索,类似概念在SQL中就是select * from TableA union select * from TableB。
IndexSearcher[] searchers = new IndexSearcher[2];
searchers[0] = new IndexSearcher(IndexPath0);
searchers[1] = new IndexSearcher(IndexPath1);
MultiSearcher multisearcher = new MultiSearcher(searchers);
TopDocs multitopdocs = multisearcher.Search(query, null, 1000);
这个搜索的结果可能有相同的信息,比如你有一条相同的信息在多个目录中索引,搜索的结果就会出现多次相同的信息。
还有一种搜索方式是用到ParallelMultiSearcher这个对象,它是从MulitSearcher继承而来。
ParallelMultiSearcher parallelmultisearcher = new ParallelMultiSearcher(searchers);
TopDocs paralleltopdocs = parallelmultisearcher.Search(query, null, 1000);
这个搜索是对搜索后的结果进行合并,剔除重复的信息。
IndexSearcher[] searchers = new IndexSearcher[2];
searchers[0] = new IndexSearcher(m_indexpath);
searchers[1] = new IndexSearcher(m_outindexpath);
MultiSearcher multiSearcher = new MultiSearcher(searchers);
TopScoreDocCollector collector = TopScoreDocCollector.create(10, false);
ScoreDoc[] hits = collector.topDocs().scoreDocs;
http://www.360doc.com/content/10/0818/14/87000_46942239.shtml
字号:大中小
一、 概述
Lucene3.0(以下简称3.0)已于2009-11-25发布,3.0版本是重大的版本,改动很大。在API上做了很多的调整,已经删除了很多之前 废弃的方法以及类,并支持了很多Java5 的新特性:包括泛型、可变参数、枚举和autoboxing等。
因此,此版本和2.x版本不能兼容,如要使用3.0版本,最好是在新项目中去使用,而不是去升级2.x或之前的版本!
二、3.0的变化
1、2.9版本介绍:
由于新版本变动很大,官方是不推荐从旧版本升级到新版本的。因为改动会很大。
其实在2.9版本时改动就很大,因为2.9版本就是为3.0做准备的,但是为了向下兼容,2.9并没有抛弃之前的旧方法,所以可以直接向下兼容。2.9版 本主要是在性能方面的优化,包括在Lucene对Lucene底层的内部结构改进、索引的管理方式等多个方面。
1.1、索引文件改进
Lucene的索引数据是存放在独立的文件中的,这些文件就是存储着索引数据库一些列分离的“片段”。当我们想索引中增加文档时,便会不断的创建一些可以 合并的新片段,因为读写文件的开销比较大,因此这些字段信息Lucene并非每次都直接加到索引文件里面去,而是先缓存,等到一定量的时候再一次写到文件 中。在2.9以后,Lucene会为每个片段分别管理FieldCache以此避开跨片段加载FieldCatch的需求,这样就解决了Lucene跨片 段加载FieldCatch的效率很低下问题,这个改动大为提高了性能。Lucid Imagination的Mark Miller运行了一个简单的性能测试,表明在5,000,000个不同字符串下的情况下,Lucene 相对于2.4版本会获得15倍左右的性能提高:
Lucene 2.4: 150.726s
Lucene 2.9: 9.695s
1.2 重开搜索
新版本引入了IndexWriter.getReader()方法,它可用于搜索目前完整的索引,包括当前IndexWriter会话中还没有提交的改 变,这带来了接近于实时搜索的能力。此外,你还可以调用IndexWriter.setMergedSegmentWarmer()方法进行“预热”,这 样那些片断便可以立即投入使用了。
1.3、数字处理
2.9版本之前的版本,都是基于文本搜索的,因为对于很多数字的处理方式就很头疼,例如在我们项目中遇到的很多问题都是由于把数字当作了文本处理出现的BUG:
1、搜索价格的5,把包含.5的也搜索出来了;
2、排序(降序)时,把800排到5000前面;
……
这些都是由于Lucene把所有的都作为文本处理的方式造成的问题。Lucene 2.9以后已经自带对数字的处理方式。Field和Query类会采取合适的精度进行索引和搜索,这样大大降低了需要搜索的关键字数量,使查询的响应能力得以显著提高。
1.4 其他优化
引入了新的查询类型和适用性更广的多关键字查询(通配、前缀等等)方式,以及新的针对波斯语,阿拉伯语及中文的分析器。此外,这次更新还包括更好 Unicode支持,一个新的查询分析框架,以及对地理位置的查询,它允许根据距离信息对文档进行过滤和排序(如“找出我家5公里范围内的所有超市”)。
2、2.9版本和3.0比较
虽然2.9是为3.0做准备的一个版本,但是3.0和2.9相比较,变化还是比较大的,这处要体现在:
1、3.0抛弃了在2.9声明废弃的方法,因此3.0无法向下兼容;
2、3.0放弃了对Java1.4的支持,改为对高版本Java1.5和ant 1.7.0支持;
3、其他内核的一些变化,如oalLock.isLocked()现在会抛出IOException异常,对于一些静态变量的改变等。
3、主要方法的改变
这里这种讲下目前新版本后使用建立索引以及搜索的不同。
3.1、建立索引
新版本在建立索引时抛弃了很多未用的方法,见下图,所有声明被抛弃的IndexWriter构造函数都在3.0中被删除。
3.0版本的IndexWriter构造函数:
在增加索引时,每个field的常量也有改变,具体如下:
3.0和之前版本的常量修改
3.2、查询
删除了Hits类,增加了TopScoreDocCollector去取得“Hits”,实际上在3.0给了个新命名:collector。使用方式和 hits类同,同时删除了Search以及QueryParser的几个构造方法,QueryParser删除了 QueryParser(String f, Analyzer a)构造方法。
新查询例子如下(蓝色部分是与以往不同的部分):
view plaincopy to clipboardprint?
3.0版本的Search构造方法:
3.0前的构造方法:
三、总体图
3.0版本的结构和之前的版本(2.9之前)相比,在程序结构上表现出来就只是多了一个message包,用来专门处理国际化。
见上图,可以看到,3.0和之前的版本一样还是由对外接口、索引核心以及基础结构封装三大部分共八个模块(也即包package),详细介绍详见附件一。
我 们从上图也可以看到Lucene搜索时的调用关系:当我们要查询一个词时,在查询模块(search)会先调用语法分析器(queryParser)对查 询语句进行分析,语法分析模块调用了词法分析器(analysis)进行词法分析,如对搜索关键字分词、过滤等,词法分析器在使用时会根据实际情况调用国 际化模块(message)进行一些国际化的处理。当这些前置工作做完之后,才真正进入到搜索核心,首先会调用索引模块(index),它负责向底层的存 储类(store)去读取索引文件里面的数据,然后返回给查询模块。其他模块在整个搜索过程中是作为公共类存在的。
附件一、 Lucnen3.0包详细介绍
1、analysis
Analysis包含一些内建的分析器,例如按空白字符分词的WhitespaceAnalyzer,添加了stopwrod过滤的StopAnalyzer,最常用的是StandardAnalyzer。
2、document
Document包含文档的数据结构,例如Document类定义了存储文档的数据结构,Field类定义了Document的一个域。
3、index
Index包含了索引的读写类,例如对索引文件的segment进行写、合并、优化的IndexWriter类和对索引进行读取和删除操作的 IndexReader类,这里要注意的是不要被IndexReader这个名字误导,以为它是索引文件的读取类,实际上删除索引也是由它完成, IndexWriter只关心如何将索引写入一个个segment,并将它们合并优化;IndexReader则关注索引文件中各个文档的组织形式。
4、queryParser
QueryParser包含了解析查询语句的类,lucene的查询语句和sql语句有点类似,有各种保留字,按照一定的语法可以组成各种查询。 Lucene有很多种Query类,它们都继承自Query,执行各种特殊的查询,QueryParser的作用就是解析查询语句,按顺序调用各种 Query类查找出结果。
5、search
Search包含了从索引中搜索结果的各种类,例如刚才说的各种Query类,包括TermQuery、BooleanQuery等就在这个包里。
6、store
Store包含了索引的存储类,例如Directory定义了索引文件的存储结构,FSDirectory为存储在文件中的索引,RAMDirectory为存储在内存中的索引,MmapDirectory为使用内存映射的索引。
7、util
Util包含一些公共工具类,例如时间和字符串之间的转换工具。
8、message
处理国际化的类。
附件二、Lucene相关名词解释(部分)
1、IndexWriter
lucene中最重要的的类之一,它主要是用来将文档加入索引,同时控制索引过程中的一些参数使用。
2、Analyzer
分析器,主要用于分析搜索引擎遇到的各种文本。常用的有StandardAnalyzer分析器,StopAnalyzer分析器,WhitespaceAnalyzer分析器等。
3、Directory
索引存放的位置;lucene提供了两种索引存放的位置,一种是磁盘,一种是内存。一般情况将索引放在磁盘上;相应地lucene提供了FSDirectory和RAMDirectory两个类。
4、Document
文档;Document相当于一个要进行索引的单元,任何可以想要被索引的文件都必须转化为Document对象才能进行索引。
5、Field
字段。
6、IndexSearcher
是lucene中最基本的检索工具,所有的检索都会用到IndexSearcher工具;
7、Query
查询,lucene中支持模糊查询,语义查询,短语查询,组合查询等等,如有TermQuery,BooleanQuery,RangeQuery,WildcardQuery等一些类。
8、QueryParser
是一个解析用户输入的工具,可以通过扫描用户输入的字符串,生成Query对象。
9、Hits
在搜索完成之后,需要把搜索结果返回并显示给用户,只有这样才算是完成搜索的目的。在lucene中,搜索的结果的集合是用Hits类的实例来表示的
http://www.360doc.com/content/10/0819/10/87000_47125110.shtml
最近项目中用到了Lucene3.0,如下:
创建索引:
-
Java code
-
public void index() throws CorruptIndexException,
LockObtainFailedException, IOException {
// 索引目录
File indexDir = new File( " D:/workspace/code/java/TestLucene3/index/txt/test/ " );
// 注意:这里建立索引用的分词方法,在搜索时分词也应该采用同样的分词方法。不然搜索数据可能会不正确
// 使用Lucene自带分词器
Analyzer luceneAnalyzer = new StandardAnalyzer(Version.LUCENE_CURRENT);
// 第一个参数是存放索引文件位置, 第二个参数是使用的分词方法, 第三个:true,建立全新的索引,false,建立增量索引。
// IndexWriter indexWriter = new IndexWriter(indexDir, luceneAnalyzer, true);
// 第一个参数是存放索引目录有FSDirectory(存储到磁盘上)和RAMDirectory(存储到内存中), 第二个参数是使用的分词器, 第三个:true,建立全新的索引,false,建立增量索引,第四个是建立的索引的最大长度。
IndexWriter indexWriter = new IndexWriter(FSDirectory.open(indexDir),
luceneAnalyzer, true , IndexWriter.MaxFieldLength.LIMITED);
// 索引合并因子
// SetMergeFactor(合并因子)
// SetMergeFactor是控制segment合并频率的,其决定了一个索引块中包括多少个文档,当硬盘上的索引块达到多少时,
// 将它们合并成一个较大的索引块。当MergeFactor值较大时,生成索引的速度较快。MergeFactor的默认值是10,建议在建立索引前将其设置的大一些。
indexWriter.setMergeFactor( 100 );
// SetMaxBufferedDocs(最大缓存文档数)
// SetMaxBufferedDocs是控制写入一个新的segment前内存中保存的document的数目,
// 设置较大的数目可以加快建索引速度,默认为10。
indexWriter.setMaxBufferedDocs( 100 );
// SetMaxMergeDocs(最大合并文档数)
// SetMaxMergeDocs是控制一个segment中可以保存的最大document数目,值较小有利于追加索引的速度,默认Integer.MAX_VALUE,无需修改。
// 在创建大量数据的索引时,我们会发现索引过程的瓶颈在于大量的磁盘操作,如果内存足够大的话,
// 我们应当尽量使用内存,而非硬盘。可以通过SetMaxBufferedDocs来调整,增大Lucene使用内存的次数。
indexWriter.setMaxMergeDocs( 1000 );
// SetUseCompoundFile这个方法可以使Lucene在创建索引库时,会合并多个 Segments 文件到一个.cfs中。
// 此方式有助于减少索引文件数量,对于将来搜索的效率有较大影响。
// 压缩存储(True则为复合索引格式)
indexWriter.setUseCompoundFile( true );
long startTime = new Date().getTime();
String temp = "" ;
// 增加索引字段
//
// 在Field中有三个内部类:Field.Index,Field.Store,Field.termVector,而构造函数也用到了它们。
// 参数说明:
// Field.Store:
// Field.Store.NO:表示该Field不需要存储。
// Field.Store.Yes:表示该Field需要存储。
// Field.Store.COMPRESS:表示使用压缩方式来存储。
// Field.Index:
// Field.Index.NO:表示该Field不需要索引。
// Field.Index.TOKENIZED:表示该Field先被分词再索引。
// Field.Index.UN_TOKENIZED:表示不对该Field进行分词,但要对其索引。
// Field.Index.NO_NORMS:表示该Field进行索引,但是要对它用Analyzer,同时禁止它参加评分,主要是为了减少内在的消耗。
// TermVector这个参数也不常用,它有五个选项。
// Field.TermVector.NO表示不索引Token的位置属性;
// Field.TermVector.WITH_OFFSETS表示额外索引Token的结束点;
// Field.TermVector.WITH_POSITIONS表示额外索引Token的当前位置;
// Field.TermVector.WITH_POSITIONS_OFFSETS表示额外索引Token的当前和结束位置;
// Field.TermVector.YES则表示存储向量。
// 增加文档 Field相当于增加数据库字段一样检索,获取都需要的内容,直接放index中,不过这样会增大index,保存文件的txt内容
/**
* Field.Store 表示“是否存储”,即该Field内的信息是否要被原封不动的保存在索引中。
* Field.Index 表示“是否索引”,即在这个Field中的数据是否在将来检索时需要被用户检索到,一个“不索引”的Field通常仅是提供辅助信息储存的功能。
* Field.TermVector 表示“是否切词”,即在这个Field中的数据是否需要被切词。
*/
Field fieldPath = new Field( " path " , "" , Field.Store.YES, Field.Index.NO);
Field fieldBody = new Field( " content " , temp, Field.Store.YES, Field.Index.ANALYZED, Field.TermVector.WITH_POSITIONS_OFFSETS);
Field fieldId = new Field( " id " , "" , Field.Store.YES, Field.Index.NOT_ANALYZED, Field.TermVector.WITH_POSITIONS_OFFSETS);
Document document = new Document();
// 做测试,循环100000遍建索引。也可以读取文件内容建索引
for ( int i = 0 ; i < 100000 ; i ++ ) {
document = new Document();
temp = " 王熙凤历幻返金陵 甄应嘉蒙恩还玉阙 " ;
fieldPath.setValue( " D:\\workspace\\code\\java\\TestLucene3\\txt\\ " + i + " .txt " );
fieldBody.setValue(temp);
fieldId.setValue(String.valueOf(i));
document.add(fieldPath);
document.add(fieldBody);
document.add(fieldId);
indexWriter.addDocument(document);
i ++ ;
}
// optimize()方法是对索引进行优化
indexWriter.optimize();
indexWriter.close();
// 若需要从索引中删除某一个或者某一类文档,IndexReader提供了两种方法:
// reader.DeleteDocument(int docNum)
// reader.DeleteDocuments(Term term)
// 前者是根据文档的编号来删除该文档,docNum是该文档进入索引时Lucene的编号,是按照顺序编的;后者是删除满足某一个条件的多个文档。
// 在执行了DeleteDocument或者DeleteDocuments方法后,系统会生成一个*.del的文件,该文件中记录了删除的文档,但并未从物理上删除这些文档。此时,这些文档是受保护的,当使用Document
// doc = reader.Document(i)来访问这些受保护的文档时,Lucene会报“Attempt to access a
// deleted document”异常。如果一次需要删除多个文档时,可以用两种方法来解决:
// 1. 删除一个文档后,用IndexWriter的Optimize方法来优化索引,这样我们就可以继续删除另一个文档。
// 2. 先扫描整个索引文件,记录下需要删除的文档在索引中的编号。然后,一次性调用DeleteDocument删除这些文档,再调用IndexWriter的Optimize方法来优化索引。
long endTime = new Date().getTime();
System.out.println( " \n这花费了 " + (endTime - startTime) + " 毫秒增加到索引! " );
}
查询:
/**
* 查询
*
* @param String word 关键词
* @param String filedName 域字段
* @param String indexDir 索引位置
* @throws CorruptIndexException
* @throws IOException
* @throws ParseException
* @auther <a href="mailto:gaoxuguo@feinno.com">Gao XuGuo</a> Nov 30, 2009
* 2:56:42 PM
*/
public List<Map<String, String>> search(String indexDir)
throws CorruptIndexException, IOException, ParseException {
File file = new File(indexDir);
IndexSearcher is = new IndexSearcher(FSDirectory.open(file), true);
String field = "content";
BooleanQuery bq = new BooleanQuery();
QueryParser parser = new QueryParser(Version.LUCENE_CURRENT, field,
new StandardAnalyzer(Version.LUCENE_CURRENT));
Query query = parser.parse("content:王熙凤");
Query q = new TermQuery(new Term("id","100"));
bq.add(q,Occur.SHOULD);
bq.add(query,Occur.SHOULD);
// 100表示取前100条数据
TopScoreDocCollector collector = TopScoreDocCollector.create(100, true);
long start = new Date().getTime();// start time
/**
* Lucene内置了三个Filter子类:
* 1)DateFilter使搜索只限于指定的日期域的值在某一时间范围内的文档空间里
* 2)QueryFilter把查询结果做为另一个新查询可搜索的文档空间
* 3)CachingWrappperFilter是其他过滤器的装饰器,将结果缓存起来以便再次使用,从而提高性能。
*
*/
String[] dirs = {indexDir};
MultiSearcher ms = this.getMultiSearcher(dirs);
ms.search(bq, collector);
// is.search(bq, collector);
ScoreDoc[] docs = collector.topDocs().scoreDocs;
Document doc;
for (ScoreDoc sd : docs) {
doc = is.doc(sd.doc);
// 取得doc里面的Field并从doc里面读取值
for (Fieldable fa : doc.getFields()) {
System.out.print(fa.name() + "=" + doc.get(fa.name()) + " ");
}
System.out.println();
}
long end = new Date().getTime();
if(is != null) is.close();
System.out.println("找到 " + collector.getTotalHits()
+ " 条数据,花费时间 " + (end - start)
+ " 秒");
return null;
}
少发了一个方法:
-
Java code
-
/**
* 得到MultiSearcher多目录查询实例
*
* @param String[] dirs 要查询的索引目录。
*
* @return MultiSearcher
* @throws IOException
* @auther <a href="mailto:gaoxuguo@feinno.com">Gao XuGuo</a>
* Jan 22, 2010 3:44:16 PM
*/
private MultiSearcher getMultiSearcher(String[] dirs) throws IOException {
// 多目录
IndexSearcher [] searchers = new IndexSearcher[dirs.length];
int i = 0 ;
for (String dir : dirs) {
searchers[i] = new IndexSearcher(FSDirectory.open( new File(dir)), true );
i ++ ;
}
// 多目录查询
return new MultiSearcher(searchers);
}