Lucene還可以這樣玩乃正?SpringBoot集成Lucene實(shí)現(xiàn)自己的輕量級(jí)搜索引擎(附源碼)

前言

哈嘍住册,大家好,我是丸子瓮具。

搜索引擎想必大家都并不陌生荧飞,比如百度凡人,谷歌都是常見(jiàn)的搜索引擎。

在我們實(shí)際的項(xiàng)目開發(fā)中叹阔,也經(jīng)常遇到類似的業(yè)務(wù)需求挠轴,比如公司要開發(fā)一個(gè)知識(shí)庫(kù)項(xiàng)目,知識(shí)庫(kù)里有上百萬(wàn)條文章耳幢,要求我們能夠輸入關(guān)鍵字岸晦,查詢出包含有關(guān)鍵字的文章內(nèi)容,并且對(duì)關(guān)鍵字進(jìn)行高亮處理帅掘,顯示查詢后的最佳摘要委煤,這個(gè)時(shí)候傳統(tǒng)的數(shù)據(jù)庫(kù)LIKE查詢雖然能勉強(qiáng)滿足業(yè)務(wù)需求,但是查詢速度令人無(wú)法忍受修档,這個(gè)時(shí)候就需要借助搜索引擎來(lái)進(jìn)行處理碧绞。

在Java開發(fā)領(lǐng)域,Lucene可以算是開山鼻祖吱窝,現(xiàn)在常用的SolrElasticSearch底層都是基于Lucene讥邻,很多開發(fā)人員并沒(méi)有系統(tǒng)的學(xué)習(xí)過(guò)Lucene,都是直接上手SolrElasticSearch進(jìn)行開發(fā)院峡,但實(shí)際上掌握Lucene的常用api兴使,理解其底層原理還是比較重要的,這有利于我們對(duì)全文檢索領(lǐng)域有更加深入的理解照激,同時(shí)我們也可以根據(jù)自己的業(yè)務(wù)需求定制個(gè)性化的搜索引擎发魄,我所在的公司使用的就是基于Lucene自研的搜索引擎服務(wù),針對(duì)公司獨(dú)特的業(yè)務(wù)場(chǎng)景俩垃,使用起來(lái)特別方便励幼。

本篇文章將詳細(xì)講解如何使用SpringBoot集成Lucene實(shí)現(xiàn)自己的輕量級(jí)搜索引擎,相關(guān)源碼資料可以查看文末獲瓤诹苹粟!

Lucene為什么查的快

Lucene之所以查的快,原因在于它內(nèi)部使用了倒排索引算法跃闹,在這里簡(jiǎn)單的介紹一下原理:
普通查詢是根據(jù)文章找關(guān)鍵字嵌削,而倒排索引是根據(jù)關(guān)鍵字找文章!

比如“我今天很開心望艺,因?yàn)轳R上就要下班了”這句話苛秕,從中搜索“開心”,普通查詢要遍歷整句話找默,直到找到“開心”二字為止艇劫,效率低下。倒排索引則是對(duì)整句話使用分詞器進(jìn)行分詞處理啡莉,從而“開心”二字可以直接指向這句話港准,搜索的時(shí)候直接就可以根據(jù)“開心”搜到所屬的內(nèi)容旨剥,達(dá)到快速響應(yīng)的效果。

springBoot集成Lucene

下面我會(huì)以Demo的形式詳細(xì)講解springBoot如何集成Lucene實(shí)現(xiàn)增刪查改浅缸,以及顯示高亮和最佳摘要(demo全部資料和源碼在文末獲裙熘摹)。

一.建表

以Mysql為例衩椒,創(chuàng)建數(shù)據(jù)庫(kù)lucene_demo蚌父,建表article,作為數(shù)據(jù)源毛萌,之后對(duì)表內(nèi)容進(jìn)行增刪查改的時(shí)候同步到Lucene索引數(shù)據(jù)苟弛,建表語(yǔ)句如下:

