您的位置:首页 > 其它

拿起笔来做刀枪 · 之五 再造一个lucene

2014-07-28 14:51 405 查看
实关于全文检索的倒排序,逻辑是非常简单的,“空间换时间”的概念也不复杂。

我们先做一个最简单的分词器,使用“二元切分”,亦即“二元” “元切” “切分”。

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;
}

}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: