如何實現分佈式搜索(二)簡單實現倒排索引表與數據導入

在上一節中,我們使用了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));
        }

    }
}

最後的效果圖如下:

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章