1. 案例分析:什么時全文檢索左胞,如何實現(xiàn)全文檢索
? 1.1 案例
? 實現(xiàn)一個文件的搜索功能杆怕,通過關(guān)鍵字搜索文件,凡是文件名或文件內(nèi)容包括關(guān)鍵字的文件都需要找出來韩脑。還可以根據(jù)中文詞語進行查詢劫谅,并且需要支持多個條件查詢见坑。
本案例中的原始內(nèi)容就是磁盤上的文件,如下圖:2. 需求分析
? 2.1. 數(shù)據(jù)庫搜索
數(shù)據(jù)庫中的搜索很容易實現(xiàn)捏检,通常都是使用sql語句進行查詢荞驴,而且能很快的得到查詢結(jié)果。
為什么數(shù)據(jù)庫搜索很容易贯城?
因為數(shù)據(jù)庫中的數(shù)據(jù)存儲是有規(guī)律的熊楼,有行有列而且數(shù)據(jù)格式、數(shù)據(jù)長度都是固定的冤狡。
? 2.2. 數(shù)據(jù)分類
我們生活中的數(shù)據(jù)總體分為兩種:結(jié)構(gòu)化數(shù)據(jù)和非結(jié)構(gòu)化數(shù)據(jù)孙蒙。
結(jié)構(gòu)化數(shù)據(jù):指具有固定格式或有限長度的數(shù)據(jù),如數(shù)據(jù)庫悲雳,元數(shù)據(jù)等挎峦。
非結(jié)構(gòu)化數(shù)據(jù):指不定長或無固定格式的數(shù)據(jù),如郵件合瓢,word文檔等磁盤上的文件
2.3. 非結(jié)構(gòu)化數(shù)據(jù)查詢方法
(1)順序掃描法(Serial Scanning)
? 所謂順序掃描坦胶,比如要找內(nèi)容包含某一個字符串的文件,就是一個文檔一個文檔的看晴楔,對于每一個文檔顿苇,從頭看到尾,如果此文檔包含此字符串税弃,則此文檔為我們要找的文件纪岁,接著看下一個文件,直到掃描完所有的文件则果。如利用windows的搜索也可以搜索文件內(nèi)容幔翰,只是相當?shù)穆?/p>
(2)全文檢索(Full-text Search)
? 將非結(jié)構(gòu)化數(shù)據(jù)中的一部分信息提取出來,重新組織西壮,使其變得有一定結(jié)構(gòu)遗增,然后對此有一定結(jié)構(gòu)的數(shù)據(jù)進行搜索,從而達到搜索相對較快的目的款青。這部分從非結(jié)構(gòu)化數(shù)據(jù)中提取出的然后重新組織的信息做修,我們稱之索引。
例如:字典抡草。字典的拼音表和部首檢字表就相當于字典的索引饰及,對每一個字的解釋是非結(jié)構(gòu)化的,如果字典沒有音節(jié)表和部首檢字表渠牲,在茫茫辭海中找一個字只能順序掃描旋炒。然而字的某些信息可以提取出來進行結(jié)構(gòu)化處理,比如讀音签杈,就比較結(jié)構(gòu)化瘫镇,分聲母和韻母,分別只有幾種可以一一列舉答姥,于是將讀音拿出來按一定的順序排列铣除,每一項讀音都指向此字的詳細解釋的頁數(shù)。我們搜索時按結(jié)構(gòu)化的拼音搜到讀音鹦付,然后按其指向的頁數(shù)尚粘,便可找到我們的非結(jié)構(gòu)化數(shù)據(jù)——也即對字的解釋。
這種先建立索引敲长,再對索引進行搜索的過程就叫全文檢索(Full-text Search)郎嫁。
雖然創(chuàng)建索引的過程也是非常耗時的秉继,但是索引一旦創(chuàng)建就可以多次使用,全文檢索主要處理的是查詢泽铛,所以耗時間創(chuàng)建索引是值得的尚辑。
? 2.4. 如何實現(xiàn)全文檢索
可以使用Lucene實現(xiàn)全文檢索。Lucene是apache下的一個開放源代碼的全文檢索引擎工具包盔腔。提供了完整的查詢引擎和索引引擎杠茬,部分文本分析引擎。Lucene的目的是為軟件開發(fā)人員提供一個簡單易用的工具包弛随,以方便的在目標系統(tǒng)中實現(xiàn)全文檢索的功能瓢喉。
? 2.5. 全文檢索的應(yīng)用場景
對于數(shù)據(jù)量大、數(shù)據(jù)結(jié)構(gòu)不固定的數(shù)據(jù)可采用全文檢索方式搜索舀透,比如百度栓票、Google等搜索引擎、論壇站內(nèi)搜索盐杂、電商網(wǎng)站站內(nèi)搜索等逗载。
3. Lucene實現(xiàn)全文檢索的流程
3.1.索引和搜索流程圖
綠色表示索引過程,對要搜索的原始內(nèi)容進行索引構(gòu)建一個索引庫链烈,索引過程包括:
確定原始內(nèi)容即要搜索的內(nèi)容 -> 采集文檔 -> 創(chuàng)建文檔 -> 分析文檔 -> 索引文檔紅色表示搜索過程厉斟,從索引庫中搜索內(nèi)容,搜索過程包括:
用戶通過搜索界面 -> 創(chuàng)建查詢 -> 執(zhí)行搜索强衡,從索引庫搜索 -> 渲染搜索結(jié)果
3.2. 創(chuàng)建索引
創(chuàng)建索引步驟:
? 1. 獲得原始文檔
? 2. 創(chuàng)建文檔對象
? 3. 分析文檔
? 4. 創(chuàng)建索引
? 對文檔索引的過程擦秽,將用戶要搜索的文檔內(nèi)容進行索引,索引存儲在索引庫(index)中漩勤。
這里我們要搜索的文檔是磁盤上的文本文件感挥,根據(jù)案例描述:凡是文件名或文件內(nèi)容包括關(guān)鍵字的文件都要找出來,這里要對文件名和文件內(nèi)容創(chuàng)建索引越败。
? 3.2.1 獲得全部文檔
? 原始文檔是指要索引和搜索的內(nèi)容触幼。原始內(nèi)容包括互聯(lián)網(wǎng)上的網(wǎng)頁、數(shù)據(jù)庫中的數(shù)據(jù)究飞、磁盤上的文件等置谦。
本案例中的原始內(nèi)容就是磁盤上的文件,如下圖:
? 從互聯(lián)網(wǎng)上亿傅、數(shù)據(jù)庫媒峡、文件系統(tǒng)中等獲取需要搜索的原始信息,這個過程就是信息采集葵擎,信息采集的目的是為了對原始內(nèi)容進行索引谅阿。
? 在Internet上采集信息的軟件通常稱為爬蟲或蜘蛛,也稱為網(wǎng)絡(luò)機器人,爬蟲訪問互聯(lián)網(wǎng)上的每一個網(wǎng)頁签餐,將獲取到的網(wǎng)頁內(nèi)容存儲起來寓涨。
? Lucene不提供信息采集的類庫,需要自己編寫一個爬蟲程序?qū)崿F(xiàn)信息采集氯檐,也可以通過一些開源軟件實現(xiàn)信息采集缅茉,如下:
開源軟件 |
---|
Nutch(http://lucene.apache.org/nutch), Nutch是apache的一個子項目,包括大規(guī)模爬蟲工具男摧,能夠抓取和分辨web網(wǎng)站數(shù)據(jù)
|
jsoup(http://jsoup.org/ ),jsoup 是一款Java 的HTML解析器译打,可直接解析某個URL地址耗拓、HTML文本內(nèi)容。它提供了一套非常省力的API奏司,可通過DOM乔询,CSS以及類似于jQuery的操作方法來取出和操作數(shù)據(jù)
|
heritrix(http://sourceforge.net/projects/archive-crawler/files/),Heritrix 是一個由 java 開發(fā)的韵洋、開源的網(wǎng)絡(luò)爬蟲竿刁,用戶可以使用它來從網(wǎng)上抓取想要的資源。其最出色之處在于它良好的可擴展性搪缨,方便用戶實現(xiàn)自己的抓取邏輯
|
? 本案例我們要獲取磁盤上文件的內(nèi)容食拜,可以通過文件流來讀取文本文件的內(nèi)容,對于pdf副编、doc负甸、xls等文件可通過第三方提供的解析工具讀取文件內(nèi)容,比如Apache POI讀取doc和xls的文件內(nèi)容痹届。
? 3.2.2 創(chuàng)建文檔對象
? 獲取原始內(nèi)容的目的是為了索引呻待,在索引前需要將原始內(nèi)容創(chuàng)建成文檔(Document),文檔中包括一個一個的域(Field)队腐,域中存儲內(nèi)容蚕捉。
? 這里我們可以將磁盤上的一個文件當成一個document,Document中包括一些Field(file_name文件名稱柴淘、file_path文件路徑迫淹、file_size文件大小、file_content文件內(nèi)容)悠就,如下圖:
注意: 每個Document可以有多個Field千绪,不同的Document可以有不同的Field,同一個Document可以有相同的Field(域名和域值都相同)
每個文檔都有一個唯一的編號梗脾,就是文檔id荸型。
? 3.2.3 分析文檔
? 將原始內(nèi)容創(chuàng)建為包含域(Field)的文檔(document),需要再對域中的內(nèi)容進行分析,分析的過程是經(jīng)過對原始文檔提取單詞瑞妇、將字母轉(zhuǎn)為小寫稿静、去除標點符號、去除停用詞等過程生成最終的語匯單元辕狰,可以將語匯單元理解為一個一個的單詞改备。
? 比如下邊的文檔經(jīng)過分析如下:
? 原文檔內(nèi)容:
Lucene is a Java full-text search engine. Lucene is not a complete
application, but rather a code library and API that can easily be used
to add search capabilities to applications.
? 分析后得到的語匯單元:
? lucene、java蔓倍、full悬钳、search、engine .....
? 每個單詞叫做一個Term偶翅,不同的域中拆分出來的相同的單詞是不同的term默勾。term中包含兩部分一部分是文檔的域名,另一部分是單詞的內(nèi)容聚谁。
例如:文件名中包含apache和文件內(nèi)容中包含的apache是不同的term母剥。
? 3.2.4 創(chuàng)建索引
? 對所有文檔分析得出的語匯單元進行索引,索引的目的是為了搜索形导,最終要實現(xiàn)只搜索被索引的語匯單元從而找到Document(文檔)环疼。
注意:創(chuàng)建索引是對語匯單元索引,通過詞語找文檔朵耕,這種索引的結(jié)構(gòu)叫倒排索引結(jié)構(gòu)炫隶。
傳統(tǒng)方法是根據(jù)文件找到該文件的內(nèi)容,在文件內(nèi)容中匹配搜索關(guān)鍵字阎曹,這種方法是順序掃描方法等限,數(shù)據(jù)量大、搜索慢芬膝。
倒排索引結(jié)構(gòu)是根據(jù)內(nèi)容(詞語)找文檔望门,如下圖:
倒排索引結(jié)構(gòu)也叫反向索引結(jié)構(gòu),包括索引和文檔兩部分锰霜,索引即詞匯表筹误,它的規(guī)模較小,而文檔集合較大癣缅。
3.3 查詢索引
? 查詢索引也是搜索的過程厨剪。搜索就是用戶輸入關(guān)鍵字,從索引(index)中進行搜索的過程友存。根據(jù)關(guān)鍵字搜索索引祷膳,根據(jù)索引找到對應(yīng)的文檔,從而找到要搜索的內(nèi)容(這里指磁盤上的文件)屡立。
? 3.3.1. 用戶查詢接口
?全文檢索系統(tǒng)提供用戶搜索的界面供用戶提交搜索的關(guān)鍵字直晨,搜索完成展示搜索結(jié)果。
? 比如:
? Lucene不提供制作用戶搜索界面的功能,需要根據(jù)自己的需求開發(fā)搜索界面勇皇。
? 3.3.2. 創(chuàng)建查詢
? 用戶輸入查詢關(guān)鍵字執(zhí)行搜索之前需要先構(gòu)建一個查詢對象罩句,查詢對象中可以指定查詢要搜索的Field文檔域、查詢關(guān)鍵字等敛摘,查詢對象會生成具體的查詢語法门烂,
? 例如:
? 語法“fileName:lucene”
表示要搜索Field域的內(nèi)容為“l(fā)ucene”的文檔
? 3.3.3. 執(zhí)行查詢
- 搜索索引過程:
根據(jù)查詢語法在倒排索引詞典表中分別找出對應(yīng)搜索詞的索引,從而找到索引所鏈接的文檔鏈表兄淫。
比如搜索語法為“fileName:lucene”表示搜索出fileName域中包含Lucene的文檔屯远。
搜索過程就是在索引上查找域為fileName,并且關(guān)鍵字為Lucene的term捕虽,并根據(jù)term找到文檔id列表氓润。
? 3.3.4. 渲染結(jié)果
- 以一個友好的界面將查詢結(jié)果展示給用戶,用戶根據(jù)搜索結(jié)果找自己想要的信息薯鳍,為了幫助用戶很快找到自己的結(jié)果,提供了很多展示的效果挨措,比如搜索結(jié)果中將關(guān)鍵字高亮顯示挖滤,百度提供的快照等。
4. 配置環(huán)境開發(fā)
4.1. Lucene下載
Lucene是開發(fā)全文檢索功能的工具包浅役,從官方網(wǎng)站下載Lucene4.10.3斩松,并解壓。
官方網(wǎng)站:http://lucene.apache.org/
版本:lucene4.10.3
Jdk要求:1.7以上
IDE:Eclipse
4.2. javaSE項目中使用的jar包
jar包 | jar包位置 | ? |
---|---|---|
lucene-4.10.3\core | Lucene包 | |
lucene-4.10.3\analysis\common | Lucene包 | |
lucene-4.10.3\queryparser | Lucene包 | |
commons-io-2.4.jar | ? | IO流包 |
5. 功能一:創(chuàng)建索引庫
使用 IndexWriter 對象創(chuàng)建
5.1 實現(xiàn)步驟
1.
創(chuàng)建一個java工程觉既,并導入jar包惧盹。
2.
創(chuàng)建一個indexwriter對象。
? 2.1
指定索引庫的存放位置Directory對象
? 2.2
指定一個分析器瞪讼,對文檔內(nèi)容進行分析钧椰。
3.
創(chuàng)建document對象。
4.
創(chuàng)建field對象符欠,將field添加到document對象中嫡霞。
5.
使用indexwriter對象將document對象寫入索引庫,此過程進行索引創(chuàng)建希柿。并將索引和document對象寫入索引庫诊沪。
6.
關(guān)閉IndexWriter對象。
5.2. Field域的屬性
是否分析:是否對域的內(nèi)容進行分詞處理曾撤。前提是我們要對域的內(nèi)容進行查詢端姚。
是否索引:將Field分析后的詞或整個Field值進行索引,只有索引方可搜索到挤悉。
比如:商品名稱渐裸、商品簡介分析后進行索引,訂單號、身份證號不用分析但也要索引橄仆,這些將來都要作為查詢條件剩膘。
是否存儲:將Field值存儲在文檔中,存儲在文檔中的Field才可以從Document中獲取
比如:商品名稱盆顾、訂單號怠褐,凡是將來要從Document中獲取的Field都要存儲。
是否存儲的標準:是否要將內(nèi)容展示給用戶
Field類 | 數(shù)據(jù)類型 | Analyzed是否分析 | Indexed是否索引 | Stored是否存儲 | 說明 |
---|---|---|---|---|---|
StringField(FieldName , FieldValue ,Store.YES ) |
字符串 | N | Y | Y或N | 這個Field用來構(gòu)建一個字符串Field您宪,但是不會進行分析奈懒,會將整個串存儲在索引中,比如(訂單號,姓名等) 是否存儲在文檔中用Store.YES或Store.NO決定 |
LongField(FieldName , FieldValue ,Store.YES ) |
Long型 | Y | Y | Y或N | 這個Field用來構(gòu)建一個Long數(shù)字型Field宪巨,進行分析和索引磷杏,比如(價格) 是否存儲在文檔中用Store.YES或Store.NO決定 |
StoredField(FieldName , FieldValue ) |
重載方法,支持多種類型 | N | N | Y | 這個Field用來構(gòu)建不同類型Field不分析捏卓,不索引极祸,但要Field存儲在文檔中 |
TextField(FieldName , FieldValue , Store.NO )或TextField(FieldName, reader ) |
字符串/流 | Y | Y | Y或N | 如果是一個Reader, lucene猜測內(nèi)容比較多,會采用Unstored的策略 |
5.3 代碼實現(xiàn):創(chuàng)建索引庫
@Test
public void createIndex() throws Exception {
// 1.指定索引庫存放的位置,可以是內(nèi)存也可以是磁盤
// Directory directory = new RAMDirectory();//保存到內(nèi)存中一般不用
Directory directory = FSDirectory.open(new File("c:\\tem\\index"));
// 2.創(chuàng)建一個IndexWriter對象怠晴,需要一個分析器對象遥金。
//Analyzer analyzer = new StandardAnalyzer();// 分析器
IKAnalyzer analyzer = new IKAnalyzer();
// 參數(shù)1:lucene的版本號,參數(shù)2:分析器對象
IndexWriterConfig conf = new IndexWriterConfig(Version.LATEST, analyzer);
// 參數(shù)1:索引庫存放的路徑蒜田,參數(shù)2:配置信息稿械,其中包含分析器對象
IndexWriter indexWriter = new IndexWriter(directory, conf);
// 3.獲得原始文檔,使用io流讀取文本文件
File docPath = new File("E:\\File\\lucene&solr\\資料\\searchsource");
for (File f : docPath.listFiles()) {
// 取文件名
String fileName = f.getName();
// 取文件路徑
String filePath = f.getPath();
// 文件內(nèi)容
String fileContent = FileUtils.readFileToString(f);
// 文件的大小
long fileSize = FileUtils.sizeOf(f);
// 4.創(chuàng)建文檔對象
Document document = new Document();
// 5.創(chuàng)建域
// 參數(shù)1:域的名稱 冲粤。參數(shù)2:域的內(nèi)容 美莫。參數(shù)3:是否存儲
TextField fileNameFiled = new TextField("name", fileName, Store.YES);
StoredField filePathField = new StoredField("path", filePath);
TextField fileContentField = new TextField("content", fileContent, Store.NO);
LongField fileSizeField = new LongField("size", fileSize, Store.YES);
// 6.向文檔中添加域
document.add(fileNameFiled);
document.add(filePathField);
document.add(fileContentField);
document.add(fileSizeField);
// 7.把文檔對象寫入索引庫
indexWriter.addDocument(document);
}
// 8.關(guān)閉IndexWriter對象
indexWriter.close();
}
6. 功能二:查詢索引
6.1 實現(xiàn)步驟
1.
創(chuàng)建一個Directory對象,也就是索引庫存放的位置梯捕。
2.
創(chuàng)建一個indexReader對象厢呵,需要指定Directory對象。
3.
創(chuàng)建一個indexsearcher對象傀顾,需要指定IndexReader對象
4.
創(chuàng)建一個TermQuery對象述吸,指定查詢的域和查詢的關(guān)鍵詞。
5.
執(zhí)行查詢锣笨。
6.
返回查詢結(jié)果蝌矛。遍歷查詢結(jié)果并輸出。
7.
關(guān)閉IndexReader對象
6.2 IndexSearcher
搜索方法
方法 | 說明 |
---|---|
indexSearcher.search(query, n ) |
根據(jù)Query 搜索错英,返回評分最高的n 條記錄 |
indexSearcher.search(query , filter , n ) |
根據(jù)Query 搜索入撒,添加過濾策略,返回評分最高的n 條記錄 |
indexSearcher.search(query ,n , sort ) |
根據(jù)Query 搜索椭岩,添加排序策略茅逮,返回評分最高的n 條記錄 |
indexSearcher.search(booleanQuery , filter ,n , sort)
|
根據(jù)Query 搜索璃赡,添加過濾策略,添加排序策略献雅,返回評分最高的n 條記錄 |
6.3 代碼實現(xiàn)
/**
* 查詢索引庫
* @throws Exception
*/
@Test
public void searchIndex() throws Exception {
// 1.指定索引庫存放的位置
Directory directory = FSDirectory.open(new File("C:\\tem\\index"));
// 2.使用IndexReader對象打開索引庫
IndexReader indexReader = DirectoryReader.open(directory);
// 3.創(chuàng)建一個IndexSearcher對象碉考,構(gòu)造方法需要一個IndexReader對象
IndexSearcher indexSearcher = new IndexSearcher(indexReader);
// 4.創(chuàng)建一個查詢對象,需要指定查詢域及要查詢的關(guān)鍵子
// term的參數(shù)1:要搜索的域 參數(shù)2:要搜索的關(guān)鍵字
Query query = new TermQuery(new Term("name", "apache"));
// 參數(shù)1:查詢條件 參數(shù)2:返回的結(jié)果數(shù)量
// 5.取查詢結(jié)果
TopDocs topDocs = indexSearcher.search(query, 10);
// 取查詢結(jié)果總記錄數(shù)
System.out.println("查詢結(jié)果總記錄數(shù):" + topDocs.totalHits);
// 6.遍歷查詢結(jié)果并打印
for (ScoreDoc doc : topDocs.scoreDocs) {
// 取文檔id
int id = doc.doc;
// 從索引庫中取文檔對象
Document document = indexSearcher.doc(id);
// 取屬性
System.out.println(document.get("name"));
System.out.println(document.get("size"));
System.out.println(document.get("content"));
System.out.println(document.get("path"));
}
// 7.關(guān)閉IndexReaer對象
indexReader.close();
}
6.4 TopDocs對象
Lucene搜索結(jié)果可通過TopDocs遍歷挺身,TopDocs類提供了少量的屬性侯谁,如下:
方法或?qū)傩?/th> | 說明 |
---|---|
totalHits | 匹配搜索條件的總記錄數(shù) |
scoreDocs | 頂部匹配記錄 |
注意:
Search方法需要指定匹配記錄數(shù)量n
:indexSearcher.search(query
,n
)
TopDocs.totalHits
:是匹配索引庫中所有記錄的數(shù)量
TopDocs.scoreDocs
:匹配相關(guān)度高的前邊記錄數(shù)組,scoreDocs
的長度小于等于search方法指定的參數(shù)n
7. 功能三:支持中文分詞
7.1. 分析器(Analyzer)的執(zhí)行過程
如下圖是語匯單元的生成過程:
從一個Reader字符流開始章钾,創(chuàng)建一個基于Reader的Tokenizer分詞器墙贱,經(jīng)過三個TokenFilter生成語匯單元Token。
要看分析器的分析效果贱傀,只需要看Tokenstream中的內(nèi)容就可以了惨撇。每個分析器都有一個方法tokenStream,返回一個tokenStream對象府寒。
7.2 分析器的分詞效果
//查看標準分析器的分詞效果
public void testTokenStream() throws Exception {
//創(chuàng)建一個標準分析器對象
Analyzer analyzer = new StandardAnalyzer();
//獲得tokenStream對象
//第一個參數(shù):域名魁衙,可以隨便給一個
//第二個參數(shù):要分析的文本內(nèi)容
TokenStream tokenStream = analyzer.tokenStream("test", "The Spring Framework provides a comprehensive programming and configuration model.");
//添加一個引用,可以獲得每個關(guān)鍵詞
CharTermAttribute charTermAttribute = tokenStream.addAttribute(CharTermAttribute.class);
//添加一個偏移量的引用株搔,記錄了關(guān)鍵詞的開始位置以及結(jié)束位置
OffsetAttribute offsetAttribute = tokenStream.addAttribute(OffsetAttribute.class);
//將指針調(diào)整到列表的頭部
tokenStream.reset();
//遍歷關(guān)鍵詞列表剖淀,通過incrementToken方法判斷列表是否結(jié)束
while(tokenStream.incrementToken()) {
//關(guān)鍵詞的起始位置
System.out.println("start->" + offsetAttribute.startOffset());
//取關(guān)鍵詞
System.out.println(charTermAttribute);
//結(jié)束位置
System.out.println("end->" + offsetAttribute.endOffset());
}
tokenStream.close();
}
7.3 中文分析器
? 7.3.1 Lucene自帶中文分詞器
- StandardAnalyzer:
單字分詞:就是按照中文一個字一個字地進行分詞。如:“我愛中國”邪狞,
效果:“我”、“愛”茅撞、“中”帆卓、“國”。 - CJKAnalyzer
二分法分詞:按兩個字進行切分米丘。如:“我是中國人”剑令,效果:“我是”、“是中”拄查、“中國”“國人”吁津。
上邊兩個分詞器無法滿足需求。
- SmartChineseAnalyzer
對中文支持較好堕扶,但擴展性差碍脏,擴展詞庫,禁用詞庫和同義詞庫等不好處理
? 7.3.2 第三方周顧問分析器
mmseg4j:最新版已從 https://code.google.com/p/mmseg4j/ 移至 https://github.com/chenlb/mmseg4j-solr稍算,支持Lucene 4.10,用了mmseg算法典尾。
IK-analyzer: 最新版在https://code.google.com/p/ik-analyzer/上,支持Lucene 4.10從2006年12月推出1.0版開始糊探, 但是也就是2012年12月后沒有在更新钾埂。
ansj_seg:最新版本在 https://github.com/NLPchina/ansj_seg tags僅有1.1版本河闰,現(xiàn)在由”nlp_china”管理。2014年11月有更新褥紫。是一個由CRF(條件隨機場)算法所做的分詞算法姜性。
Jcseg:最新版本在https://git.oschina.net/lionsoul/jcseg,支持Lucene 4.10髓考,作者有較高的活躍度部念。利用mmseg算法。
? 7.3.3 IKAnalyzer
使用方法:
第一步:把jar包添加到工程中
第二步:把配置文件
IKAnalyzer.cfg.xml
和擴展詞典ext.dic
和停用詞詞典stopword.dic
添加到classpath下注意:ext.dic和stopword.dic文件的格式為UTF-8绳军,注意是無BOM的UTF-8編碼印机。
7.4. Analyzer使用時機
? 7.4.1 搜索時使用Analyzer
? 輸入關(guān)鍵字進行搜索,當需要讓該關(guān)鍵字與文檔域內(nèi)容所包含的詞進行匹配時需要對文檔域內(nèi)容進行分析门驾,需要經(jīng)過Analyzer分析器處理生成語匯單元(Token)射赛。分析器分析的對象是文檔中的Field域。當Field的屬性tokenized(是否分詞)為true時會對Field值進行分析奶是,如下圖:
對于一些Field可以不用分析:
- 不作為查詢條件的內(nèi)容楣责,比如文件路徑
- 不是匹配內(nèi)容中的詞而匹配Field的整體內(nèi)容,比如訂單號聂沙、身份證號等秆麸。
? 7.4.2 搜索時使用Analyzer
? 對搜索關(guān)鍵字進行分析和索引分析一樣,使用Analyzer對搜索關(guān)鍵字進行分析及汉、分詞處理沮趣,使用分析后每個詞語進行搜索。比如:搜索關(guān)鍵字:spring web 坷随,經(jīng)過分析器進行分詞房铭,得出:spring web拿詞去索引詞典表查找 ,找到索引鏈接到Document温眉,解析Document內(nèi)容缸匪。
對于匹配整體Field域的查詢可以在搜索時不分析,比如根據(jù)訂單號、身份證號查詢等。
注意:搜索使用的分析器要和索引使用的分析器一致闲礼。
8. 功能四:索引庫的維護
8.1 索引庫的添加
? 8.1.1. 步驟
? 向索引庫中添加document對象。
? 1.
先創(chuàng)建一個indexwriter對象
? 2.
創(chuàng)建一個document對象
? 3.
把document對象寫入索引庫
? 4.
關(guān)閉indexwriter砂心。
? 8.1.2. 代碼實現(xiàn)
//添加索引
@Test
public void addDocument() throws Exception {
//指定索引庫位置
Directory directory = FSDirectory.open(new File("c:\\tem\\index"));
//創(chuàng)建分詞分析器
IKAnalyzer analyzer = new IKAnalyzer();
//創(chuàng)建IndexWriter的config
IndexWriterConfig config = new IndexWriterConfig(Version.LATEST, analyzer);
//創(chuàng)建IndexWriter
IndexWriter indexWriter = new IndexWriter(directory, config);
//創(chuàng)建文檔
Document document = new Document();
//創(chuàng)建域
TextField fileNameField = new TextField("name","測試文本.txt",Store.YES);
StoredField filePathField = new StoredField("path", "c:\\tem\\測試文本.txt");
//向document對象中添加域。
//不同的document可以有不同的域蛇耀,同一個document可以有相同的域计贰。
document.add(new TextField("filename", "新添加的文檔", Store.YES));
document.add(new TextField("content", "新添加的文檔的內(nèi)容", Store.NO));
document.add(new TextField("content", "新添加的文檔的內(nèi)容第二個content", Store.YES));
document.add(new TextField("content1", "新添加的文檔的內(nèi)容要能看到", Store.YES));
//添加文檔到索引庫
indexWriter.addDocument(document);
indexWriter.close();
}
8.2 索引庫刪除
? 8.2.1. 刪除全部
//刪除全部索引
@Test
public void deleteAllIndex() throws Exception {
//指定索引庫位置
Directory directory = FSDirectory.open(new File("c:\\tem\\index"));
//創(chuàng)建分詞分析器
IKAnalyzer analyzer = new IKAnalyzer();
//創(chuàng)建IndexWriter的config
IndexWriterConfig config = new IndexWriterConfig(Version.LATEST, analyzer);
//創(chuàng)建IndexWriter
IndexWriter indexWriter = new IndexWriter(directory, config);
//刪除全部索引
indexWriter.deleteAll();
//關(guān)閉indexwriter
indexWriter.close();
}
說明:將索引目錄的索引信息全部刪除,直接徹底刪除蒂窒,無法恢復躁倒。
? 8.2.2. 指定查詢條件刪除
//根據(jù)查詢條件刪除索引
@Test
public void deleteIndexByQuery() throws Exception {
//指定索引庫位置
Directory directory = FSDirectory.open(new File("c:\\tem\\index"));
//創(chuàng)建分詞分析器
IKAnalyzer analyzer = new IKAnalyzer();
//創(chuàng)建IndexWriter的config
IndexWriterConfig config = new IndexWriterConfig(Version.LATEST, analyzer);
//創(chuàng)建IndexWriter
IndexWriter indexWriter = new IndexWriter(directory, config);
//創(chuàng)建一個查詢條件
Query query = new TermQuery(new Term("filename", "apache"));
//根據(jù)查詢條件刪除
indexWriter.deleteDocuments(query);
//關(guān)閉indexwriter
indexWriter.close();
}
8.3 索引庫的修改
原理就是先刪除后添加
//修改索引庫
@Test
public void updateIndex() throws Exception {
//指定索引庫位置
Directory directory = FSDirectory.open(new File("c:\\tem\\index"));
//創(chuàng)建分詞分析器
IKAnalyzer analyzer = new IKAnalyzer();
//創(chuàng)建IndexWriter的config
IndexWriterConfig config = new IndexWriterConfig(Version.LATEST, analyzer);
//創(chuàng)建IndexWriter
IndexWriter indexWriter = new IndexWriter(directory, config);
//創(chuàng)建一個Document對象
Document document = new Document();
//向document對象中添加域荞怒。
//不同的document可以有不同的域,同一個document可以有相同的域秧秉。
document.add(new TextField("filename", "要更新的文檔", Store.YES));
document.add(new TextField("content", "2013年11月18日 - Lucene 簡介 Lucene 是一個基于 Java 的全文信息檢索工具包,它不是一個完整的搜索應(yīng)用程序,而是為你的應(yīng)用程序提供索引和搜索功能褐桌。", Store.YES));
indexWriter.updateDocument(new Term("content", "java"), document);
//關(guān)閉indexWriter
indexWriter.close();
}
9. Lucene索引庫查詢
Lucene的查詢:
- 使用Query的子類查詢
- MatchAllDocsQuery
- TermQuery
- NumericRangeQuery
- BooleanQuery
- 使用QueryParser
- QueryParser
- MulitFieldQueryParser
? 對要搜索的信息創(chuàng)建Query查詢對象,Lucene會根據(jù)Query查詢對象生成最終的查詢語法象迎,類似關(guān)系數(shù)據(jù)庫Sql語法一樣Lucene也有自己的查詢語法荧嵌,比如:“name:lucene”
表示查詢Field
的name
為“l(fā)ucene”
的文檔信息。
可通過兩種方法創(chuàng)建查詢對象:
- 使用Lucene提供Query子類
Query是一個抽象類砾淌,lucene提供了很多查詢對象啦撮,比如TermQuery項精確查詢,NumericRangeQuery數(shù)字范圍查詢等汪厨。
如下代碼:
Query query = new TermQuery(new Term("name", "lucene"));
- 使用QueryParse解析查詢表達式
QueryParse會將用戶輸入的查詢表達式解析成Query對象實例赃春。
如下代碼:
QueryParser queryParser = new QueryParser("name", new IKAnalyzer());
Query query = queryParser.parse("name:lucene");
9.1 使用query的子類查詢
? 9.1.1 MatchAllDocsQuery 查詢索引目錄中的所有文檔
/**
* 使用MatchAllDocsQuery查詢索引目錄中的所有文檔
*/
@Test
public void testMatchAllDocsQuery() throws Exception {
//指定索引庫存放的位置
Directory directory = FSDirectory.open(new File("C:\\tem\\index"));
//創(chuàng)建一個IndexReader對象
DirectoryReader indexReader = DirectoryReader.open(directory);
//創(chuàng)建IndexSearcher對象
IndexSearcher indexSearcher = new IndexSearcher(indexReader);
//創(chuàng)建一個Query對象
Query query = new MatchAllDocsQuery();
System.out.println(query);//*:*
//查詢索引庫獲取查詢結(jié)果
TopDocs topDocs = indexSearcher.search(query, 100);
ScoreDoc[] scoreDocs = topDocs.scoreDocs;
System.out.println("查詢結(jié)果總記錄數(shù):"+topDocs.totalHits);
//遍歷查詢結(jié)果
for (ScoreDoc scoreDoc : scoreDocs) {
int id = scoreDoc.doc;//獲取每個文檔的id
Document document = indexSearcher.doc(id);//同過id查詢文檔對象
//獲取文檔對象的屬性
System.out.println(document.get("name"));
System.out.println(document.get("size"));
System.out.println(document.get("path"));
System.out.println(document.get("content"));
}
//關(guān)閉索引庫
indexReader.close();
}
? 9.1.2 TermQuery 指定要查詢的域和要查詢的關(guān)鍵詞
//使用Termquery查詢
@Test
public void testTermQuery() throws Exception {
IndexSearcher indexSearcher = getIndexSearcher();
//創(chuàng)建查詢對象
Query query = new TermQuery(new Term("content", "lucene"));
//執(zhí)行查詢
TopDocs topDocs = indexSearcher.search(query, 10);
//共查詢到的document個數(shù)
System.out.println("查詢結(jié)果總數(shù)量:" + topDocs.totalHits);
//遍歷查詢結(jié)果
for (ScoreDoc scoreDoc : topDocs.scoreDocs) {
Document document = indexSearcher.doc(scoreDoc.doc);
System.out.println(document.get("filename"));
//System.out.println(document.get("content"));
System.out.println(document.get("path"));
System.out.println(document.get("size"));
}
//關(guān)閉indexreader
indexSearcher.getIndexReader().close();
}
? 9.1.3 NumericRangeQuery 可以根據(jù)數(shù)值范圍查詢
//創(chuàng)建一個數(shù)值范圍查詢對象
//參數(shù)1:要查詢的域 參數(shù)2:最小值 參數(shù)3:最大值 參數(shù)4:是否包含最小值 參數(shù)5:是否包含最大值
Query query = NumericRangeQuery.newLongRange("size", 1000l, 10000l, false, true);
System.out.println(query);//size:{1000 TO 10000]
//打印結(jié)果
//printResult , getIndexSearcher 抽取通用的方法
printResult(getIndexSearcher(), query);
? 9.1.3 BooleanQuery 可可以組合查詢條件
@Test
public void testBooleandQuery() throws Exception {
//創(chuàng)建一個BooleanQuery對象
BooleanQuery booleanQuery = new BooleanQuery();
//創(chuàng)建子查詢,文件大于1000小于10000
Query query1 = NumericRangeQuery.newLongRange("size", 1000l, 10000l, false, true);
//文件命中包含mybatis關(guān)鍵字
TermQuery query2 = new TermQuery(new Term("name","mybatis"));
//添加包BooleanQuery對象中
booleanQuery.add(query1,Occur.MUST);
booleanQuery.add(query2,Occur.MUST);
//執(zhí)行查詢
printResult(getIndexSearcher(), booleanQuery);
}
Occur.MUST:必須滿足此條件劫乱,相當于and
Occur.SHOULD:應(yīng)該滿足织中,但是不滿足也可以,相當于or
Occur.MUST_NOT:必須不滿足衷戈。相當于not
9.2 使用queryparser查詢
? 通過QueryParser也可以創(chuàng)建Query狭吼,QueryParser提供一個Parse方法,此方法可以直接根據(jù)查詢語法來查詢殖妇。Query對象執(zhí)行的查詢語法可通過System.out.println(query);查詢刁笙。
需要使用到分析器。建議創(chuàng)建索引時使用的分析器和查詢索引時使用的分析器要一致谦趣。
? 9.2.1 queryParser查詢
? 加入queryParser依賴的jar包
/**
* QueryParser 查詢
*/
@Test
public void testQueryParser() throws Exception {
//創(chuàng)建一個QueryPareser對象疲吸。 參數(shù)1:默認搜索域 參數(shù)2:分析器對象
QueryParser queryParser = new QueryParser("name",new IKAnalyzer());
//調(diào)用pares方法可以獲得一個Query對象
//參數(shù):要查詢的內(nèi)容,可以是一句話蔚润。先分詞在查詢
//Query query = queryParser.parse("cotent:mybatis");
Query query = queryParser.parse("mybatis is a apache project");
printResult(getIndexSearcher(), query);
}
查詢語法
- 基礎(chǔ)的查詢語法磅氨,關(guān)鍵詞查詢:
域名+“:”+搜索的關(guān)鍵字
例如:content:java - 范圍查詢
域名+“:”+[最小值 TO 最大值]
例如:size:[1 TO 1000]
范圍查詢在lucene中不支持數(shù)值類型尺栖,支持字符串類型嫡纠。在solr中支持數(shù)值類型。 - 組合條件查詢
- +條件1 +條件2:兩個條件之間是并且的關(guān)系and
例如:+filename:apache +content:apache - +條件1 條件2:必須滿足第一個條件延赌,應(yīng)該滿足第二個條件
例如:+filename:apache content:apache - 條件1 條件2:兩個條件滿足其一即可除盏。
例如:filename:apache content:apache - -條件1 條件2:必須不滿足條件1,要滿足條件2
例如:-filename:apache content:apache
- +條件1 +條件2:兩個條件之間是并且的關(guān)系and
Occur.MUST 查詢條件必須滿足挫以,相當于and | +(加號 |
---|---|
Occur.SHOULD 查詢條件可選者蠕,相當于or | 空(不用符號) |
Occur.MUST 查詢條件必須滿足,相當于and | +(加號 |
第二種寫法:
條件1 AND 條件2
條件1 OR 條件2
條件1 NOT 條件2
? 9.2.2 MulitFieldQueryParser查詢
? 可以指定多個默認搜索域
/**
* MulitFieldQueryParser 可以指定多個默認搜索域
*/
@Test
public void testMultiFileQuryParser() throws Exception {
//指定默認搜索域
String[] fields = {"name","content"};
MultiFieldQueryParser queryParser = new MultiFieldQueryParser(fields, new IKAnalyzer());
Query query = queryParser.parse("mytatis is a apache project");
System.out.println(query);
printResult(getIndexSearcher(), query);
}