1. 程式人生 > >Lucene全文檢索學習

Lucene全文檢索學習

花了一段時間學習lucene今天有時間把所學的寫下來,網上有很多文章但大部分都是2.X和3.X版本的(當前最新版本4.9),希望這篇文章對自己和初學者有所幫助。

   學習目錄
(1)什麼是lucene
(2)lucene常用類詳解
(3)lucene簡單例項
(4)lucene常用分詞器
(5)lucene多條件查詢
(6)修改刪除索引
(7)lucene優化、排序
(8)lucene高亮顯示
(9)lucene分頁
(10)lucene注意幾點

一、什麼是lucene

        Lucene是一套用於全文檢索和搜尋的開源程式庫是全文檢索的框架而不是產品(不像百度不同), lucene其實就做兩種工作:一入一出。所謂入是寫入,即將你提供的源(本質是字串)寫入索引或者將其從索引中刪除;所謂出是讀出,即向用戶提供全文搜尋服務,讓使用者可以通過關鍵詞定位源。

        百科是這樣說的:Lucene是apache軟體基金會4 jakarta專案組的一個子專案,是一個開放原始碼的全文檢索引擎工具包,即它不是一個完整的全文檢索引擎,而是一個全文檢索引擎的架構,提供了完整的查詢引擎和索引引擎,部分文字分析引擎(英文與德文兩種西方語言)。Lucene的目的是為軟體開發人員提供一個簡單易用的工具包,以方便的在目標系統中實現全文檢索的功能,或者是以此為基礎建立起完整的全文檢索引擎。

二、lucene常用類

    (1)IndexWriter 索引過程的核心元件。這個類負責建立新索引或者開啟已有索引,以及向索引中新增、刪除或更新索引文件的資訊。可以把IndexWriter看做這樣一個物件:提供針對索引檔案的寫入操作,但不能用於讀取或搜尋索引。IndexWriter需要開闢一定空間來儲存索引,該功能可以由Directory完成。

    (2)Diretory 索引存放的位置,它是一個抽象類,它的子類負責具體制定索引的儲存路徑。lucene提供了兩種索引存放的位置,一種是磁碟,一種是記憶體。一般情況將索引放在磁碟上;相應地lucene提供了FSDirectory和RAMDirectory兩個類。

    (3)Analyzer 分析器,主要用於分析搜尋引擎遇到的各種文字,Analyzer的工作是一個複雜的過程:把一個字串按某種規則劃分成一個個詞語,並去除其中的無效詞語(停用詞),這裡說的無效詞語如英文中的“of”、“the”,中文中的“的”、“地”等詞語,這些詞語在文章中大量出現,但是本身不包含什麼關鍵資訊,去掉有利於縮小索引檔案、提高效率、提高命中率。分詞的規則千變萬化,但目的只有一個:按語義劃分。這點在英文中比較容易實現,因為英文字身就是以單詞為單位的,已經用空格分開;而中文則必須以某種方法將連成一片的句子劃分成一個個詞語。具體劃分方法下面再詳細介紹,這裡只需瞭解分析器的概念即可。

   (4)Document 文件 Document相當於一個要進行索引的單元,可以是文字檔案、字串或者資料庫表的一條記錄等等,一條記錄經過索引之後,就是以一個Document的形式儲存在索引檔案,索引的檔案都必須轉化為Document物件才能進行索引。

   (5)Field  一個Document可以包含多個資訊域,比如一篇文章可以包含“標題”、“正文”等資訊域,這些資訊域就是通過Field在Document中儲存的。
    Field有兩個屬性可選:儲存和索引。通過儲存屬性你可以控制是否對這個Field進行儲存;通過索引屬性你可以控制是否對該Field進行索引。這看起來似乎有些廢話,事實上對這兩個屬性的正確組合很重要,下面舉例說明:還是以剛才的文章為例子,我們需要對標題和正文進行全文搜尋,所以我們要把索引屬性設定為true,同時我們希望能直接從搜尋結果中提取文章標題,所以我們把標題域的儲存屬性設定為true,但是由於正文域太大了,我們為了縮小索引檔案大小,將正文域的儲存屬性設定為false,當需要時再直接讀取檔案;我們只是希望能從搜尋解果中提取最後修改時間,不需要對它進行搜尋,所以我們把最後修改時間域的儲存屬性設定為true,索引屬性設定為false。上面的三個域涵蓋了兩個屬性的三種組合,還有一種全為false的沒有用到,事實上Field不允許你那麼設定,因為既不儲存又不索引的域是沒有意義的。

  (6)IndexSearcher 是lucene中最基本的檢索工具,所有的檢索都會用到IndexSearcher工具。

  (7)IndexReader開啟一個Directory讀取索引類。

  (8)Query 查詢,抽象類,必須通過一系列子類來表述檢索的具體需求,lucene中支援模糊查詢,語義查詢,短語查詢,組合查詢等等,如有TermQuery,BooleanQuery,RangeQuery,WildcardQuery等一些類。

  (9)QueryParser 解析使用者的查詢字串進行搜尋,是一個解析使用者輸入的工具,可以通過掃描使用者輸入的字串,生成Query物件。

  (10)TopDocs 根據關鍵字搜尋整個索引庫,然後對所有結果進行排序,取指定條目的結果。

  (11)TokenStream Token 分詞器Analyzer通過對文字的分析來建立TokenStreams(分詞資料流)。TokenStream是由一個個Token(分片語成的資料流)。所以說Analyzer就代表著一個從文字資料中抽取索引詞(Term)的一種策略。

 (12)AttributeSource TokenStream即是從Document的域(field)中或者查詢條件中抽取一個個分詞而組成的一個數據流。TokenSteam中是一個個的分詞,而每個分詞又是由一個個的屬性(Attribute)組成。對於所有的分詞來說,每個屬性只有一個例項。這些屬性都儲存在AttributeSource中,而AttributeSource正是TokenStream的父類。

