1.首先引入依赖
org.apache.lucene
lucene-suggest
7.2.1
2.既然要进行智能联想,那么我们需要为提供联想的数据建立一个联想索引(而不是使用原来的数据索引),既然要建立索引,那么我们需要知道建立索引的数据来源。我们使用一个扩展自InputIterator的类来定义数据来源。首先我们看看被扩展的类InputIterator
public interface InputIterator extendsBytesRefIterator {
InputIterator EMPTY= newInputIterator.InputIteratorWrapper(BytesRefIterator.EMPTY);longweight();
BytesRef payload();booleanhasPayloads();
Setcontexts();booleanhasContexts();public static class InputIteratorWrapper implementsInputIterator {private finalBytesRefIterator wrapped;publicInputIteratorWrapper(BytesRefIterator wrapped) {this.wrapped =wrapped;
}public longweight() {return 1L;
}public BytesRef next() throwsIOException {return this.wrapped.next();
}publicBytesRef payload() {return null;
}public booleanhasPayloads() {return false;
}public Setcontexts() {return null;
}public booleanhasContexts() {return false;
}
}
weight():此方法设置某个term的权重,设置的越高suggest的优先级越高;
payload():每个suggestion对应的元数据的二进制表示,我们在传输对象的时候需要转换对象或对象的某个属性为BytesRef类型,相应的suggester调用lookup的时候会返回payloads信息;
hasPayload():判断iterator是否有payloads;
contexts():获取某个term的contexts,用来过滤suggest的内容,如果suggest的列表为空,返回null
hasContexts():获取iterator是否有contexts;
lucene suggest提供了几个InputIteratior的默认实现
BufferedInputIterator:对二进制类型的输入进行轮询;
DocumentInputIterator:从索引中被store的field中轮询;
FileIterator:从文件中每次读出单行的数据轮询,以\t进行间隔(且\t的个数最多为2个);
HighFrequencyIterator:从索引中被store的field轮询,忽略长度小于设定值的文本;
InputIteratorWrapper:遍历BytesRefIterator并且返回的内容不包含payload且weight均为1;
SortedInputIterator:二进制类型的输入轮询且按照指定的comparator算法进行排序;
3.既然指定了数据源,下一步就是如何建立suggest索引
RAMDirectory indexDir = newRAMDirectory();
StandardAnalyzer analyzer= newStandardAnalyzer();
AnalyzingInfixSuggester suggester= newAnalyzingInfixSuggester(indexDir, analyzer);//创建索引,根据InputIterator的具体实现决定数据源以及创建索引的规则
suggester.build(new InputIterator{});
4.索引建立完毕即可在索引上进行查询,输入模糊的字符,Lucene suggest的内部算法会根据索引的建立规则提出suggest查询的内容。
private static voidlookup(AnalyzingInfixSuggester suggester, String name,
String region)throwsIOException {
HashSet contexts = new HashSet();//使用Contexts域对suggest结果进行过滤
contexts.add(new BytesRef(region.getBytes("UTF8")));//num决定了返回几条数据,参数四表明是否所有TermQuery是否都需要满足,参数五表明是否需要高亮显示
List results = suggester.lookup(name, contexts, 2, true, false);
System.out.println("-- \"" + name + "\" (" + region + "):");for(Lookup.LookupResult result : results) {
System.out.println(result.key);//result.key中存储的是根据用户输入内部算法进行匹配后返回的suggest内容
}
5.下面提供一个实例说明完整的suggest索引创建,查询过程
实体类
packagecom.cfh.study.lucence_test6;importjava.io.Serializable;/*** @Author: cfh
* @Date: 2018/9/17 10:18
* @Description: 用来测试suggest功能的pojo类*/
public class Product implementsSerializable {/**产品名称*/
privateString name;/**产品图片*/
privateString image;/**产品销售地区*/
privateString[] regions;/**产品销售量*/
private intnumberSold;publicProduct() {
}public Product(String name, String image, String[] regions, intnumberSold) {this.name =name;this.image =image;this.regions =regions;this.numberSold =numberSold;
}publicString getName() {returnname;
}public voidsetName(String name) {this.name =name;
}publicString getImage() {returnimage;
}public voidsetImage(String image) {this.image =image;
}publicString[] getRegions() {returnregions;
}public voidsetRegions(String[] regions) {this.regions =regions;
}public intgetNumberSold() {returnnumberSold;
}public void setNumberSold(intnumberSold) {this.numberSold =numberSold;
}
}
指定数据源,这里的数据源是传入的一个product集合的迭代器,可以根据实际情况更换数据源为文件或者数据库等。
packagecom.cfh.study.lucence_test6;importorg.apache.lucene.search.suggest.InputIterator;importorg.apache.lucene.util.BytesRef;importjava.io.ByteArrayOutputStream;importjava.io.IOException;importjava.io.ObjectOutputStream;importjava.io.UnsupportedEncodingException;importjava.util.Comparator;importjava.util.HashSet;importjava.util.Iterator;importjava.util.Set;/*** @Author: cfh
* @Date: 2018/9/17 10:21
* @Description: 这个类是核心,决定了你的索引是如何创建的,决定了最终返回的提示关键词列表数据及其排序*/
public class ProductIterator implementsInputIterator {private IteratorproductIterator;privateProduct currentProduct;
ProductIterator(IteratorproductIterator) {this.productIterator =productIterator;
}/*** 设置是否启用Contexts域
*@return
*/
public booleanhasContexts() {return true;
}/*** 是否有设置payload信息*/
public booleanhasPayloads() {return true;
}public ComparatorgetComparator() {return null;
}/*** next方法的返回值指定的其实就是就是可能返回给我们的suggest的值的结果集合(LookUpResult.key),这里我们选择了商品名。*/
publicBytesRef next() {if(productIterator.hasNext()) {
currentProduct=productIterator.next();try{//返回当前Project的name值,把product类的name属性值作为key
return new BytesRef(currentProduct.getName().getBytes("UTF8"));
}catch(UnsupportedEncodingException e) {throw new RuntimeException("Couldn't convert to UTF-8",e);
}
}else{return null;
}
}/*** 将Product对象序列化存入payload
* [这里仅仅是个示例,其实这种做法不可取,一般不会把整个对象存入payload,这样索引体积会很大,浪费硬盘空间]*/
publicBytesRef payload() {try{
ByteArrayOutputStream bos= newByteArrayOutputStream();
ObjectOutputStream out= newObjectOutputStream(bos);
out.writeObject(currentProduct);
out.close();return newBytesRef(bos.toByteArray());
}catch(IOException e) {throw new RuntimeException("Well that's unfortunate.");
}
}/*** 把产品的销售区域存入context,context里可以是任意的自定义数据,一般用于数据过滤
* Set集合里的每一个元素都会被创建一个TermQuery,你只是提供一个Set集合,至于new TermQuery
* Lucene底层API去做了,但你必须要了解底层干了些什么*/
public Setcontexts() {try{
Set regions = new HashSet();for(String region : currentProduct.getRegions()) {
regions.add(new BytesRef(region.getBytes("UTF8")));
}returnregions;
}catch(UnsupportedEncodingException e) {throw new RuntimeException("Couldn't convert to UTF-8");
}
}/*** 返回权重值,这个值会影响排序
* 这里以产品的销售量作为权重值,weight值即最终返回的热词列表里每个热词的权重值
* 怎么设计返回这个权重值,发挥你们的想象力吧*/
public longweight() {returncurrentProduct.getNumberSold();
}
}
最后当然是测试suggest的结果啦,可以看到我们根据product的name进行了suggest并使用product的region域对suggest结果进行了过滤
private static voidlookup(AnalyzingInfixSuggester suggester, String name,
String region)throwsIOException {
HashSet contexts = new HashSet();//先根据region域进行suggest再根据name域进行suggest
contexts.add(new BytesRef(region.getBytes("UTF8")));//num决定了返回几条数据,参数四表明是否所有TermQuery是否都需要满足,参数五表明是否需要高亮显示
List results = suggester.lookup(name, contexts, 2, true, false);
System.out.println("-- \"" + name + "\" (" + region + "):");for(Lookup.LookupResult result : results) {
System.out.println(result.key);//result.key中存储的是根据用户输入内部算法进行匹配后返回的suggest内容//从载荷(payload)中反序列化出Product对象(实际生产中出于降低内存占用考虑一般不会在载荷中存储这么多内容)
BytesRef bytesRef =result.payload;
ObjectInputStream is= new ObjectInputStream(newByteArrayInputStream(bytesRef.bytes));
Product product= null;try{
product=(Product)is.readObject();
}catch(ClassNotFoundException e) {//TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println("product-Name:" +product.getName());
System.out.println("product-regions:" +product.getRegions());
System.out.println("product-image:" +product.getImage());
System.out.println("product-numberSold:" +product.getNumberSold());
}
System.out.println();
}
当然也可以参考原博主的github:study-lucene
原文博主:https://blog.csdn.net/m0_37556444/article/details/82734959