java實(shí)現(xiàn)簡(jiǎn)單的搜索引擎
記得java老師曾經(jīng)說(shuō)過(guò)百度的一個(gè)面試題目,大概意思是“有1W條無(wú)序的記錄,如何從其中快速的查找到自己想要的記錄”。這個(gè)就相當(dāng)于一個(gè)簡(jiǎn)單的搜索引擎。最近在整理這一年的工作中,自己竟然已經(jīng)把這個(gè)實(shí)現(xiàn)了,今天對(duì)其進(jìn)一步的抽象,和大家分享下。
先寫(xiě)具體的實(shí)現(xiàn)代碼,具體的實(shí)現(xiàn)思路和邏輯寫(xiě)在代碼之后。
搜索時(shí)用于排序的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;
}
}
構(gòu)造的搜索數(shù)據(jù)結(jié)構(gòu)以及簡(jiǎn)單的搜索算法
/**
*@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 存儲(chǔ)搜素對(duì)象的詳細(xì)信息,其中key作為區(qū)分Object的唯一標(biāo)識(shí)
private HashMap<String, Object> details = new HashMap<String, Object>();
//對(duì)于參與搜索的關(guān)鍵詞,這里采用的稀疏數(shù)組存儲(chǔ),也可以采用HashMap來(lái)存儲(chǔ),定義格式如下
//private static HashMap<Integer, HashSet<String>> keySearch = new HashMap<Integer, HashSet<String>>();
//HashMap中額key值相當(dāng)于稀疏數(shù)組中的下標(biāo),value相當(dāng)于稀疏數(shù)組在該位置的值
private final static int maxLength = Character.MAX_VALUE;
@SuppressWarnings("unchecked")
private HashSet<String>[] keySearch = new HashSet[maxLength];
/**
*@Description: 實(shí)現(xiàn)單例模式,采用Initialization on Demand Holder加載
*@Version:1.1.0
*/
private static class lazyLoadSerachBase {
private static final SerachBase serachBase = new SerachBase();
}
/**
* 這里把構(gòu)造方法設(shè)置成私有為的是單例模式
*/
private SerachBase() {
}
/**
* @return
* @Description: 獲取單例
*/
public static SerachBase getSerachBase() {
return lazyLoadSerachBase.serachBase;
}
/**
* @param id
* @return
* @Description: 根據(jù)id獲取詳細(xì)
*/
public Object getObject(String id) {
return details.get(id);
}
/**
* @param ids
* @return
* @Description: 根據(jù)ids獲取詳細(xì),id之間用","隔開(kāi)
*/
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
* @Description: 根據(jù)搜索詞查找對(duì)應(yīng)的id,id之間用","分割
*/
public String getIds(String key) {
if (key == null || "".equals(key)) {
return null;
}
//查找
//idTimes存儲(chǔ)搜索詞每個(gè)字符在id中是否出現(xiàn)
HashMap<String, Integer> idTimes = new HashMap<String, Integer>();
//ids存儲(chǔ)出現(xiàn)搜索詞中的字符的id
HashSet<String> ids = new HashSet<String>();
//從搜索庫(kù)中去查找
for (int i = 0; i < key.length(); i++) {
int at = key.charAt(i);
//搜索詞庫(kù)中沒(méi)有對(duì)應(yīng)的字符,則進(jìn)行下一個(gè)字符的匹配
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);
}
}
}
//使用數(shù)組排序
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();
}
});
//構(gòu)建返回字符串
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
* @Description: 添加搜索記錄
*/
public void add(String id, String searchKey, Object obj) {
//參數(shù)有部分為空,不加載
if (id == null || searchKey == null || obj == null) {
return;
}
//保存對(duì)象
details.put(id, obj);
//保存搜索詞
addSearchKey(id, searchKey);
}
/**
* @param id
* @param searchKey
* @Description: 將搜索詞加入到搜索域中
*/
private void addSearchKey(String id, String searchKey) {
//參數(shù)有部分為空,不加載
//這里是私有方法,可以不做如下判斷,但為了設(shè)計(jì)規(guī)范,還是加上
if (id == null || searchKey == null) {
return;
}
//下面采用的是字符分詞,這里也可以使用現(xiàn)在成熟的其他分詞器
for (int i = 0; i < searchKey.length(); i++) {
//at值相當(dāng)于是數(shù)組的下標(biāo),id組成的HashSet相當(dāng)于數(shù)組的值
int at = searchKey.charAt(i);
if (keySearch[at] == null) {
HashSet<String> value = new HashSet<String>();
keySearch[at] = value;
}
keySearch[at].add(id);
}
}
}
測(cè)試用例:
/**
*@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", "你是誰(shuí)?", "你是誰(shuí)?");
serachBase.add("5", "高數(shù)這門(mén)學(xué)科很難", "高數(shù)確實(shí)很難。");
serachBase.add("6", "測(cè)試", "上面的只是測(cè)試");
String ids = serachBase.getIds("你的高數(shù)");
System.out.println(ids);
List<Object> objs = serachBase.getObjects(ids);
if (objs != null) {
for (Object obj : objs) {
System.out.println((String) obj);
}
}
}
}
測(cè)試輸出結(jié)果如下:
5,3,2,1,4, 高數(shù)確實(shí)很難。 今天的天氣挺好的。 你好!我是張三。 你好! 你是誰(shuí)?
這樣一個(gè)簡(jiǎn)單的搜索引擎也就算是完成了。
問(wèn)題一:這里面的分詞采用的是字符分詞,對(duì)漢語(yǔ)的處理還是挺不錯(cuò)的,但是對(duì)英文的處理就很弱。
改進(jìn)方法:采用現(xiàn)在成熟的分詞方法,比如IKAnalyzer、StandardAnalyzer等,這樣修改,keySearch的數(shù)據(jù)結(jié)構(gòu)就需要做下修改,可以修改為 private HashMap<String, String>[] keySearch = new HashMap[maxLength]; 其中key存儲(chǔ)分的詞元,value存儲(chǔ)唯一標(biāo)識(shí)id。
問(wèn)題二:本文實(shí)現(xiàn)的搜索引擎對(duì)詞元并沒(méi)有像lucene設(shè)置權(quán)重,只是簡(jiǎn)單的判斷詞元是否在對(duì)象中出現(xiàn)。
改進(jìn)方法:暫無(wú)。添加權(quán)重處理,使數(shù)據(jù)結(jié)構(gòu)更加復(fù)雜,所以暫時(shí)沒(méi)有對(duì)其做處理,在今后的文章中會(huì)實(shí)現(xiàn)權(quán)重的處理。
下面就簡(jiǎn)單的介紹一下搜索引擎的實(shí)現(xiàn)思路。
在SerachBase類(lèi)中設(shè)置details和keySearch兩個(gè)屬性,details用于存儲(chǔ)Object的詳細(xì)信息,keySearch用于對(duì)搜索域做索引。details數(shù)據(jù)格式為HashMap,keySearch的數(shù)據(jù)格式為稀疏數(shù)組(也可以為HashMap,HashMap中額key值相當(dāng)于稀疏數(shù)組中的下標(biāo),value相當(dāng)于稀疏數(shù)組在該位置的值)。
對(duì)于details我就不做太多的介紹。
keySearch中數(shù)組下標(biāo)(如用HashMap就是key)的計(jì)算方法是獲取詞元的第一個(gè)字符int值(因?yàn)楸疚牡姆衷~采用的是字符分詞,所以一個(gè)字符就是一個(gè)詞元),該int值就是數(shù)組的下標(biāo),相應(yīng)的數(shù)組值就是Object的唯一標(biāo)識(shí)。這樣keySearch的數(shù)據(jù)結(jié)構(gòu)就如下圖

因此想添加新紀(jì)錄的時(shí)候只需要調(diào)用add方法即可。
對(duì)于搜索的實(shí)現(xiàn)邏輯和上面的keySearch類(lèi)似。對(duì)于id的搜索直接使用HashMap的get方法即可。對(duì)于搜索詞的一個(gè)搜索,整體的過(guò)程也是采用先分詞、其次查詢(xún)、最后排序。當(dāng)然這里面的分詞要和創(chuàng)建采用的分詞要一致(即創(chuàng)建的時(shí)候采用字符分詞,查找的時(shí)候也采用字符分詞)。
在getIds方法中,HashMap<String, Integer> idTimes = new HashMap<String, Integer>();idTimes 變量用來(lái)存儲(chǔ)搜索詞中的詞元有多少個(gè)在keySearch中出現(xiàn),key值為唯一標(biāo)識(shí)id,value為出現(xiàn)的詞元個(gè)數(shù)。HashSet<String> ids = new HashSet<String>(); ids變量用來(lái)存儲(chǔ)出現(xiàn)的詞元的ids。這樣搜索的復(fù)雜度就是搜索詞的詞元個(gè)數(shù)n。獲得包含詞元的ids,構(gòu)造SortBean數(shù)組,對(duì)其排序,排序規(guī)則是出現(xiàn)詞元個(gè)數(shù)的降序排列。最后返回ids字符串,每個(gè)id用","分割。如要獲取詳細(xì)信息
再使用getObjects方法即可。
上述的只是一個(gè)簡(jiǎn)單的搜索引擎,并沒(méi)有設(shè)計(jì)太多的計(jì)算方法,希望對(duì)大家的學(xué)習(xí)有所啟發(fā)。
- Java中EnumMap代替序數(shù)索引代碼詳解
- Java應(yīng)用開(kāi)源框架實(shí)現(xiàn)簡(jiǎn)易web搜索引擎
- Java使用分治算法實(shí)現(xiàn)排序數(shù)索引功能示例【二分搜索】
- mongodb索引知識(shí)_動(dòng)力節(jié)點(diǎn)Java學(xué)院整理
- Java使用強(qiáng)大的Elastisearch搜索引擎實(shí)例代碼
- JAVA實(shí)現(xiàn)空間索引編碼——GeoHash的示例
- java編程實(shí)現(xiàn)根據(jù)EXCEL列名求其索引的方法
- java多線程處理執(zhí)行solr創(chuàng)建索引示例
- Java數(shù)組索引異常產(chǎn)生及解決方案
相關(guān)文章
Java中main函數(shù)的String[]?args用法舉例詳解
這篇文章主要給大家介紹了關(guān)于Java中main函數(shù)的String[]?args用法的相關(guān)資料,JAVA類(lèi)中main函數(shù)的參數(shù)String[]?args指的是運(yùn)行時(shí)給main函數(shù)傳遞的參數(shù),文中通過(guò)圖文以及代碼介紹的非常詳細(xì),需要的朋友可以參考下2023-12-12
Springboot注解之@EnableAutoConfiguration詳解
這篇文章主要介紹了Springboot注解之@EnableAutoConfiguration詳解,@EnableAutoConfiguration是一個(gè)加載Starter目錄包之外的需要Spring自動(dòng)生成bean對(duì)象,本文對(duì)其進(jìn)行總結(jié),需要的朋友可以參考下2023-08-08
springbooot整合dynamic?datasource數(shù)據(jù)庫(kù)密碼加密方式
這篇文章主要介紹了springbooot整合dynamic?datasource?數(shù)據(jù)庫(kù)密碼加密方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2024-01-01
解決feignclient調(diào)用服務(wù),傳遞的中文數(shù)據(jù)成???問(wèn)題
這篇文章主要介紹了解決feignclient調(diào)用服務(wù),傳遞的中文數(shù)據(jù)成???問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2024-01-01
java 運(yùn)行報(bào)錯(cuò)has been compiled by a more recent version of the J
java 運(yùn)行報(bào)錯(cuò)has been compiled by a more recent version of the Java Runtime (class file version 54.0)2021-04-04
Java中生產(chǎn)者消費(fèi)者問(wèn)題總結(jié)
這篇文章主要介紹了Java中生產(chǎn)者消費(fèi)者問(wèn)題總結(jié),文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2020-07-07
MyBatis-Plus動(dòng)態(tài)返回實(shí)體類(lèi)示例詳解
這篇文章主要為大家介紹了MyBatis-Plus動(dòng)態(tài)返回實(shí)體類(lèi)示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-07-07
java實(shí)現(xiàn)隨機(jī)數(shù)生成器
這篇文章主要為大家詳細(xì)介紹了java實(shí)現(xiàn)隨機(jī)數(shù)生成器,隨機(jī)數(shù)生成小程序,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2018-12-12