三、lucene簡單例項

    1、  首先要匯入需要的JAR包由於後面會用到其他的包,這裡我把所有的包都加進去,關於去那下載大家都知道(apache官網或者從http://download.csdn.net/detail/mdcmy/7683533),包截圖如下。

     

 

    2、定義三個常量

private String filePath = "F:/myEclipse10/workspace/luceneTest/src/resource.txt";// 原始檔所在位置
	private String indexDir = "F:/myEclipse10/workspace/luceneTest/src/index";// 索引目錄
	private static final Version VERSION = Version.LUCENE_47;// lucene版本

 3、建立索引方法

/**
	 * 建立索引
	 * 
	 * @throws IOException
	 */
	@Test
	public void createIndex() throws IOException {
		Directory director = FSDirectory.open(new File(indexDir));// 建立Directory關聯原始檔
		Analyzer analyzer = new StandardAnalyzer(VERSION);// 建立一個分詞器
		IndexWriterConfig indexWriterConfig = new IndexWriterConfig(VERSION, analyzer);// 建立索引的配置資訊
		IndexWriter indexWriter = new IndexWriter(director, indexWriterConfig);
		Document doc = new Document();// 建立文件
		String str = fileToString();// 讀取txt中內容
		Field field1 = new StringField("title", "lucene測試", Store.YES);// 標題 StringField索引儲存不分詞
		Field field2 = new TextField("content", str, Store.NO);// 內容 TextField索引分詞不儲存
		Field field3 = new DoubleField("version", 1.2, Store.YES);// 版本 DoubleField型別
		Field field4 = new IntField("score", 90, Store.YES);// 評分 IntField型別
		doc.add(field1);// 新增field域到文件中
		doc.add(field2);
		doc.add(field3);
		doc.add(field4);
		indexWriter.addDocument(doc);// 新增文字到索引中
		indexWriter.close();// 關閉索引
	}

 4、查詢搜尋方法

/**
	 * 查詢搜尋
	 * 
	 * @throws IOException
	 * @throws ParseException
	 */
	@Test
	public void query() throws IOException, ParseException {
		IndexReader reader = DirectoryReader.open(FSDirectory.open(new File(indexDir)));// 索引讀取類
		IndexSearcher search = new IndexSearcher(reader);// 搜尋入口工具類
		String queryStr = "life";// 搜尋關鍵字
		QueryParser queryParser = new QueryParser(VERSION, "content", new StandardAnalyzer(VERSION));// 例項查詢條件類
		Query query = queryParser.parse(queryStr);
		TopDocs topdocs = search.search(query, 100);// 查詢前100條
		System.out.println("查詢結果總數---" + topdocs.totalHits);
		ScoreDoc scores[] = topdocs.scoreDocs;// 得到所有結果集
		for (int i = 0; i < scores.length; i++) {
			int num = scores[i].doc;// 得到文件id
			Document document = search.doc(num);// 拿到指定的文件
			System.out.println("內容====" + document.get("content"));// 由於內容沒有儲存所以執行結果為null
			System.out.println("標題====" + document.get("title"));
			System.out.println("版本====" + document.get("version"));
			System.out.println("評分====" + document.get("score"));
			System.out.println("id--" + num + "---scors--" + scores[i].score + "---index--" + scores[i].shardIndex);
		}
	}

 5、讀取文字內容

/**
	 * 讀取檔案的內容
	 * 
	 * @return
	 * @throws IOException
	 */
	public String fileToString() throws IOException {
		StringBuffer sb = new StringBuffer();
		InputStream inputStream = new FileInputStream(new File(filePath));
		InputStreamReader inputStreamReader = new InputStreamReader(inputStream);
		BufferedReader br = new BufferedReader(inputStreamReader);
		String line = null;
		while ((line = br.readLine()) != null) {
			sb.append(line);
		}
		br.close();
		inputStreamReader.close();
		inputStream.close();
		return sb.toString();
	}

執行結果:

(1)先執行createIndex方法index資料夾下會生成幾個檔案

(2)執行query方法得出查詢的結果

 

最後附加上resource.txt的內容:ccasionally, life can be undeniably, impossibly difficult. We are faced with challenges and events that can seem overwhelming, life-destroying to the point where it may be hard to decide whether to keep going. But you always have a choice. Jessica Heslop shares her powerful, inspiring journey from the worst times in her life to the new life she has created for herself

四、lucene常用分詞器

  前面用到StandardAnalyzer分詞器這是一個標準分詞器,下面介紹另外幾個分詞器SimpleAnalyzer、CJKAnalyzer、IKAnalyzer對這四個分詞器(當然分詞器有很多種大家有興趣可以研究下)結果一一對比。

public class AnalyzerTest {
	private static final Version VERSION = Version.LUCENE_47;// lucene版本
 
	@Test
	public void test() throws IOException {
		String txt = "我是中國人";
		Analyzer analyzer1 = new StandardAnalyzer(VERSION);// 標準分詞器
		// Analyzer analyzer2 = new SimpleAnalyzer(VERSION);// 簡單分詞器
		// Analyzer analyzer3 = new CJKAnalyzer(VERSION);// 二元切分
		// Analyzer analyzer4 = new IKAnalyzer(false);// 語意分詞
		TokenStream tokenstream = analyzer1.tokenStream("content", new StringReader(txt));// 生成一個分詞流
		// TokenStream tokenstream = analyzer2.tokenStream("content", new StringReader(txt));
		// TokenStream tokenstream = analyzer3.tokenStream("content", new StringReader(txt));
		// TokenStream tokenstream = analyzer4.tokenStream("content", new StringReader(txt));
		CharTermAttribute termAttribute = tokenstream.addAttribute(CharTermAttribute.class);// 為token設定屬性類
		tokenstream.reset();// 重新設定
		while (tokenstream.incrementToken()) {// 遍歷得到token
			System.out.print(new String(termAttribute.buffer(), 0, termAttribute.length()) + "  ");
		}
	}
}

執行結果:

(1)我  是  中  國  人  
(2)我是中國人  
(3)我是  是中  中國  國人  
(4)我  是  中國人  中國  國人  

從結果上看StandardAnalyzer是把每個字都拆分開了、SimpleAnalyzer沒做任何改變、CJKAnalyzer是將相鄰的兩個字做為一個詞、IKAnalyzer從結果上看有一定的語意。

五、lucene多條件查詢

利用測試一建立的索引事例多條件查詢

public class MultiseQueryTest {
	private String indexDir = "F:/myEclipse10/workspace/luceneTest/src/index";// 索引目錄
	private static final Version VERSION = Version.LUCENE_47;// lucene版本
 
	/**
	 * 多條件查詢 查詢內容必須包含life內容和評分大於等於80分的結果
	 * 
	 * @throws IOException
	 * @throws ParseException
	 */
	@Test
	public void query() throws IOException, ParseException {
		IndexReader reader = DirectoryReader.open(FSDirectory.open(new File(indexDir)));// 索引讀取類
		IndexSearcher search = new IndexSearcher(reader);// 搜尋入口工具類
		String queryStr1 = "life";// 搜尋關鍵字
		BooleanQuery booleanQuery = new BooleanQuery();
		// 條件一內容中必須要有life內容
		QueryParser queryParser = new QueryParser(VERSION, "content", new StandardAnalyzer(VERSION));// 例項查詢條件類
		Query query1 = queryParser.parse(queryStr1);
		// 條件二評分大於等於80
		Query query2 = NumericRangeQuery.newIntRange("score", 80, null, true, false);
		booleanQuery.add(query1, BooleanClause.Occur.MUST);
		booleanQuery.add(query2, BooleanClause.Occur.MUST);
		TopDocs topdocs = search.search(booleanQuery, 100);// 查詢前100條
		System.out.println("查詢結果總數---" + topdocs.totalHits);
		ScoreDoc scores[] = topdocs.scoreDocs;// 得到所有結果集
		for (int i = 0; i < scores.length; i++) {
			int num = scores[i].doc;// 得到文件id
			Document document = search.doc(num);// 拿到指定的文件
			System.out.println("內容====" + document.get("content"));// 由於內容沒有儲存所以執行結果為null
			System.out.println("標題====" + document.get("title"));
			System.out.println("版本====" + document.get("version"));
			System.out.println("評分====" + document.get("score"));
			System.out.println("id--" + num + "---scors--" + scores[i].score + "---index--" + scores[i].shardIndex);
		}
	}
}

執行結果就不粘上去了。本例子運用了NumericRangeQuery建立一個查詢條件(還有很多其他的類有興趣可以一一實驗下),五個引數分別為欄位域、最小值、最大值、是否包含最小值、是否包含最大值。一開始很迷茫為什麼必須要設定最大值和最小值呢?如果我是單範圍查詢呢?後來看api才發現單範圍時可以用null或者把是否包含範圍值設為false就行了。

BooleanClause用於表示布林查詢子句關係的類,包括:BooleanClause.Occur.MUST,BooleanClause.Occur.MUST_NOT,BooleanClause.Occur.SHOULD。有以下6種組合: 
1.MUST和MUST:取得連個查詢子句的交集。 
2.MUST和MUST_NOT:表示查詢結果中不能包含MUST_NOT所對應得查詢子句的檢索結果。 
3.MUST_NOT和MUST_NOT:無意義,檢索無結果。 
4.SHOULD與MUST、SHOULD與MUST_NOT:SHOULD與MUST連用時,無意義,結果為MUST子句的檢索結果。與MUST_NOT連用時,功能同MUST。 
5.SHOULD與SHOULD:表示“或”關係,最終檢索結果為所有檢索子句的並集。

對於多條件查詢還有其他的一些實現類比如 MultiFieldQueryParser.parse()來建立一個Query有多個引數,比較簡單查下api就能明白。

六、修改刪除索引

同樣利用測試一建立的索引。

(1)更新索引

/**
	 * 修改索引
	 * 
	 * @throws IOException
	 */
	@Test
	public void updateIndex() throws IOException {
		Directory director = FSDirectory.open(new File(indexDir));// 建立Directory關聯原始檔
		Analyzer analyzer = new StandardAnalyzer(VERSION);// 建立一個分詞器
		IndexWriterConfig indexWriterConfig = new IndexWriterConfig(VERSION, analyzer);// 建立索引的配置資訊
		IndexWriter indexWriter = new IndexWriter(director, indexWriterConfig);
		Document doc = new Document();// 建立文件
		Field field1 = new StringField("title", "lucene", Store.YES);// 標題 StringField索引儲存不分詞
		Field field2 = new TextField("content", "Is there life on Mars", Store.NO);// 內容 TextField索引分詞不儲存
		Field field3 = new DoubleField("version", 2.0, Store.YES);// 版本 DoubleField型別
		Field field4 = new IntField("score", 90, Store.YES);// 評分 IntField型別
		doc.add(field1);// 新增field域到文件中
		doc.add(field2);
		doc.add(field3);
		doc.add(field4);
		indexWriter.updateDocument(new Term("title", "lucene測試"), doc);
		indexWriter.commit();
		indexWriter.close();
	}

執行結果:前後對比
注意:(1)所謂的更新索引是分兩步進行的:先刪除然後再新增索引,新增的索引佔用刪除前索引的位置;如果在刪除索引時lucene在索引檔案中找不到相應的資料,就會在索引檔案的最後面新增新的索引。

           (2)如果我把indexWriter.updateDocument(new Term("title", "lucene測試"), doc);換成indexWriter.updateDocument(new Term("title"), doc);的話同樣是新建立索引。

           (3)更新索引時必須把其他的欄位域都加上否則你只是建立了你添加了的欄位域索引。例如你只添加了title欄位域,在查詢時其他的欄位都會查不到。

(2)刪除索引

/**
	 * 刪除索引
	 * 
	 * @throws IOException
	 */
	@Test
	public void deleteIndex() throws IOException {
		Directory director = FSDirectory.open(new File(indexDir));// 建立Directory關聯原始檔
		Analyzer analyzer = new StandardAnalyzer(VERSION);// 建立一個分詞器
		IndexWriterConfig indexWriterConfig = new IndexWriterConfig(VERSION, analyzer);// 建立索引的配置資訊
		IndexWriter indexWriter = new IndexWriter(director, indexWriterConfig);
		indexWriter.deleteDocuments(new Term("title", "lucene"));
		indexWriter.commit();
		// indexWriter.rollback();
		indexWriter.close();
	}

刪除索引就比較簡單了,直接找到對應的欄位域呼叫deleteDocuments方法。

注意:網上有很多說在以前的版本可以能過IndexWriter.undeleteAll()恢復已刪除的版本,在4.7中已經沒有這個方法了,要想恢復只有通過indexWriter.rollback();有點像資料庫中的事務。但有個疑問不解:IndexWriter.forceMergeDeletes();在之前版本是刪除已經刪除的索引刪除回收站的檔案,但在4.7中還有這個方法,已經沒有undeleteAll方法了那forceMergeDeletes方法又起什麼作用呢?

七、lucene優化、排序

(1)優化lucene能過forceMerge方法來將當小檔案達到多少個時,就自動合併多個小檔案為一個大檔案,因為它的使用代價較高不意見使用此方法,預設情況下lucene會自己合併。

	/**
	 * 優化
	 * 
	 * @throws IOException
	 */
	@Test
	public void optimize() throws IOException {
		Directory director = FSDirectory.open(new File(indexDir));// 建立Directory關聯原始檔
		Analyzer analyzer = new StandardAnalyzer(VERSION);// 建立一個分詞器
		IndexWriterConfig indexWriterConfig = new IndexWriterConfig(VERSION, analyzer);// 建立索引的配置資訊
		IndexWriter indexWriter = new IndexWriter(director, indexWriterConfig);
		indexWriter.forceMerge(1);// 當小檔案達到多少個時,就自動合併多個小檔案為一個大檔案
		indexWriter.close();
	}

(2)排序lucene  lucene預設情況下是根據“評分機制”來進行排序的,也就是scores[i].score屬性值。如果兩個文件得分相同,那麼就按照發布時間倒序排列;否則就按照分數排列。

修改下建立索引的方法如下:

/**
	 * 建立索引
	 * 
	 * @throws IOException
	 */
	@Test
	public void createIndex() throws IOException {
		Directory director = FSDirectory.open(new File(indexDir));// 建立Directory關聯原始檔
		Analyzer analyzer = new StandardAnalyzer(VERSION);// 建立一個分詞器
		IndexWriterConfig indexWriterConfig = new IndexWriterConfig(VERSION, analyzer);// 建立索引的配置資訊
		IndexWriter indexWriter = new IndexWriter(director, indexWriterConfig);
		for (int i = 1; i <= 5; i++) {
			Document doc = new Document();// 建立文件
			Field field1 = new StringField("title", "標題" + i, Store.YES);// 標題 StringField索引儲存不分詞
			Field field2 = new TextField("content", "201" + i + "文章內容", Store.NO);// 內容 TextField索引分詞不儲存
			Field field3 = new DoubleField("version", 1.2, Store.YES);// 版本 DoubleField型別
			Field field4 = new IntField("score", 90 + i, Store.YES);// 評分 IntField型別
			Field field5 = new StringField("date", "2014-07-0" + i, Store.YES);// 評分 IntField型別
			doc.add(field1);// 新增field域到文件中
			doc.add(field2);
			doc.add(field3);
			doc.add(field4);
			doc.add(field5);
			indexWriter.addDocument(doc);// 新增文字到索引中
		}
		indexWriter.close();// 關閉索引
	}

首先看下預設情況下排序結果:

/**
	 * 排序
	 * 
	 * @throws IOException
	 * @throws ParseException
	 */
	@Test
	public void defaultSortTest() throws IOException, ParseException {
		IndexReader reader = DirectoryReader.open(FSDirectory.open(new File(indexDir)));// 索引讀取類
		IndexSearcher search = new IndexSearcher(reader);// 搜尋入口工具類
		String queryStr = "文章";// 搜尋關鍵字
		QueryParser queryParser = new QueryParser(VERSION, "content", new StandardAnalyzer(VERSION));// 例項查詢條件類
		Query query = queryParser.parse(queryStr);
		TopDocs topdocs = search.search(query, 100);// 查詢前100條
		System.out.println("查詢結果總數---" + topdocs.totalHits);
		ScoreDoc scores[] = topdocs.scoreDocs;// 得到所有結果集
		for (int i = 0; i < scores.length; i++) {
			int num = scores[i].doc;// 得到文件id
			Document document = search.doc(num);// 拿到指定的文件
			System.out.println("內容====" + document.get("content"));// 由於內容沒有儲存所以執行結果為null
			System.out.println("標題====" + document.get("title"));
			System.out.println("版本====" + document.get("version"));
			System.out.println("評分====" + document.get("score"));
			System.out.println("日期====" + document.get("date"));
			System.out.println("id--" + num + "---scors--" + scores[i].score + "---index--" + scores[i].shardIndex);
		}
	}
}


