自定義查詢--關於倒排索引的研究

来源:https://www.cnblogs.com/machine-matrix/archive/2022/07/30/16534476.html
-Advertisement-
Play Games

最近學習了es的視頻,感覺這個產品對於查詢來說非常方便,但是如何應用到我們自己的 產品中來呢。因為我們的產品數據更新太快,其實不太適合用es做主力存儲。並且我們的業務還沒有到那種巨量級別,產品的伺服器容量也有限,所以我打算根據es的倒排索引的原理,自己寫一個查詢的組件。 我的理解是這樣的,有大量的文 ...


最近學習了es的視頻,感覺這個產品對於查詢來說非常方便,但是如何應用到我們自己的 產品中來呢。因為我們的產品數據更新太快,其實不太適合用es做主力存儲。並且我們的業務還沒有到那種巨量級別,產品的伺服器容量也有限,所以我打算根據es的倒排索引的原理,自己寫一個查詢的組件。

我的理解是這樣的,有大量的文字需要進行模糊查詢,在mysql中,如果使用like的話是非常合適的,目前我就是採用這種方式查詢的,因為數據量還未到千萬級別,速度也還行,不過馬上要突破了,所以要考慮優化的事情了。所以我的思路是這樣的:

1 首先將資料庫中的大段文字和標題都提取出來。

2 這些文字都對應了主鍵。

3 使用jcseg分詞將一段文字進行分詞,然後將分好的詞語主鍵保存到redis中去。

4 為了節省空間,只分重要的業務關鍵字,其他無關的分詞都不需要。

5 因為數據量巨大,在進行數據提取的時候,採用了線程池,優化了採集速度。

 

使用的代碼如下:

package com.liandyao.caop.caopdata.service.impl.ESearch;

import cn.hutool.core.util.StrUtil;
import com.liandyao.caop.caopdata.entity.CaiCaop;
import com.liandyao.caop.caopdata.mapper.CaiMapper;
import com.liandyao.caop.utils.ChineseSegment;
import com.liandyao.caop.utils.async.AsyncManager;
import com.liandyao.caop.utils.redis.RedisUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.List;
import java.util.TimerTask;
import java.util.concurrent.atomic.AtomicInteger;

/**
 * 倒排索引的研究
 * @author liandyao
 * @date 2022年6月24日
 */
@Service
public class CaiInvertedIndex {

    /**
     * 原子類型的數字
     */
    public static AtomicInteger atomic = new AtomicInteger();


    /**
     * 每頁查詢多少條
     */
    public static int SIZE = 1000 ;

    /**
     * KEY
     */
    public static String REDIS_KEY = "INVD_INDX:MYSQL_DATA:CAOPU";

    @Autowired
    CaipMapper caopMapper;

    @Autowired
    RedisUtil redisUtil ;

    /**
     * 同步數據到redis
     */
    @Transactional
    public void sysnCaopDataToRedis(int pages){
        System.out.println(pages);
        //每頁顯示1000條

        int startRows = (pages - 1) * SIZE ;
        List<CaiCaop> listCaop =caopMapper.selectListByPage(startRows,SIZE);

        System.out.println("查詢的條數:"+listCaop.size());
        listCaop.forEach(caop->{
            //加入到redis中
            redisUtil.leftPush(REDIS_KEY,caop);
            //最後一個執行的id,因為多線程的原因可能不是最後一個,這裡只是記錄一下
            redisUtil.set(REDIS_KEY+"LAST_ID",caop.getId());
        });
    }

    /**
     * 加入分詞信息
     */
    public void segCaopData(){
        long caopSize = redisUtil.lGetListSize(REDIS_KEY);
        System.out.println("正在處理,共有:"+caopSize+"條數據");
        int i = 0;
        while(i<caopSize){
            //運行一次增加1
            i++;
            AsyncManager.me().execute(new TimerTask() {
                @Override
                public void run() {
                    CaiCaop caop = (CaiCaop) redisUtil.rightPop(REDIS_KEY);
                    if(caop!=null){
                        String content = caop.getContent()+" "+caop.getAddress();
                        List<String> typeNames = StrUtil.split(caop.getTypeName(),",");

                        //先將種類作為倒序索引加入redis
                        typeNames.forEach(str->{
                            if(StrUtil.isNotBlank(str)){
                                redisUtil.zsetAdd(REDIS_KEY+":"+str,caop.getId(),caop.getUpdateDate().getTime());
                            }

                        });
                        //再進行分詞
                        List<String> list = ChineseSegment.segment(content);
                        list.forEach(segWord->{
                            redisUtil.zsetAdd(REDIS_KEY+":"+segWord,caop.getId(),caop.getUpdateDate().getTime());
                        });
                        System.out.println("處理成功:"+caop.getId());
                    }
                }
            });
        }



    }


    public static void main(String[] args) {


    }

}

 

中文分詞代碼

package com.liandyao.caop.utils;

import cn.hutool.core.util.ArrayUtil;
import lombok.extern.slf4j.Slf4j;
import org.junit.Test;
import org.lionsoul.jcseg.ISegment;
import org.lionsoul.jcseg.IWord;
import org.lionsoul.jcseg.dic.ADictionary;
import org.lionsoul.jcseg.dic.DictionaryFactory;
import org.lionsoul.jcseg.extractor.impl.TextRankKeyphraseExtractor;
import org.lionsoul.jcseg.extractor.impl.TextRankKeywordsExtractor;
import org.lionsoul.jcseg.segmenter.SegmenterConfig;

import java.io.IOException;
import java.io.StringReader;
import java.util.ArrayList;
import java.util.List;

/**
 * 中文分詞
 * @author liandyao
 * @date 2021/12/10 19:16
 */
@Slf4j
public  class  ChineseSegment {
    static  ISegment seg=null;

    /**
     * 初始化
     */
    public static synchronized void init(){
        seg=null;
        SegmenterConfig config = new SegmenterConfig(true);
        String lexicon[] = {"e://lexicon"};
        config.setLexiconPath(lexicon);
        ADictionary dic = DictionaryFactory.createSingletonDictionary(config);

        seg = ISegment.COMPLEX.factory.create(config, dic);

    }

    static{
        init();
    }

    /**
     * 取地方分詞結果
     * @param str
     * @return
     */
    public static synchronized List<String> segmentAddress(String str){

        List<String> list = new ArrayList<>();
        try {

            seg.reset(new StringReader(str));

            //System.out.println("====>"+str+"  ==> "+seg);
            //獲取分詞結果
            IWord word = null;
            while ( (word = seg.next()) != null ) {
                //1 普通詞語
                if(word.getType()==1){
                    //17表示數字
                    if(word.getType()==17 || word.getPartSpeech()==null) {
                        continue;
                    }
                    //處所詞
                    if(ArrayUtil.contains(word.getPartSpeech(),"ns")){
                        list.add(word.getValue());
                        log.info("分詞結果"+word.getValue());
                    }
                }
            }
        } catch (Exception e) {
            init();
            e.printStackTrace();
        }
        return list;
    }

    /**
     * 分析工種分詞結果
     * @param str
     * @return
     */
    public static synchronized List<String> segmentCaitype(String str){
        List<String> list = new ArrayList<>();
        try {
            seg.reset(new StringReader(str));
            //獲取分詞結果
            IWord word = null;
            while ( (word = seg.next()) != null ) {

                //1 普通詞語
                if(word.getType()==1){
                    //17表示數字
                    if(word.getType()==17 || word.getPartSpeech()==null) {
                        continue;
                    }
                    //工種分詞
                    if(ArrayUtil.contains(word.getPartSpeech(),"ncn")){
                        list.add(word.getValue());
                        log.info("分詞結果"+word.getValue());
                    }
                }

            }
        } catch (IOException e) {
            init();
            e.printStackTrace();
        }
        return list;
    }

    /**
     * 綜合分詞
     * @param str
     * @return
     */
    public static synchronized List<String> segment(String str){
        List<String> list = new ArrayList<>();
        try {
            seg.reset(new StringReader(str));
            //獲取分詞結果
            IWord word = null;
            while ( (word = seg.next()) != null ) {
                /**


                 名詞n、時間詞t、處所詞s、方位詞f、數詞m、量詞q、區別詞b、代詞r、動詞v、形容詞a、狀態詞z、副詞d、
                 介詞p、連詞c、助詞u、語氣詞y、嘆詞e、擬聲詞o、成語i、習慣用語l、簡稱j、前接成分h、後接成分k、語素g、非語素字x、標點符號w)外,
                 從語料庫應用的角度,增加了專有名詞(人名nr、地名ns、機構名稱nt、其他專有名詞nz)



                 */
                //1 普通詞語
                if(word.getType()==1){
                    //17表示數字
                    if(word.getType()==17 || word.getPartSpeech()==null) {
                        continue;
                    }
                    /*

                    //工種分詞
                    if(ArrayUtil.contains(word.getPartSpeech(),"ncn")){
                        list.add(word.getValue());
                        log.info("分詞結果"+word.getValue());
                    }
                    //處所詞
                    if(ArrayUtil.contains(word.getPartSpeech(),"ns")){
                        list.add(word.getValue());
                        log.info("分詞結果"+word.getValue());
                    }

                    */

                    if(word.getValue().length()>=2){
                        list.add(word.getValue());
                    }

                }

            }
        } catch (IOException e) {
            init();
            e.printStackTrace();
        }
        return list;
    }

    public static void main(String[] args) {
        //設置要分詞的內容
        String str = "今天真是陽光明媚,夕陽西下,白日依山盡!";

            int i = 0 ;

        List<String> keywords = segment(str);

        keywords.forEach(str1 -> {
            System.out.println(str1);
        });


    }

    @Test
    public void test1() throws IOException {
        String str = "四川成都市長期招工信息:招2個砌築工維修師傅,少數民族勿擾,1個力工,電話微信同步,活在成都";
     str
=""; //2, 構建TextRankKeywordsExtractor關鍵字提取器 TextRankKeywordsExtractor extractor = new TextRankKeywordsExtractor(seg); //extractor.setMaxIterateNum(100); //設置pagerank演算法最大迭代次數,非必須,使用預設即可 //extractor.setWindowSize(5); //設置textRank計算視窗大小,非必須,使用預設即可 // extractor.setKeywordsNum(10); //設置最大返回的關鍵詞個數,預設為10 List<String> keywords = extractor.getKeywords(new StringReader(str)); keywords.forEach(str2 -> System.out.println(str2)); } @Test public void test2() throws IOException { //String str = " 四川成都市長期招工信息:招2個砌築工維修師傅,少數民族勿擾,1個力工,電話微信同步, "; //2, 構建TextRankKeyphraseExtractor關鍵短語提取器 TextRankKeyphraseExtractor extractor = new TextRankKeyphraseExtractor(seg); extractor.setMaxIterateNum(100); //設置pagerank演算法最大迭代詞庫,非必須,使用預設即可 extractor.setWindowSize(5); //設置textRank視窗大小,非必須,使用預設即可 extractor.setKeywordsNum(15); //設置最大返回的關鍵詞個數,預設為10 extractor.setMaxWordsNum(4); //設置最大短語詞長,預設為5 //3, 從一個輸入reader輸入流中獲取短語 String str = "支持向量機廣泛應用於文本挖掘,例如,基於支持向量機的文本自動分類技術研究一文中很詳細的介紹支持向量機的演算法細節,文本自動分類是文本挖掘技術中的一種!"; List<String> keyphrases = extractor.getKeyphrase(new StringReader(str)); keyphrases.forEach(str2 -> System.out.println(str2)); } }

 

結語

1 本文提供了倒排索引的思路,比較淺顯,還可以深入研究

2 使用本組件將關鍵字放入redis之後,頁面上傳入的關鍵字就可以在redis中對應key,這樣的速度將非常快,從key中可以找到主鍵,再用主鍵到mysql中查詢,大大提高了查詢速度。

3 需要考慮的問題,如何做到更新就加入關鍵字到redis中去。是採用實時變更就加入,還是定時一分鐘,或者一小時加入,需要結合業務來處理。

 


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 1、引例 【例1】 分析該程式,有哪些問題 int main() { swap(int p, int q); int a = 10, b = 20; printf("(1)a = %d, b = %d\n", a, b); swap(&a, &b); printf("(2)a = %d, b = % ...
  • 精華筆記: 1. 運算符: - 算術:+、-、*、/、%、++、-- - 關係:>、<、>=、<=、==、!= - 邏輯:&&、||、! - 賦值:=、+=、-=、*=、/=、%= - 字元串連接:+ - 條件/三目:boolean?數1:數2 2. 分支結構:基於條件執行的語句 - if結構:1條 ...
  • 精華筆記: 1. 變數:存數的 - 聲明: 在銀行開了個帳戶 - 初始化: 給帳戶存錢 - 使用: 使用的是帳戶裡面的錢 - 對變數的使用就是對它所存的那個數的使用 - 變數在使用之前必須聲明並初始化 - 命名: - 只能包含字母、數字、_和$符,並且不能以數字開頭 - 嚴格區分大小寫 - 不能使用 ...
  • 一、python簡介:是什麼,特點是什麼,有什麼用 1、python是什麼? python是一門結合解釋型,編譯性,互動性和麵向對象的腳本語言,具有很強的可讀性,相比其他例如Java語言,C語言更加容易入門。 2、python有哪些特點? 易於學習:python有相對較少的關鍵字,結構簡單,和一個明 ...
  • JetBrAIns GoLand 是Mac os系統上由JetBrAIns推出的一個GO語言集成開發工具環境,基於IntelliJ平臺,支持JetBrAIns插件體系,擁有針對GO語言的代碼助手、代碼編輯器、代碼調試等工具,支持前端和後端開發,並且支持IntelliJ插件,可以大大提高Go語言開發者 ...
  • 配置不同生產環境 本文適用於開發環境下需要打包項目至生產環境,避免開發環境的配置文件泄露。 設置maven 作用:1. 手動調節運行時的不同環境 2. 打包時可以不會有其它環境的文件 註:每次換環境前(打包前)記得手動clean清楚,因為idea不會在換環境後自動清除另一個環境的文件 在pom文件中 ...
  • 介紹 本篇文章主要針對於電腦二級考試的崽崽,當然想瞭解Python和學習Python的崽崽也是可以看本篇文章的;畢竟,手機和電腦都可以運行Python;本篇我文章雖然是筆記,但是也純靠手打,希望關註和點贊一下,期待我的其他隨筆和文章;文章作者由博客園狐小妖用戶撰寫,非來自於博客園且不帶轉載註明,均 ...
  • 前言:最近幾個月很忙,都沒有時間寫文章了,今天周末剛好忙完下班相對早點(20:00下班)就在家把之前想總結的知識點寫出來,於是就有了這篇文章。雖無很高深的技術,但小技巧有大用處。 有時我們經常需要將實現了某個基類或某個介面的所有Bean進行分類管理,在需要用到的時候按需獲取實現了某個基類或某個介面的 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...