1. 程式人生 > >lucene的suggest(搜索提示功能的實現)

lucene的suggest(搜索提示功能的實現)

nds ber fin 所有 strong 浪費 input ads true

1.首先引入依賴

<!-- https://mvnrepository.com/artifact/org.apache.lucene/lucene-suggest -->
<!-- 搜索提示 -->
<dependency>
  <groupId>org.apache.lucene</groupId>
  <artifactId>lucene-suggest</artifactId>
  <version>7.2.1</version>
</dependency>

2.既然要進行智能聯想,那麽我們需要為提供聯想的數據建立一個聯想索引(而不是使用原來的數據索引),既然要建立索引,那麽我們需要知道建立索引的數據來源。我們使用一個擴展自InputIterator的類來定義數據來源。首先我們看看被擴展的類InputIterator

public interface InputIterator extends BytesRefIterator {
    InputIterator EMPTY = new InputIterator.InputIteratorWrapper(BytesRefIterator.EMPTY);

    long weight();

    BytesRef payload();

    boolean hasPayloads();

    Set<BytesRef> contexts();

    boolean hasContexts();

    public static
class InputIteratorWrapper implements InputIterator { private final BytesRefIterator wrapped; public InputIteratorWrapper(BytesRefIterator wrapped) { this.wrapped = wrapped; } public long weight() { return 1L; } public BytesRef next() throws
IOException { return this.wrapped.next(); } public BytesRef payload() { return null; } public boolean hasPayloads() { return false; } public Set<BytesRef> contexts() { return null; } public boolean hasContexts() { 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 = new RAMDirectory();
            StandardAnalyzer analyzer = new StandardAnalyzer();
            AnalyzingInfixSuggester suggester = new AnalyzingInfixSuggester(indexDir, analyzer);

            // 創建索引,根據InputIterator的具體實現決定數據源以及創建索引的規則
            suggester.build(new InputIterator{});

4.索引建立完畢即可在索引上進行查詢,輸入模糊的字符,Lucene suggest的內部算法會根據索引的建立規則提出suggest查詢的內容。

private static void lookup(AnalyzingInfixSuggester suggester, String name,
                               String region) throws IOException {
        HashSet<BytesRef> contexts = new HashSet<BytesRef>();
        //使用Contexts域對suggest結果進行過濾
        contexts.add(new BytesRef(region.getBytes("UTF8")));
        //num決定了返回幾條數據,參數四表明是否所有TermQuery是否都需要滿足,參數五表明是否需要高亮顯示
        List<Lookup.LookupResult> 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索引創建,查詢過程
實體類

package com.cfh.study.lucence_test6;

import java.io.Serializable;

/**
 * @Author: cfh
 * @Date: 2018/9/17 10:18
 * @Description: 用來測試suggest功能的pojo類
 */
public class Product implements Serializable {
    /** 產品名稱 */
    private String name;
    /** 產品圖片 */
    private String image;
    /** 產品銷售地區 */
    private String[] regions;
    /** 產品銷售量 */
    private int numberSold;

    public Product() {
    }

    public Product(String name, String image, String[] regions, int numberSold) {
        this.name = name;
        this.image = image;
        this.regions = regions;
        this.numberSold = numberSold;
    }

    public String getName() {

        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getImage() {
        return image;
    }

    public void setImage(String image) {
        this.image = image;
    }

    public String[] getRegions() {
        return regions;
    }

    public void setRegions(String[] regions) {
        this.regions = regions;
    }

    public int getNumberSold() {
        return numberSold;
    }

    public void setNumberSold(int numberSold) {
        this.numberSold = numberSold;
    }
}

指定數據源,這裏的數據源是傳入的一個product集合的叠代器,可以根據實際情況更換數據源為文件或者數據庫等。

package com.cfh.study.lucence_test6;

import org.apache.lucene.search.suggest.InputIterator;
import org.apache.lucene.util.BytesRef;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;
import java.io.UnsupportedEncodingException;
import java.util.Comparator;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;

/**
 * @Author: cfh
 * @Date: 2018/9/17 10:21
 * @Description: 這個類是核心,決定了你的索引是如何創建的,決定了最終返回的提示關鍵詞列表數據及其排序
 */
public class ProductIterator implements InputIterator {
    private Iterator<Product> productIterator;
    private Product currentProduct;

    ProductIterator(Iterator<Product> productIterator) {
        this.productIterator = productIterator;
    }

    /**
     * 設置是否啟用Contexts域
     * @return
     */
    public boolean hasContexts() {
        return true;
    }

    /**
     * 是否有設置payload信息
     */
    public boolean hasPayloads() {
        return true;
    }

    public Comparator<BytesRef> getComparator() {
        return null;
    }

    /**
    * next方法的返回值指定的其實就是就是可能返回給我們的suggest的值的結果集合(LookUpResult.key),這裏我們選擇了商品名。
    */
    public BytesRef 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,這樣索引體積會很大,浪費硬盤空間]
     */
    public BytesRef payload() {
        try {
            ByteArrayOutputStream bos = new ByteArrayOutputStream();
            ObjectOutputStream out = new ObjectOutputStream(bos);
            out.writeObject(currentProduct);
            out.close();
            return new BytesRef(bos.toByteArray());
        } catch (IOException e) {
            throw new RuntimeException("Well that‘s unfortunate.");
        }
    }

    /**
     * 把產品的銷售區域存入context,context裏可以是任意的自定義數據,一般用於數據過濾
     * Set集合裏的每一個元素都會被創建一個TermQuery,你只是提供一個Set集合,至於new TermQuery
     * Lucene底層API去做了,但你必須要了解底層幹了些什麽
     */
    public Set<BytesRef> contexts() {
        try {
            Set<BytesRef> regions = new HashSet<BytesRef>();
            for (String region : currentProduct.getRegions()) {
                regions.add(new BytesRef(region.getBytes("UTF8")));
            }
            return regions;
        } catch (UnsupportedEncodingException e) {
            throw new RuntimeException("Couldn‘t convert to UTF-8");
        }
    }

    /**
     * 返回權重值,這個值會影響排序
     * 這裏以產品的銷售量作為權重值,weight值即最終返回的熱詞列表裏每個熱詞的權重值
     * 怎麽設計返回這個權重值,發揮你們的想象力吧
     */
    public long weight() {
        return currentProduct.getNumberSold();
    }
}

最後當然是測試suggest的結果啦,可以看到我們根據product的name進行了suggest並使用product的region域對suggest結果進行了過濾

private static void lookup(AnalyzingInfixSuggester suggester, String name,
                               String region) throws IOException {
        HashSet<BytesRef> contexts = new HashSet<BytesRef>();
        //先根據region域進行suggest再根據name域進行suggest
        contexts.add(new BytesRef(region.getBytes("UTF8")));
        //num決定了返回幾條數據,參數四表明是否所有TermQuery是否都需要滿足,參數五表明是否需要高亮顯示
        List<Lookup.LookupResult> 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(new ByteArrayInputStream(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

lucene的suggest(搜索提示功能的實現)