现在的位置: 首页 > 综合 > 正文

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

2014年11月22日 ⁄ 综合 ⁄ 共 4525字 ⁄ 字号 评论关闭

实关于全文检索的倒排序,逻辑是非常简单的,“空间换时间”的概念也不复杂。

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

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


}

抱歉!评论已关闭.