Spring Boot 中集成Lucence
本來(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)位置 |
|---|---|---|
| guangzhou | 1[2] | 3,6 |
| he | 2[1] | 1 |
| i | 1[1] | 4 |
| live | 1[2],2[1] | 2,5,2 |
| shanghai | 2[1] | 3 |
| tom | 1[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[] = {1, 2, 3};
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)贊是最大的支持

