我的笔记:
1.分布搜索(多目录搜索)
我们可以使用 multireader 或 multisearcher 搜索多个索引库。
multireader reader = new multireader(new indexreader[] { indexreader.open(@”c:\index”), indexreader.open(@”\\server\index”) });
indexsearcher searcher = new indexsearcher(reader);
hits hits = searcher.search(query);
或
indexsearcher searcher1 = new indexsearcher(reader1);
indexsearcher searcher2 = new indexsearcher(reader2);
multisearcher searcher = new multisearcher(new searchable[] { searcher1, searcher2 });
hits hits = searcher.search(query);
还可以使用 parallelmultisearcher 进行多线程并行搜索。
2.多样化的搜索
/** *** 一个关键字,对一个字段进行查询 **** */
QueryParser qp = new QueryParser("content",analyzer);
query = qp.parse(keyword);
Hits hits = searcher.search(query);
/** *** 模糊查询 **** */
Term term = new Term("content",keyword);
FuzzyQuery fq = new FuzzyQuery(term);
Hits hits = searcher.search(fq);
/** *** 一个关键字,在两个字段中查询 **** */
/*
* 1.BooleanClause.Occur[]的三种类型: MUST : + and MUST_NOT : - not SHOULD : or
* 2.下面查询的意思是:content中必须包含该关键字,而title有没有都无所谓
* 3.下面的这个查询中,Occur[]的长度必须和Fields[]的长度一致。每个限制条件对应一个字段
*/
BooleanClause.Occur[] flags = new BooleanClause.Occur[]{BooleanClause.Occur.SHOULD,BooleanClause.Occur.MUST};
query=MultiFieldQueryParser.parse(keyword,new String[]{"title","content"},flags,analyzer);
/** *** 两个(多个)关键字对两个(多个)字段进行查询,默认匹配规则 **** */
/*
* 1.关键字的个数必须和字段的个数相等
* 2.由于没有指定匹配规定,默认为"SHOULD" 因此,下面查询的意思是:"title"中含有keyword1 或 "content"含有keyword2.
* 在此例中,把keyword1和keyword2相同
*/
query=MultiFieldQueryParser.parse(new String[]{keyword,keyword},new
String[]{"title","content"},analyzer);
/** *** 两个(多个)关键字对两个(多个)字段进行查询,手工指定匹配规则 **** */
/*
* 1.必须 关键字的个数 == 字段名的个数 == 匹配规则的个数
* 2.下面查询的意思是:"title"必须不含有keyword1,并且"content"中必须含有keyword2
*/
BooleanClause.Occur[] flags = new
BooleanClause.Occur[]{BooleanClause.Occur.MUST_NOT,BooleanClause.Occur.MUST};
query=MultiFieldQueryParser.parse(new String[]{keyword,keyword},new
String[]{"title","content"},flags,analyzer);
//设置权重
String[] fields = { "name", "so_province", "so_city","so_level", "so_kind", "so_type"};
Map<String,Float> m = new HashMap<String,Float>();
m.put("name", 2.0f);//对“name”的field设置权重
m.put("so_province", 1.0f);
m.put("so_city", 1.0f);
m.put("so_level", 1.0f);
m.put("so_kind", 1.0f);
m.put("so_type", 1.0f);
MultiFieldQueryParser p = new MultiFieldQueryParser(fields,SearchTools.getStandardAnalyzer(), m );
Query mulQ = p.parse("+\""+key+"\"~0 ");
/** *** 对日期型字段进行查询 **** */
/** *** 对数字范围进行查询 **** */
/*
* 1.两个条件必须是同一个字段
* 2.前面一个条件必须比后面一个条件小,否则找不到数据
* 3.new RangeQuery中的第三个参数,表示是否包含"=" true: >= 或 <= false: > 或 <
* 4.找出 55>=id>=53 or 60>=id>=57:
*/
Term lowerTerm1 = new Term("id","53");
Term upperTerm1 = new Term("id","55");
RangeQuery rq1 = new RangeQuery(lowerTerm1,upperTerm1,true);
Term lowerTerm2 = new Term("id","57");
Term upperTerm2 = new Term("id","60");
RangeQuery rq2 = new RangeQuery(lowerTerm2,upperTerm2,true);
BooleanQuery bq = new BooleanQuery();
bq.add(rq1,BooleanClause.Occur.SHOULD);
bq.add(rq2,BooleanClause.Occur.SHOULD);
Hits hits = searcher.search(bq);
排序的关键点有两个:
1:首先你要排序的字段必须是被index的,并且是untokenized的。
如:
doc.add(new Field("click", dv.get("click").toString(), Field.Store.NO, Field.Index.UN_TOKENIZED));
2:在检索时候:
如:
/***** 排序 *****/
/*
* 1.被排序的字段必须被索引过(Indexecd),在索引时不能 用 Field.Index.TOKENIZED
* (用UN_TOKENIZED可以正常实现.用NO时查询正常,但排序不能正常设置升降序)
* 2.SortField类型
* SCORE、DOC、AUTO、STRING、INT、FLOAT、CUSTOM 此类型主要是根据字段的类型选择
* 3.SortField的第三个参数代表是否是降序true:降序 false:升序
*/
Sort sort = new Sort(new SortField[]{new SortField("click", SortField.INT, true)});
Hits hits = searcher.search(querystring,sort);
/*
* 按日期排序
*/
Sort sort = new Sort(new SortField[]{new SortField("createTime", SortField.INT, false)});
/***** 过滤器 ******/ 不建议使用过滤器
QueryParser qp1 = new QueryParser("content",analyzer);
Query fquery = qp1.parse("我");
BooleanQuery bqf = new BooleanQuery();
bqf.add(fquery,BooleanClause.Occur.SHOULD);
QueryFilter qf = new QueryFilter(bqf);
Hits hits = searcher.search(query);
4. doc部分字段的高效查找
我只需要ID字段,但是返回整个Doc,其他两个文本Field也返回了。因为Lucene是倒索引保存信息的,每一个文本Field需要重新组合成原始的字符串,这也是要耗时间的。
searcher的doc函数有一个可以限定只取部分域的:Document doc(int n, FieldSelector fieldSelector)
我下面定义一个FieldSelector,只取某一个给定名字的Field
class SpecialFieldSelector implements FieldSelector {
protected String m_szFieldName;
public SpecialFieldSelector( String szFieldName ) {
m_szFieldName = szFieldName;
}
public FieldSelectorResult accept(String fieldName) {
if( fieldName.equalsIgnoreCase(m_szFieldName)) {
return FieldSelectorResult.LOAD;
}
else {
return FieldSelectorResult.NO_LOAD;
}
}
}
再修改我的代码:
ScoreDoc[] scoreDocs = topDoc.scoreDocs;
ArrayList<Document> szTest = new ArrayList<Document>();
FieldSelector fieldSelector = new SpecialFieldSelector(FIELD_ID);
for( int i=0;i<scoreDocs.length;i++) {
Document doc = searcher.doc(scoreDocs[i].doc, fieldSelector);
szTest.add(doc);
}
现在返回1.2W个ID耗时0.25秒。虽然比前面只少了大约150毫秒,但是是接近40%的提高了,在负载比较大的应用中还是很重要的。
/** 将小索引文件合并到大的索引文件中去
* @param from 将要合并到to文件的文件
* @param to 将from文件合并到该文件
* @param analyzer
*/
private void mergeIndex(File from,File to,Analyzer analyzer)
{
IndexWriter indexWriter = null;
try{
System.out.println("正在合并索引文件!\t");
indexWriter = new IndexWriter(to,analyzer, false);
indexWriter.setMergeFactor(100000);
indexWriter.setMaxFieldLength(Integer.MAX_VALUE);
indexWriter.setMaxBufferedDocs(Integer.MAX_VALUE);
indexWriter.setMaxMergeDocs(Integer.MAX_VALUE);
FSDirectory[] fs = {FSDirectory.getDirectory(from,false)};
indexWriter.addIndexes(fs);
indexWriter.optimize();
indexWriter.close();
System.out.println("已完成合并!\t");
}
catch(Exception e)
{
Utility.writeLog("合并索引文件出错!mergeIndex()"+e.getMessage(),"");
}
finally
{
try{
if(indexWriter!=null)
indexWriter.close();
}
catch(Exception e ){}
}
}
合并时间是从每天的凌晨3点钟开始,一直到早上9点左右,足足用5个小时才合并完成,其中大索引文件大小为4G,小索引为10MB.
6、建引时候的优化方案
优化方针:
1、多线程构建索引
采用jdk1.5提供的线程池多线程构建索引
2、构建索引的脚本调优
采用游标方式抓取数据而不是分页抓取数据
3、lucene api调优
indexwriter.setRAMBufferSizeMB(256);// M
indexwriter.setUseCompoundFile(false);// 多文件索引
indexwriter.setMergeFactor(10);