用java实现简单的搜索引擎

转载请注明出处:http://blog.csdn.net/xiaojimanman/article/details/37956749

在上学的时候,java老师曾经说过百度的一个面试题目,大概意思是“有1W条无序的记录,如何从其中快速的查找到自己想要的记录”。这个就相当于一个简单的搜索引擎。最近在整理这一年的工作中,自己竟然已经把这个实现了,今天对其进一步的抽象,和大家分享下。

先写具体的实现代码,具体的实现思路和逻辑写在代码之后。

搜索时用于排序的Bean

 /**
 *@Description:
 */
package cn.lulei.search.engine.model;  

public class SortBean {
	private String id;
	private int times;

	public String getId() {
		return id;
	}
	public void setId(String id) {
		this.id = id;
	}
	public int getTimes() {
		return times;
	}
	public void setTimes(int times) {
		this.times = times;
	}
}

构造的搜索数据结构以及简单的搜索算法

 /**
 *@Description:
 */
package cn.lulei.search.engine;  

import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;

import cn.lulei.search.engine.model.SortBean;

public class SerachBase {
	//details 存储搜素对象的详细信息,其中key作为区分Object的唯一标识
	private HashMap<String, Object> details = new HashMap<String, Object>();
	//对于参与搜索的关键词,这里采用的稀疏数组存储,也可以采用HashMap来存储,定义格式如下
	//private static HashMap<Integer, HashSet<String>> keySearch = new HashMap<Integer, HashSet<String>>();
	//HashMap中额key值相当于稀疏数组中的下标,value相当于稀疏数组在该位置的值
	private final static int maxLength = Character.MAX_VALUE;
	@SuppressWarnings("unchecked")
	private HashSet<String>[] keySearch = new HashSet[maxLength];

	/**
	 *@Description: 实现单例模式,采用Initialization on Demand Holder加载
	 *@Author:lulei
	 *@Date:2014-7-19
	 *@Version:1.1.0
	 */
	private static class lazyLoadSerachBase {
		private static final SerachBase serachBase = new SerachBase();
	}

	/**
	 * 这里把构造方法设置成私有为的是单例模式
	 */
	private  SerachBase() {

	}

	/**
	 * @return
	 * @Date:2014-7-19
	 * @Author:lulei
	 * @Description: 获取单例
	 */
	public static SerachBase getSerachBase() {
		return lazyLoadSerachBase.serachBase;
	}

	/**
	 * @param id
	 * @return
	 * @Date:2014-7-19
	 * @Author:lulei
	 * @Description: 根据id获取详细
	 */
	public Object getObject(String id) {
		return details.get(id);
	}

	/**
	 * @param ids
	 * @return
	 * @Date:2014-7-19
	 * @Author:lulei
	 * @Description: 根据ids获取详细,id之间用","隔开
	 */
	public List<Object> getObjects(String ids) {
		if (ids == null || "".equals(ids)) {
			return null;
		}
		List<Object> objs = new ArrayList<Object>();
		String[] idArray = ids.split(",");
		for (String id : idArray) {
			objs.add(getObject(id));
		}
		return objs;
	}

	/**
	 * @param key
	 * @return
	 * @Date:2014-7-19
	 * @Author:lulei
	 * @Description: 根据搜索词查找对应的id,id之间用","分割
	 */
	public String getIds(String key) {
		if (key == null || "".equals(key)) {
			return null;
		}
		//查找
		//idTimes存储搜索词每个字符在id中是否出现
		HashMap<String, Integer> idTimes = new HashMap<String, Integer>();
		//ids存储出现搜索词中的字符的id
		HashSet<String> ids = new HashSet<String>();

		//从搜索库中去查找
		for (int i = 0; i < key.length(); i++) {
			int at = key.charAt(i);
			//搜索词库中没有对应的字符,则进行下一个字符的匹配
			if (keySearch[at] == null) {
				continue;
			}
			for (Object obj : keySearch[at].toArray()) {
				String id = (String) obj;
				int times = 1;
				if (ids.contains(id)) {
					times += idTimes.get(id);
					idTimes.put(id, times);
				} else {
					ids.add(id);
					idTimes.put(id, times);
				}
			}
		}

		//使用数组排序
		List<SortBean> sortBeans = new ArrayList<SortBean>();
		for (String id : ids) {
			SortBean sortBean = new SortBean();
			sortBeans.add(sortBean);
			sortBean.setId(id);
			sortBean.setTimes(idTimes.get(id));
		}
		Collections.sort(sortBeans, new Comparator<SortBean>(){
			public int compare(SortBean o1, SortBean o2){
				return o2.getTimes() - o1.getTimes();
			}
		});

		//构建返回字符串
		StringBuffer sb = new StringBuffer();
		for (SortBean sortBean : sortBeans) {
			sb.append(sortBean.getId());
			sb.append(",");
		}

		//释放资源
		idTimes.clear();
		idTimes = null;
		ids.clear();
		ids = null;
		sortBeans.clear();
		sortBeans = null;

		//返回
		return sb.toString();
	}

