搜索为王 -- lucene 6.6 学习心得

1,375 阅读6分钟

前言

学习的资料是 lucene 4.10 版本,比较沉旧,查阅最新的 lucene 版本 6.6 的官方文档,整理出以下几个使用中的不同。
从浅入深依次为 (注:不是根据版本先后)

  1. IndexWriterConfig 的构造方法
  2. Directory 的生成方法 FSDirectory.open()
  3. legacyXXField 与legacyNumericRangeQuery 的废弃
  4. BooleanQuery() 方法的改变
  5. setBoost()方法的废除
  6. 中文分词器的改进

下面,就让我详细的整理出不同。

1.IndexWriterConfig

以下是 IndexWriterConfig 4.10 的源码部分

public IndexWriterConfig(Version matchVersion, Analyzer analyzer) {
    super(analyzer, matchVersion);
}
//这里的version 一般要写为     
Version 类中
public static final Version LATEST;

而 IndexWriterConfig 6.6 中

//无参构造方法 
public IndexWriterConfig() {
    this(new StandardAnalyzer());
}
//有参构造方法
public IndexWriterConfig(Analyzer analyzer) {
    super(analyzer);
    this.writer = new SetOnce();
}

可以看出,在 6.6 版本中 version 不再是必要的,并且,存在无参构造方法,可以直接使用默认的 StandardAnalyzer 分词器。

2.Directory

正常创建 Directory 类的方法如下

Directory di = FSdirectory.open();

以下是 IndexWriterConfig 4.10 的源码部分

public static FSDirectory open(File path) throws IOException {
    return open(path, (LockFactory)null);
}

这里可以看出 open 方法 用的参数类型 为File

而 IndexWriterConfig 6.6 中

public static FSDirectory open(Path path) throws IOException {
    return open(path, FSLockFactory.getDefault());
}

open 方法使用了 Path 类,这个类是 NIO 中的类,可以提高查询的效率。
由 File 转化为 Path 的 方法
—>

File file = new File (absolutePath);
Path path = file.toPath()

3.legacyXXField 与legacyNumericRangeQuery

1. 分析

根据 官方的 Migration Guide 中的说法

PointValues replaces NumericField (LUCENE-6917)

PointValues provides faster indexing and searching, a smaller index size, and less heap used at search time. See org.apache.lucene.index.PointValues for an introduction.

Legacy numeric encodings from previous versions of Lucene are deprecated as LegacyIntField, LegacyFloatField, LegacyLongField, and LegacyDoubleField, and can be searched with LegacyNumericRangeQuery.

以及开发者的测试

DimensionalValues seems to be better across the board (indexing time, indexing size, search-speed, search-time heap required) than NumericField, at least in my testing so far.
I think for 6.0 we should move IntField, LongField, FloatField, DoubleField and NumericRangeQuery to backward-codecs, and rename with Legacy prefix?

2.结论:

PointValues 取代了NumericField
因为PointValues 更快,更小,更便于资源的利用。所以,所有的 legacy**都被取代了。

3.代码对比

代码的话,lucene 的官方文档给了一个简单的例子

// add year 1970 to document
document.add(new IntPoint("year", 1970));
// index document
writer.addDocument(document);
...
// issue range query of 1960-1980
Query query = IntPoint.newRangeQuery("year", 1960, 1980);
TopDocs docs = searcher.search(query, ...);

另外我自己写了一个 已经@Deprecated的方法 与上面 进行对比

   // add year 1970 to document
document.add(new IntField("year", 1970));
// index document
writer.addDocument(document);
...
// issue range query of 1960-1980
Query query = new NumericRangeQuery("year", 1960, 1980,false,false);
TopDocs docs = searcher.search(query, ...);

还要注意的是:

  1. 如果要存储,必须创建同名的StoredField类
  2. 如果要排序使用,必须同时创建同名的StoredField类与NumericDocValuesField类
    例:
    doc.add(new NumericDocValuesField("price",price));  
    doc.add(new IntPoint("price",price));  
    doc.add(new StoredField("price",price));

4.BooleanQuery() 的构造方法改变

1.分析

根据 官方的 Migration Guide 中的说法

PhraseQuery, MultiPhraseQuery, and BooleanQuery made immutable (LUCENE-6531 LUCENE-7064 LUCENE-6570)
也就是说, BooleanQuery这个类 一旦建立就不能再改变了。

从源码中我们可以更好的看出改变
lucene 4.10 的源码里 BooleanQuery 的类 主要方法如下

/* 构造器*/
public BooleanQuery() {
    this.disableCoord = false;
}
public BooleanQuery(boolean disableCoord) {
    this.disableCoord = disableCoord;
}
/*主要方法*/
public void add(BooleanClause clause) {
    if(this.clauses.size() >= maxClauseCount) {
        throw new BooleanQuery.TooManyClauses();
    } else {
        this.clauses.add(clause);
    }
}

lucene 6.6 的源码里, BooleanQuery 的主要方法如下:

private BooleanQuery(boolean disableCoord, int minimumNumberShouldMatch, BooleanClause[] clauses)

可以看出 , BooleanQuery 的构造器的范围是 private 的,只能在类的内部调用。
并且最大的改变是多出了静态内部类 Builder
以下是 Builder 的部分源码