CREATE TABLE `article` (
  `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主鍵id',
  `title` varchar(200) DEFAULT NULL COMMENT '標(biāo)題',
  `content` longtext COMMENT '內(nèi)容',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

二.創(chuàng)建SpringBoot項(xiàng)目

在這里我直接拿自己的代碼生成器生成,配置好基礎(chǔ)內(nèi)容點(diǎn)擊生成阁将,即可生成一個(gè)完整的前后臺(tái)項(xiàng)目框架膏秫,省去了搭建項(xiàng)目的繁瑣步驟,這樣我們可以在生成的代碼基礎(chǔ)上進(jìn)行開發(fā):

代碼生成器

生成的項(xiàng)目結(jié)構(gòu)和代碼如下:
生成的項(xiàng)目

三.引入Lucene相關(guān)依賴

pom.xml引入Lucene相關(guān)依賴:

<dependency>
            <groupId>org.apache.lucene</groupId>
            <artifactId>lucene-core</artifactId>
            <version>8.1.0</version>
        </dependency>
        <dependency>
            <groupId>org.apache.lucene</groupId>
            <artifactId>lucene-analyzers-common</artifactId>
            <version>8.1.0</version>
        </dependency>
        <dependency>
            <groupId>org.apache.lucene</groupId>
            <artifactId>lucene-queryparser</artifactId>
            <version>8.1.0</version>
        </dependency>
        <dependency>
            <groupId>org.apache.lucene</groupId>
            <artifactId>lucene-highlighter</artifactId>
            <version>8.1.0</version>
        </dependency>

四.引入IK分詞器依賴

目前市面上有不少中文分詞器做盅,但最受歡迎的還是IK分詞器缤削,Lucene自帶的分詞器對(duì)中文只能單字拆分,顯然不符合我們的需求吹榴,但I(xiàn)K分詞器解決了這個(gè)問(wèn)題亭敢,他可以把一段話分成多組不同的中文單詞,幫助建立搜索索引图筹。

公共maven倉(cāng)庫(kù)中沒(méi)有IK分詞器的依賴帅刀,需要我們install一下,文末資料中有IK分詞器的源碼远剩,可以導(dǎo)入idea直接install到自己的maven倉(cāng)庫(kù)扣溺,然后引入依賴到項(xiàng)目即可。


install

pom.xml引入Ik分詞器相關(guān)依賴(因?yàn)橹耙呀?jīng)引入了Lucene相關(guān)依賴民宿,所以引入Ik的時(shí)候去除一下娇妓,防止依賴沖突):

 <dependency>
            <groupId>org.wltea.ik-analyzer</groupId>
            <artifactId>ik-analyzer</artifactId>
            <version>8.1.0</version>
            <exclusions>
                <exclusion>
                    <groupId>org.apache.lucene</groupId>
                    <artifactId>lucene-analyzers-common</artifactId>
                </exclusion>
                <exclusion>
                    <groupId>org.apache.lucene</groupId>
                    <artifactId>lucene-queryparser</artifactId>
                </exclusion>
                <exclusion>
                    <groupId>org.apache.lucene</groupId>
                    <artifactId>lucene-core</artifactId>
                </exclusion>
                <exclusion>
                    <groupId>org.slf4j</groupId>
                    <artifactId>slf4j-api</artifactId>
                </exclusion>
            </exclusions>
        </dependency>

五.項(xiàng)目啟動(dòng)時(shí)加載IK分詞器

最好在我們啟動(dòng)項(xiàng)目的時(shí)候就把IK分詞器加載進(jìn)內(nèi)存當(dāng)中像鸡,這樣第一次查詢就不必再進(jìn)行加載活鹰,避免第一次查詢因?yàn)榧虞d分詞器造成卡頓,創(chuàng)建init包只估,建立BusinessInitializer類志群,如下:

初始化加載IK分詞器

代碼如下:

package lucenedemo.init;

import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.stereotype.Component;
import org.wltea.analyzer.cfg.DefaultConfig;
import org.wltea.analyzer.dic.Dictionary;

/**
 * 業(yè)務(wù)初始化器
 *
 * @author zrx
 */
@Component
public class BusinessInitializer implements ApplicationRunner {

    @Override
    public void run(ApplicationArguments args) {
        //加載ik分詞器配置 防止第一次查詢慢
        Dictionary.initial(DefaultConfig.getInstance());
    }
}

引入IK的配置文件IKAnalyzer.cfg.xml以及擴(kuò)展字典ext.dic和停止詞字典stopword.dic,可以添加和屏蔽某些詞語(yǔ)蛔钙,把配置文件放入resources下:

Ik配置文件

在這里我們添加兩個(gè)擴(kuò)展詞小螺旋丸小千鳥锌云,查詢的時(shí)候可以用來(lái)做測(cè)試,如果測(cè)試的時(shí)候可以被完整標(biāo)記高亮吁脱,說(shuō)明詞語(yǔ)被成功識(shí)別桑涎,因?yàn)镮K自帶的字典里彬向,沒(méi)有這兩個(gè)單詞,IK自帶的字典位于IK源碼的resources包下攻冷,感興趣的朋友可以通過(guò)源碼自行查看:
添加擴(kuò)展詞

添加完畢娃胆,我們啟動(dòng)項(xiàng)目,發(fā)現(xiàn)詞典被成功加載等曼,如下:
詞典被加載

接下來(lái)我們進(jìn)行增刪查改的開發(fā)里烦。

六.增刪查改業(yè)務(wù)開發(fā):

1、配置索引庫(kù)存放位置

首先我們需要配置索引的存放位置禁谦,可以把它理解為一個(gè)數(shù)據(jù)庫(kù)胁黑,只不過(guò)這個(gè)數(shù)據(jù)庫(kù)存放的是一些索引文件,我們?cè)趛ml中指定位置州泊,創(chuàng)建Config配置類丧蘸,用@value注解獲取它的值,方便隨時(shí)在代碼中獲取遥皂,如下:

yml和Config

2触趴、增刪查改的時(shí)候同步索引

數(shù)據(jù)庫(kù)的增刪查改方法代碼生成器已經(jīng)幫助我們生成完畢,只需要在原來(lái)的功能基礎(chǔ)上添加對(duì)于索引庫(kù)相關(guān)的代碼邏輯即可渴肉!

首先是添加和更新操作冗懦,添加更新放在一起,根據(jù)主鍵id判斷仇祭,如果索引中存在此id披蕉,則更新,否則添加乌奇,在service實(shí)現(xiàn)類中添加addOrUpIndex方法没讲,同時(shí)每次添加和更新的時(shí)候都要調(diào)一下此方法,同步索引礁苗,代碼基本每一行都有完整注釋爬凑,如下:

/**
     * mapper文件里增加 useGeneratedKeys="true" keyProperty="id" keyColumn="id"屬性,否則自增主鍵映射不上
     *
     * @param entity
     */
    @Override
    public void add(ArticleEntity entity) {
        dao.add(entity);
        addOrUpIndex(entity);
    }

    @Override
    public void update(ArticleEntity entity) {
        dao.update(entity);
        addOrUpIndex(entity);
    }

    /**
     * 添加或更新索引
     * @param entity
     */
    private void addOrUpIndex(ArticleEntity entity) {
        IndexWriter indexWriter = null;
        IndexReader indexReader = null;
        Directory directory = null;
        Analyzer analyzer = null;
        try {
            //創(chuàng)建索引目錄文件
            File indexFile = new File(config.getIndexLibrary());
            File[] files = indexFile.listFiles();
            // 1. 創(chuàng)建分詞器,分析文檔试伙,對(duì)文檔進(jìn)行分詞
            analyzer = new IKAnalyzer();
            // 2. 創(chuàng)建Directory對(duì)象,聲明索引庫(kù)的位置
            directory = FSDirectory.open(Paths.get(config.getIndexLibrary()));
            // 3. 創(chuàng)建IndexWriteConfig對(duì)象嘁信,寫入索引需要的配置
            IndexWriterConfig writerConfig = new IndexWriterConfig(analyzer);
            // 4.創(chuàng)建IndexWriter寫入對(duì)象
            indexWriter = new IndexWriter(directory, writerConfig);
            // 5.寫入到索引庫(kù),通過(guò)IndexWriter添加文檔對(duì)象document
            Document doc = new Document();
            //查詢是否有該索引疏叨,沒(méi)有添加潘靖,有則更新
            TopDocs topDocs = null;
            //判斷索引目錄文件是否存在文件,如果沒(méi)有文件蚤蔓,則為首次添加卦溢,有文件,則查詢id是否已經(jīng)存在
            if (files != null && files.length != 0) {
                //創(chuàng)建查詢對(duì)象
                QueryParser queryParser = new QueryParser("id", analyzer);
                Query query = queryParser.parse(String.valueOf(entity.getId()));
                indexReader = DirectoryReader.open(directory);
                IndexSearcher indexSearcher = new IndexSearcher(indexReader);
                //查詢獲取命中條目
                topDocs = indexSearcher.search(query, 1);
            }
            //StringField 不分詞 直接建索引 存儲(chǔ)
            doc.add(new StringField("id", String.valueOf(entity.getId()), Field.Store.YES));
            //TextField 分詞 建索引 存儲(chǔ)
            doc.add(new TextField("title", entity.getTitle(), Field.Store.YES));
            //TextField 分詞 建索引 存儲(chǔ)
            doc.add(new TextField("content", entity.getContent(), Field.Store.YES));
            //如果沒(méi)有查詢結(jié)果,添加
            if (topDocs != null && topDocs.totalHits.value == 0) {
                indexWriter.addDocument(doc);
                //否則单寂,更新
            } else {
                indexWriter.updateDocument(new Term("id", String.valueOf(entity.getId())), doc);
            }
        } catch (Exception e) {
            e.printStackTrace();
            throw new RuntimeException("添加索引庫(kù)出錯(cuò):" + e.getMessage());
        } finally {
            if (indexWriter != null) {
                try {
                    indexWriter.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if (indexReader != null) {
                try {
                    indexReader.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if (directory != null) {
                try {
                    directory.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if (analyzer != null) {
                analyzer.close();
            }
        }
    }

代碼應(yīng)該很容易就可以看明白贬芥,這里我們把實(shí)體的titlecontent進(jìn)行分詞,并存儲(chǔ)為索引文件宣决,所以接下來(lái)查詢的時(shí)候也要根據(jù)這兩個(gè)字段來(lái)進(jìn)行查詢誓军,查詢的時(shí)候我們要對(duì)查詢結(jié)果進(jìn)行分頁(yè),Lucene的分頁(yè)方式比較特別疲扎,他沒(méi)有類似數(shù)據(jù)庫(kù)那種提供開始和結(jié)束下標(biāo)定位元素的方法昵时,而是只能指定查詢的總條目數(shù),然后把所有的命中結(jié)果查詢出來(lái)椒丧,比如一共有100條數(shù)據(jù)壹甥,查詢第一頁(yè)返回10條,查詢第十頁(yè)則會(huì)返回100條壶熏,需要我們?cè)谶壿嬌蠈?duì)查詢結(jié)果進(jìn)行分頁(yè)句柠,取我們想要的數(shù)據(jù),也可以利用Luncene提供的SearchAfter方法進(jìn)行查詢棒假,它可以根據(jù)指定的最后一個(gè)元素查詢接下來(lái)指定數(shù)目的元素溯职,但這需要我們查詢出前n個(gè)元素然后取最后一個(gè)元素傳給SearchAfter方法,兩種方法效率上并沒(méi)有太大區(qū)別帽哑,畢竟Lucene本身就很快谜酒。但這也涉及到一個(gè)問(wèn)題,如果查詢的數(shù)據(jù)量過(guò)多妻枕,比如上千萬(wàn)條可能會(huì)導(dǎo)致內(nèi)存溢出僻族,這就需要我們根據(jù)業(yè)務(wù)做一個(gè)取舍,用戶在查詢的時(shí)候通常只會(huì)看前幾頁(yè)的數(shù)據(jù)屡谐,所以我們可以指定一下最大的查詢數(shù)量述么,比如10000條,無(wú)論實(shí)際符合條件的結(jié)果有多少愕掏,我們最多只查詢前10000條度秘,這樣問(wèn)題便得到解決,其實(shí)很多搜索引擎也是這樣做的饵撑!

如果你說(shuō)我就要看全部的數(shù)據(jù)剑梳,那就涉及到了數(shù)據(jù)的分布式存儲(chǔ),在分頁(yè)的時(shí)候就需要每臺(tái)服務(wù)器進(jìn)行查詢?nèi)缓髤R總查詢結(jié)果肄梨,這里的問(wèn)題就比較復(fù)雜了阻荒,在此處不做深究挠锥,以后可以專門聊一聊众羡,其實(shí)業(yè)界已經(jīng)有了幾種比較成熟的解決方案,可以較好的解決分布式存儲(chǔ)的分頁(yè)問(wèn)題蓖租。

這里代碼中并沒(méi)有指定查詢的最大數(shù)量粱侣,畢竟是個(gè)demo羊壹,沒(méi)必要弄的這么復(fù)雜,代碼如下:

    @Override
    public PageData<ArticleEntity> fullTextSearch(String keyWord, Integer page, Integer limit) {
        List<ArticleEntity> searchList = new ArrayList<>(10);
        PageData<ArticleEntity> pageData = new PageData<>();
        File indexFile = new File(config.getIndexLibrary());
        File[] files = indexFile.listFiles();
        //沒(méi)有索引文件齐婴,不然沒(méi)有查詢結(jié)果
        if (files == null || files.length == 0) {
            pageData.setCount(0);
            pageData.setTotalPage(0);
            pageData.setCurrentPage(page);
            pageData.setResult(new ArrayList<>());
            return pageData;
        }
        IndexReader indexReader = null;
        Directory directory = null;
        try (Analyzer analyzer = new IKAnalyzer()) {
            directory = FSDirectory.open(Paths.get(config.getIndexLibrary()));
            //多項(xiàng)查詢條件
            QueryParser queryParser = new MultiFieldQueryParser(new String[]{"title", "content"}, analyzer);
            //單項(xiàng)
            //QueryParser queryParser = new QueryParser("title", analyzer);
            Query query = queryParser.parse(!StringUtils.isEmpty(keyWord) ? keyWord : "*:*");
            indexReader = DirectoryReader.open(directory);
            //索引查詢對(duì)象
            IndexSearcher indexSearcher = new IndexSearcher(indexReader);
            TopDocs topDocs = indexSearcher.search(query, 1);
            //獲取條數(shù)
            int total = (int) topDocs.totalHits.value;
            pageData.setCount(total);
            int realPage = total % limit == 0 ? total / limit : total / limit + 1;
            pageData.setTotalPage(realPage);
            //獲取結(jié)果集
            ScoreDoc lastSd = null;
            if (page > 1) {
                int num = limit * (page - 1);
                TopDocs tds = indexSearcher.search(query, num);
                lastSd = tds.scoreDocs[num - 1];
            }
            //通過(guò)最后一個(gè)元素去搜索下一頁(yè)的元素 如果lastSd為null油猫,查詢第一頁(yè)
            TopDocs tds = indexSearcher.searchAfter(lastSd, query, limit);
            QueryScorer queryScorer = new QueryScorer(query);
            //最佳摘要
            SimpleSpanFragmenter fragmenter = new SimpleSpanFragmenter(queryScorer, 200);
            //高亮前后標(biāo)簽
            SimpleHTMLFormatter formatter = new SimpleHTMLFormatter("<b><font color='red'>", "</font></b>");
            //高亮對(duì)象
            Highlighter highlighter = new Highlighter(formatter, queryScorer);
            //設(shè)置高亮最佳摘要
            highlighter.setTextFragmenter(fragmenter);
            //遍歷查詢結(jié)果 把標(biāo)題和內(nèi)容替換為帶高亮的最佳摘要
            for (ScoreDoc sd : tds.scoreDocs) {
                Document doc = indexSearcher.doc(sd.doc);
                ArticleEntity articleEntity = new ArticleEntity();
                Integer id = Integer.parseInt(doc.get("id"));
                //獲取標(biāo)題的最佳摘要
                String titleBestFragment = highlighter.getBestFragment(analyzer, "title", doc.get("title"));
                //獲取文章內(nèi)容的最佳摘要
                String contentBestFragment = highlighter.getBestFragment(analyzer, "content", doc.get("content"));
                articleEntity.setId(id);
                articleEntity.setTitle(titleBestFragment);
                articleEntity.setContent(contentBestFragment);
                searchList.add(articleEntity);
            }
            pageData.setCurrentPage(page);
            pageData.setResult(searchList);
            return pageData;
        } catch (Exception e) {
            e.printStackTrace();
            throw new RuntimeException("全文檢索出錯(cuò):" + e.getMessage());
        } finally {
            if (indexReader != null) {
                try {
                    indexReader.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if (directory != null) {
                try {
                    directory.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }

最后是刪除索引,根據(jù)唯一標(biāo)識(shí)id刪除即可柠偶,代碼如下:

    @Override
    public void delete(ArticleEntity entity) {
        dao.delete(entity);
        //同步刪除索引
        deleteIndex(entity);
    }

    private void deleteIndex(ArticleEntity entity) {
        //刪除全文檢索
        IndexWriter indexWriter = null;
        Directory directory = null;
        try (Analyzer analyzer = new IKAnalyzer()) {
            directory = FSDirectory.open(Paths.get(config.getIndexLibrary()));
            IndexWriterConfig writerConfig = new IndexWriterConfig(analyzer);
            indexWriter = new IndexWriter(directory, writerConfig);
            //根據(jù)id字段進(jìn)行刪除
            indexWriter.deleteDocuments(new Term("id", String.valueOf(entity.getId())));
        } catch (Exception e) {
            e.printStackTrace();
            throw new RuntimeException("刪除索引庫(kù)出錯(cuò):" + e.getMessage());
        } finally {
            if (indexWriter != null) {
                try {
                    indexWriter.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if (directory != null) {
                try {
                    directory.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }

至此情妖,Lucene的后臺(tái)增刪查改功能開發(fā)完畢!

3诱担、利用swagger測(cè)試

接下來(lái)我們利用swagger對(duì)功能進(jìn)行測(cè)試毡证,測(cè)試之前我們把controller層增刪查改方法的 @LoginRequired 注解去掉(@LoginRequired是代碼生成器最新版添加的注解,可以控制方法必須登錄才可以調(diào)用)蔫仙,這樣可以不必登錄料睛,打開swagger,添加一條數(shù)據(jù)摇邦,如下:


添加數(shù)據(jù)

如上恤煞,數(shù)據(jù)添加成功,數(shù)據(jù)庫(kù)數(shù)據(jù)添加成功施籍,Lucene索引文件夾也生成了相關(guān)索引文件居扒,如下:


數(shù)據(jù)庫(kù)
Lucene索引文件

接下里我們測(cè)一下全文檢索功能,如下:
查詢結(jié)果

刪除功能也可正常使用并同步刪除索引丑慎,此處就不截圖了苔货。這樣一來(lái),后臺(tái)api測(cè)試完畢立哑,符合預(yù)期效果夜惭,接下來(lái)進(jìn)入前臺(tái)實(shí)現(xiàn)階段。

4铛绰、前臺(tái)實(shí)現(xiàn)

前臺(tái)實(shí)現(xiàn)沒(méi)有什么好說(shuō)的诈茧,就是跟后端對(duì)接口進(jìn)行交互,前端真是我的硬傷捂掰,我根據(jù)代碼生成器生成的列表頁(yè)做了調(diào)整敢会,最終實(shí)現(xiàn)效果如下:


前端效果

前臺(tái)代碼就不貼了,沒(méi)有太大意義这嚣,畢竟有了后臺(tái)的數(shù)據(jù)返回鸥昏,前臺(tái)有n多種展示方式,大家根據(jù)自己的習(xí)慣去對(duì)接口就好了姐帚,完整的前后臺(tái)代碼以及sql文件等可于文末獲取吏垮。

結(jié)語(yǔ)

本篇文章我們利用Lucene自己實(shí)現(xiàn)了一個(gè)非常輕量的搜索引擎,其實(shí)我們可以利用反射把它做成一個(gè)通用的查詢框架,這樣無(wú)論實(shí)體的屬性名稱怎么變膳汪,都可以靈活應(yīng)對(duì)唯蝶。

全文檢索在Java開發(fā)領(lǐng)域是一個(gè)重要的知識(shí)點(diǎn),需要我們深入理解和掌握遗嗽,希望通過(guò)本篇文章可以讓你對(duì)Lucene有一個(gè)更加全面的認(rèn)識(shí)粘我,代碼生成器不出意外本月會(huì)更新一版,我們下次更新痹换,再見(jiàn)啦征字!

附:關(guān)注公眾號(hào) 螺旋編程極客 獲取更多精彩內(nèi)容,我們一起進(jìn)步娇豫,一起成長(zhǎng)柔纵,回復(fù) 1024 可獲取本篇文章的項(xiàng)目源碼等資料,期待您的關(guān)注锤躁!

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末搁料,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子系羞,更是在濱河造成了極大的恐慌郭计,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,525評(píng)論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件椒振,死亡現(xiàn)場(chǎng)離奇詭異昭伸,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)澎迎,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,203評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門庐杨,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人夹供,你說(shuō)我怎么就攤上這事灵份。” “怎么了哮洽?”我有些...
    開封第一講書人閱讀 164,862評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵填渠,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我鸟辅,道長(zhǎng)氛什,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,728評(píng)論 1 294
  • 正文 為了忘掉前任匪凉,我火速辦了婚禮枪眉,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘再层。我一直安慰自己贸铜,他們只是感情好堡纬,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,743評(píng)論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著萨脑,像睡著了一般隐轩。 火紅的嫁衣襯著肌膚如雪饺饭。 梳的紋絲不亂的頭發(fā)上渤早,一...
    開封第一講書人閱讀 51,590評(píng)論 1 305
  • 那天,我揣著相機(jī)與錄音瘫俊,去河邊找鬼鹊杖。 笑死,一個(gè)胖子當(dāng)著我的面吹牛扛芽,可吹牛的內(nèi)容都是我干的骂蓖。 我是一名探鬼主播,決...
    沈念sama閱讀 40,330評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼川尖,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼登下!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起叮喳,我...
    開封第一講書人閱讀 39,244評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤被芳,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后馍悟,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體畔濒,經(jīng)...
    沈念sama閱讀 45,693評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,885評(píng)論 3 336
  • 正文 我和宋清朗相戀三年锣咒,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了侵状。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,001評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡毅整,死狀恐怖趣兄,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情悼嫉,我是刑警寧澤诽俯,帶...
    沈念sama閱讀 35,723評(píng)論 5 346
  • 正文 年R本政府宣布,位于F島的核電站承粤,受9級(jí)特大地震影響暴区,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜辛臊,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,343評(píng)論 3 330
  • 文/蒙蒙 一仙粱、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧彻舰,春花似錦伐割、人聲如沸候味。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,919評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)白群。三九已至,卻和暖如春硬霍,著一層夾襖步出監(jiān)牢的瞬間帜慢,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,042評(píng)論 1 270
  • 我被黑心中介騙來(lái)泰國(guó)打工唯卖, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留粱玲,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,191評(píng)論 3 370
  • 正文 我出身青樓拜轨,卻偏偏與公主長(zhǎng)得像抽减,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子橄碾,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,955評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容