	/**
	 * @param id
	 * @param searchKey
	 * @param obj
	 * @Date:2014-7-19
	 * @Author:lulei
	 * @Description: 添加搜索记录
	 */
	public void add(String id, String searchKey, Object obj) {
		//参数有部分为空,不加载
		if (id == null || searchKey == null || obj == null) {
			return;
		}
		//保存对象
		details.put(id, obj);
		//保存搜索词
		addSearchKey(id, searchKey);
	}

	/**
	 * @param id
	 * @param searchKey
	 * @Date:2014-7-19
	 * @Author:lulei
	 * @Description: 将搜索词加入到搜索域中
	 */
	private void addSearchKey(String id, String searchKey) {
		//参数有部分为空,不加载
		//这里是私有方法,可以不做如下判断,但为了设计规范,还是加上
		if (id == null || searchKey == null) {
			return;
		}
		//下面采用的是字符分词,这里也可以使用现在成熟的其他分词器
		for (int i = 0; i < searchKey.length(); i++) {
			//at值相当于是数组的下标,id组成的HashSet相当于数组的值
			int at = searchKey.charAt(i);
			if (keySearch[at] == null) {
				HashSet<String> value = new HashSet<String>();
				keySearch[at] = value;
			}
			keySearch[at].add(id);
		}
	}

}

测试用例:

 /**
 *@Description:
 */
package cn.lulei.search.engine.test;  

import java.util.List;

import cn.lulei.search.engine.SerachBase;

public class Test {
	public static void main(String[] args) {
		// TODO Auto-generated method stub
		SerachBase serachBase = SerachBase.getSerachBase();
		serachBase.add("1", "你好!", "你好!");
		serachBase.add("2", "你好!我是张三。", "你好!我是张三。");
		serachBase.add("3", "今天的天气挺好的。", "今天的天气挺好的。");
		serachBase.add("4", "你是谁?", "你是谁?");
		serachBase.add("5", "高数这门学科很难", "高数确实很难。");
		serachBase.add("6", "测试", "上面的只是测试");
		String ids = serachBase.getIds("你的高数");
		System.out.println(ids);
		List<Object> objs = serachBase.getObjects(ids);
		if (objs != null) {
			for (Object obj : objs) {
				System.out.println((String) obj);
			}
		}
	}

}

测试输出结果如下:

5,3,2,1,4,

高数确实很难。

今天的天气挺好的。

你好!我是张三。

你好!

你是谁?

这样一个简单的搜索引擎也就算是完成了。

问题一:这里面的分词采用的是字符分词,对汉语的处理还是挺不错的,但是对英文的处理就很弱。

改进方法:采用现在成熟的分词方法,比如IKAnalyzer、StandardAnalyzer等,这样修改,keySearch的数据结构就需要做下修改,可以修改为 private HashMap<String, String>[] keySearch = new HashMap[maxLength]; 其中key存储分的词元,value存储唯一标识id。

问题二:本文实现的搜索引擎对词元并没有像lucene设置权重,只是简单的判断词元是否在对象中出现。

改进方法:暂无。添加权重处理,使数据结构更加复杂,所以暂时没有对其做处理,在今后的文章中会实现权重的处理。

