<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          Spring Boot 中集成Lucence

          共 25802字,需瀏覽 52分鐘

           ·

          2021-03-14 21:44

          本來(lái)已收錄到我寫的10萬(wàn)字Springboot經(jīng)典學(xué)習(xí)筆記中,筆記在持續(xù)更新……文末有領(lǐng)取方式

          1. Lucence 和全文檢索

          Lucene 是什么?Lucene 目前是 Apache Jakarta 家族中的一個(gè)開源項(xiàng)目,它不是一個(gè)完整的搜索應(yīng)用程序,而是為我們的應(yīng)用程序提供索引和搜索功能。Lucene 也是目前最為流行的基于 Java 開源全文檢索工具包。

          目前已經(jīng)有很多應(yīng)用程序的搜索功能是基于 Lucene 的,比如 Eclipse 幫助系統(tǒng)的搜索功能。Lucene 能夠?yàn)槲谋绢愋偷臄?shù)據(jù)建立索引,所以我們只要能把需要索引的數(shù)據(jù)格式轉(zhuǎn)化的文本的,Lucene 就能對(duì)我們的文檔進(jìn)行索引和搜索。

          1.1 全文檢索

          這里提到了全文檢索的概念,我們先來(lái)分析一下什么是全文檢索,理解了全文檢索之后,再理解 Lucene 的原理就非常簡(jiǎn)單了。

          何為全文檢索?舉個(gè)例子,比如現(xiàn)在要在一個(gè)文件中查找某個(gè)字符串,最直接的想法就是從頭開始檢索,查到了就OK,這種對(duì)于小數(shù)據(jù)量的文件來(lái)說(shuō),很實(shí)用,但是對(duì)于大數(shù)據(jù)量的文件來(lái)說(shuō),就有點(diǎn)吃力了。或者說(shuō)找包含某個(gè)字符串的文件,也是這樣,如果在一個(gè)擁有幾十個(gè) G 的硬盤中找那效率可想而知,是很低的。

          文件中的數(shù)據(jù)是屬于非結(jié)構(gòu)化數(shù)據(jù),也就是說(shuō)它沒有什么結(jié)構(gòu)可言,要解決上面提到的效率問(wèn)題,首先我們得將非結(jié)構(gòu)化數(shù)據(jù)中的一部分信息提取出來(lái),重新組織,使其變得有一定結(jié)構(gòu),然后對(duì)這些有一定結(jié)構(gòu)的數(shù)據(jù)進(jìn)行搜索,從而達(dá)到搜索相對(duì)較快的目的。這就叫全文搜索。即先建立索引,再對(duì)索引進(jìn)行搜索的過(guò)程。

          1.2 Lucene 建立索引的方式

          那么 Lucene 中是如何建立索引的呢?假設(shè)現(xiàn)在有兩篇文章,內(nèi)容如下:

          文章1的內(nèi)容為:Tom lives in Guangzhou, I live in Guangzhou too.
          文章2的內(nèi)容為:He once lived in Shanghai.

          首先第一步是將文檔傳給分詞組件(Tokenizer),分詞組件會(huì)將文檔分成一個(gè)個(gè)單詞,并去除標(biāo)點(diǎn)符號(hào)和停詞。所謂的停詞指的是沒有特別意義的詞,比如英文中的 a,the,too 等。經(jīng)過(guò)分詞后,得到詞元(Token) 。如下:

          文章1經(jīng)過(guò)分詞后的結(jié)果:[Tom] [lives] [Guangzhou] [I] [live] [Guangzhou]
          文章2經(jīng)過(guò)分詞后的結(jié)果:[He] [lives] [Shanghai]

          然后將詞元傳給語(yǔ)言處理組件(Linguistic Processor),對(duì)于英語(yǔ),語(yǔ)言處理組件一般會(huì)將字母變?yōu)樾懀瑢卧~縮減為詞根形式,如 ”lives” 到 ”live” 等,將單詞轉(zhuǎn)變?yōu)樵~根形式,如 ”drove” 到 ”drive” 等。然后得到詞(Term)。如下:

          文章1經(jīng)過(guò)處理后的結(jié)果:[tom] [live] [guangzhou] [i] [live] [guangzhou]文章2經(jīng)過(guò)處理后的結(jié)果:[he] [live] [shanghai]

          最后將得到的詞傳給索引組件(Indexer),索引組件經(jīng)過(guò)處理,得到下面的索引結(jié)構(gòu):

          關(guān)鍵詞文章號(hào)[出現(xiàn)頻率]出現(xiàn)位置
          guangzhou1[2]3,6
          he2[1]1
          i1[1]4
          live1[2],2[1]2,5,2
          shanghai2[1]3
          tom1[1]1

          以上就是Lucene 索引結(jié)構(gòu)中最核心的部分。它的關(guān)鍵字是按字符順序排列的,因此 Lucene 可以用二元搜索算法快速定位關(guān)鍵詞。實(shí)現(xiàn)時(shí) Lucene 將上面三列分別作為詞典文件(Term Dictionary)、頻率文件(frequencies)和位置文件(positions)保存。其中詞典文件不僅保存有每個(gè)關(guān)鍵詞,還保留了指向頻率文件和位置文件的指針,通過(guò)指針可以找到該關(guān)鍵字的頻率信息和位置信息。搜索的過(guò)程是先對(duì)詞典二元查找、找到該詞,通過(guò)指向頻率文件的指針讀出所有文章號(hào),然后返回結(jié)果,然后就可以在具體的文章中根據(jù)出現(xiàn)位置找到該詞了。所以 Lucene 在第一次建立索引的時(shí)候可能會(huì)比較慢,但是以后就不需要每次都建立索引了,就快了。

          理解了 Lucene 的分詞原理,接下來(lái)我們?cè)?Spring Boot 中集成 Lucene 并實(shí)現(xiàn)索引和搜索的功能。

          2. Spring Boot 中集成 Lucence

          2.1 依賴導(dǎo)入

          首先需要導(dǎo)入 Lucene 的依賴,它的依賴有好幾個(gè),如下:

          <!-- Lucence核心包 -->
          <dependency>
           <groupId>org.apache.lucene</groupId>
           <artifactId>lucene-core</artifactId>
           <version>5.3.1</version>
          </dependency>

          <!-- Lucene查詢解析包 -->
          <dependency>
           <groupId>org.apache.lucene</groupId>
           <artifactId>lucene-queryparser</artifactId>
           <version>5.3.1</version>
          </dependency>

          <!-- 常規(guī)的分詞(英文) -->
          <dependency>
           <groupId>org.apache.lucene</groupId>
           <artifactId>lucene-analyzers-common</artifactId>
           <version>5.3.1</version>
          </dependency>

          <!--支持分詞高亮  -->
          <dependency>
           <groupId>org.apache.lucene</groupId>
           <artifactId>lucene-highlighter</artifactId>
           <version>5.3.1</version>
          </dependency>

          <!--支持中文分詞  -->
          <dependency>
           <groupId>org.apache.lucene</groupId>
           <artifactId>lucene-analyzers-smartcn</artifactId>
           <version>5.3.1</version>
          </dependency>

          最后一個(gè)依賴是用來(lái)支持中文分詞的,因?yàn)槟J(rèn)是支持英文的。那個(gè)高亮的分詞依賴是最后我要做一個(gè)搜索,然后將搜到的內(nèi)容高亮顯示,模擬當(dāng)前互聯(lián)網(wǎng)上的做法,大家可以運(yùn)用到實(shí)際項(xiàng)目中去。

          2.2 快速入門

          根據(jù)上文的分析,全文檢索有兩個(gè)步驟,先建立索引,再檢索。所以為了測(cè)試這個(gè)過(guò)程,我新建兩個(gè) java 類,一個(gè)用來(lái)建立索引的,另一個(gè)用來(lái)檢索。

          2.2.1 建立索引

          我們自己弄幾個(gè)文件,放到 D:\lucene\data 目錄下,新建一個(gè) Indexer 類來(lái)實(shí)現(xiàn)建立索引功能。首先在構(gòu)造方法中初始化標(biāo)準(zhǔn)分詞器和寫索引實(shí)例。

          public class Indexer {

              /**
               * 寫索引實(shí)例
               */

              private IndexWriter writer;

              /**
               * 構(gòu)造方法,實(shí)例化IndexWriter
               * @param indexDir
               * @throws Exception
               */

              public Indexer(String indexDir) throws Exception {
                  Directory dir = FSDirectory.open(Paths.get(indexDir));
                  //標(biāo)準(zhǔn)分詞器,會(huì)自動(dòng)去掉空格啊,is a the等單詞
                  Analyzer analyzer = new StandardAnalyzer();
                  //將標(biāo)準(zhǔn)分詞器配到寫索引的配置中
                  IndexWriterConfig config = new IndexWriterConfig(analyzer);
                  //實(shí)例化寫索引對(duì)象
                  writer = new IndexWriter(dir, config);
              }
          }

          在構(gòu)造放發(fā)中傳一個(gè)存放索引的文件夾路徑,然后構(gòu)建標(biāo)準(zhǔn)分詞器(這是英文的),再使用標(biāo)準(zhǔn)分詞器來(lái)實(shí)例化寫索引對(duì)象。接下來(lái)就開始建立索引了,我將解釋放到代碼注釋里,方便大家跟進(jìn)。

          /**
           * 索引指定目錄下的所有文件
           * @param dataDir
           * @return
           * @throws Exception
           */

          public int indexAll(String dataDir) throws Exception {
              // 獲取該路徑下的所有文件
              File[] files = new File(dataDir).listFiles();
              if (null != files) {
                  for (File file : files) {
                      //調(diào)用下面的indexFile方法,對(duì)每個(gè)文件進(jìn)行索引
                      indexFile(file);
                  }
              }
              //返回索引的文件數(shù)
              return writer.numDocs();
          }

          /**
           * 索引指定的文件
           * @param file
           * @throws Exception
           */

          private void indexFile(File file) throws Exception {
              System.out.println("索引文件的路徑:" + file.getCanonicalPath());
              //調(diào)用下面的getDocument方法,獲取該文件的document
              Document doc = getDocument(file);
              //將doc添加到索引中
              writer.addDocument(doc);
          }

          /**
           * 獲取文檔,文檔里再設(shè)置每個(gè)字段,就類似于數(shù)據(jù)庫(kù)中的一行記錄
           * @param file
           * @return
           * @throws Exception
           */

          private Document getDocument(File file) throws Exception {
              Document doc = new Document();
              //開始添加字段
              //添加內(nèi)容
              doc.add(new TextField("contents"new FileReader(file)));
              //添加文件名,并把這個(gè)字段存到索引文件里
              doc.add(new TextField("fileName", file.getName(), Field.Store.YES));
              //添加文件路徑
              doc.add(new TextField("fullPath", file.getCanonicalPath(), Field.Store.YES));
              return doc;
          }

          這樣就建立好索引了,我們?cè)谠擃愔袑懸粋€(gè) main 方法測(cè)試一下:

          public static void main(String[] args) {
                  //索引保存到的路徑
                  String indexDir = "D:\\lucene";
                  //需要索引的文件數(shù)據(jù)存放的目錄
                  String dataDir = "D:\\lucene\\data";
                  Indexer indexer = null;
                  int indexedNum = 0;
                  //記錄索引開始時(shí)間
                  long startTime = System.currentTimeMillis();
                  try {
                      // 開始構(gòu)建索引
                      indexer = new Indexer(indexDir);
                      indexedNum = indexer.indexAll(dataDir);
                  } catch (Exception e) {
                      e.printStackTrace();
                  } finally {
                      try {
                          if (null != indexer) {
                              indexer.close();
                          }
                      } catch (Exception e) {
                          e.printStackTrace();
                      }
                  }
                  //記錄索引結(jié)束時(shí)間
                  long endTime = System.currentTimeMillis();
                  System.out.println("索引耗時(shí)" + (endTime - startTime) + "毫秒");
                  System.out.println("共索引了" + indexedNum + "個(gè)文件");
              }

          我搞了兩個(gè) tomcat 相關(guān)的文件放到 D:\lucene\data 下了,文件名為 catalina.properties 和 logging.properties,文件可以在 tomcat 的 conf 目錄下拷貝,也可以在我的源代碼中獲取,我也上傳了。執(zhí)行完之后,看到控制臺(tái)輸出:

          索引文件的路徑:D:\lucene\data\catalina.properties
          索引文件的路徑:D:\lucene\data\logging.properties
          索引耗時(shí)882毫秒
          共索引了2個(gè)文件

          然后我們?nèi)?D:\lucene\ 目錄下可以看到一些索引文件,這些文件不能刪除,刪除了就需要重新構(gòu)建索引,否則沒了索引,就無(wú)法去檢索內(nèi)容了。

          ####2.2.2 檢索內(nèi)容

          上面把這兩個(gè)文件的索引建立好了,接下來(lái)我們就可以寫檢索程序了,在這兩個(gè)文件中查找特定的詞。

          public class Searcher {

              public static void search(String indexDir, String q) throws Exception {

                  //獲取要查詢的路徑,也就是索引所在的位置
                  Directory dir = FSDirectory.open(Paths.get(indexDir));
                  IndexReader reader = DirectoryReader.open(dir);
                  //構(gòu)建IndexSearcher
                  IndexSearcher searcher = new IndexSearcher(reader);
                  //標(biāo)準(zhǔn)分詞器,會(huì)自動(dòng)去掉空格啊,is a the等單詞
                  Analyzer analyzer = new StandardAnalyzer();
                  //查詢解析器
                  QueryParser parser = new QueryParser("contents", analyzer);
                  //通過(guò)解析要查詢的String,獲取查詢對(duì)象,q為傳進(jìn)來(lái)的待查的字符串
                  Query query = parser.parse(q);

                  //記錄索引開始時(shí)間
                  long startTime = System.currentTimeMillis();
                  //開始查詢,查詢前10條數(shù)據(jù),將記錄保存在docs中
                  TopDocs docs = searcher.search(query, 10);
                  //記錄索引結(jié)束時(shí)間
                  long endTime = System.currentTimeMillis();
                  System.out.println("匹配" + q + "共耗時(shí)" + (endTime-startTime) + "毫秒");
                  System.out.println("查詢到" + docs.totalHits + "條記錄");

                  //取出每條查詢結(jié)果
                  for(ScoreDoc scoreDoc : docs.scoreDocs) {
                      //scoreDoc.doc相當(dāng)于docID,根據(jù)這個(gè)docID來(lái)獲取文檔
                      Document doc = searcher.doc(scoreDoc.doc);
                      //fullPath是剛剛建立索引的時(shí)候我們定義的一個(gè)字段,表示路徑。也可以取其他的內(nèi)容,只要我們?cè)诮⑺饕龝r(shí)有定義即可。
                      System.out.println(doc.get("fullPath"));
                  }
                  reader.close();
              }
          }

          ok,這樣我們檢索的代碼就寫完了,每一步解釋我寫在代碼中的注釋上了,下面寫個(gè) main 方法來(lái)測(cè)試一下:

          public static void main(String[] args) {
              String indexDir = "D:\\lucene";
              //查詢這個(gè)字符串
              String q = "security";
              try {
                  search(indexDir, q);
              } catch (Exception e) {
                  e.printStackTrace();
              }
          }

          查一下 security 這個(gè)字符串,執(zhí)行一下看控制臺(tái)打印的結(jié)果:

          匹配security共耗時(shí)23毫秒
          查詢到1條記錄
          D:\lucene\data\catalina.properties

          可以看出,耗時(shí)了23毫秒在其中一個(gè)文件中找到了 security 這個(gè)字符串,并輸出了文件的名稱。上面的代碼我寫的很詳細(xì),這個(gè)代碼已經(jīng)比較全了,可以用在生產(chǎn)環(huán)境上。

          2.3 中文分詞檢索高亮實(shí)戰(zhàn)

          上文已經(jīng)寫了建立索引和檢索的代碼,但是在實(shí)際項(xiàng)目中,我們往往是結(jié)合頁(yè)面做一些查詢結(jié)果的展示,比如我要查某個(gè)關(guān)鍵字,查到了之后,將相關(guān)的信息點(diǎn)展示出來(lái),并將查詢的關(guān)鍵字高亮等等。這種需求在實(shí)際項(xiàng)目中非常常見,而且大多數(shù)網(wǎng)站中都會(huì)有這種效果。所以這一小節(jié)我們就使用 Lucene 來(lái)實(shí)現(xiàn)這種效果。

          2.3.1 中文分詞

          我們新建一個(gè) ChineseIndexer 類來(lái)建立中文索引,建立過(guò)程和英文索引一樣的,不同的地方在于使用的是中文分詞器。上面我們是通過(guò)讀取文件去建立索引,這里我們不讀取文件,直接對(duì)一個(gè)字符串建立索引,因?yàn)樵趯?shí)際項(xiàng)目中,絕大部分情況是獲取到一些文本字符串(比如從表中查詢出來(lái)的結(jié)果),然后對(duì)該文本字符串建立索引。

          索引建立的過(guò)程,先要獲取 IndexWriter 對(duì)象,然后將相關(guān)的內(nèi)容生成索引,索引的 key 可以自己根據(jù)項(xiàng)目中的情況來(lái)自定義,內(nèi)容是自己處理過(guò)的文本,或者從數(shù)據(jù)庫(kù)中查詢出來(lái)的文本。生成的時(shí)候,我們需要使用中文分詞器。代碼如下:

          public class ChineseIndexer {

              /**
               * 存放索引的位置
               */

              private Directory dir;

              //準(zhǔn)備一下用來(lái)測(cè)試的數(shù)據(jù)
              //用來(lái)標(biāo)識(shí)文檔
              private Integer ids[] = {123};
              private String citys[] = {"上海""南京""青島"};
              private String descs[] = {
                      "上海是個(gè)繁華的城市。",
                      "南京是一個(gè)文化的城市南京,簡(jiǎn)稱寧,是江蘇省會(huì),地處中國(guó)東部地區(qū),長(zhǎng)江下游,瀕江近海。全市下轄11個(gè)區(qū),總面積6597平方公里,2013年建成區(qū)面積752.83平方公里,常住人口818.78萬(wàn),其中城鎮(zhèn)人口659.1萬(wàn)人。[1-4] “江南佳麗地,金陵帝王州”,南京擁有著6000多年文明史、近2600年建城史和近500年的建都史,是中國(guó)四大古都之一,有“六朝古都”、“十朝都會(huì)”之稱,是中華文明的重要發(fā)祥地,歷史上曾數(shù)次庇佑華夏之正朔,長(zhǎng)期是中國(guó)南方的政治、經(jīng)濟(jì)、文化中心,擁有厚重的文化底蘊(yùn)和豐富的歷史遺存。[5-7] 南京是國(guó)家重要的科教中心,自古以來(lái)就是一座崇文重教的城市,有“天下文樞”、“東南第一學(xué)”的美譽(yù)。截至2013年,南京有高等院校75所,其中211高校8所,僅次于北京上海;國(guó)家重點(diǎn)實(shí)驗(yàn)室25所、國(guó)家重點(diǎn)學(xué)科169個(gè)、兩院院士83人,均居中國(guó)第三。[8-10] 。",
                      "青島是一個(gè)美麗的城市。"
              };

              /**
               * 生成索引
               * @param indexDir
               * @throws Exception
               */

              public void index(String indexDir) throws Exception {
                  dir = FSDirectory.open(Paths.get(indexDir));
                  // 先調(diào)用 getWriter 獲取IndexWriter對(duì)象
                  IndexWriter writer = getWriter();
                  for(int i = 0; i < ids.length; i++) {
                      Document doc = new Document();
                      // 把上面的數(shù)據(jù)都生成索引,分別用id、city和desc來(lái)標(biāo)識(shí)
                      doc.add(new IntField("id", ids[i], Field.Store.YES));
                      doc.add(new StringField("city", citys[i], Field.Store.YES));
                      doc.add(new TextField("desc", descs[i], Field.Store.YES));
                      //添加文檔
                      writer.addDocument(doc);
                  }
                  //close了才真正寫到文檔中
                  writer.close();
              }

              /**
               * 獲取IndexWriter實(shí)例
               * @return
               * @throws Exception
               */

              private IndexWriter getWriter() throws Exception {
                  //使用中文分詞器
                  SmartChineseAnalyzer analyzer = new SmartChineseAnalyzer();
                  //將中文分詞器配到寫索引的配置中
                  IndexWriterConfig config = new IndexWriterConfig(analyzer);
                  //實(shí)例化寫索引對(duì)象
                  IndexWriter writer = new IndexWriter(dir, config);
                  return writer;
              }

              public static void main(String[] args) throws Exception {
                  new ChineseIndexer().index("D:\\lucene2");
              }
          }

          這里我們用 id、city、desc 分別代表 id、城市名稱和城市描述,用他們作為關(guān)鍵字來(lái)建立索引,后面我們獲取內(nèi)容的時(shí)候,主要來(lái)獲取城市描述。南京的描述我故意寫的長(zhǎng)一點(diǎn),因?yàn)橄挛臋z索的時(shí)候,根據(jù)不同的關(guān)鍵字會(huì)檢索到不同部分的信息,有個(gè)權(quán)重的概念在里面。然后執(zhí)行一下 main 方法,將索引保存到 D:\lucene2\ 中。

          2.3.2 中文分詞查詢

          中文分詞查詢代碼邏輯和默認(rèn)的查詢差不多,有一些區(qū)別在于,我們需要將查詢出來(lái)的關(guān)鍵字標(biāo)紅加粗等需要處理,需要計(jì)算出一個(gè)得分片段,這是什么意思呢?比如我搜索 “南京文化” 跟搜索 “南京文明”,應(yīng)該會(huì)返回不同的結(jié)果,這個(gè)結(jié)果是根據(jù)計(jì)算出的得分片段來(lái)確定的。

          舉個(gè)簡(jiǎn)單的例子,比如有一段文本:“你好,我的名字叫倪升武,科大訊飛軟件開發(fā)工程師……,江湖人都叫我武哥,我一直覺得,人與人之間講的是真誠(chéng),而不是套路。……”。

          如果我搜 “倪升武”,可能會(huì)給我返回結(jié)果:“我的名字叫倪升武,科大訊飛軟件開發(fā)工程師”
          如果我搜 ”武哥“,可能會(huì)給我返回結(jié)果:”江湖人都叫我武哥,我一直覺得“

          Lucene 會(huì)根據(jù)搜索的關(guān)鍵字,在待搜索文本中計(jì)算關(guān)鍵字出現(xiàn)片段的得分,返回最能反映出用戶搜索意圖的一段文本作為結(jié)果。明白了原理,我們看一下代碼和注釋:

          public class ChineseSearch {

              private static final Logger logger = LoggerFactory.getLogger(ChineseSearch.class);

              public static List<String> search(String indexDir, String q) throws Exception {

                  //獲取要查詢的路徑,也就是索引所在的位置
                  Directory dir = FSDirectory.open(Paths.get(indexDir));
                  IndexReader reader = DirectoryReader.open(dir);
                  IndexSearcher searcher = new IndexSearcher(reader);
                  //使用中文分詞器
                  SmartChineseAnalyzer analyzer = new SmartChineseAnalyzer();
                  //由中文分詞器初始化查詢解析器
                  QueryParser parser = new QueryParser("desc", analyzer);
                  //通過(guò)解析要查詢的String,獲取查詢對(duì)象
                  Query query = parser.parse(q);

                  //記錄索引開始時(shí)間
                  long startTime = System.currentTimeMillis();
                  //開始查詢,查詢前10條數(shù)據(jù),將記錄保存在docs中
                  TopDocs docs = searcher.search(query, 10);
                  //記錄索引結(jié)束時(shí)間
                  long endTime = System.currentTimeMillis();
                  logger.info("匹配{}共耗時(shí){}毫秒", q, (endTime - startTime));
                  logger.info("查詢到{}條記錄", docs.totalHits);

                  //如果不指定參數(shù)的話,默認(rèn)是加粗,即<b><b/>
                  SimpleHTMLFormatter simpleHTMLFormatter = new SimpleHTMLFormatter("<b><font color=red>","</font></b>");
                  //根據(jù)查詢對(duì)象計(jì)算得分,會(huì)初始化一個(gè)查詢結(jié)果最高的得分
                  QueryScorer scorer = new QueryScorer(query);
                  //根據(jù)這個(gè)得分計(jì)算出一個(gè)片段
                  Fragmenter fragmenter = new SimpleSpanFragmenter(scorer);
                  //將這個(gè)片段中的關(guān)鍵字用上面初始化好的高亮格式高亮
                  Highlighter highlighter = new Highlighter(simpleHTMLFormatter, scorer);
                  //設(shè)置一下要顯示的片段
                  highlighter.setTextFragmenter(fragmenter);

                  //取出每條查詢結(jié)果
                  List<String> list = new ArrayList<>();
                  for(ScoreDoc scoreDoc : docs.scoreDocs) {
                      //scoreDoc.doc相當(dāng)于docID,根據(jù)這個(gè)docID來(lái)獲取文檔
                      Document doc = searcher.doc(scoreDoc.doc);
                      logger.info("city:{}", doc.get("city"));
                      logger.info("desc:{}", doc.get("desc"));
                      String desc = doc.get("desc");

                      //顯示高亮
                      if(desc != null) {
                          TokenStream tokenStream = analyzer.tokenStream("desc"new StringReader(desc));
                          String summary = highlighter.getBestFragment(tokenStream, desc);
                          logger.info("高亮后的desc:{}", summary);
                          list.add(summary);
                      }
                  }
                  reader.close();
                  return list;
              }
          }

          每一步的注釋我寫的很詳細(xì),在這就不贅述了。接下來(lái)我們來(lái)測(cè)試一下效果。

          2.3.3 測(cè)試一下

          這里我們使用 thymeleaf 來(lái)寫個(gè)簡(jiǎn)單的頁(yè)面來(lái)展示獲取到的數(shù)據(jù),并高亮展示。在 controller 中我們指定索引的目錄和需要查詢的字符串,如下:

          @Controller
          @RequestMapping("/lucene")
          public class IndexController {

              @GetMapping("/test")
              public String test(Model model) {
                  // 索引所在的目錄
                  String indexDir = "D:\\lucene2";
                  // 要查詢的字符
          //        String q = "南京文明";
                  String q = "南京文化";
                  try {
                      List<String> list = ChineseSearch.search(indexDir, q);
                      model.addAttribute("list", list);
                  } catch (Exception e) {
                      e.printStackTrace();
                  }
                  return "result";
              }
          }

          直接返回到 result.html 頁(yè)面,該頁(yè)面主要來(lái)展示一下 model 中的數(shù)據(jù)即可。

          <!DOCTYPE html>
          <html lang="en" xmlns:th="http://www.thymeleaf.org">
          <head>
              <meta charset="UTF-8">
              <title>Title</title>
          </head>
          <body>
          <div th:each="desc : ${list}">
              <div th:utext="${desc}"></div>
          </div>
          </body>
          </html>

          這里注意一下,不能使用 th:test,否則字符串中的 html 標(biāo)簽都會(huì)被轉(zhuǎn)義,不會(huì)被渲染到頁(yè)面。下面啟動(dòng)服務(wù),在瀏覽器中輸入 http://localhost:8080/lucene/test,測(cè)試一下效果,我們搜索的是 “南京文化”。

          南京文化

          再將 controller 中的搜索關(guān)鍵字改成 “南京文明”,看下命中的效果。

          南京文明

          可以看出,不同的關(guān)鍵詞,它會(huì)計(jì)算一個(gè)得分片段,也就是說(shuō)不同的關(guān)鍵字會(huì)命中不同位置的內(nèi)容,然后將關(guān)鍵字根據(jù)我們自己設(shè)定的形式高亮顯示。從結(jié)果中可以看出,Lucene 也可以很智能的將關(guān)鍵字拆分命中,這在實(shí)際項(xiàng)目中會(huì)很好用。

          3. 總結(jié)

          本節(jié)課首先詳細(xì)的分析了全文檢索的理論規(guī)則,然后結(jié)合 Lucene,系統(tǒng)的講述了在 Spring Boot 的集成步驟,首先快速帶領(lǐng)大家從直觀上感受 Lucene 如何建立索引已經(jīng)如果檢索,其次通過(guò)中文檢索的具體實(shí)例,展示了 Lucene 在全文檢索中的廣泛應(yīng)用。Lucene 不難,主要就是步驟比較多,代碼不用死記硬背,拿到項(xiàng)目中根據(jù)實(shí)際情況做對(duì)應(yīng)的修改即可。

          該文已收錄到我寫的《10萬(wàn)字Springboot經(jīng)典學(xué)習(xí)筆記》中,點(diǎn)擊下面小卡片,進(jìn)入【Java開發(fā)寶典】,回復(fù):筆記,即可免費(fèi)獲取。

          點(diǎn)贊是最大的支持 

          瀏覽 70
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  少妇双乳好大有奶水视频 | 一区二区黄色电影 | 在线观看国产黄片 | 欧美黄色视屏一区二区三区 | 一本色道久久综合无码人妻四虎 |