??xml version="1.0" encoding="utf-8" standalone="yes"?> 在IndexSearchercM有一个管理Lucene得分情况的方法,如下所C: public Explanation explain(Weight weight, int doc) throws IOException { q回的这个Explanation的实例解释了Lucene中Document的得分情c我们可以测试一下,直观地感觉一下到底这个Explanation的实例都记录了一个Document的哪些信息?/span> 写一个测试类Q如下所C: package org.shirdrn.lucene.learn; import java.io.IOException; import net.teamhot.lucene.ThesaurusAnalyzer; import org.apache.lucene.document.Document; public class AboutLuceneScore { writer.addDocument(docA); 该测试类中实C一个徏立烦引的ҎcreateIndex()ҎQ然后通过索一个关键字“一?#8221;Q获取到与它相关的Document的信息?/span> 打印出结果的W一部分为:q个索关键字“一?#8221;在每个Document中出现的ơ数?/span> 打印出结果的W二部分为:相关的Explanation及其得分情况的信息?/span> 试l果输出如下所C: 搜烦关键?lt;一?gt;在编号ؓ 0 的Document中出现过 1 ?br />
搜烦关键?lt;一?gt;在编号ؓ 1 的Document中出现过 1 ?br />
搜烦关键?lt;一?gt;在编号ؓ 2 的Document中出现过 1 ?br />
搜烦关键?lt;一?gt;在编号ؓ 3 的Document中出现过 2 ?br />
搜烦关键?lt;一?gt;在编号ؓ 4 的Document中出现过 2 ?br />
******************************************************************** Document对应的Explanation的一些参数值如下: Document对应的Explanation的一些参数值如下: Document对应的Explanation的一些参数值如下: Document对应的Explanation的一些参数值如下: Document对应的Explanation的一些参数值如下: 先从试的输出结果进行分析,可以获得到如下信息: ?试cMhits.score(i)的gExplanation的getValue()的值是一LQ即Lucene默认使用的得分; ?默认情况下,Lucene按照Document的得分进行排序检索结果; ?默认情况下,如果两个Document的得分相同,按照Document的内部编可行排序,比如上面~号?3?)?1?)是两l得分相同的DocumentQ结果排序时按照Document的编可行了排序Q?/span> 通过从IndexSearchercM的explainҎQ?/span> public Explanation explain(Weight weight, int doc) throws IOException { 可以看出Q实际上是调用了Weight接口cM的explain()ҎQ而Weight是与一个Query相关的,它记录了一ơ查询构造的Query的情况,从而保证一个Query实例可以重用?/span> 具体圎ͼ可以在实现Weight接口的具体类TermWeight中追溯到explain()ҎQ而TermWeightcL一个内部类Q定义在TermQuerycd部。TermWeightcȝexplain()Ҏ如下所C: public Explanation explain(IndexReader reader, int doc) ComplexExplanation result = new ComplexExplanation(); Explanation idfExpl = new Explanation(idf, "idf(docFreq=" + reader.docFreq(term) + ")"); // explain query weight Explanation boostExpl = new Explanation(getBoost(), "boost"); Explanation queryNormExpl = new Explanation(queryNorm,"queryNorm"); queryExpl.setValue(boostExpl.getValue() *idfExpl.getValue() *queryNormExpl.getValue()); result.addDetail(queryExpl); // 说明Field的权?br />
String field = term.field(); Explanation tfExpl = scorer(reader).explain(doc); Explanation fieldNormExpl = new Explanation(); result.addDetail(fieldExpl); if (queryExpl.getValue() == 1.0f) return result; Ҏ索结果,以及上面的TermWeightcȝexplain()ҎQ可以看出的字符串部分正好一一对应Q比如:idf(Inverse Document FrequencyQ即反{文档频率)、fieldNorm、fieldWeight?/span> 索结果的W一个Document的信息: Document的内部编号ؓ Q?0 Document对应的Explanation的一些参数值如下: tf的计?/span> 上面的tf值Term FrequencyQ即词条频率Q可以在org.apache.lucene.search.SimilaritycM看到具体地说明。在Lucene中,q不是直接用的词条的频率,而实际用的词条频率的^ҎQ即Q?/span>
q是使用org.apache.lucene.search.Similaritycȝ子类DefaultSimilarity中的Ҏ计算的,如下Q?/span> /** Implemented as <code>sqrt(freq)</code>. */ 卻I某个Document的tf = 索的词条在该Document中出现次数freq取^Ҏ?/span> 也就是freq的^Ҏ?/span> 例如Q从我们的检索结果来看: 搜烦关键?lt;一?gt;在编号ؓ 0 的Document中出现过 1 ?br />
搜烦关键?lt;一?gt;在编号ؓ 1 的Document中出现过 1 ?br />
搜烦关键?lt;一?gt;在编号ؓ 2 的Document中出现过 1 ?br />
搜烦关键?lt;一?gt;在编号ؓ 3 的Document中出现过 2 ?br />
搜烦关键?lt;一?gt;在编号ؓ 4 的Document中出现过 2 ?/span> 各个Document的tf计算如下所C: ~号?的Document?tf 为: (float)Math.sqrt(1) = 1.0Q?br />
~号?的Document?tf 为: (float)Math.sqrt(1) = 1.0Q?br />
~号?的Document?tf 为: (float)Math.sqrt(1) = 1.0Q?br />
~号?的Document?tf 为: (float)Math.sqrt(2) = 1.4142135Q?br />
~号?的Document?tf 为: (float)Math.sqrt(2) = 1.4142135Q?/span> idf的计?/span> 索结果中Q每个检索出来的Document的都对应一个idfQ在DefaultSimilaritycM可以看到idf计算的实现方法,如下Q?/span> /** Implemented as <code>log(numDocs/(docFreq+1)) + 1</code>. */ 其中QdocFreq是根据指定关键字q行索,索到的Document的数量,我们试的docFreq=5QnumDocs是指索引文g中d的Document的数量,我们的测试比较特D,全部的Document都检索出来了Q我们测试的numDocs=5?/span> 各个Document的idf的计如下所C: ~号?的Document?idf 为:(float)(Math.log(5/(double)(5+1)) + 1.0) = 0.81767845Q?br />
~号?的Document?idf 为:(float)(Math.log(5/(double)(5+1)) + 1.0) = 0.81767845Q?br />
~号?的Document?idf 为:(float)(Math.log(5/(double)(5+1)) + 1.0) = 0.81767845Q?br />
~号?的Document?idf 为:(float)(Math.log(5/(double)(5+1)) + 1.0) = 0.81767845Q?br />
~号?的Document?idf 为:(float)(Math.log(5/(double)(5+1)) + 1.0) = 0.81767845Q?/span> lengthNorm的计?/span> 在DefaultSimilaritycM可以看到lengthNorm计算的实现方法,如下Q?/span> public float lengthNorm(String fieldName, int numTerms) { 各个Document的lengthNorm的计如下所C: ~号?的Document?lengthNorm 为:(float)(1.0 / Math.sqrt(1)) = 1.0/1.0 = 1.0Q?br />
~号?的Document?lengthNorm 为:(float)(1.0 / Math.sqrt(1)) = 1.0/1.0 = 1.0Q?br />
~号?的Document?lengthNorm 为:(float)(1.0 / Math.sqrt(1)) = 1.0/1.0 = 1.0Q?br />
~号?的Document?lengthNorm 为:(float)(1.0 / Math.sqrt(2)) = 1.0/1.4142135 = 0.7071068Q?br />
~号?的Document?lengthNorm 为:(float)(1.0 / Math.sqrt(2)) = 1.0/1.4142135 = 0.7071068Q?/span> 关于fieldNorm fieldNorm是在建立索引的时候写入的Q而检索的时候需要从索引文g中读取,然后通过解码Q得到fieldNorm的float型|用于计算Document的得分?/span> 在org.apache.lucene.search.TermQuery.TermWeightcMQexplainҎ通过打开的IndexReader读取fieldNormQ写入烦引文件的是byte[]cdQ需要解码,如下所C: byte[] fieldNorms = reader.norms(field); 调用SimilaritycȝdecodeNormҎQ将byte[]cdD{化ؓfloat点| public static float decodeNorm(byte b) { q样Q一个Q点型的fieldNorm的值就被读取出来了Q可以参加一些运,最l实现Lucene的Document的得分的计算?/span> queryWeight的计?/span> queryWeight的计可以在org.apache.lucene.search.TermQuery.TermWeightcM的sumOfSquaredWeightsҎ中看到计的实现Q?/span> public float sumOfSquaredWeights() { 其实默认情况下,queryWeight = idfQ因为Lucune中默认的Ȁ励因子boost = 1.0?/span> 各个Document的queryWeight的计如下所C: queryWeight = 0.81767845 * 0.81767845 = 0.6685980475944025Q?/span> queryNorm的计?/span> queryNorm的计在DefaultSimilaritycM实现Q如下所C: /** Implemented as <code>1/sqrt(sumOfSquaredWeights)</code>. */ q里QsumOfSquaredWeights的计是在org.apache.lucene.search.TermQuery.TermWeightcM的sumOfSquaredWeightsҎ实现Q?/span> public float sumOfSquaredWeights() { 其实默认情况下,sumOfSquaredWeights = idf * idfQ因为Lucune中默认的Ȁ励因子boost = 1.0?/span> 上面试例子中sumOfSquaredWeights的计如下所C: sumOfSquaredWeights = 0.81767845*0.81767845 = 0.6685980475944025Q?/span> 然后Q就可以计算queryNorm的gQ计如下所C: queryNorm = (float)(1.0 / Math.sqrt(0.6685980475944025) = 1.2229746301862302962735534977105Q?/span> value的计?/span> org.apache.lucene.search.TermQuery.TermWeightcȝ中还定义了一个value成员Q?/span> private float value; 关于value的计,可以在它的子corg.apache.lucene.search.TermQuery.TermWeightcM看到计算的实玎ͼ public void normalize(float queryNorm) { q里Q用normalizeҎ计算value的|卻I value = queryNorm * queryWeight * idf; 上面试例子中value的D如下: value = 1.2229746301862302962735534977105 * 0.6685980475944025 * 0.81767845 = 0.66859804759440249999999999999973Q?/span> 关于fieldWeight 从检索结果中Q可以看刎ͼ 0.81767845 = (MATCH) fieldWeight(contents:一?in 0), product of: 字符?(MATCH) "的输在ComplexExplanationcM的getSummaryҎ中可以看刎ͼ protected String getSummary() { q个fieldWeight的值其实和Document的得分是相等的,先看q个fieldWeight是如何计出来的Q在org.apache.lucene.search.TermQuery.TermWeightcM的explainҎ中可以看刎ͼ ComplexExplanation fieldExpl = new ComplexExplanation(); Explanation tfExpl = scorer(reader).explain(doc); Explanation fieldNormExpl = new Explanation(); result.addDetail(fieldExpl); if (queryExpl.getValue() == 1.0f) 上面QComplexExplanation fieldExpl被设|了很多内容,我们׃q里来获取fieldWeight的计的实现?/span> 关键是在下面q行了计: fieldExpl.setValue(tfExpl.getValue() * 使用计算式表C就?/span> fieldWeight = tf * idf * fieldNorm fieldNorm的值因为是在徏立烦引的时候写入到索引文g中的Q烦引只需要从上面的测试结果中取来Q进行如下关于Document的分数的计算的验证?/span> 使用我们q个例子来计检索出来的Docuyment的fieldWeightQ需要用到前面计出来的l果Q如下所C: ~号?的Document?fieldWeight 为:1.0 * 0.81767845 * 1.0 = 0.81767845Q?br />
~号?的Document?fieldWeight 为:1.0 * 0.81767845 * 0.5 = 0.408839225Q?br />
~号?的Document?fieldWeight 为:1.0 * 0.81767845 * 0.5 = 0.408839225Q?br />
~号?的Document?fieldWeight 为:1.4142135 * 0.81767845 * 0.4375 = 0.5059127074089703125Q?br />
~号?的Document?fieldWeight 为:1.4142135 * 0.81767845 * 0.4375 = 0.5059127074089703125Q?/span> Ҏ一下,其实索结果中Document的得分就是这个fieldWeight的|验证后,正好相符(注意Q我q里没有q行舍入q算)?/span> ȝ说明 上面的计得分是按照Lucene默认讄的情况下q行的,比如Ȁ励因子的默认gؓ1.0Q它体现的是一个Document的重要性,x谓的fieldWeight?/span> 不仅可以通过Z个Document讄Ȁ励因子boostQ而且可以通过Z个Document中的Field讄boostQ因Z个Document的权重体现在它当中的Field上,即上面计出来的fieldWeight与Document的得分是相等的?/span> 提高一个Document的激励因子boostQ可以该Document被检索出来的默认排序靠前Q即说明比较重要。也是_修改Ȁ励因子boost能够改变索结果的排序?/span> Boosting Documents and Fields
boost是怎样存储到index中的Q利用norms Lucene的打分公式非常复杂,如下Q?/p>
在推g前,先逐个介绍每部分的意义Q?/p>
以上在Lucene的文档中已经详细提到Qƈ在很多文章中也被阐述q,如何调整上面的各部分Q以影响文的打分,请参?a >有关Lucene的问?4):影响LuceneҎ打分的四种方式一文?/p>
然而上面各部分Z么要q样计算在一起呢Q这么复杂的公式是怎么得出来的呢?下面我们来推对{?/p>
首先Q将以上各部分代入score(q, d)公式Q将得到一个非常复杂的公式Q让我们忽略所有的boostQ因些属于h为的调整Q也省略coordQ这和公式所要表辄原理无关。得C面的公式Q?/p>
然后Q有Lucene学习ȝ之一Q全文检索的基本原理中的描述我们知道QLucene的打分机制是采用向量I间模型的: 我们把文档看作一pd?Term)Q每一个词(Term)都有一个权?Term weight)Q不同的?Term)Ҏ自己在文中的权重来影响文相关性的打分计算? 于是我们把所有此文中词(term)的权?term weight) 看作一个向量? Document = {term1, term2, …… ,term N} Document Vector = {weight1, weight2, …… ,weight N} 同样我们把查询语句看作一个简单的文Q也用向量来表示? Query = {term1, term 2, …… , term N} Query Vector = {weight1, weight2, …… , weight N} 我们把所有搜索出的文向量及查询向量攑ֈ一个Nl空间中Q每个词(term)是一l? 我们认ؓ两个向量之间的夹角越,相关性越大? 所以我们计夹角的余ug为相x的打分Q夹角越,余uD大,打分高Q相x越大? 余u公式如下Q?/p>
下面我们假设Q?/p>
查询向量为Vq = <w(t1, q), w(t2, q), ……, w(tn, q)> 文向量为Vd = <w(t1, d), w(t2, d), ……, w(tn, d)> 向量I间l数为nQ是查询语句和文的q的长度,当某个Term不在查询语句中出现的时候,w(t, q)为零Q当某个Term不在文中出现的时候,w(t, d)为零?/p>
w代表weightQ计公式一般ؓtf*idf?/p>
我们首先计算余u公式的分子部分,也即两个向量的点U: Vq*Vd = w(t1, q)*w(t1, d) + w(t2, q)*w(t2, d) + …… + w(tn ,q)*w(tn, d) 把w的公式代入,则ؓ Vq*Vd = tf(t1, q)*idf(t1, q)*tf(t1, d)*idf(t1, d) + tf(t2, q)*idf(t2,
q)*tf(t2, d)*idf(t2, d) + …… + tf(tn ,q)*idf(tn, q)*tf(tn, d)*idf(tn,
d) 在这里有三点需要指出: Z上述三点Q点U公式ؓQ?/p>
Vq*Vd = tf(t1, d) * idf(t1) * idf(t1) + tf(t2, d) * idf(t2) * idf(t2) + …… + tf(tn, d) * idf(tn) * idf(tn) 所以余弦公式变为: 下面要推导的是查询语句的长度了?/p>
׃面的讨论Q查询语句中tf都ؓ1Qidf都忽略查询语句这小文档Q得到如下公?/p>
所以余弦公式变为: 下面推导的就是文的长度了,本来文档长度的公式应该如下: q里需要讨论的是,Z么在打分q程中,需要除以文档的长度呢? 因ؓ在烦引中Q不同的文长度不一P很显Ӟ对于L一个termQ在长的文中的tf要大的多Q因而分C高Q这样对的文不公qI举一?
极端的例子,在一?000万个词的鸿篇巨著中,"lucene"q个词出C11ơ,而在一?2个词的短文中Q?lucene"q个词出C10
ơ,如果不考虑长度在内Q当焉`巨著应该分数更高,然而显然这小文档才是真正x"lucene"的?/p>
然而如果按照标准的余u计算公式Q完全消除文档长度的影响Q则又对长文不公^(毕竟它是包含了更多的信息)Q偏向于首先q回短小的文的Q这样在实际应用中得搜索结果很隄?/p>
所以在Lucene中,Similarity的lengthNorm接口是开攑և来,用户可以Ҏ自己应用的需要,改写lengthNorm的计?
公式。比如我惛_一个经学论文的搜索系l,l过一定时间的调研Q发现大多数的经学论文的长度在8000?0000词,因而lengthNorm的公
式应该是一个倒抛物线型的Q?000?10000词的论文分数最高,更短或更长的分数都应该偏低,方能够返回给用户最好的数据?/p>
在默认状况下QLucene采用DefaultSimilarityQ认为在计算文的向量长度的时候,每个Term的权重就不再考虑在内了,而是全部Z?/p>
而从Term的定义我们可以知道,Term是包含域信息的,也即title:hello和content:hello是不同的TermQ也即一个Term只可能在文中的一个域中出现?/p>
所以文长度的公式为: 代入余u公式Q?/p>
再加上各Uboost和coordQ则可得出Lucene的打分计公式?/p>
Lucene面向全文索的优化在于首次索引索后Qƈ不把所有的记录QDocumentQ具体内容读取出来,而是只将所有结果中匚w度最高的?br />
100条结果(TopDocsQ的ID攑ֈl果集缓存中q返回,q里可以比较一下数据库索:如果是一?0,000条的数据库检索结果集Q数据库是一?br />
要把所有记录内定w取得以后再开始返回给应用l果集的。所以即使检索匹配L很多QLucene的结果集占用的内存空间也不会很多。对于一般的模糊索应 如果首批~存l果数用完后q要d更后面的l果时Searcher会再ơ检索ƈ生成一个上ơ的搜烦~存数大1倍的~存Qƈ再重新向后抓取。所以如?br />
构造一个SearcherL1Q?20条结果,Searcher其实是进行了2ơ搜索过E:?00条取完后Q缓存结果用完,Searcher重新?br />
再构造一?00条的l果~存Q依此类推,400条缓存,800条缓存。由于每ơSearcher对象消失后,q些~存也访问那不到了,你有可能惛_l果 Lucene的另外一个特Ҏ在收集结果的q程中将匚w度低的结果自动过滤掉了。这也是和数据库应用需要将搜烦的结果全部返回不同之处?/p>
刚刚开始学LuceneQ看的是Lucene in W一个想到的Ҏ是把我的全部数据域都做成Lucene的烦引,然后全部通过LuceneL索。但是由于我的很多域是数字,全部转换?
Lucene能接受的字符Ԍ感觉性能不会好。另外如果我想针Ҏ索的l果做统计,也没法避免需要遍历全部的搜烦l果Q如?W个结果就需?分钟的话Q?
q不用处理其他的域Q也是不能忍受的?/p>
开源Y件的好处是可以M码。通过阅读Hits的代码,l于扑ֈ了解决问题的办法?/p>
Lucene Hits(Searcher s, Query q, Filter f) throws IOException { Hits(Searcher s, Query q, Filter f, Sort o) 一般我们是通过Document doc(int 但是getMoreDocs的代码比较让人疑惑,里面一D代码是q样的: Hits hits = searcher.search(query); ScoreDoc[] scoreDocs = topDoc.scoreDocs; {等Q还没完?br />
我只需要ID字段Q但是返回整个DocQ其他两个文本Field也返回了。因为Lucene是倒烦引保存信息的Q每一个文本Field需要重新组合成原始
的字W串Q这也是要耗时间的。searcher的doc函数有一个可以限定只取部分域的: Document doc(int n, FieldSelector fieldSelector) 我下面定义一个FieldSelectorQ只取某一个给定名字的Field
return weight.explain(reader, doc);
}
import java.util.Date;
import org.apache.lucene.document.Field;
import org.apache.lucene.index.CorruptIndexException;
import org.apache.lucene.index.IndexWriter;
import org.apache.lucene.index.Term;
import org.apache.lucene.index.TermDocs;
import org.apache.lucene.search.Explanation;
import org.apache.lucene.search.Hits;
import org.apache.lucene.search.IndexSearcher;
import org.apache.lucene.search.Query;
import org.apache.lucene.search.TermQuery;
import org.apache.lucene.store.LockObtainFailedException;
private String path = "E:\\Lucene\\index";
public void createIndex(){
IndexWriter writer;
try {
writer = new IndexWriter(path,new ThesaurusAnalyzer(),true);
Field fieldA = new Field("contents","一?,Field.Store.YES,Field.Index.TOKENIZED);
Document docA = new Document();
docA.add(fieldA);
Field fieldB = new Field("contents","一?之交 一Z?,Field.Store.YES,Field.Index.TOKENIZED);
Document docB = new Document();
docB.add(fieldB);
Field fieldC = new Field("contents","一?之下 一Z?,Field.Store.YES,Field.Index.TOKENIZED);
Document docC = new Document();
docC.add(fieldC);
Field fieldD = new Field("contents","一?做事 一人当 一人做事一人当",Field.Store.YES,Field.Index.TOKENIZED);
Document docD = new Document();
docD.add(fieldD);
Field fieldE = new Field("contents","一?做事 一人當 一人做事一人當",Field.Store.YES,Field.Index.TOKENIZED);
Document docE = new Document();
docE.add(fieldE);
writer.addDocument(docB);
writer.addDocument(docC);
writer.addDocument(docD);
writer.addDocument(docE);
writer.close();
} catch (CorruptIndexException e) {
e.printStackTrace();
} catch (LockObtainFailedException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
AboutLuceneScore aus = new AboutLuceneScore();
aus.createIndex(); // 建立索引
try {
String keyword = "一?;
Term term = new Term("contents",keyword);
Query query = new TermQuery(term);
IndexSearcher searcher = new IndexSearcher(aus.path);
Date startTime = new Date();
Hits hits = searcher.search(query);
TermDocs termDocs = searcher.getIndexReader().termDocs(term);
while(termDocs.next()){
System.out.print("搜烦关键?lt;"+keyword+">在编号ؓ "+termDocs.doc());
System.out.println(" 的Document中出现过 "+termDocs.freq()+" ?);
}
System.out.println("********************************************************************");
for(int i=0;i<hits.length();i++){
System.out.println("Document的内部编号ؓ Q?"+hits.id(i));
System.out.println("Document内容?Q?"+hits.doc(i));
System.out.println("Document得分?Q?"+hits.score(i));
Explanation e = searcher.explain(query, hits.id(i));
System.out.println("Explanation?Q?\n"+e);
System.out.println("Document对应的Explanation的一些参数值如下: ");
System.out.println("Explanation的getValue()?Q?"+e.getValue());
System.out.println("Explanation的getDescription()?Q?"+e.getDescription());
System.out.println("********************************************************************");
}
System.out.println("共检索出W合条g的Document "+hits.length()+" 个?);
Date finishTime = new Date();
long timeOfSearch = finishTime.getTime() - startTime.getTime();
System.out.println("本次搜烦所用的旉?"+timeOfSearch+" ms");
} catch (CorruptIndexException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
}
Document的内部编号ؓ Q?0
Document内容?Q?Document<stored/uncompressed,indexed,tokenized<contents:一?gt;>
Document得分?Q?0.81767845
Explanation?Q?
0.81767845 = (MATCH) fieldWeight(contents:一?in 0), product of:
1.0 = tf(termFreq(contents:一?=1)
0.81767845 = idf(docFreq=5)
1.0 = fieldNorm(field=contents, doc=0)
Explanation的getValue()?Q?0.81767845
Explanation的getDescription()?Q?fieldWeight(contents:一?in 0), product of:
********************************************************************
Document的内部编号ؓ Q?3
Document内容?Q?Document<stored/uncompressed,indexed,tokenized<contents:一?做事 一人当 一人做事一人当>>
Document得分?Q?0.5059127
Explanation?Q?
0.5059127 = (MATCH) fieldWeight(contents:一?in 3), product of:
1.4142135 = tf(termFreq(contents:一?=2)
0.81767845 = idf(docFreq=5)
0.4375 = fieldNorm(field=contents, doc=3)
Explanation的getValue()?Q?0.5059127
Explanation的getDescription()?Q?fieldWeight(contents:一?in 3), product of:
********************************************************************
Document的内部编号ؓ Q?4
Document内容?Q?Document<stored/uncompressed,indexed,tokenized<contents:一?做事 一人當 一人做事一人當>>
Document得分?Q?0.5059127
Explanation?Q?
0.5059127 = (MATCH) fieldWeight(contents:一?in 4), product of:
1.4142135 = tf(termFreq(contents:一?=2)
0.81767845 = idf(docFreq=5)
0.4375 = fieldNorm(field=contents, doc=4)
Explanation的getValue()?Q?0.5059127
Explanation的getDescription()?Q?fieldWeight(contents:一?in 4), product of:
********************************************************************
Document的内部编号ؓ Q?1
Document内容?Q?Document<stored/uncompressed,indexed,tokenized<contents:一?之交 一Z?gt;>
Document得分?Q?0.40883923
Explanation?Q?
0.40883923 = (MATCH) fieldWeight(contents:一?in 1), product of:
1.0 = tf(termFreq(contents:一?=1)
0.81767845 = idf(docFreq=5)
0.5 = fieldNorm(field=contents, doc=1)
Explanation的getValue()?Q?0.40883923
Explanation的getDescription()?Q?fieldWeight(contents:一?in 1), product of:
********************************************************************
Document的内部编号ؓ Q?2
Document内容?Q?Document<stored/uncompressed,indexed,tokenized<contents:一?之下 一Z?gt;>
Document得分?Q?0.40883923
Explanation?Q?
0.40883923 = (MATCH) fieldWeight(contents:一?in 2), product of:
1.0 = tf(termFreq(contents:一?=1)
0.81767845 = idf(docFreq=5)
0.5 = fieldNorm(field=contents, doc=2)
Explanation的getValue()?Q?0.40883923
Explanation的getDescription()?Q?fieldWeight(contents:一?in 2), product of:
********************************************************************
共检索出W合条g的Document 5 个?br />
本次搜烦所用的旉?79 ms
return weight.explain(reader, doc);
}
throws IOException {
result.setDescription("weight("+getQuery()+" in "+doc+"), product of:");
Explanation queryExpl = new Explanation();
queryExpl.setDescription("queryWeight(" + getQuery() + "), product of:");
if (getBoost() != 1.0f)
queryExpl.addDetail(boostExpl);
queryExpl.addDetail(idfExpl);
queryExpl.addDetail(queryNormExpl);
ComplexExplanation fieldExpl = new ComplexExplanation();
fieldExpl.setDescription("fieldWeight("+term+" in "+doc+"), product of:");
fieldExpl.addDetail(tfExpl);
fieldExpl.addDetail(idfExpl);
byte[] fieldNorms = reader.norms(field);
float fieldNorm =
fieldNorms!=null ? Similarity.decodeNorm(fieldNorms[doc]) : 0.0f;
fieldNormExpl.setValue(fieldNorm);
fieldNormExpl.setDescription("fieldNorm(field="+field+", doc="+doc+")");
fieldExpl.addDetail(fieldNormExpl);
fieldExpl.setMatch(Boolean.valueOf(tfExpl.isMatch()));
fieldExpl.setValue(tfExpl.getValue() *idfExpl.getValue() *fieldNormExpl.getValue());
result.setMatch(fieldExpl.getMatch());
// combine them
result.setValue(queryExpl.getValue() * fieldExpl.getValue());
return fieldExpl;
}
Document内容?Q?Document<stored/uncompressed,indexed,tokenized<contents:一?gt;>
Document得分?Q?0.81767845
Explanation?Q?
0.81767845 = (MATCH) fieldWeight(contents:一?in 0), product of:
1.0 = tf(termFreq(contents:一?=1)
0.81767845 = idf(docFreq=5)
1.0 = fieldNorm(field=contents, doc=0)
Explanation的getValue()?Q?0.81767845
Explanation的getDescription()?Q?fieldWeight(contents:一?in 0), product of:
tf(t in d)
=frequency½
public float tf(float freq) {
return (float)Math.sqrt(freq);
}
public float idf(int docFreq, int numDocs) {
return (float)(Math.log(numDocs/(double)(docFreq+1)) + 1.0);
}
return (float)(1.0 / Math.sqrt(numTerms));
}
float fieldNorm = fieldNorms!=null ? Similarity.decodeNorm(fieldNorms[doc]) : 0.0f;
return NORM_TABLE[b & 0xFF]; // & 0xFF maps negative bytes to positive above 127
}
queryWeight = idf * getBoost(); // compute query weight
return queryWeight * queryWeight; // square it
}
public float queryNorm(float sumOfSquaredWeights) {
return (float)(1.0 / Math.sqrt(sumOfSquaredWeights));
}
queryWeight = idf * getBoost(); // compute query weight
return queryWeight * queryWeight; // square it
}
this.queryNorm = queryNorm;
queryWeight *= queryNorm; // normalize query weight
value = queryWeight * idf; // idf for document
}
if (null == getMatch())
return super.getSummary();
return getValue() + " = "
+ (isMatch() ? "(MATCH) " : "(NON-MATCH) ")
+ getDescription();
}
fieldExpl.setDescription("fieldWeight("+term+" in "+doc+
"), product of:");
fieldExpl.addDetail(tfExpl);
fieldExpl.addDetail(idfExpl);
byte[] fieldNorms = reader.norms(field);
float fieldNorm =
fieldNorms!=null ? Similarity.decodeNorm(fieldNorms[doc]) : 0.0f;
fieldNormExpl.setValue(fieldNorm);
fieldNormExpl.setDescription("fieldNorm(field="+field+", doc="+doc+")");
fieldExpl.addDetail(fieldNormExpl);
fieldExpl.setMatch(Boolean.valueOf(tfExpl.isMatch()));
fieldExpl.setValue(tfExpl.getValue() *
idfExpl.getValue() *
fieldNormExpl.getValue());
result.setMatch(fieldExpl.getMatch());
// combine them
result.setValue(queryExpl.getValue() * fieldExpl.getValue());
return fieldExpl;
idfExpl.getValue() *
fieldNormExpl.getValue());
]]>
先将 mysql 服务停掉 解压 mysql-5.0.45-sphinxse-0.9.8-win32.zip ?bin ?share
覆盖?mysql 目录中的 bin ?share 解压 sphinx-0.9.8.1-win32.zip
到独立的目录Q如:d:/www/sphinx/?
接着开?mysql 服务Q徏?"test" 数据库,q导?sql 语句,如下Q?
-----------------------------------------------------------
CREATE TABLE `documents` (
`id` int(11) NOT NULL auto_increment,
`group_id` int(11) NOT NULL,
`group_id2` int(11) NOT NULL,
`date_added` datetime NOT NULL,
`title` varchar(255) NOT NULL,
`content` text NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=5;
INSERT INTO `documents` VALUES ('1', '1', '5', '2008-09-13
21:37:47', 'test one', 'this is my test document number one. also
checking search within phrases.');
INSERT INTO `documents` VALUES ('2', '1', '6', '2008-09-13 21:37:47', 'test two', 'this is my test document number two');
INSERT INTO `documents` VALUES ('3', '2', '7', '2008-09-13 21:37:47', 'another doc', 'this is another group');
INSERT INTO `documents` VALUES ('4', '2', '8', '2008-09-13 21:37:47', 'doc number four', 'this is to test groups');
-------------------------------------------实际上,q个新徏立的表就?Sphinx 中的 example.sql
我们的测试表已经建立完成Q接下来我们要配|?sphinx-doc.conf 文gQ重要)
先将 sphinx 下的 sphinx-min.conf 复制一份改名ؓ sphinx-doc.confQ接着 修改?
----------------------------------------------------------------------
#
# Minimal Sphinx configuration sample (clean, simple, functional)
#
# type----------------------------------------数据库类型,目前支持 mysql ?pgsql
# strip_html--------------------------------是否Lhtml 标签
# sql_host----------------------------------数据库主机地址
# sql_user----------------------------------数据库用户名
# sql_pass----------------------------------数据库密?
# sql_db-------------------------------------数据库名U?
# sql_port-----------------------------------数据库采用的端口
# sql_query_pre--------------------------执行sql前要讄的字W集Q用utf8必须SET NAMES utf8
#
sql_query---------------------------------全文索要昄的内容,在这里尽可能不用where?
group byQ将 where ?groupby 的内容交l?sphinxQ由 sphinx q行条gqo?groupby 效率会更?
# 注意: select 出来的字D必至包括一个唯一主键 (ARTICLESID) 以及要全文检索的字段Q你计划原本?where 中要用到的字D也?select 出来
# q里不用使用orderby
# sql_attr_ 开头的表示一些属性字D,你原计划要用?where, orderby, groupby 中的字段要在q里定义(# 己添加的注释内容)
#source 数据源名:
source documents
{
type = mysql
sql_host = localhost
sql_user = root
sql_pass = yourpassword
sql_db = test
sql_port = 3306 # optional, default is 3306
sql_query_pre = SET NAMES utf8
sql_query = \
SELECT id, group_id, UNIX_TIMESTAMP(date_added) AS date_added, title, content \
FROM documents
sql_attr_uint = group_id
sql_attr_timestamp = date_added
sql_query_info = SELECT * FROM documents WHERE id=$id
}
index documents
{
source = documents
#path 索引记录存放目录Q如 d:/sphinx/data/cgfinal ,实际存放时会存放?d:/sphinx/data 目录Q然后创建多?cgfinal 名称Q不同扩展名的烦引文件?
path = d:/www/sphinx/data/doc
docinfo = extern
enable_star = 1
min_word_len = 3
min_prefix_len = 0
min_infix_len = 3
charset_type = sbcs
# 其他的配|如 min_word_len, charset_type, charset_table, ngrams_chars, ngram_len q些则是支持中文索需要设|的内容?
# 如果索的不是中文Q则 charset_table, ngrams_chars, min_word_len p讄不同的内容,具体官方|站的论坛中有很多,大家可以L索看看?
}
# mem_limit 索引使用内存最大限ӞҎ机器情况而定Q默认是32MQ太的会媄响烦引的性能?
indexer
{
mem_limit = 32M
}
# 搜烦的守护进E配|?
# 在进行全文检索过E中Qsearchd要先开启,mysql在全文检索时才能q接到sphinxQ由sphinxq行全文索,再将l果q回lmysql
# address 侦听h的地址Q不讄则侦听所有地址
# port 侦听端口
searchd
{
port = 3312
log =d:/www/sphinx/logs/searched_doc.log
query_log = d:/www/sphinx/logs/query_doc.log
read_timeout = 5
max_children = 30
pid_file = d:/www/sphinx/logs/searched-doc.pid
max_matches = 1000
seamless_rotate = 0
preopen_indexes = 0
unlink_old = 1
}
----------------------------------------------------------------------
Z试Q我们的 Sphinx 配置文g已经写好Q确保我们的 Mysql 数据库已l启动,如果没有启动则在 cmd 中键? net start mysql "
接下来,我们的测试正式开始:
1Q生成数据烦引或重徏索引Q?
Q最好再复制一?sphinx-doc.conf 配置文gQƈ把它攑օ bin 文g夹中Q下面的举例 假设我们已经q样做)Q?
?cmd 模式下:输入Q?
d:/www/sphinx/bin/indexer.exe --config d:/www/sphinx/bin/sphinx-doc.conf documents
2Q运行检索守护进E?searchd.exeQ?
d:/www/sphinx/bin/searchd.exe --config d:/www/sphinx/bin/sphinx-doc.conf
如过q两步没有报错的话,说明我们?Sphinx 已经正常q行了!可以通过 netstat -an 查看是否 3312 端口是否处如监听状态?
3Q现在来?sphinx 自带的工?search.exe 来测试一下:
试Q?
索引关键字: this is m
D:\www\sphinx\bin>search.exe -c d:/www/sphinx/bin/sphinx-doc.conf this is m
l果Q?
Sphinx 0.9.8-release (r1371)
Copyright (c) 2001-2008, Andrew Aksyonoff
using config file 'd:/www/sphinx/bin/sphinx-doc.conf'...
WARNING: index 'documents': invalid morphology option 'extern' - IGNORED
index 'documents': query 'this is m ': returned 4 matches of 4 total in 0.000 s
c
displaying matches:
1. document=1, weight=1, group_id=1, date_added=Sat Sep 13 21:37:47 2008
id=1
group_id=1
group_id2=5
date_added=2008-09-13 21:37:47
title=test one
content=this is my test document number one. also checking search withi
phrases.
2. document=2, weight=1, group_id=1, date_added=Sat Sep 13 21:37:47 2008
id=2
group_id=1
group_id2=6
date_added=2008-09-13 21:37:47
title=test two
content=this is my test document number two
3. document=3, weight=1, group_id=2, date_added=Sat Sep 13 21:37:47 2008
id=3
group_id=2
group_id2=7
date_added=2008-09-13 21:37:47
title=another doc
content=this is another group
4. document=4, weight=1, group_id=2, date_added=Sat Sep 13 21:37:47 2008
id=4
group_id=2
group_id2=8
date_added=2008-09-13 21:37:47
title=doc number four
content=this is to test groups
words:
1. 'this': 4 documents, 4 hits
-------------------
索引关键字: this is another group
D:\www\sphinx\bin>search.exe -c d:/www/sphinx/bin/sphinx-doc.conf this is another group
l果Q?
Sphinx 0.9.8-release (r1371)
Copyright (c) 2001-2008, Andrew Aksyonoff
-------------------
到此sphinx在win上算正常q行了,sphinx-doc.conf文g配置比较灉|Q根据你需要烦引的数据库进行灵z配|来辑ֈ你需要的效果
如果配置q程中出现运行参数配|问题可以查?doc/sphinx.html文gQ里面对各种参数都要详细的说?
using config file 'd:/www/sphinx/bin/sphinx-doc.conf'...
WARNING: index 'documents': invalid morphology option 'extern' - IGNORED
index 'documents': query 'this is another group ': returned 1 matches of 1 total
in 0.000 sec
displaying matches:
1. document=3, weight=4, group_id=2, date_added=Sat Sep 13 21:37:47 2008
id=3
group_id=2
group_id2=7
date_added=2008-09-13 21:37:47
title=another doc
content=this is another group
words:
1. 'this': 4 documents, 4 hits
2. 'another': 1 documents, 2 hits
3. 'group': 1 documents, 1 hits
]]>
setBoost(float) 讄Documents和Fields在index中的重要?br />
可以ldocument讄boostQ也可以lfield讄boost
讄boost会删除原来的document然后重新建立索引
doc.setBoost();
field.setBoost();
在徏立烦引过E中生成的boosts会被l合在一起变成一个QҎQ然后每个文档每个字D?br />
都会存ؓ一个byte。在查询q程中,每个field的norms会被装入内存Q重新解码ؓ一个QҎ
即norms在徏立烦引的q程中得刎ͼ我们也可以用IndexReader的setNormҎ来改?br />
norms会在搜烦q程中消耗过多的内存
我们可以norms关闭QField.setOmitNorms(true)Q这h可能影响评分Q但是媄响效?br />
可以忽略
indexing dates×
DataTools.dateToString(new Date(),DateTools.Resolution.DAY);
Indexing numbers
lucene利用词典~排来给field排序Q也是说如果有3个数Q?Q?1Q?0Q正常的排序是:7Q?0Q?1。但是词典排序是Q?0Q?Q?1。一个简单和通用的方法是l数字加前缀0Q?07Q?20Q?71
indexing fields for sorting
field建立索引但是不分词Field.Index.NOT_ANALYZEDQ字D必d储Integers,Floats,Strings
Field truncation
比如说你只想l一个文前200个字建立索引
在indexWriter的构造方法中传递MaxFieldLength参数
pȝ讑֮的值MaxFieldLength.UNLIMITED和MaxFieldLength.LIMITED
可以调用setMaxFieldLength()Ҏ来修?br />
IndexWriter.setInfoStream(System.out) 关于合ƈQ删除的信息以及当maxFieldLength到达会显CZ?br />
Optimizing an index
索引优化只能提高搜烦的速度Q不会加快徏立烦引的速度,不进行优化也有可能获得很好的搜烦吞吐?/p>
IndexWriter提供4个优化方?/p>
]]>
用是用不到这么多的结果的Q头100条已l可以满?0%以上的检索需求?/p>
记录~存下来Q缓存数量保证?00以下以充分利用首ơ的l果~存Q不让Lucene费多次索,而且可以分q行l果~存?/p>
Action。顺着看下去,很自然的是使用Hits来访问Search的结果。但是用v来,发现Search的速度是很快,不过如果l果很多的话Q比
?W个)Q通过Hits讉K所有的l果速度非常慢,是单地从每个结果中M个FieldQ在我的机器上用了接q?分钟。因为我的应用烦引的只是我的
数据的两个域包含文本信息的域Q我本希望通过Lucene查找出符合需求的数据IDQ再通过IDd断数据库中的其他域来军_最l的l果。这栯取ID?
需?分钟Q我的应用可受不了?/p>
的代码看hq不是特别Professional。比如下面这两个Hits的初始化函数。首先里面的q,s,f什么的让h看v来就不是太舒服(其他的代?
里还用i,j做@环变量)。其ơ这两个函数只有o那一个赋g一P明显应该只写一个,让另一个来调用。最后程序里面直接用?0q个常数Q编E的?
忌。(50在其他函数里面也有)
weight =
q.weight(s);
searcher =
s;
filter =
f;
nDeletions =
countDeletions(s);
getMoreDocs(50); // retrieve 100 initially
lengthAtStart = length;
}
throws IOException {
weight =
q.weight(s);
searcher =
s;
filter =
f;
sort =
o;
nDeletions =
countDeletions(s);
getMoreDocs(50); // retrieve 100 initially
lengthAtStart = length;
}
通过q两个函敎ͼ应该看出Hits初始化的时候只调入了前100个文档?/p>
n)函数来访问的。这个函数里面先判断了有多少数据已经被调入了Q如果要讉K的数据不在,去调用getMoreDocs函数QgetMoreDocs?
取得需要的2倍文进来?/p>
int n = min
* 2; //
double # retrieved
TopDocs
topDocs = (sort == null) ? searcher.search(weight, filter, n) :
searcher.search(weight, filter, n, sort);
q不成了每次d的时候都要去调search重新查找吗?除非search里面有缓存,否则性能一定指C降啊Q?br />
实际上Hits最l用的也是TopDocsQSearcherl合来实现输出结果,那不如我们来直接使用下层一点的对象了。我原来的代码是Q?/p>
for( int i=0;i<hits .lengthQ);i++) {
Document doc
= hits .doc(i );
szTest.add(doc);
}
现在改ؓQ?br />
TopDocs topDoc = searcher.search(query.weight(searcher), null,
100000);//注意最后一个参敎ͼ是searchq回的结果数量,应该比你最大可能返回的数量大,否则ScoreDoc里面是你设|的数量?/p>
for( int i=0;i<scoreDocs.length;i++) {
Document doc
= searcher.doc(scoreDocs[i].doc );
szTest.add(doc);
}
l果?2000个ID加入ArrayList用时0.4U,快了几百倍?/p>
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);
}
现在q回1.2W个ID耗时0.25U。虽然比前面只少了大U?50毫秒Q但是是接近40%的提高了Q在负蝲比较大的应用中还是很重要的?/p>
注:
有些可以借鉴?br />
public static String analyze(Analyzer analyzer, String keyword) throws IOException {
StringBuffer sb = new StringBuffer();
TokenStream tokenStream = analyzer.tokenStream("content", new StringReader(keyword));
for (Token token = new Token(); (token = tokenStream.next(token)) != null;) {
sb.append(token.term() + " ");
}
return sb.toString();
}
讄关键词之间空格的与或关系
queryParser_or.setDefaultOperator(QueryParser.OR_OPERATOR);
qoҎ字符
遇到多余一个空格后的处?br />
*
* 把超q一个空格后面的转化?nbsp;OR 可辑ּ
*
* @param wd
* @return eg: ibm t60 mp3 液晶 ibm t60 OR mp3 OR 液晶
*/
public static String nvl(String value) {
return value == null ? "" : value;
}
public static String parseWd(String wd) {
String retwd = nvl(wd).replaceAll(" ", " ").replaceAll(" ", " ");
String[] arr = nvl(retwd).split(" ");
if (arr != null && arr.length > 2) {
retwd = (arr[0].trim().equals("OR") ? "" : arr[0] + " ") + (arr[1].trim().equals("OR") ? "" : arr[1]);
for (int i = 2; i < arr.length; i++) {
if (!arr[i].trim().equals("OR")) {
retwd += " OR " + arr[i];
}
}
}
return retwd;
}
]]>
Directory directory = new SimpleFSDirectory(new
File(path),new SimpleFSLockFactory()); // 先要建立directory
IndexWriter writer = new IndexWriter(directory,new WhitespaceAnalyzer(),
cover,IndexWriter.MaxFieldLength.UNLIMITED); //
q里最大字D长度无限(大字D|contentQ,cover为true表示覆盖写用于初始化Qfalse用于更新Q这里就?
WhitespaceAnalyzer()分词?nbsp;
Directory directory = new SimpleFSDirectory(new File(path),new
SimpleFSLockFactory()); // 先要建立directory
IndexWriter writer = new IndexWriter(directory,new WhitespaceAnalyzer(),
cover,IndexWriter.MaxFieldLength.UNLIMITED); //
q里最大字D长度无限(大字D|contentQ,cover为true表示覆盖写用于初始化Qfalse用于更新Q这里就?
WhitespaceAnalyzer()分词?/font>
IndexWriter 参数调整
writer.setMergeFactor(50); // 多少个合q一?nbsp;
writer.setMaxMergeDocs(5000); // 一个segment最多有多少个documentQnbsp;
writer.setMergeFactor(50); // 多少个合q一?br style="word-wrap: break-word;" />
writer.setMaxMergeDocs(5000); // 一个segment最多有多少个documentQ?font>
把其他格式{化ؓlucene需要的documentQ式
documentQdoc = new
documentQ?; //每一个doc相当于数据库的一条记?nbsp;
doc.add(new Field("uid", line.getUid().toString(),
Store.YES,Index.NO)); //每一个fieldQ相当于数据库的字段
doc.add(new Field("title", line.getTitle(),
Store.NO,Index.ANALYZED));
doc.add(new Field("content", line.getContent(),Store.NO,
Index.ANALYZED));
documentQdoc = new documentQ?; //每一个doc相当于数据库的一条记?br style="word-wrap: break-word;" />
doc.add(new Field("uid", line.getUid().toString(),
Store.YES,Index.NO)); //每一个fieldQ相当于数据库的字段
doc.add(new Field("title",
line.getTitle(), Store.NO,Index.ANALYZED));
doc.add(new Field("content", line.getContent(),Store.NO,
Index.ANALYZED));
向IndexWriterddocQ可以插入多条doc
writer.adddocumentQdoc);
writer.adddocumentQdoc2);
writer.adddocumentQdoc3);
writer.adddocumentQdoc);
writer.adddocumentQdoc2);
writer.adddocumentQdoc3);
开始写入(close的时候ؓ实际写入q程Q?/p>
writer.close();
writer = null;
writer.close();
writer = null;
d写入的烦引数
writer.numDocs()
writer.maxDoc()
writer.numDocs()
writer.maxDoc()
在close之前可以q行优化Q不在徏立烦引时候用)
writer.optimize()
2、清I烦?br style="word-wrap: break-word;" />
Directory directory = new SimpleFSDirectory(new File(path),new
SimpleFSLockFactory());
IndexWriter.unlock(directory); //关键是这一步要q行目录解锁Q这里解的是write.lock?nbsp;
IndexWriter writer = new IndexWriter(directory,new WhitespaceAnalyzer(),
false,IndexWriter.MaxFieldLength.LIMITED);
writer.deleteAll(); //标识删除全部
writer.optimize(); //q个步骤才是实际删除的过E?nbsp;
writer.close();
Directory directory = new SimpleFSDirectory(new File(path),new
SimpleFSLockFactory());
IndexWriter.unlock(directory); //关键是这一步要q行目录解锁Q这里解的是write.lock?br style="word-wrap: break-word;" />
IndexWriter writer = new IndexWriter(directory,new WhitespaceAnalyzer(),
false,IndexWriter.MaxFieldLength.LIMITED);
writer.deleteAll(); //标识删除全部
writer.optimize(); //q个步骤才是实际删除的过E?br style="word-wrap: break-word;" />
writer.close();
3、删除指定烦引(和清I差不多Q?br style="word-wrap: break-word;" />
writer.deletedocumentQ?new Term("uri", uri)); //q里是删除term满条g的一条或多条
writer.deletedocumentQ?query); //q里是删除一个查询出来的内容
writer.deletedocumentQ?new Term("uri", uri)); //q里是删除term满条g的一条或多条
writer.deletedocumentQ?query); //q里是删除一个查询出来的内容
4、更新烦?br style="word-wrap: break-word;" /> 是先删除再d的过E,没有直接update的办?/p>
5、读取徏立的索引分词
TermEnum terms = indexReader.terms(new Term(index, ""));
Term term = terms.term(); //获取一条烦?nbsp;
term().field(); //获取索引的fieldQ字D名Q?nbsp;
term().text(); //获取索引的?nbsp;
TermEnum terms = indexReader.terms(new Term(index, ""));
Term term = terms.term(); //获取一条烦?br style="word-wrap: break-word;" />
term().field(); //获取索引的fieldQ字D名Q?br style="word-wrap: break-word;" />
term().text(); //获取索引的?/p>
6、搜?br style="word-wrap: break-word;" /> 最?.9的IndexSearcher 建立方式Q?/p>
Directory directory = new
SimpleFSDirectory(new File(path),new SimpleFSLockFactory());
IndexSearcher indexSearcher = new IndexSearcher(directory, true);
Directory directory = new SimpleFSDirectory(new File(path),new
SimpleFSLockFactory());
IndexSearcher indexSearcher = new IndexSearcher(directory, true);
创徏查询条gQ这里徏一个最复杂的,Ҏ多个限定条g查找Qƈ 且有的限定条件放在多个field中查找,有精限定和范围限定Q?/p>
BooleanQuery bQuery = new BooleanQuery();
Query query1 = null, query2 = null, query3 = null;
BooleanClause.Occur[] flags = new BooleanClause.Occur[]
{BooleanClause.Occur.SHOULD, BooleanClause.Occur.SHOULD };
query1 = MultiFieldQueryParser.parse(params.get("keywords"),new String[]
{ "title", "content" }, flags, new WhitespaceAnalyzer());
bQuery.add(query1, Occur.MUST); //query1是把关键字分别在title和content中匹配!
query2 = new TermQuery(new Term("startgui", params.get("startgui")));
bQuery.add(query2, Occur.MUST); //query2是精匹?nbsp;
Long minPriceLong = Long.parseLong(params.get("minPrice"));
Long maxPriceLong = Long.parseLong(params.get("maxPrice"));
query5 = NumericRangeQuery.newLongRange("price", minPriceLong,
maxPriceLong, true, true);
bQuery.add(query5, Occur.MUST); //query3是按范围匚w
BooleanQuery bQuery = new BooleanQuery();
Query query1 = null, query2 = null, query3 = null;
BooleanClause.Occur[] flags = new BooleanClause.Occur[]
{BooleanClause.Occur.SHOULD, BooleanClause.Occur.SHOULD };
query1 = MultiFieldQueryParser.parse(params.get("keywords"),new String[]
{ "title", "content" }, flags, new WhitespaceAnalyzer());
bQuery.add(query1, Occur.MUST); //query1是把关键字分别在title和content中匹配!
query2 = new TermQuery(new Term("startgui", params.get("startgui")));
bQuery.add(query2, Occur.MUST); //query2是精匹?br style="word-wrap: break-word;" />
Long minPriceLong = Long.parseLong(params.get("minPrice"));
Long maxPriceLong = Long.parseLong(params.get("maxPrice"));
query5 = NumericRangeQuery.newLongRange("price", minPriceLong,
maxPriceLong, true, true);
bQuery.add(query5, Occur.MUST); //query3是按范围匚w
排序情况
SortField[] sortField = new
SortField[] { SortField.FIELD_SCORE,new SortField(null, SortField.DOC,
true) }; // 默认排序
SortField sortPriceField = new SortField("sortPrice",SortField.LONG,
sortPrice);
sortField = new SortField[] { sortPriceField,SortField.FIELD_SCORE,new
SortField(null, SortField.DOC, true) }; //按自定义h排序
SortField[] sortField = new SortField[] { SortField.FIELD_SCORE,new
SortField(null, SortField.DOC, true) }; // 默认排序
SortField sortPriceField = new SortField("sortPrice",SortField.LONG,
sortPrice);
sortField = new SortField[] { sortPriceField,SortField.FIELD_SCORE,new
SortField(null, SortField.DOC, true) }; //按自定义h排序
2.9最新查询方式,只是获取id
TopFieldDocs docs =
indexSearcher.search(query, null, indexSearcher.maxDoc(), new
Sort(sortField));
ScoreDoc[] scoreDocs = docs.scoreDocs;
docCount = scoreDocs.length;
TopFieldDocs docs = indexSearcher.search(query, null,
indexSearcher.maxDoc(), new Sort(sortField));
ScoreDoc[] scoreDocs = docs.scoreDocs;
docCount = scoreDocs.length;
加入分页
List<documentQgt; docList =
new ArrayList<documentQgt;();
int max = ((startIndex + pageSize) >= docCount) ? docCount :
(startIndex + pageSize); // max防止arrayindexoutofbounds
for (int i = startIndex; i < max; i++) {
ScoreDoc scoredoc = scoreDocs[i];
documentQdoc = indexSearcher.doc(scoredoc.doc); // 新的使用Ҏ
docList.add(doc);
}
List<documentQgt; docList = new ArrayList<documentQgt;();
int max = ((startIndex + pageSize) >= docCount) ? docCount :
(startIndex + pageSize); // max防止arrayindexoutofbounds
for (int i = startIndex; i < max; i++) {
ScoreDoc scoredoc = scoreDocs[i];
documentQdoc = indexSearcher.doc(scoredoc.doc); // 新的使用Ҏ
docList.add(doc);
}
循环解析docList中的documentQ取所需要的?/p>
doc.get("title");
...
7、关于分?br style="word-wrap: break-word;" /> 注意建立索引和搜索时候的analyzer必须一_而且建立索引和搜索时候目录也要保持一?/p>
lucene自带的一些分词器
StandardAnalyzer() 会按I格和标点符号划?/p>
WhitespaceAnalyzer() 会按I格划分
中文分词q里使用的是paoding的中文分?/p>
是先按词库划分,当词库中不存在时按二分法q行划分