五個文章評分相同會那麼就按照發布時間倒序排列。

接下來根據欄位score域降序排序,只需修改兩行程式碼。

Sort sort = new Sort(new SortField("score", SortField.Type.INT, true));// false升序true降序
		TopDocs topdocs = search.search(query, 100, sort);// 查詢前100條

執行結果:

八、lucene高亮顯示

 高亮顯示,就是根據使用者輸入的檢索關鍵字,檢索找到該關鍵字對應的檢索結果檔案,提取對應於該檔案的摘要文字,然後根據設定的高亮格式,將格式寫入到摘要文字中對應的與關鍵字相同或相似的詞條上,在網頁上顯示出來,該摘要中的與關鍵字有關的文字就會以高亮的格式顯示出來。

高亮顯示需要幾個實現類:
(1)Fragmenter介面。作用是將原始字串拆分成獨立的片段。有三個實現類: NullFragmenter 是該介面的一個具體實現類,它將整個字串作為單個片段返回,這適合於處理title域和前臺文字較短的域,而對於這些域來說,我們是希望在搜尋結果中全部展示。SimpleFragmenter 是負責將文字拆分封固定字元長度的片段,但它並處理子邊界。你可以指定每個片段的字元長度(預設情況100)但這類片段有點過於簡單,在建立片段時,他並不限制查詢語句的位置,因此對於跨度的匹配操作會輕易被拆分到兩個片段中; SimpleSpanFragmenter 是嘗試將讓片段永遠包含跨度匹配的文件。
(2)Scorer介面。Fragmenter輸出的是文字片段序列,而Highlighter必須從中挑選出最適合的一個或多個片段呈現給客戶,為了做到這點,Highlighter會要求Scorer來對每個片段進行評分。有兩個實現類:QueryTermScorer 基於片段中對應Query的項數進行評分。QueryScorer只對促成文件匹配的實際項進行評分。
(3)Formatter介面。它負責將片段轉換成String形式,以及將被高亮顯示的項一起用於搜尋結果展示以及高亮顯示。有兩個類:SimpleHTMLFormatter簡單的html格式。GradientFormatter複雜型式對不同的得分實現不同的樣式。

