實關於全文檢索的倒排序,邏輯是非常簡單的,“空間換時間”的概念也不復雜。
我們先做一個最簡單的分詞器,使用“二元切分”,亦即“二元” “元切” “切分”。
package net.csdn.blog.deltatang.lucene4me;
import java.util.HashMap;
import java.util.Map;
public class Analyzer {
public Map<String, Integer> analyze(String text){
Map<String, Integer> words = new HashMap<String, Integer>();
for(int i = 0; i < text.length() - 1; i++) {
String word = text.substring(i, i + 1);
int freq = 1;
if(words.keySet().contains(word)) {
freq += words.get(word);
}
words.put(word, freq);
}
return words;
}
}
這樣,我們記錄了一個文檔內容的所有切分詞(token)以及其對應的詞頻(在文檔中出現的次數)
當然,在查詢中,查詢詞也是需要切分詞的,這時候需要注意的是,
編制索引所用的分詞器和查詢詞的分詞器必須是同一個實現,否則,就會驢脣不對馬嘴:)
然後,簡便起見,我們用一個hash表來作爲內存索引存儲。
package net.csdn.blog.deltatang.lucene4me;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
public class DataStore {
private static Map<String, Set<Doc>> dataset = new HashMap<String, Set<Doc>>();
public static void insert(String token, int docID, int freq) {
Set<Doc> docIDs = dataset.get(token);
if(docIDs == null) {
docIDs = new HashSet<Doc>();
dataset.put(token, docIDs);
}
docIDs.add(new Doc(docID, freq));
}
public static Set<Doc> get(String token) {
return dataset.get(token);
}
public static void display() {
System.out.println(dataset);
}
}
這個存儲裏面,存儲了 token 和 文檔一對多的關係。
雖然實現比較粗糙,但是,所謂“倒排序”的含義就在於此。
我們前面提到的爲什麼要前後端都用同一分詞器的原因也在於此。試想,如果前端對查詢詞采取不同的分詞器,那就不能確保會生成 存儲表裏面 對應的token。
也就無法檢索到需要的結果。
這個,其實就是lucene最關鍵的部分——簡單就是美——沒錯兒:)
接下來,我們打造一個工具類,用於支持創建索引和查詢索引,
創建索引的方法如下:
public static void buildIndex(int id, String text) {
Map<String, Integer> datas = analyzer.analyze(text);
for(String token : datas.keySet()) {
DataStore.insert(token, id, datas.get(token));
}
}
爲了便於測試和說明,我們新增一個初始化方法,添加一部分模擬數據:
static {
init();
}
private static void init() {
buildIndex(1, "解放區的天是明朗的天");
buildIndex(2, "從來就沒有什麼救世主,也沒有神仙皇帝");
buildIndex(3, "中國人民從此站立起來了");
buildIndex(4, "東風吹,戰鼓擂,如今世上誰怕誰");
buildIndex(5, "不是東風壓倒西風,就是西風壓倒東風");
buildIndex(6, "人民萬歲,人民萬歲");
}
然後我們做一個查詢方法,並且實現查詢結果根據詞頻排序:
public static int[] findDocIDs(String keywords) {
TreeSet<Doc> results = new TreeSet<Doc>(new Comparator<Doc>() {
@Override
public int compare(Doc o1, Doc o2) {
int val = o2.freq - o1.freq;
return val == 0 ? 1 : val;
}
});
Map<String, Integer> datas = analyzer.analyze(keywords);
for(String token : datas.keySet()) {
Set<Doc> docs = DataStore.get(token);
if(docs != null && !docs.isEmpty()) {
results.addAll(docs);
}
}
int[] ret = new int[results.size()];
Iterator<Doc> itor = results.iterator();
for(int i = 0; i < ret.length && itor.hasNext(); i++) {
ret[i] = itor.next().id;
}
return ret;
}
OK,最後,我們測試一下:
public static void main(String[] args) {
DataStore.display();
int[] ret;
ret = findDocIDs("東風");
for(int r : ret) {
System.out.print(r + ", ");
}
System.out.println();
ret = findDocIDs("人民");
for(int r : ret) {
System.out.print(r + ", ");
}
System.out.println();
ret = findDocIDs("東風人民");
for(int r : ret) {
System.out.print(r + ", ");
}
System.out.println();
}
輸出的結果爲:
{從來=[<2, 1>], 民從=[<3, 1>], 放區=[<1, 1>], 有什=[<2, 1>], 就是=[<5, 1>], 是東=[<5, 1>], 世上=[<4, 1>], 區的=[<1, 1>], 的天=[<1, 2>], 擂,=[<4, 1>], 來了=[<3, 1>], 中國=[<3, 1>], 人民=[<3, 1>, <6, 2>], 國人=[<3, 1>], 今世=[<4, 1>], 明朗=[<1, 1>], 西風=[<5, 2>], 如今=[<4, 1>], ,戰=[<4, 1>], 萬歲=[<6, 2>], 歲,=[<6, 1>], 沒有=[<2, 2>], 救世=[<2, 1>], 上誰=[<4, 1>], 風壓=[<5, 2>], 是明=[<1, 1>], 戰鼓=[<4, 1>], 是西=[<5, 1>], 吹,=[<4, 1>], 也沒=[<2, 1>], 民萬=[<6, 2>], 倒西=[<5, 1>], 從此=[<3, 1>], 什麼=[<2, 1>], 仙皇=[<2, 1>], 壓倒=[<5, 2>], 朗的=[<1, 1>], ,如=[<4, 1>], ,也=[<2, 1>], 神仙=[<2, 1>], 誰怕=[<4, 1>], 風,=[<5, 1>], 天是=[<1, 1>], 起來=[<3, 1>], 世主=[<2, 1>], ,就=[<5, 1>], 麼救=[<2, 1>], ,人=[<6, 1>], 倒東=[<5, 1>], 皇帝=[<2, 1>], 有神=[<2, 1>], 此站=[<3, 1>], 東風=[<4, 1>, <5, 2>], 就沒=[<2, 1>], 解放=[<1, 1>], 立起=[<3, 1>], 主,=[<2, 1>], 站立=[<3, 1>], 風吹=[<4, 1>], 鼓擂=[<4, 1>], 怕誰=[<4, 1>], 不是=[<5, 1>], 來就=[<2, 1>]}
5, 4,
6, 3,
6, 5, 3, 4,
可以看到,結果正確,排序正確。
bingo!我們繼續前進了一步!
LuceneTool 的完整代碼如下:
package net.csdn.blog.deltatang.lucene4me;
import java.util.Comparator;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import java.util.TreeSet;
public class LuceneTool {
private static Analyzer analyzer = new Analyzer();
static {
init();
}
private static void init() {
buildIndex(1, "解放區的天是明朗的天");
buildIndex(2, "從來就沒有什麼救世主,也沒有神仙皇帝");
buildIndex(3, "中國人民從此站立起來了");
buildIndex(4, "東風吹,戰鼓擂,如今世上誰怕誰");
buildIndex(5, "不是東風壓倒西風,就是西風壓倒東風");
buildIndex(6, "人民萬歲,人民萬歲");
}
public static void buildIndex(int id, String text) {
Map<String, Integer> datas = analyzer.analyze(text);
for(String token : datas.keySet()) {
DataStore.insert(token, id, datas.get(token));
}
}
public static int[] findDocIDs(String keywords) {
TreeSet<Doc> results = new TreeSet<Doc>(new Comparator<Doc>() {
@Override
public int compare(Doc o1, Doc o2) {
int val = o2.freq - o1.freq;
return val == 0 ? 1 : val;
}
});
Map<String, Integer> datas = analyzer.analyze(keywords);
for(String token : datas.keySet()) {
Set<Doc> docs = DataStore.get(token);
if(docs != null && !docs.isEmpty()) {
results.addAll(docs);
}
}
int[] ret = new int[results.size()];
Iterator<Doc> itor = results.iterator();
for(int i = 0; i < ret.length && itor.hasNext(); i++) {
ret[i] = itor.next().id;
}
return ret;
}
}