描述
我們生活中的數(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文檔等磁盤上的文件
非結(jié)構(gòu)化數(shù)據(jù)查詢方法
- 順序掃描法(Serial Scanning)
所謂順序掃描,比如要找內(nèi)容包含某一個字符串的文件,就是一個文檔一個文檔的看桅锄,對于每一個文檔,從頭看到尾样眠,如果此文檔包含此字符串友瘤,則此文檔為我們要找的文件,接著看下一個文件檐束,直到掃描完所有的文件辫秧。如利用windows的搜索也可以搜索文件內(nèi)容,只是相當(dāng)?shù)穆?/li> - 全文檢索(Full-text Search)
將非結(jié)構(gòu)化數(shù)據(jù)中的一部分信息提取出來被丧,重新組織盟戏,使其變得有一定結(jié)構(gòu)绪妹,然后對此有一定結(jié)構(gòu)的數(shù)據(jù)進(jìn)行搜索,從而達(dá)到搜索相對較快的目的柿究。這部分從非結(jié)構(gòu)化數(shù)據(jù)中提取出的然后重新組織的信息邮旷,我們稱之索引。
實現(xiàn)全文檢索的方式
可以使用Lucene實現(xiàn)全文檢索蝇摸。Lucene是apache下的一個開放源代碼的全文檢索引擎工具包婶肩。提供了完整的查詢引擎和索引引擎,部分文本分析引擎貌夕。Lucene的目的是為軟件開發(fā)人員提供簡單易用的工具包律歼,以方便的在目標(biāo)系統(tǒng)中實現(xiàn)全文檢索的功能
Lucene實現(xiàn)全文檢索的流程
索引和搜索流程圖
圖中綠色表示索引過程,對要搜索的原始內(nèi)容進(jìn)行索引構(gòu)建一個索引表啡专。索引過程包括:確定原始內(nèi)容即要搜索的內(nèi)容---》采集文檔---》創(chuàng)建文檔 ---》分析文檔 ---》索引文檔
紅色表示搜索過程苗膝,從索引庫中搜索內(nèi)容,搜索過程包括:用戶通過搜索界面---》創(chuàng)建查詢---》執(zhí)行搜索 從索引庫搜索---》渲染搜索結(jié)果
創(chuàng)建索引
獲得原始文檔
lucene不提供信息采集的類庫植旧,需要用戶自己實現(xiàn)一個爬蟲程序辱揭。
創(chuàng)建文檔對象
獲取原始內(nèi)容是為了簡歷索引,在索引前需要將原始內(nèi)容創(chuàng)建文檔病附,文檔中包括一個一個的域问窃,域中存儲內(nèi)容
每個Document可以有多個Field,不同的Document可以有不同的Field,同一個Document可以有相同的Field
每個Document都有一個唯一的編號,就是文檔id完沪,id從0開始域庇,并且自增長
分析文檔
將原始內(nèi)容創(chuàng)建為包含域(Field)的文檔(document),需要再對域中的內(nèi)容進(jìn)行分析覆积,分析的過程是經(jīng)過對原始文檔提取單次听皿、將字母轉(zhuǎn)為小寫、去除標(biāo)點符號宽档、去除停用詞等過程生成最終的語匯單元尉姨。
一個語匯單元稱為一個Term,不同的域中拆出來的相同的單詞是不同的Term吗冤,term中包含兩部分一部分是文檔的域名又厉,另一部分是單詞的內(nèi)容
eg:文件名中包含apache和文件內(nèi)容中包含的apache是不同的term
確定兩個term是不是同一個term看兩個地方,一個是域名是否相同 第二個是內(nèi)容是否相同
創(chuàng)建索引
對所有文檔分析得出的語匯單元進(jìn)行索引椎瘟,索引的目的是為了搜索覆致,最終要實現(xiàn)只搜索被索引的語匯單元從而找到Document(文檔)。
找到的Document按照索引出現(xiàn)的次數(shù)從高到低排序
注意:創(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)容(詞語)找文檔鉴扫,如下圖:
查詢索引
查詢索引也是搜索的過程赞枕。搜索就是用戶輸入關(guān)鍵字澈缺,從索引(index)中進(jìn)行搜索的過程。根據(jù)關(guān)鍵字搜索索引炕婶,根據(jù)索引找到對應(yīng)的文檔,從而找到要搜索的內(nèi)容(這里指磁盤上的文件)柠掂。
執(zhí)行查詢
搜索索引過程:
根據(jù)查詢語法在倒排索引詞典表中分別找出對應(yīng)搜索詞的索引项滑,從而找到索引所鏈接的文檔鏈表。
比如搜索語法為“fileName:lucene”表示搜索出fileName域中包含Lucene的文檔涯贞。
搜索過程就是在索引上查找域為fileName宋渔,并且關(guān)鍵字為Lucene的term州疾,并根據(jù)term找到文檔id列表。
渲染結(jié)果
使用Lucene開發(fā)
下載開發(fā)環(huán)境
Lucene是開發(fā)全文檢索功能的工具包皇拣,從官方網(wǎng)站下載Lucene4.10.3严蓖,并解壓。
官方網(wǎng)站:http://lucene.apache.org/
版本:lucene4.10.3
Jdk要求:1.7以上
jar包
Lucene包:
lucene-core-4.10.3.jar
lucene-analyzers-common-4.10.3.jar
lucene-queryparser-4.10.3.jar
其它:
commons-io-2.4.jar
junit-4.9.jar
創(chuàng)建索引庫
使用indexwriter對象創(chuàng)建索引
創(chuàng)建索引庫步驟
1.創(chuàng)建indexWriter對象
1.1 指定索引庫的存放位置Directory對象
1.2 指定一個分析器氧急,對文檔內(nèi)容進(jìn)行分析
2.創(chuàng)建Document對象
3.創(chuàng)建Field對象颗胡,將Field添加到Document對象中
4.使用indexwriter對象將document對象寫入索引庫,此過程進(jìn)行索引創(chuàng)建吩坝。并將索引和Document對象寫入索引庫
5.關(guān)閉IndexWriter對象
Field域的屬性
是否分析:是否對域的內(nèi)容進(jìn)行分詞處理毒姨。前提是我們要對域的內(nèi)容進(jìn)行查詢。
是否索引:將Field分析后的詞或整個Field值進(jìn)行索引钉寝,只有索引方可搜索到手素。
比如:商品名稱、商品簡介分析后進(jìn)行索引瘩蚪,訂單號泉懦、身份證號不用分析但也要索引,這些將來都要作為查詢條件疹瘦。
是否存儲:將Field值存儲在文檔中崩哩,存儲在文檔中的Field才可以從Document中獲取
比如:商品名稱、訂單號,凡是將來要從Document中獲取的Field都要存儲邓嘹。
Field類 | 數(shù)據(jù)類型 | Analyzed是否分析 | Indexed是否索引 | Stored是否存儲 | 說明 |
---|---|---|---|---|---|
StringField(FieldName, FieldValue,Store.YES)) | 字符串 | N | Y | Y或N | 這個Field用來構(gòu)建一個字符串Field酣栈,但是不會進(jìn)行分析,會將整個串存儲在索引中汹押,比如(訂單號,姓名等)是否存儲在文檔中用Store.YES或Store.NO決定 |
LongField(FieldName, FieldValue,Store.YES) | Long型 | Y | Y | Y或N | 這個Field用來構(gòu)建一個Long數(shù)字型Field矿筝,進(jìn)行分析和索引,比如(價格)是否存儲在文檔中用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的策略. |
@Test
public void createIndex() throws IOException {
//創(chuàng)建分析器
StandardAnalyzer analyzer = new StandardAnalyzer();
//創(chuàng)建IndexWriterConfig對象
IndexWriterConfig config = new IndexWriterConfig(Version.LATEST, analyzer);
//創(chuàng)建Directory對象
FSDirectory directory = FSDirectory.open(new File("/Users/wxblack-mac/temp/index"));
//創(chuàng)建IndexWriter對象
IndexWriter indexWriter = new IndexWriter(directory, config);
// 遍歷文件
File file = new File("/Users/wxblack-mac/temp/searchsource");
File[] files = file.listFiles();
for (File file1 : files) {
//創(chuàng)建Document對象
Document document = new Document();
//文件名
String fileName = file1.getName();
Field fileNameField = new TextField("fileNameField", fileName, Field.Store.YES);
//文件大小
long fileSize = FileUtils.sizeOf(file1);
LongField fileSizeField = new LongField("fileSizeField", fileSize, Field.Store.YES);
//文件路徑
String filePath = file1.getPath();
TextField filePathField = new TextField("filePathField", filePath, Field.Store.YES);
//文件內(nèi)容
String fileContent = FileUtils.readFileToString(file1);
TextField fileContentField = new TextField("fileContentField", fileContent, Field.Store.YES);
document.add(fileNameField);
document.add(fileSizeField);
document.add(filePathField);
document.add(fileContentField);
//將document對象寫入索引庫
indexWriter.addDocument(document);
}
//釋放資源
indexWriter.close();
}
LUCK 工具查看索引
查詢索引
實現(xiàn)步驟
第一步:創(chuàng)建一個Directory對象妙痹,也就是索引庫存放的位置铸史。
第二步:創(chuàng)建一個indexReader對象,需要指定Directory對象怯伊。
第三步:創(chuàng)建一個indexsearcher對象琳轿,需要指定IndexReader對象
第四步:創(chuàng)建一個TermQuery對象,指定查詢的域和查詢的關(guān)鍵詞耿芹。
第五步:執(zhí)行查詢崭篡。
第六步:返回查詢結(jié)果。遍歷查詢結(jié)果并輸出吧秕。
第七步:關(guān)閉IndexReader對象
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條記錄 |
@Test
public void testSearch() throws IOException {
// 第一步:創(chuàng)建一個Directory對象绽淘,也就是索引庫存放的位置涵防。
FSDirectory directory = FSDirectory.open(new File("/Users/wxblack-mac/temp/index"));
// 第二步:創(chuàng)建一個indexReader對象,需要指定Directory對象沪铭。
IndexReader indexReader = DirectoryReader.open(directory);
// 第三步:創(chuàng)建一個indexsearcher對象壮池,需要指定IndexReader對象
IndexSearcher searcher = new IndexSearcher(indexReader);
// 第四步:創(chuàng)建一個TermQuery對象,指定查詢的域和查詢的關(guān)鍵詞杀怠。
TermQuery termQuery = new TermQuery(new Term("fileNameField", "apache"));
// 第五步:執(zhí)行查詢椰憋。
TopDocs topDocs = searcher.search(termQuery, 2);
// 第六步:返回查詢結(jié)果。遍歷查詢結(jié)果并輸出赔退。
ScoreDoc[] scoreDocs = topDocs.scoreDocs;
for (ScoreDoc scoreDoc : scoreDocs) {
//獲得文檔id
int doc = scoreDoc.doc;
//根據(jù)id查詢document
Document document = searcher.doc(doc);
String nameField = document.get("fileNameField");
System.out.println(nameField);
String fileSizeField = document.get("fileSizeField");
System.out.println(fileSizeField);
String filePathField = document.get("filePathField");
System.out.println(filePathField);
String fileContentField = document.get("fileContentField");
System.out.println(fileContentField);
System.out.println("----********-------------");
}
// 第七步:關(guān)閉IndexReader對象
indexReader.close();
}
TopDocs
Lucene搜索結(jié)果可通過TopDoc遍歷橙依,TopDoc類提供了少量的屬性
- totalHits 匹配搜索條件的總記錄數(shù)
- scoreDocs 頂部匹配記錄
注意:
Search方法需要指定匹配記錄數(shù)量n:indexSearcher.search(query, n)
TopDocs.totalHits:是匹配索引庫中所有記錄的數(shù)量
TopDocs.scoreDocs:匹配相關(guān)度高的前邊記錄數(shù)組证舟,scoreDocs的長度小于等于search方法指定的參數(shù)n
分析器功能
分析器Analyzer的執(zhí)行過程
//查看標(biāo)準(zhǔn)分析器的分詞效果
public void testTokenStream() throws Exception {
//創(chuàng)建一個標(biāo)準(zhǔn)分析器對象
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();
}
支持中文分詞
Lucene自帶中文分詞器
StandardAnalyzer:
單字分詞:就是按照中文一個字一個字地進(jìn)行分詞创译。如:“我愛中國”抵知,效果:“我”、“愛”软族、“中”刷喜、“國”。
CJKAnalyzer
二分法分詞:按兩個字進(jìn)行切分互订。如:“我是中國人”吱肌,效果:“我是”痘拆、“是中”仰禽、“中國”“國人”。
SmartChineseAnalyzer
對中文支持較好纺蛆,但擴(kuò)展性差吐葵,擴(kuò)展詞庫,禁用詞庫和同義詞庫等不好處理
第三方中文分析器
? IK-analyzer: 最新版在https://code.google.com/p/ik-analyzer/上桥氏,支持Lucene 4.10從2006年12月推出1.0版開始温峭, IKAnalyzer已經(jīng)推出了4個大版本。最初字支,它是以開源項目Luence為應(yīng)用主體的凤藏,結(jié)合詞典分詞和文法分析算法的中文分詞組件。從3.0版本開 始堕伪,IK發(fā)展為面向Java的公用分詞組件揖庄,獨立于Lucene項目,同時提供了對Lucene的默認(rèn)優(yōu)化實現(xiàn)欠雌。在2012版本中蹄梢,IK實現(xiàn)了簡單的分詞 歧義排除算法,標(biāo)志著IK分詞器從單純的詞典分詞向模擬語義分詞衍化富俄。 但是也就是2012年12月后沒有在更新禁炒。
索引庫維護(hù)
刪除
@Test
public void deleteAllIndex() throws IOException {
IndexWriter indexWriter = getIndexWriter();
//刪除全部
indexWriter.deleteAll();
indexWriter.close();
}
@Test
public void deleteQuery() throws IOException {
IndexWriter indexWriter = getIndexWriter();
//創(chuàng)建查詢條件
Query query = new TermQuery(new Term("fileContentField", "apache"));
//刪除條件
indexWriter.deleteDocuments(query);
indexWriter.close();
}
修改
修改的原理:先刪除后添加
@Test
public void updateIndex() throws IOException {
IndexWriter indexWriter = getIndexWriter();
Document document = new Document();
document.add(new TextField("filename", "要更新的文檔", Field.Store.YES));
document.add(new TextField("fileContent", "要更新的內(nèi)容", Field.Store.YES));
indexWriter.updateDocument(new Term("fileNameField", "create"), document);
indexWriter.close();
}
索引庫的查詢
對要搜索的信息創(chuàng)建Query查詢對象,Lucene會根據(jù)Query查詢對象生成最終的查詢語法霍比,類似關(guān)系數(shù)據(jù)庫Sql語法一樣Lucene也有自己的查詢語法幕袱,比如:“name:lucene”表示查詢Field的name為“l(fā)ucene”的文檔信息。
使用Query的子類查詢
MatchAllDocsQuery
使用MatchAllDocsQuery查詢索引目錄中的所有文檔
@Test
public void testMatchAllDocsQuery() throws Exception {
IndexSearcher indexSearcher = getIndexSearcher();
//創(chuàng)建查詢條件
Query query = new MatchAllDocsQuery();
//執(zhí)行查詢
printResult(query, indexSearcher);
}
TermQuery 精確查詢
//使用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();
}
NumericRangeQuery 根據(jù)數(shù)值范圍查詢
//數(shù)值范圍查詢
@Test
public void testNumericRangeQuery() throws Exception {
IndexSearcher indexSearcher = getIndexSearcher();
//創(chuàng)建查詢
//參數(shù):
//1.域名
//2.最小值
//3.最大值
//4.是否包含最小值
//5.是否包含最大值
Query query = NumericRangeQuery.newLongRange("size", 1l, 1000l, true, true);
//執(zhí)行查詢
printResult(query, indexSearcher);
}
BooleanQuery 可以組合查詢條件
//組合條件查詢
@Test
public void testBooleanQuery() throws Exception {
IndexSearcher indexSearcher = getIndexSearcher();
//創(chuàng)建一個布爾查詢對象
BooleanQuery query = new BooleanQuery();
//創(chuàng)建第一個查詢條件
Query query1 = new TermQuery(new Term("filename", "apache"));
Query query2 = new TermQuery(new Term("content", "apache"));
//組合查詢條件
query.add(query1, Occur.MUST);
query.add(query2, Occur.MUST);
//執(zhí)行查詢
printResult(query, indexSearcher);
}
條件解釋:
- Occur.MUST:必須滿足此條件悠瞬,相當(dāng)于and
- Occur.SHOULD:應(yīng)該滿足们豌,但是不滿足也可以,相當(dāng)于or
- Occur.MUST_NOT:必須不滿足。相當(dāng)于not
使用解析查詢
通過QueryParser也可以創(chuàng)建Query玛痊,QueryParser提供一個Parse方法汰瘫,此方法可以直接根據(jù)查詢語法來查詢。Query對象執(zhí)行的查詢語法可通過System.out.println(query);查詢擂煞。
需要使用到分析器混弥。建議創(chuàng)建索引時使用的分析器和查詢索引時使用的分析器要一致。
查詢語法
1对省、基礎(chǔ)的查詢語法蝗拿,關(guān)鍵詞查詢:
域名+“:”+搜索的關(guān)鍵字
例如:content:java
范圍查詢
域名+“:”+[最小值 TO 最大值]
例如:size:[1 TO 1000]
范圍查詢在lucene中支持?jǐn)?shù)值類型,不支持字符串類型蒿涎。在solr中支持字符串類型哀托。
組合條件查詢
1)+條件1 +條件2:兩個條件之間是并且的關(guān)系and
例如:+filename:apache +content:apache
+條件1 條件2:必須滿足第一個條件,應(yīng)該滿足第二個條件
例如:+filename:apache content:apache
條件1 條件2:兩個條件滿足其一即可劳秋。
例如:filename:apache content:apache
4)-條件1 條件2:必須不滿足條件1仓手,要滿足條件2
例如:-filename:apache content:apache
Occur.MUST 查詢條件必須滿足,相當(dāng)于and | +(加號) |
---|---|
Occur.SHOULD 查詢條件可選玻淑,相當(dāng)于or | 空(不用符號 ) |
Occur.MUST_NOT 查詢條件不能滿足嗽冒,相當(dāng)于not非 | -(減號) |
@Test
public void testQueryParser() throws Exception {
IndexSearcher indexSearcher = getIndexSearcher();
//創(chuàng)建queryparser對象
//第一個參數(shù)默認(rèn)搜索的域
//第二個參數(shù)就是分析器對象
QueryParser queryParser = new QueryParser("content", new IKAnalyzer());
Query query = queryParser.parse("Lucene是java開發(fā)的");
//執(zhí)行查詢
printResult(query, indexSearcher);
}