/**
	 * 高亮
	 * 
	 * @throws IOException
	 * @throws ParseException
	 * @throws InvalidTokenOffsetsException
	 */
	@Test
	public void highlighter() throws IOException, ParseException, InvalidTokenOffsetsException {
		IndexReader reader = DirectoryReader.open(FSDirectory.open(new File(indexDir)));// 索引讀取類
		IndexSearcher search = new IndexSearcher(reader);// 搜尋入口工具類
		Analyzer analyzer = new StandardAnalyzer(Version.LUCENE_47);// 分詞器
		QueryParser qp = new QueryParser(Version.LUCENE_47, "content", analyzer);// 例項查詢條件類
		Query query = qp.parse("文章");
		TopDocs topDocs = search.search(query, 100);// 查詢前100條
		System.out.println("共查詢出:" + topDocs.totalHits + "條資料");
		ScoreDoc scoreDoc[] = topDocs.scoreDocs;// 結果集
		// 高亮
		Formatter formatter = new SimpleHTMLFormatter("<font color='red'>", "</font>");// 高亮html格式
		Scorer score = new QueryScorer(query);// 檢索評份
		Fragmenter fragmenter = new SimpleFragmenter(100);// 設定最大片斷為100
		Highlighter highlighter = new Highlighter(formatter, score);// 高亮顯示類
		highlighter.setTextFragmenter(fragmenter);// 設定格式
		for (int i = 0; i < scoreDoc.length; i++) {// 遍歷結果集
			int docnum = scoreDoc[i].doc;
			Document doc = search.doc(docnum);
			String content = doc.get("content");
			System.out.println(content);// 原內容
			if (content != null) {
				TokenStream tokenStream = analyzer.tokenStream("content", new StringReader(content));
				String str = highlighter.getBestFragment(tokenStream, content);// 得到高亮顯示後的內容
				System.out.println(str);
			}
		}
	}


選擇結果:

九、lucene分頁

lucene的分頁有兩種方式:(1)查詢出所有結果然後進行分佈。(2)通過TopScoreDocCollector.topDocs(int num1,int num2)來實現分頁。

/**
	 * 分頁
	 * 
	 * @throws IOException
	 * @throws ParseException
	 */
	@Test
	public void pageTest() throws IOException, ParseException {
		IndexReader reader = DirectoryReader.open(FSDirectory.open(new File(indexDir)));// 索引讀取類
		IndexSearcher search = new IndexSearcher(reader);// 搜尋入口工具類
		String queryStr = "文章";// 搜尋關鍵字
		QueryParser queryParser = new QueryParser(VERSION, "content", new StandardAnalyzer(VERSION));// 例項查詢條件類
		Query query = queryParser.parse(queryStr);// 查詢
		TopScoreDocCollector results = TopScoreDocCollector.create(100, false);// 結果集
		search.search(query, results);// 查詢前100條
		TopDocs topdocs = results.topDocs(1, 2);// 從結果集中第1條開始取2條
		ScoreDoc scores[] = topdocs.scoreDocs;// 得到所有結果集
		for (int i = 0; i < scores.length; i++) {
			int num = scores[i].doc;// 得到文件id
			Document document = search.doc(num);// 拿到指定的文件
			System.out.println("內容====" + document.get("content"));// 由於內容沒有儲存所以執行結果為null
			System.out.println("標題====" + document.get("title"));
			System.out.println("版本====" + document.get("version"));
			System.out.println("評分====" + document.get("score"));
			System.out.println("id--" + num + "---scors--" + scores[i].score + "---index--" + scores[i].shardIndex);
		}
	}

十、lucene注意幾點

其實任何好的框架都有他的缺陷,lucene也不例外,下面這幾點是網上找的也是我們開發中要注意的地方。
1、lucene的索引不能太大,要不然效率會很低。大於1G的時候就必須考慮分佈索引的問題
2、不建議用多執行緒來建索引,產生的互鎖問題很麻煩。經常發現索引被lock,無法重新建立的情況
3、中文分詞是個大問題,目前免費的分詞效果都很差。如果有能力還是自己實現一個分詞模組,用最短路徑的切分方法,網上有教材和demo原始碼,可以參考。
4、建增量索引的時候很耗cpu,在訪問量大的時候會導致cpu的idle為0
5、預設的評分機制不太合理,需要根據自己的業務定製
整體來說lucene要用好不容易,必須在上述方面擴充他的功能,才能作為一個商用的搜尋引擎。

最後附加上例項程式碼下載地址:點選開啟連結

轉載自:https://blog.csdn.net/mdcmy/article/details/38167955?utm_source=tuicool&utm_medium=referral