public static class Builder {
    private boolean disableCoord;
    private int minimumNumberShouldMatch;
    private final List<BooleanClause> clauses = new ArrayList();
    /* 无参构造器 */
    // 相当于 BooleanQuery 的 构造器
    public Builder() {
    }
    //相当于 BooleanQuery 的 add 方法
    public BooleanQuery.Builder add(Query query, Occur occur) {
        return this.add(new BooleanClause(query, occur));
    }
    //返回值是 BooleanQuery, 构造一个BooleanQuery 类。
    public BooleanQuery build() {
        return new BooleanQuery(this.disableCoord, this.minimumNumberShouldMatch, (BooleanClause[])this.clauses.toArray(new BooleanClause[0]), null);
    }
}

2.结论

通过用静态内部类实例化自身的方法,加强了类自身的稳定性与安全性。避免可能发生的小意外,而导致代码出现问题的可能性

3.代码对比

下面给出代码,可以更好的看出差别

//原先的 使用方法
BooleanQuery bq = new BooleanQuery();     
bq.add(q1, Occur.SHOULD);     
bq.add(q2, Occur.SHOULD); bq.add(q3, Occur.MUST); 
//现在的 使用方法
BooleanQuery bq = new BooleanQuery.Builder()
                                     .add(q1, Occur.SHOULD)    
                                     .add(q2, Occur.SHOULD)
                                     .add(q3, Occur.SHOULD)
                                     .build();

5. setBoost()方法的废除

在 lucene 4.10 包中, setBoost方法被用于 相关度 的排序中。改变创建索引时的 Boost – 权值。根据一系列计算方法 (旧版采用的 空间向量模型算法),最终得出其打分。
代码如下 :

Field fi1 = new Field("id" , 1, STORE.YES);
// Boost 值默认为 1.0f 
fi1.setBoost(100f)
Document do = new Document();
do.add(fi1);
``` 
而在新版 lucene 6.6 中, setBoost 方法被 废除    
根据[lucene 的官方文档](https://lucene.apache.org/core/6_6_0/core/index.html)中的说法
>org.apache.lucene.document.Field.setBoost(float)    
Index-time boosts are deprecated, please index index-time scoring factors into a doc value field and combine them with the score at query time using eg. FunctionScoreQuery.
> 中文翻译后:    
索引时权值被废除,请将索引时打分因素添加入field域中,然后在查询时,使用功能性打分查询语句,进行关联查询。
> 我在查看了 大部分关联的 api 后,发现了几个与之相关的 类    
1. BoostAttribute
2. termsEnum
3. MultiQuery    
>[lucene 的官方文档](https://lucene.apache.org/core/6_6_0/core/index.html)中对 BoostAttribute 的描述是这样的。    
BoostAttribute	---   >    
Add this Attribute to a TermsEnum returned by MultiTermQuery.getTermsEnum(Terms,AttributeSource) and update the boost on each returned term.    
方法描述如下

protected abstract TermsEnum getTermsEnum(Terms terms,AttributeSource atts)
//Construct the enumeration to be used, expanding the pattern term.
//很明显,这是个抽象方法,必须由子类实现

**BoostAttribute **是个接口,其实现类 BoostAttributeImpl 中源码如下

public BoostAttributeImpl() {
}
public void setBoost(float boost) {
    this.boost = boost;
}
> 推测使用如下     ---  以下是伪代码

//设置 Boost 属性
BoostAttribute ba = new BoostAttributeImpl();
ba.setBoost(100f);
//设置 Query 的实现类
Query query = new MultiTermqueryChildren(new Terms());
TermEnum te = query.getTermsEnum(Terms,ba);

**具体方法还不清楚,希望知道的大神可以给我解答** 
另外,还有两个便于操作的类:
1. BoostQuery
2. MultiFieldQueryParser
### 1.BoostQuery
源码如下:

public BoostQuery(Query query, float boost) {
    this.query = (Query)Objects.requireNonNull(query);
    this.boost = boost;
}
分析:    
相当于一个包装类,将 Query 设置 Boost 值 ,然后包装起来。    
再通过复合查询语句,可以突出 Query 的优先级。    
使用如下:

//查询 索引域 中的 file_name , file_content
Query q1 = new TermQuery(new Term(“file_name” ,”springmvc.txt”);
Query q2 = new TermQuery(new Term(“file_content”,”springmvc.txt”);
//将 q1 设置 Boost 值
BoostQuery q3 = new BoostQuery(q1,100f);
//复合语句查询
BooleanQuery.Builder() builder = new new BooleanQuery.Builder();
builder.add(q3, Occur.MUST)
builder.add(q2, Occur.MUST)
//由于 file_name 的查询语句经过 BoostQuery 的包装
//因此 file_name 的优先级更高。
BooleanQuery query = builder.build();


2.MultiFieldQueryParser
和原先版本相同 , 就不阐述源码,直接上使用方法    
使用如下 :

//设置组合查询域
String[] fields = {“file_name”,”file_content”};

//设置评分,文件名称中包括关键字的评分高
Map boosts = new HashMap();
boosts.put(“file_name”, 10.0f);

//创建查询解析器
QueryParser queryParser = new MultiFieldQueryParser(fields, new IKAnalyzer(), boosts);
//查询文件名、文件内容中包括“springmvc.txt”关键字的文档,由于设置了文件名称域的加权值高,所以名称中匹配到关键字的应该排在前边
Query query = queryParser.parse(“springmvc.txt”);
```

6. 中文分词器 – iKAnalyzer 的 lucene 6.6 适配

调皮的超链接个人博客中有更多