回覆列表
-
1 # 成都朗沃教育
-
2 # java攻城獅愛好者
但是如果後面做的更復雜,隨著使用者數的增多,資料量的增大,頻繁的搜尋會增大web應用或db的壓力,後面考慮採用快取,分頁。但後期想做的更智慧,我們可以使用lucene全文搜尋引擎,基於lucene的應用有solr,elasticsearch等。再到後面,我們會考慮到智慧分詞,這裡會涉及到nlp。再到後面我們可以根據使用者輸入的關鍵字推薦給使用者不同的商品或資料,這裡考慮使用ai+hadoop分析使用者喜歡的東西,然後推薦給使用者
先寫具體的實現程式碼,具體的實現思路和邏輯寫在程式碼之後。
搜尋時用於排序的Bean
構造的搜尋資料結構以及簡單的搜尋演算法
[java] view plain copy print? /** *@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); } } }測試用例:
[java] view plain copy print? /** *@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設定權重,只是簡單的判斷詞元是否在物件中出現。
改進方法:暫無。新增權重處理,使資料結構更加複雜,所以暫時沒有對其做處理,在今後的文章中會實現權重的處理。
在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方法即可。