在上一節中,我們使用了HashMap來模擬trie字典樹,通過使用字典樹,我們就能夠將一段字符串中的關鍵詞給提取出來。
那麼現在我們要實現兩件事情即可:
一、數據導入
問題所在:
所謂巧婦難爲無米之炊,雖然我不是巧婦,也不會做飯,但我會喫啊~
迴歸正題,既然是做搜索,那你至少得有數據,有人會說:直接用數據庫不就好了?
顯然這樣是不行得,數據庫封裝得太好了,因此你根本無法修改它的內部結構,而它本身的內部結構也不適合做我們想要的全站搜索。
這個時候我們就需要自己搭建一個數據庫來解決問題了。
解決辦法:
解決辦法其實比較簡單,我們直接通過jdbc的方式,把所有的跟搜索相關的表數據全部導入到內存的一個數組上就好了。
測試表sql語句如下:
create table goods(
id int auto_increment primary key,
name VARCHAR(40) not null,
price int not null
)
INSERT INTO goods values(null,"高露潔牙膏",720);
INSERT INTO goods values(null,'黑人牙膏',720);
INSERT INTO goods values(null,'黑妹牙膏',720);
INSERT INTO goods values(null,'千與千尋電影',720);
INSERT INTO goods values(null,'螺旋炮彈',720);
INSERT INTO goods values(null,'韓款男士風衣',720);
INSERT INTO goods values(null,'李白樺紅筆',720);
INSERT INTO goods values(null,'修身款白色襯衣',720);
INSERT INTO goods values(null,'修仙辟穀丹',720);
INSERT INTO goods values(null,'999感冒靈顆粒',720);
接下來就是編寫程序,將sql語句導入到內存的一個數組上。
這個很基礎,沒啥好說的,不過爲了省事我並沒有封裝的很徹底,所以如果想通用的話還需要自己動動腦筋哦~
public class ElasticSearchTest {
private ArrayList<Map<String,String>> goods = new ArrayList<Map<String,String>>();
//初始化數組的方法,從數據庫讀取數據到數組
public void init(){
//有一說一,jdbc好惡心,我純憑映像敲了五六分鐘纔出來
try {
Class.forName("com.mysql.jdbc.Driver");
} catch (ClassNotFoundException e) {
System.out.println("驅動jar包不存在");
}
Connection connection = null;
try {
connection = DriverManager.getConnection("jdbc:mysql://ip:端口/數據庫名?useUnicode=true&characterEncoding=utf-8", "用戶名", "密碼");
Statement statement = connection.createStatement();
statement.execute("select * from goods");
ResultSet resultSet = statement.getResultSet();
ResultSetMetaData metaData = resultSet.getMetaData();
while(resultSet.next()){
HashMap<String, String> map = new HashMap<>();
map.put("id",resultSet.getString(1));
map.put("name",resultSet.getString(2));
map.put("price",resultSet.getString(3));
goods.add(map);
}
resultSet.close();
statement.close();
connection.close();
System.out.println(goods);
} catch (SQLException e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
ElasticSearchTest elasticSearchTest = new ElasticSearchTest();
elasticSearchTest.init();
}
}
二、建立倒排索引表
什麼叫做倒排索引表,什麼叫做索引表呢?
索引表:就像我們的數據庫索引。它是通過內容去找數據,就比如我給id建立索引,最後我就可以根據id來快速找到我需要的數據所在的位置。
倒排索引表:根據數據去找內容,其實我覺得這兩個索引表沒啥特別大區別,不過是前人爲了裝逼而給後人留下的坑罷了。
畫個圖表示一下:
假設我們現在用數組存放的元素是這樣子的:
接下來我們用分詞器,對name字段進行分詞,關鍵詞如下:
[牙膏,風衣,紅筆];
那麼我們將關鍵詞對應的數組的索引關聯起來,形成我們所謂的倒排索引表:
接下來,我們搜索牙膏的時候,就可以直接把牙膏對應的數組的索引拿出來,也就是goods[0],goods[1],goods[2]對應的數據,這樣我們的搜索功能就算是完成了。
代碼實現:
根據倒排索引表的特性,我們也可以使用HashMap來完成,當然還有更好的數據結構,不過HashMap永遠都是最快的,當然了,也是內存開銷最大的。
ParticipleBuilder.java 分詞器實現類
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;
//構建一個分詞器
public class ParticipleBuilder {
//分詞器關鍵詞
private Set<String> keyWords = new HashSet<String>();
//分詞器關鍵詞最大長度和最小長度
private int keyWordMaxLength = 3;
private int keyWordMinLength = 2;
public ParticipleBuilder(String[] keyWordsArray) {
for (String keyWord : keyWordsArray) {
keyWords.add(keyWord);
}
}
public List<String> executor(String search) {
List containWords = new LinkedList<String>();
for (int index = 0; index < search.length(); index++) {
//根據分詞器關鍵詞最大長度和最小長度,獲取一個詞然後判斷他是不是敏感詞
for (int subLength = keyWordMinLength; subLength <= keyWordMaxLength; subLength++) {
//防止下標越界
if (index + subLength > search.length()) break;
//截取關鍵詞並且判斷是否是關鍵詞
String word = search.substring(index, index + subLength);
// System.out.println(word+"是關鍵詞嗎?");
if (keyWords.contains(word)) {
//返回true則是關鍵詞,添加,跳過該詞繼續向下掃描
// System.out.println("是");
containWords.add(word);
index += subLength - 1;
break;
} else {
// System.out.println("不是");
continue;
}
}
}
return containWords;
}
}
ElasticSearchTest.java 實現類
import java.sql.*;
import java.util.*;
public class ElasticSearchTest {
//關鍵詞
private final String[] keyWordsArray = {"牙膏", "風衣", "紅筆"};
//分詞器
private ParticipleBuilder participleBuilder = new ParticipleBuilder(keyWordsArray);
//數據存放
private ArrayList<Map<String, String>> goods = new ArrayList<Map<String, String>>();
//倒排索引表
private HashMap<String, List<Integer>> invertedIndexMap = new HashMap<>();
//初始化數組的方法,從數據庫讀取數據到數組
public void initData() {
//有一說一,jdbc好惡心
try {
Class.forName("com.mysql.jdbc.Driver");
} catch (ClassNotFoundException e) {
System.out.println("驅動jar包不存在");
}
Connection connection = null;
try {
connection = DriverManager.getConnection("jdbc:mysql://ip:port/dbname?useUnicode=true&characterEncoding=utf-8", "username", "password");
Statement statement = connection.createStatement();
statement.execute("select * from goods");
ResultSet resultSet = statement.getResultSet();
ResultSetMetaData metaData = resultSet.getMetaData();
while (resultSet.next()) {
HashMap<String, String> map = new HashMap<>();
map.put("id", resultSet.getString(1));
map.put("name", resultSet.getString(2));
map.put("price", resultSet.getString(3));
goods.add(map);
}
resultSet.close();
statement.close();
connection.close();
System.out.println(goods);
} catch (SQLException e) {
e.printStackTrace();
}
}
//根據數組內容構建倒排索引表
public void indexBuilder() {
//先把關鍵詞添加到索引表做爲key
for (String keyWord : keyWordsArray) {
invertedIndexMap.put(keyWord, new ArrayList<>());
}
//使用分詞器進行分詞,並且將對飲的數組索引添加到索引表
for (int i = 0; i < goods.size(); i++) {
String goodName = goods.get(i).get("name");
//分詞
List<String> executor = participleBuilder.executor(goodName);
//遍歷分詞結果,將數組索引i存放到倒排索引表
for (String key : executor) {
System.out.println(key);
invertedIndexMap.get(key).add(i);
}
}
//打印倒排索引表
System.out.println(invertedIndexMap);
}
//根據倒排索引表搜索內容
public ArrayList<Map<String, String>> search(String searchString) {
//先分詞
List<String> keyWords = participleBuilder.executor(searchString);
//保存分詞對應的索引,使用Set去重
Set<Integer> indexes = new HashSet<>();
for (String keyWord : keyWords) {
List<Integer> integers = invertedIndexMap.get(keyWord);
indexes.addAll(integers);
}
//最後根據索引返回結果就行了。
ArrayList<Map<String, String>> results = new ArrayList<>();
for (Integer index : indexes) {
results.add(goods.get(index));
}
return results;
}
public static void main(String[] args) {
System.out.println("簡易搜索器正在啓動,請稍後~");
ElasticSearchTest elasticSearchTest = new ElasticSearchTest();
elasticSearchTest.initData();
elasticSearchTest.indexBuilder();
boolean flag = true;
Scanner scanner = new Scanner(System.in);
while (flag) {
System.out.println("你想要什麼?");
String search = scanner.next();
System.out.println(elasticSearchTest.search(search));
}
}
}
最後的效果圖如下: