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

来源: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
  • Dapr Outbox 是1.12中的功能。 本文只介紹Dapr Outbox 執行流程,Dapr Outbox基本用法請閱讀官方文檔 。本文中appID=order-processor,topic=orders 本文前提知識:熟悉Dapr狀態管理、Dapr發佈訂閱和Outbox 模式。 Outbo ...
  • 引言 在前幾章我們深度講解了單元測試和集成測試的基礎知識,這一章我們來講解一下代碼覆蓋率,代碼覆蓋率是單元測試運行的度量值,覆蓋率通常以百分比表示,用於衡量代碼被測試覆蓋的程度,幫助開發人員評估測試用例的質量和代碼的健壯性。常見的覆蓋率包括語句覆蓋率(Line Coverage)、分支覆蓋率(Bra ...
  • 前言 本文介紹瞭如何使用S7.NET庫實現對西門子PLC DB塊數據的讀寫,記錄了使用電腦模擬,模擬PLC,自至完成測試的詳細流程,並重點介紹了在這個過程中的易錯點,供參考。 用到的軟體: 1.Windows環境下鏈路層網路訪問的行業標準工具(WinPcap_4_1_3.exe)下載鏈接:http ...
  • 從依賴倒置原則(Dependency Inversion Principle, DIP)到控制反轉(Inversion of Control, IoC)再到依賴註入(Dependency Injection, DI)的演進過程,我們可以理解為一種逐步抽象和解耦的設計思想。這種思想在C#等面向對象的編 ...
  • 關於Python中的私有屬性和私有方法 Python對於類的成員沒有嚴格的訪問控制限制,這與其他面相對對象語言有區別。關於私有屬性和私有方法,有如下要點: 1、通常我們約定,兩個下劃線開頭的屬性是私有的(private)。其他為公共的(public); 2、類內部可以訪問私有屬性(方法); 3、類外 ...
  • C++ 訪問說明符 訪問說明符是 C++ 中控制類成員(屬性和方法)可訪問性的關鍵字。它們用於封裝類數據並保護其免受意外修改或濫用。 三種訪問說明符: public:允許從類外部的任何地方訪問成員。 private:僅允許在類內部訪問成員。 protected:允許在類內部及其派生類中訪問成員。 示 ...
  • 寫這個隨筆說一下C++的static_cast和dynamic_cast用在子類與父類的指針轉換時的一些事宜。首先,【static_cast,dynamic_cast】【父類指針,子類指針】,兩兩一組,共有4種組合:用 static_cast 父類轉子類、用 static_cast 子類轉父類、使用 ...
  • /******************************************************************************************************** * * * 設計雙向鏈表的介面 * * * * Copyright (c) 2023-2 ...
  • 相信接觸過spring做開發的小伙伴們一定使用過@ComponentScan註解 @ComponentScan("com.wangm.lifecycle") public class AppConfig { } @ComponentScan指定basePackage,將包下的類按照一定規則註冊成Be ...
  • 操作系統 :CentOS 7.6_x64 opensips版本: 2.4.9 python版本:2.7.5 python作為腳本語言,使用起來很方便,查了下opensips的文檔,支持使用python腳本寫邏輯代碼。今天整理下CentOS7環境下opensips2.4.9的python模塊筆記及使用 ...