下面就简单的介绍一下这篇博客中的搜索引擎的实现思路。(启发于博客屏蔽词功能实现:http://blog.csdn.net/xiaojimanman/article/details/16852791)

在SerachBase类中设置details和keySearch两个属性,details用于存储Object的详细信息,keySearch用于对搜索域做索引。details数据格式为HashMap,keySearch的数据格式为稀疏数组(也可以为HashMap,HashMap中额key值相当于稀疏数组中的下标,value相当于稀疏数组在该位置的值)。

对于details我就不做太多的介绍。

keySearch中数组下标(如用HashMap就是key)的计算方法是获取词元的第一个字符int值(因为本文的分词采用的是字符分词,所以一个字符就是一个词元),该int值就是数组的下标,相应的数组值就是Object的唯一标识。这样keySearch的数据结构就如下图

因此想添加新纪录的时候只需要调用add方法即可。

对于搜索的实现逻辑和上面的keySearch类似。对于id的搜索直接使用HashMap的get方法即可。对于搜索词的一个搜索,整体的过程也是采用先分词、其次查询、最后排序。当然这里面的分词要和创建采用的分词要一致(即创建的时候采用字符分词,查找的时候也采用字符分词)。

在getIds方法中,HashMap<String, Integer> idTimes = new HashMap<String, Integer>();idTimes 变量用来存储搜索词中的词元有多少个在keySearch中出现,key值为唯一标识id,value为出现的词元个数。HashSet<String> ids = new HashSet<String>(); ids变量用来存储出现的词元的ids。这样搜索的复杂度就是搜索词的词元个数n。获得包含词元的ids,构造SortBean数组,对其排序,排序规则是出现词元个数的降序排列。最后返回ids字符串,每个id用","分割。如要获取详细信息

再使用getObjects方法即可。

上述的只是一个简单的搜索引擎,并没有设计太多的计算方法,欢迎大家批评。

时间: 2024-10-11 03:32:54

用java实现简单的搜索引擎的相关文章

使用java搭建简单的ligerui环境

最近因为工作需要,学习了ligerui框架.但是,因为在公司,我们只是作为最低层的码农,所以环境都已经搭建好了,我们这些码农平时都是直接拿到工程,然后在别人的框架上不断的ctrl + c.ctrl + v,然后修修补补.所以为了摆脱这种困境,决定自己使用简单的servlet搭建一个ligerui,然后测试下ligerui这玩意到底是怎么跑起来的. 1.下载ligerui相关组件.这个很简单,直接去www.ligerui.com即可找到. 2.使用Eclipse创建一个web工程,然后搭建下面这个

Java数据类型简单总结

Java数据类型简单总结 一:Java数据类型总的分为两大类:基本数据类型和引用数据类型. 基本数据类型:byte.short.int.long.float.double.char.boolean八种. 引用数据类型: 1:除了基本数据类型之外都是引用数据类型, 2:API类:String.File 3:自定义类:Test01...... 4:数组:int [ ].String [ ] 二:基本数据类型 1:分类 数据类型 位(bit) 取值范围 数据类型 位 取值范围 byte 8 -128~

Java对象简单实用(计算器案例)

Java对象简单实用(计算器案例) 对 Java中的对象与属性,方法的使用,简单写了个案例 1 import java.util.Scanner; 2 class Calculste 3 { 4 int a; //定义两个整数 5 int b; 6 String option; //定义接收操作符的字符串 7 public void count(){ 8 9 //对操作符进行判断 10 switch(option){ 11 case "+": 12 System.out.println

[SE0]简单的搜索引擎原理

1.简单了解搜索引擎收录的原理  包括baidu. google .yahoo 在内的各大搜索引擎在内基本上搜录网站的原理大致相同(除了国内某些网站 网1新 l 等采取人工登记的办法),搜索引擎都是采用自己的算法在网络上遍历搜索,一般靠有效的连接和网页的跳转访问地址,同时也登记网站的<title>和<meta>,并对一部分关键字进行屏蔽.遍历的结果也要进行筛选,重复的网址和不可靠的<title>甚至网页内容会被删除.并定时更新搜索引擎的数据. 百度: http://ww

Java实现简单版SVM

最近的图像分类工作要用到latent svm,为了更加深入了解svm,自己动手实现一个简单版的. 之所以说是简单版,因为没有用到拉格朗日,对偶,核函数等等.而是用最简单的梯度下降法求解.其中的数学原理我参考了http://blog.csdn.net/lifeitengup/article/details/10951655,文中是用matlab实现的svm. 源代码和数据集下载:https://github.com/linger2012/simpleSvm 其中数据集来自于libsvm,我找了其中

java 的简单介绍

一.Java 特点     1.简单: 和C++相比较     2.面向对象     3.跨平台    由于它是先编译后解释 + JVM(屏蔽底层操作系统的差异) 二.程序的运行一般有两种:     1.编译  源文件 ------> 编译器  机器码文件       快  无法跨平台     2.解释  源文件 ------> 解释器  逐行翻译并运行   慢  可以跨平台 三.Java运行机制: 先编译,后解释     .java 源文件  -------> 编译器 .class 字

去掉有序数组中重复数字 原地 leetcode java (最简单的方法)

1.利用荷兰国旗的思路,每次记住最后一个位置,遇到一个不重复的数,放在它后面,代码很简单. Given a sorted array, remove the duplicates in place such that each element appear only once and return the new length. Do not allocate extra space for another array, you must do this in place with consta

java htmlparser 简单使用入门

请尊重本人的工作成果,转载请留言,并说明转载地址,谢谢.地址如下: http://blog.csdn.net/fukainankai/article/details/27710883 前几节中,我们利用GDI+在窗口中绘制了各种各样的图形.图像,这一节,我们将会将这些图像保存成简单图像.所谓简单图像,指的是bmp/jpg/png等图像或者单帧的gif图像.保存成多帧的gif图像稍微复杂一点,本节中暂时不做说明.保存成动态的tiff文件也比较简单,但这里也不做说明,下次有机会和gif一起介绍. 另

HDU 1316 How Many Fibs?(java,简单题,大数)

题目 /** * compareTo:根据该数值是小于.等于.或大于 val 返回 -1.0 或 1: public int compareTo(BigInteger val) 将此 BigInteger 与指定的 BigInteger 进行比较. 对于针对六个布尔比较运算符 (<, ==, >, >=, !=, <=)中的每一个运算符的各个方法,优先提供此方法. 执行这些比较的建议语句是:(x.compareTo(y) <op> 0), 其中 <op> 是