拿起笔来做刀枪 · 之五 再造一个lucene
2014-07-28 14:51
405 查看
实关于全文检索的倒排序,逻辑是非常简单的,“空间换时间”的概念也不复杂。
我们先做一个最简单的分词器,使用“二元切分”,亦即“二元” “元切” “切分”。
当然,在查询中,查询词也是需要切分词的,这时候需要注意的是,
编制索引所用的分词器和查询词的分词器必须是同一个实现,否则,就会驴唇不对马嘴:)
然后,简便起见,我们用一个hash表来作为内存索引存储。
虽然实现比较粗糙,但是,所谓“倒排序”的含义就在于此。
我们前面提到的为什么要前后端都用同一分词器的原因也在于此。试想,如果前端对查询词采取不同的分词器,那就不能确保会生成 存储表里面 对应的token。
也就无法检索到需要的结果。
这个,其实就是lucene最关键的部分——简单就是美——没错儿:)
接下来,我们打造一个工具类,用于支持创建索引和查询索引,
创建索引的方法如下:
为了便于测试和说明,我们新增一个初始化方法,添加一部分模拟数据:
然后我们做一个查询方法,并且实现查询结果根据词频排序:
OK,最后,我们测试一下:
输出的结果为:
可以看到,结果正确,排序正确。
bingo!我们继续前进了一步!
LuceneTool 的完整代码如下:
我们先做一个最简单的分词器,使用“二元切分”,亦即“二元” “元切” “切分”。
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; } }
相关文章推荐
- 拿起笔来做刀枪 · 之六 再造一个hibernate
- 拿起笔来做刀枪 · 之四 再造一个struts
- 拿起笔来做刀枪 · 之三 再造一个jsp(java sign pages)
- 拿起笔来做刀枪 · 之一 再造一个dom4j
- 拿起笔来做刀枪 · 之二 再造一个spring
- 拿起笔来做刀枪 · 之七 最终幻想 Final Fantasy
- 拿起笔来做刀枪 · 序言
- 《编程导论(Java)·例程》tips.Print的一个bug
- 切·格瓦拉 ———— 一个英雄的一生!
- 悼念传奇,约翰询问·纳什和他的妻子艾丽西亚致敬,创建一个传奇,爱数学
- 一个男人关心的东西 决定了他的层次·
- 开发,从需求出发 · 之五 麦克斯韦妖
- [每天一个知识点]7-冯·诺依曼体系结构
- 写一个sql中视图和存储过程的简单创建和调用方法!!!自我备忘用····
- javascript入门·脚本执行的时间的四种类型(赠送一个转换的小例题)
- 又是一个新的‘站点·
- 如何捕捉短线(一个局外人的思考与联想)待续···
- 怎么样从一个疯狂下载者成为一个学习者!!!值得反省下的问题·~~
- 微软二十五年(比尔·盖茨) --- 我们都在同一个游泳池里游泳
- ·Lucene索引文件格式(中文版,有chris翻译)