1. 程式人生 > >JAVAWEB開發之Lucene詳解——Lucene入門及使用場景、全文檢索、索引CRUD、優化索引庫、分詞器、高亮、相關度排序、各種查詢

JAVAWEB開發之Lucene詳解——Lucene入門及使用場景、全文檢索、索引CRUD、優化索引庫、分詞器、高亮、相關度排序、各種查詢

Lucene入門

應用場景

windows系統中的有搜尋功能:開啟“我的電腦”,按“F3”就可以使用查詢的功能,查詢指定的檔案或資料夾。搜尋的範圍是整個電腦中的檔案資源。
Eclipse中的幫助子系統:點選Help->Help Contents,可以查找出相關的幫助資訊。搜尋的範圍是Eclipse的所有幫助檔案。  
在BBS(論壇)、BLOG(部落格)、新聞,電子商務系統等系統中提供的搜尋文章的功能。 搜尋引擎,如Baidu和Google等,可以查詢到網際網路中的網頁、PDF、PPT、DOC、圖片、音樂、視訊等 如下所示:
以上的查詢功能都類似。都是查詢的文字內容,都是相同的查詢方式,即找出含有指定字串的資源,不同的只是查詢範圍(分別為硬碟、所有幫助檔案、資料庫、網際網路)

全文檢索

什麼是全文檢索

全文檢索是計算機程式通過掃描文章中的每一個詞,對每一個詞建立一個索引,指明該詞在文章中出現的次數和位置。當用戶查詢時根據建立的索引進行查詢,類似於通過字典的檢索字表查字的過程。其實Lucene就是一個專門做索引的框架。 對於搜尋,按被搜尋的資源型別,分為兩種:可以轉為文字的、多媒體型別。上面應用場景提到的搜尋功能都是搜尋的可以轉為文字的資源(第一種)。
注意,百度或谷歌提供的音樂或視訊搜尋不是多媒體搜尋,他們是按檔名搜尋。在智慧手機上有一款音樂搜尋的軟體,可以讓他聽10秒鐘的音樂,然後他就能上網找出這段音樂的名稱、演奏者等資訊。這是多媒體搜尋。

全文檢索特性

全文檢索(Full-Text Retirval)是指以文字作為檢索物件,找出含有指定詞彙的文字。全面準確和快速是衡量全文檢索系統的關鍵指標。
全文檢索的特性主要以下幾點: (1) 只處理文字 (2) 不處理語義(例如百度搜索中國現任主席是誰 搜尋出來的頁面是不包含習近平的 而是將搜尋的關鍵字進行顯示) (3) 搜尋時英文不區分大小寫 (4) 結果列表有相關度排序 在資訊檢索工具中,全文檢索是最具有通用性和實用性的。

全文檢索應用場景

使用Lucene主要是做站內搜尋,即對一個系統內的資源進行搜尋。如BBS、BLOG中的文章搜尋,網上商店中的商品搜尋等等。使用Lucene的專案有Eclipse軟體等。一般不做網際網路中資源的搜尋,因為不易獲取與管理海量資源(當然,專業做搜尋引擎的公司除外)

全文檢索不同於資料庫檢索

  全文檢索不同於資料庫的SQL查詢。(他們所解決的問題不一樣,解決的方案也不一樣,所以不應進行對比)。在資料庫中的搜尋就是使用SQL,如:SELECT * FROM t WHERE content like ‘%ant%’。這樣會有如下問題:
1、匹配效果:如搜尋ant會搜尋出planting。這樣就會搜出很多無關的資訊。
2、相關度排序:查出的結果沒有相關度排序,不知道我想要的結果在哪一頁。我們在使用百度搜索時,一般不需要翻頁,因為百度做了相關度排序:為每一條結果打一個分數,這條結果越符合搜尋條件,得分就越高,叫做相關度得分,結果列表會按照這個分數由高到低排列,所以第1頁的結果就是我們最想要的結果。
3、全文檢索的速度大大快於SQL的like搜尋的速度。這是因為查詢方式不同造成的,以查字典舉例:資料庫的like就是一頁一頁的翻,一行一行的找,而全文檢索是先查目錄,得到結果所在的頁碼,再直接翻到這一頁。

Lucene簡介

全文檢索就如同ORM,是一個概念。ORM的框架有很多種:Hibernate、TopLink、iBatis等,我們之前學習的是Hibernate。同樣的,全文檢索領域中也有多種框架,Lucene就是其中的一個用開源的全文檢索框架。
Lucene的主頁為:http://lucene.apache.org/

第一個Lucene程式

準備Lucene的開發環境

搭建Lucene的開發環境只需要加入Lucene的Jar包,要加入的jar包至少要有:
lucene-core-4.4.0.jar(核心包)
contrib\analyzers\common\lucene-analyzers-common-4.4.0.jar(分詞器)
contrib\highlighter\lucene-highlighter-4.4.0.jar(高亮)
contrib\memory\lucene-memory-4.4.0.jar(高亮)
如下所示:

實現建立索引功能(Indexer類)

/**
	 * 通過Lucene提供的API對資料建立索引。indexwriter
	 * @throws IOException
	 */
	@Test
	public void testAdd() throws IOException {
		// 索引在硬碟上面的儲存位置
		Directory directory = FSDirectory.open(new File("/Users/liuxun/Desktop/indexes"));
		// lucene 當前使用的版本
		Version matchVersion = Version.LUCENE_44;
		// 分詞器, 作用是將一段文字分詞
		// analyzer 是一個抽象類,具體的切分詞規則由子類實現
		Analyzer analyzer = new StandardAnalyzer(matchVersion);

		IndexWriterConfig config = new IndexWriterConfig(matchVersion, analyzer);
		// 構造索引寫入的物件
		IndexWriter indexWriter = new IndexWriter(directory, config);

		// 往索引庫裡寫資料
		// 索引庫裡的資料都是document,一個document相當於一條記錄
		// 這個document裡面的資料相當於是索引結構
		Document document = new Document();
		IndexableField intField = new IntField("id", 1, Store.YES);
		IndexableField stringField = new StringField("title", "劉勳簡介", Store.YES);
		IndexableField textField = new TextField("content", "在程式路上奮鬥的一屌絲", Store.YES);

		// document.add(field) 包含了兩個過程:儲存資料和建立索引
		// 使用分詞器分詞後每個分詞部分都是一個索引,如果索引在索引庫中不存在便會建立索引,每個索引都有一個儲存指向document的DocId陣列
		// 指向document.add(field)後會根據分詞器規則將filed的內容切分成多個索引,並將當前document的DocId新增到切分後每個索引中的DocId內
		// 而持久化記錄欄位值到Document是與Store.YES有關,如果為NO 該欄位的內容就不會持久化到document檔案中
		document.add(intField);
		document.add(stringField);
		document.add(textField);
		// 索引庫裡面接收的都是document物件
		indexWriter.addDocument(document);
		indexWriter.close();
	}

實現搜尋功能(Searcher類)

/**
	 * 對建立的索引進行搜尋... 通過indexSearcher去搜索
	 * @throws IOException
	 */
	@Test
	public void testSearcher() throws IOException {
		// 索引在硬碟上面的儲存位置
		Directory directory = FSDirectory.open(new File("/Users/liuxun/Desktop/indexes"));
		//將索引目錄裡的索引讀取到IndexReader中
		IndexReader indexReader = DirectoryReader.open(directory);
		//構造搜尋索引的物件(索引搜尋器)
		IndexSearcher indexSearcher = new IndexSearcher(indexReader);
		
		//Query 是一個查詢條件物件,它是一個抽象類,不同的查詢條件就構造不同的子類
		//Term(fieldName,value) 會將value與當前欄位值的分詞結果(多個索引)進行匹配 匹配到則命中
		Query query = new TermQuery(new Term("title", "劉勳簡介"));
		//Query query = new TermQuery(new Term("content", "序"));
		
		//檢索符合query條件的前N條記錄
		TopDocs topDocs = indexSearcher.search(query, 10);
		//返回總記錄數(命中數)
		System.out.println(topDocs.totalHits);
		
		//存放的是document的id
		ScoreDoc[] scoreDocs = topDocs.scoreDocs;
		for (ScoreDoc scoreDoc : scoreDocs) {
			//返回的就是document id  注意不是設定的欄位由Lucene自動生成維護的
			int docID= scoreDoc.doc;
			//還需要根據document的id 檢索到對應的document
			Document document = indexSearcher.doc(docID);
			
			System.out.println("id== "+document.get("id"));
			System.out.println("title== "+document.get("title"));
			System.out.println("content== "+document.get("content"));
		}
		
	}
檢視生成的index索引以及搜尋結果如下:

Lucene的核心概念

全文檢索的工作流程

如果資訊檢索系統在使用者發出了檢索請求後再去網際網路上找查詢結果,根本無法在有限的時間內返回結果。所以要先把要檢索的資源集合放在本地,並使用某種特定的結構進行儲存,稱為索引。這個索引的集合稱為索引庫。由於索引庫的結構是專門為快速查詢設計的,所以查詢的速度非常快。我們每次搜尋都是在本地的索引庫中進行如下圖:
從圖片上可以看出,我們不僅要搜尋,還要保證資料集合與索引庫的一致性。所以對於全文檢索功能的開發,要做的有兩個方面:索引庫管理(維護索引庫中的資料)、在索引庫中進行搜尋。而Lucene就是操作索引庫的工具。

使用Lucene的API操作索引庫


索引庫是一個目錄,裡面是一些二進位制檔案,就如同資料庫,所有的資料也是以檔案的形式存在檔案系統的。我們不能直接操作這些二進位制檔案,而是使用Lucene提供的API完成相應的操作,就像操作資料庫應該使用SQL語句一樣。 對於索引庫的操作可以分為兩種:管理和查詢。管理索引庫使用IndexWriter,從索引庫中查詢使用後IndexSearcher。Lucene的資料結構分為Document和Field。Document代表一條資料,Field代表資料中的一個屬性。一個Document中有多個Field,Field型別為String型別,因為Lucene只處理文字。 在開發時,我們只需要將程式中的物件轉成Document,就可以交給Lucene管理了,搜尋結果中的資料列表也是Document的集合。

索引庫的大致結構與索引的建立過程


索引庫的大致結構

我們需要對文件進行預處理,建立一種便於檢索的資料結構,以此來提高資訊檢索的速度,這種資料結構就是索引。目前廣泛使用的一種索引方式是倒排序索引。
倒排序索引的原理就如同查字典。要先查目錄,得到資料對應的頁碼,在直接翻到指定的頁碼。不是在文章中找詞,而是從目錄中找詞所在的文章。這需要在索引庫中生成一個詞彙表(目錄),在詞彙表中的每一個條記錄都是類似於“詞à所在文件的編號列表”的結構,記錄了每一個出現過的單詞,和單詞出現的地方(哪些文件)。查詢時先查詞彙表,得到文件的編號,再直接取出相應的文件。
把資料轉成指定格式放到索引庫中的操作叫做建立索引。建立索引時,在把資料存到索引庫後,再更新詞彙表。進行搜尋時,先從檢索詞彙表開始,然後找到相對應的文件。如果查詢中僅包含一個關鍵詞,則在詞彙表中找到該單詞,並取出他對應的文件就可以了。如果查詢中包含多個關鍵詞,則需要將各個單詞檢索出的記錄進行合併再取出相應的文件記錄。

建立索引執行過程

1. 我們做的操作:把資料物件轉成相應的Document,其中的屬性轉為Field。
2. 我們做的操作:呼叫工具IndexWriter的addDocument(doc),把Document新增到索引庫中。
3. Lucene做的操作:把文件存到索引庫中,並自動指定一個內部編號,用來唯一標識這條資料。內部編號類似於這條資料的地址,在索引庫內部的資料進行調整後,這個編號就可能會改變,同時詞彙表中引用的編號也會做相應改變,以保證正確。但我們如果在外面引用了這個編號,前後兩次去取,得到的可能不是同一個文件!所以內部編號最好只在內部用。
4. Lucene做的操作:更新詞彙表。把文字中的詞找出並放到詞彙表中,建立與文件的對應關係。要把哪些詞放到詞彙表中呢,也就是文字中包含哪些詞呢?這就用到了一個叫做Analyzer(分詞器)的工具。他的作用是把一段文字中的詞按規則取出所包含的所有詞。對應的是Analyzer類,這是一個抽象類,切分詞的具體規則是由子類實現的,所以對於不同的語言(規則),要用不同的分詞器。如下圖:

​在把物件的屬性轉為Field時,相關程式碼為:doc.add(new Field("title", article.getTitle(), Store.YES, Index.ANALYZED))。第三與第四個引數的意思為:

從索引庫中搜索的執行過程

在進行搜尋時,先在詞彙表中查詢,得到符合條件的文件編號列表,再根據文件編號真正的去取出資料(Document).r如下圖:
1, 把要查詢字串轉為Query物件。這就像在Hibernate中使用HQL查詢時,也要先呼叫Session.createQuery(hql)轉成Hibernate的Query物件一樣。把查詢字串轉換成Query是使用QueryParser,或使用MultiFieldQueryParser。查詢字串也要先經過Analyzer(分詞器)。要求搜尋時使用的Analyzer要與建立索引時使用的Analzyer要一致,否則可能搜不出正確的結果。
2, 呼叫IndexSearcher.search(),進行查詢,得到結果。此方法返回值為TopDocs,是包含結果的多個資訊的一個物件。其中有totalHits 代表總記錄數,ScoreDoc的陣列。ScoreDoc是代表一個結果的相關度得分與文件編號等資訊的物件。
3, 取出要用到的資料列表。呼叫IndexSearcher.doc(scoreDoc.doc)以取出指定編號對應的Document資料。在分頁時要用到:一次只取一頁的資料。

Lucene操作索引之增刪改查

保持索引庫和資料庫狀態一致

Lucene在實際電商網站的中的應用 主要用於搜尋符合條件的商品 由於資料量很大 為了快速檢索 搜尋結果資訊從索引庫中獲取。
索引庫中只是儲存商品的少量資訊(資訊量越少 檢索速度越快),當購買某商品時 必須從資料庫中查詢該商品的全部資訊。
可以發現索引庫的商品資訊是和資料庫中的商品是一一對應的,如果資料庫中的某個商品被移除,而這個商品在索引庫中的資訊沒有被移除,那麼在搜尋相關資訊時在主頁會顯示到該商品,當點選商品進入商品詳情進行購買時就會出現異常(因為根據索引裡儲存的商品id去商品詳情頁 後臺根據id查詢從資料庫查詢商品資訊 發現不存在就會丟擲異常 頁面會顯示404資訊)。如果在索引庫中刪除了某商品的索引資訊,而資料庫中沒有刪除該商品。那麼在任何情況下都無法在主頁搜尋到該商品 也就不會有顧客看到該商品。所以說索引庫中的資訊和資料庫中的資訊必須一一對應。其大致流程如下:
建立索引時的注意事項 如果想讓內容儲存到索引庫 在建立欄位時指定儲存引數為Store.YES  如果想根據欄位的內容搜尋的話就不能指定索引引數為Index.NO  例如:在網上購物時主頁中顯示的商品資訊內容有關的欄位都需要指定Store.YES 例如imageURL(StringField)、productName(TextField) 、description(TextField)還有一些必須使用的屬性例如商品的id、與類別有關的外來鍵欄位 以及與搜尋相關的關聯表資訊欄位 商品類別表中的productTypeName即商品類別名稱(TextField) 都要儲存在索引中。為了保證準確性和提交效率 表中的主鍵以及URL地址一類的欄位必須使用但是又不參與搜尋的就在建立索引欄位時就指定引數Store.YES和Index.NO .而對於既與商品搜尋無關 也不需要在主頁顯示 並且也不涉及多表關聯的欄位 例如:庫存量、評論、商品的規格引數等 根本不用建立索引。

Lucene索引庫操作CRUD

模擬需要建立的Bean類Article

Artlcle.java
public class Article {
	private int id;
	private String title;
	private String content;
	private String author;
	private String url;
      //對應的setter和getter方法
      ......
}

Bean類和Document之間的轉換

public class ArticleDocumentUtils {
	/**
	 * 將文章轉化為文件物件
	 * @param article
	 * @return
	 */
	public static Document articleToDocument(Article article) {
		Document document = new Document();
		IntField idField = new IntField("id", article.getId(), Store.YES);
		// StringField 對應的值不分詞,TextField分詞
		TextField titleField = new TextField("title", article.getTitle(), Store.YES);
		TextField contentField = new TextField("content", article.getContent(), Store.YES);
		StringField authorField = new StringField("author", article.getAuthor(), Store.YES);
		StringField urlField = new StringField("url", article.getUrl(), Store.YES);
		document.add(idField);
        document.add(titleField);
        document.add(contentField);
        document.add(authorField);
        document.add(urlField);
		return document;
	}
	
	/**
	 * 將文件物件轉為Article物件
	 * @param document
	 * @return
	 */
	public static Article documentToArticle(Document document){
		Article article=new Article();
		article.setId(Integer.parseInt(document.get("id")));
		article.setAuthor(document.get("author"));
		article.setContent(document.get("content"));
		article.setUrl(document.get("url"));
		article.setTitle(document.get("title"));
		return article;
	}
}

封裝Lucene操作的工具類

/**
 * lucene工具類
 * 
 * @author liuxun
 *
 */
public class LuceneUtils {
	private static IndexWriter indexWriter = null;
	private static IndexSearcher indexSearcher = null;

	// 索引存放目錄
	private static Directory directory = null;
	// 索引寫入器配置
	private static IndexWriterConfig indexWriterConfig = null;
	// 當前使用Lucene的版本
	private static Version version = null;
	// 分詞器
	private static Analyzer analyzer = null;

	static {
		try {
			// 按照標準開發應該將地址配置在properties資原始檔中然後進行讀取
			directory = FSDirectory.open(new File("/Users/liuxun/Desktop/indexes"));
			version = Version.LUCENE_44;
			analyzer = new StandardAnalyzer(version);
			indexWriterConfig = new IndexWriterConfig(version, analyzer);
			// 初始化IndexWriter
			indexWriter = new IndexWriter(directory, indexWriterConfig);
			System.out.println("==>> 已經初始化IndexWriter <<==");
			
			//指定在JVM退出前要執行的程式碼
			Runtime.getRuntime().addShutdownHook(new Thread(){
				public void run(){
					try {
						closeIndexWriter();
						closeIndexSearcher();
					} catch (IOException e) {
						e.printStackTrace();
					}
				}
			});

		} catch (IOException e) {
			e.printStackTrace();
		}
	}

	/**
	 * 
	 * @return 返回用於操作索引的物件
	 * @throws IOException
	 */
	public static IndexWriter getIndexWriter() throws IOException {
		// 全域性唯一 在執行過程中不關閉,在JVM退出時才進行關閉操作
		
		return indexWriter;
	}

	/**
	 * 
	 * @return 返回搜尋索引的物件
	 * @throws IOException
	 */
	public static IndexSearcher getIndexSearcher() throws IOException {
		if (indexSearcher == null) {
			IndexReader indexReader = DirectoryReader.open(directory);
			indexSearcher = new IndexSearcher(indexReader);
		}
		return indexSearcher;
	}

	/**
	 * 關閉IndexWriter
	 * @throws IOException 
	 */
	private static void closeIndexWriter() throws IOException {
		if (indexWriter != null) {
				indexWriter.close();
				System.out.println("==>> 已經關閉IndexWriter <<==");
		}
	}

	/**
	 * 關閉IndexSearcher
	 * @throws IOException 
	 */
	private static void closeIndexSearcher() throws IOException {
		if (indexSearcher != null) {
				indexSearcher.getIndexReader().close();
				System.out.println("-->> 關閉了IndexSearcher <<--");
		}
	}
	/**
	 * 通知索引庫更改
	 * 當索引庫中的值發生改變的時候,需要關閉IndexSearcher物件(實質是關閉IndexReader)
	 * @throws IOException 
	 */
	public static void indexChanged() throws IOException{
		if (indexSearcher!=null) {
			closeIndexSearcher();
			indexSearcher = null;
		}
	}

	/**
	 * 返回Lucene當前的版本
	 * 
	 * @return
	 */
	public static Version getVersion() {
		return version;
	}

	/**
	 * 返回Lucene當前使用的分詞器
	 * 
	 * @return
	 */
	public static Analyzer getAnalyzer() {
		return analyzer;
	}

}
注意事項:
(1) 如果IndexWriter不關閉 會報異常,再建立新的IndexWriter物件就會丟擲異常:org.apache.lucene.store.LockObtainFailedException: Lock obtain timed out: [email protected]:.... (處於多執行緒併發操作的安全的考慮 建立一個IndexWriter例項時 會在內部為它分配一把鎖,如果當前的IndexWriter不關閉 再建立新的IndexWriter是會丟擲異常的 也就是說系統中只允許存在執行一個IndexWriter物件 即單例) (2) 可以程式碼塊中執行一個新的執行緒 當JVM釋放資源時 再關閉IndexWriter (3)在索引發生更改時,需要關閉IndexSearcher物件,實質上是關閉IndexReader物件

建立操作索引的LuceneDao

建立索引操作

 /**
	 * 建立索引
	 * @param article
	 */
	public void addIndex(Article article) {
		// 將Article轉為Document
		Document doc = ArticleDocumentUtils.articleToDocument(article);
		// 建立索引
		try {
			LuceneUtils.getIndexWriter().addDocument(doc);
			// 提交更改
			LuceneUtils.getIndexWriter().commit();
			LuceneUtils.indexChanged();
		} catch (IOException e) {
			try {
				LuceneUtils.getIndexWriter().rollback();
			} catch (IOException e1) {
				throw new RuntimeException("建立索引失敗");
			}
		}
	}
涉及到索引庫的操作 和資料庫類似 都有提交commit和rollback回滾操作

刪除索引操作

      /**
	 * 刪除索引——根據非IntField型別的
	 */
	public void delIndexByTerm(String fieldName,String fieldValue){
		Term term = new Term(fieldName,fieldValue);
		try {
			// 刪除所有含有指定term的所有Document
			LuceneUtils.getIndexWriter().deleteDocuments(term);
			// 提交更改
			LuceneUtils.getIndexWriter().commit();
			LuceneUtils.indexChanged();
		} catch (IOException e) {
			try {
				LuceneUtils.getIndexWriter().rollback();
			} catch (IOException e1) {
				throw new RuntimeException("刪除索引失敗");
			}
		}
	}
	/**
	 * 刪除索引——根據IntField型別刪除
	 */
	public void delIndexByNumeric(Integer id){
		Query query = NumericRangeQuery.newIntRange("id", id-1, id+1, false, false);
		try {
			// 刪除所有含有指定term的所有Document
			LuceneUtils.getIndexWriter().deleteDocuments(query);;
			// 提交更改
			LuceneUtils.getIndexWriter().commit();
			LuceneUtils.indexChanged();
		} catch (IOException e) {
			try {
				LuceneUtils.getIndexWriter().rollback();
			} catch (IOException e1) {
				throw new RuntimeException("刪除索引失敗");
			}
		}
	}
注意:對於IntField型別的欄位 預設的Index屬性是Index.NO 所以根據IntField型別的欄位使用Term(分詞單元) 是根本搜尋不到的 因為它不參與搜尋,也不會分詞。如果想搜尋數字型別的欄位可以使用NumericRangeQuery 建立查詢。 對於刪除索引方法主要有兩種: (1) 根據term刪除 indexWriter.deleteDocuments(Term term)  根據一個屬性進行分割的詞元內容進行搜尋刪除 indexWriter.deleteDocuments(Term ... term)  可以根據多個屬性的分割詞元進行搜尋刪除 (2) 根據Query進行刪除 ndexWriter.deleteDocuments(Query query)  根據一個查詢條件進行搜尋刪除 indexWriter.deleteDocuments(Query ... query)  可以根據多個查詢條件進行搜尋刪除

更新索引操作

/**
	 * 更新索引——根據非IntField型別的欄位屬性更新
	 * @param article
	 */
	public void updateIndex(String fieldName,String fieldString,Article article) {
		Term term = new Term(fieldName,fieldString);
		try {
			Document doc = ArticleDocumentUtils.articleToDocument(article);
			// 更新索引,就是先查詢刪除再建立,如果原有的不存在 直接建立新的索引
			LuceneUtils.getIndexWriter().updateDocument(term, doc);
			// 提交更改
			LuceneUtils.getIndexWriter().commit();
			LuceneUtils.indexChanged();
		} catch (IOException e) {
			try {
				LuceneUtils.getIndexWriter().rollback();
			} catch (IOException e1) {
				throw new RuntimeException("更新索引失敗");
			}
		}
	}
索引檔案的檢索與維護,更新是先刪除後建立
維護倒排索引有三個操作:新增、刪除和更新文件。但是更新操作需要較高的代價。因為文件修改後(即使是很小的修改),就可能會造成文件中的很多的關鍵詞的位置都發生了變化,這就需要頻繁的讀取和修改記錄,這種代價是相當高的。因此,一般不進行真正的更新操作,而是使用“先刪除,再建立”的方式代替更新操作。 Lucene的indexWriter.update(term,doc)  的更新方式是:先對某屬性的欄位內容進行分詞,然後根據這些分詞後的結果從索引庫中的詞彙表(相當於字典中的查詢目錄) 中進行搜尋 然後將這些搜尋到的Document全部刪除,然後建立一個新的document到索引庫,如果索引庫中沒有符合條件的Document 則直接建立一個新的document。

檢索索引操作

// 注意使用:QueryParser時需要匯入QueryParser所在的jar包 lucene-queryparser-4.4.0.jar
	public SearchResult<Article> findIndex(String queryString, int firstResult, int maxResult) throws IOException, ParseException {
		// 1.將查詢字串轉為Query物件,單欄位查詢 例如預設只在"title"中搜索 
		// 1.1 先使用TermQuery 只對分詞結果進行搜尋,不會拼接各個分詞部分
		// Query query = new TermQuery(new Term("title", queryString));
		
		// 1.2 使用QueryParser 搜尋時會將分詞結果 組合拼接 進行搜尋
		// QueryParser queryParser = new QueryParser(LuceneUtils.getVersion(), "title", LuceneUtils.getAnalyzer());
        
		// 1.3 使用QueryParser的子類可以進行多欄位查詢 例如預設在title和content欄位中搜索
		String[] fields = {"title","content"};
		QueryParser queryParser = new MultiFieldQueryParser(LuceneUtils.getVersion(), fields, LuceneUtils.getAnalyzer());
		Query query = queryParser.parse(queryString);
		
		// 2.執行查詢得到中間結果
		IndexSearcher indexSearcher = LuceneUtils.getIndexSearcher();
		// 返回前n個結果 0 10 10    10 10 20   20 10 30
		TopDocs topDocs = indexSearcher.search(query, firstResult+maxResult);
		int count = topDocs.totalHits; //符合query的總結果數
		ScoreDoc[] scoreDocs = topDocs.scoreDocs;
		// 處理結果
		List<Article> list = new ArrayList<Article>();
		int endIndex = Math.min(firstResult+maxResult, scoreDocs.length);
		
		for(int i=firstResult;i<endIndex;i++){
			//根據編號取出真正的Document資料
			Document doc = LuceneUtils.getIndexSearcher().doc(scoreDocs[i].doc);
			//將document轉為Article
			Article article = ArticleDocumentUtils.documentToArticle(doc);
			list.add(article);
		}
		
		return new SearchResult<Article>(count, list);
	}
注意:使用TermQuery和QueryParser是有區別的,TermQuery只能根據某屬性值的分詞單元(詞元)進行搜尋,不會進行拼接查詢,而QueryParser會對切分後的分詞單元進行拼接搜尋。

優化索引庫

在Lucene3.6版本以後不,Lucene自動會根據生成的索引檔案的段數來判斷,進而自己進行索引檔案的合併和索引的優化等操作,建議不要手動優化,因為優化是一個非常耗費資源的過程。如果要進行優化方式有以下幾種: (1) 在建立索引時 通過IndexWriterConfig優化 設定mergePolicy(合併策略) (2) 使用RAMDirectory(虛擬目錄)在記憶體中操作索引庫,然後合併到硬碟上。(記憶體讀寫速度遠遠大於IO讀寫速度) (3) 排除停用詞,以減少索引檔案的大小。 (4)  將索引資料歸類,分目錄存放。(減少單個資料夾中的檔案數量) 具體描述如下: 第一種方式:通過IndexWriterConfig進行優化,主要通過為IndexWriterConfig設定合併策略(設定合併策略中合併文件數的大小影響 追加和建立索引的速度,設定合併因子的大小影響搜尋索引和建立索引的速度)
@Test
// 優化的第一種方式:通過 IndexWriterConfig 優化設定mergePolicy(合併策略)
public void testoptimize_1() throws IOException {
	IndexWriterConfig indexWriterConfig = new IndexWriterConfig(Version.LUCENE_44,
			new StandardAnalyzer(Version.LUCENE_44));
	// 建立索引庫優化物件...
	LogMergePolicy logMergePolicy = new LogByteSizeMergePolicy();
	// 值越小,搜尋的時候越快,建立索引的時候越慢
	// 值越大,搜尋的時候越慢,建立索引的時候越快。
	logMergePolicy.setMergeFactor(3);
	// 設定segment最大合併文件(Document)數
	// 值較小有利於追加索引的速度
	// 值較大,適合批量建立索引和更快的搜尋
	logMergePolicy.setMaxMergeDocs(1000);
	indexWriterConfig.setMergePolicy(logMergePolicy);

	Directory directory = FSDirectory.open(new File("/Users/liuxun/Desktop/indexes"));
	IndexWriter indexWriter = new IndexWriter(directory, indexWriterConfig);
}
第二種方式:RAMDirectory Lucene的API介面設計的比較通用,輸入輸出結構都很像:
資料庫的表==>記錄==>欄位。
所以很多傳統的應用的檔案、資料庫等都可以比較方便的對映到Lucene的儲存結構/介面中。總體上看:可以先把Lucene當成一個支援全文索引的資料庫系統。
Lucene的索引儲存位置使用的是一個介面(抽象類),也就可以實現各種各樣的實際儲存方式(實現類、子類),比如存到檔案系統中,存在記憶體中、存在資料庫中等等。
Lucene提供了兩個子類:FSDirectory與RAMDirectory。
FSDirectory:在檔案系統中,是真實的資料夾與檔案。
RAMDirectory:在記憶體中,是模擬的資料夾與檔案。 
RAMDirectory與FSDirectory相比:
1.因為沒有IO操作,所以速度快(優點)。
2.因為在記憶體中,所以在程式退出後索引庫資料就不存在了(缺點)
@Test
// 測試RAMDirectory
public void testoptimize_randdirectory() throws IOException {
	// 檔案系統中的真是目錄,可儲存,但是隻能通過IO讀寫,速度相對較慢
	Directory fsDir = FSDirectory.open(new File("/Users/liuxun/Desktop/indexes"));
	// 在記憶體中虛擬的目錄,速度快,但不可儲存,還對機器的記憶體大小有要求
	Directory randDir = new RAMDirectory();

	IndexWriter ramIndexWriter = new IndexWriter(randDir,
			new IndexWriterConfig(LuceneUtils.getVersion(), LuceneUtils.getAnalyzer()));
	Article article = new Article();
	article.setId(5);
	article.setTitle("Lucene是什麼");
	article.setContent("Lucene是全文檢索框架");
	Document document = ArticleDocumentUtils.articleToDocument(article);
	ramIndexWriter.close();
}
因此可以使用虛擬目錄操作索引庫,然後合併到硬碟上。
@Test
//優化的第二種方式:使用虛擬目錄操作索引庫,然後合併到硬碟上
public void testoptimize_2() throws IOException {
	//建立索引目錄
	Directory fsDir = FSDirectory.open(new File("/Users/liuxun/Desktop/indexes"));
	//通過IOContext物件可將硬碟上的額索引讀到記憶體中,涉及IO操作
	IOContext context = new IOContext();
	//將磁碟上的索引載入到記憶體中,以後每次操作索引的時候,直接操作記憶體中的索引即可,不用操作硬碟
	Directory ramDir = new RAMDirectory(fsDir, context);
	// 構造索引讀取器
	IndexReader indexReader = DirectoryReader.open(ramDir);
	IndexSearcher indexSearcher = new IndexSearcher(indexReader);
	Query query = new TermQuery(new Term("title","羅貫中"));
	TopDocs topDocs = indexSearcher.search(query, 100);
	System.out.println(topDocs.totalHits);
}
第三種方式:排除停用詞,減小索引檔案的大小,索引檔案越小,檢索速度就越快。 第四種方式:之所以將索引資料歸類分目錄存放,是因為資料夾中的檔案數量越大 檢索速度就越慢。

分詞器

分詞器的作用

在建立索引時會使用分詞器,在使用字串搜尋時也會使用分詞器,這兩個地方要使用同一個分詞器,否則可能搜尋出不同結果。 Analyzer(分詞器)的作用是把一段文字中的詞按照規則取出所包含的所有詞,對應的是Analyzer類,它是一個抽象類,切分詞的具體規則是由子類實現的,所以對於不同語言(規則),要使用不同的分詞器。它操作流程大致是: 一段文字 ——>按照分詞器規則切分——>得到其中出現的所有的詞。

分詞器的工作流程

英文分詞器一般的工作流程是: 1.切分關鍵詞 2.去除停用詞 3.對於英文單詞,把所有的字母轉為小寫(搜尋時不區分大小寫) 說明:有的分詞器還對英文進行形態還原,就是去除單詞詞尾的形態變化,將其還原為詞的原形。這樣做可以搜尋出更有意義的結果。如搜尋student時 也可以搜尋出students

停用詞

有些詞在文字中出現的頻率非常高,但是對文字所攜帶的資訊基本不產生影響,例如英文的“a、an、the、of”,或中文的“的、了、著、是”,以及各種標點符號等,這樣的詞稱為停用詞(stop word)。文字經過分詞之後,停用詞通常被過濾掉,不會被進行索引。在檢索的時候,使用者的查詢中如果含有停用詞,檢索系統也會將其過濾掉(因為使用者輸入的查詢字串也要進行分詞處理)。排除停用詞可以加快建立索引的速度,減小索引庫檔案的大小

常用的中文分詞器

中文的分詞比較複雜,,因為不是一個字就是一個詞,而且一個詞在另外一個地方就可能不是一個詞,比如"帽子和服裝","和服"在這句話中就不是一個詞。對於中文的分詞,通常有三種方式:單字分詞、二分法分詞、詞典分詞 1.單字分詞:就是按照中文一個字一個字的進行分詞。如"我們是中國人",單字分詞後效果為:"我"、"們"、"是"、"中"、"國"、"人"。常見的單字分詞器有StandardAnalyzer、ChineseAnalyzer 2.二分法分詞:按照兩個字進行切分,如"我們是中國人",效果:"我們"、"們是"、"是中"、“中國”、"國人"。常見的二分法分詞器有CJKAnalyzer 3、詞庫分詞:按照某種演算法構造詞,然後去匹配已經建好的詞庫集合,如果匹配到就切分出來稱為詞語。通常詞庫分詞被分為是最理想的中文分詞演算法。如:"我們是中國人",效果為:"我們"、"中國人"。常見的詞庫分詞器有極易分詞器MMAnalyzer,也可以使用庖丁分詞器(IKAnalyzer)

使用庖丁分詞的步驟

下載庖丁分詞zip包 解壓後文件夾"IK Analyzer 2012FF_hf1" 其目錄結構如下:

1.匯入IK Analyzer 2012FF_hf1.zip中的jar包
2.引入分詞器的配置檔案IKAnalyzer.cfg.xml(配置停用詞和自定義擴充套件詞),放到類路徑下
3.引入ext_stopword.dic檔案(存放停用詞),放置到類路徑下
4.新建ext_dict.dic檔案(存放自定義的擴充套件詞),放置到類路徑下

測試分詞器的程式碼

/**
 * 使用指定的分詞器,對指定的文字進行分詞
 * @param analyzer
 * @param text
 * @throws IOException 
 */
public  void testAnalyzer(Analyzer analyzer,String text) throws IOException{
	System.out.println("當前使用的分詞器:"+analyzer.getClass().getSimpleName());
	//切分關鍵字,切分後的關鍵字都存放在tokenStream裡面
	TokenStream tokenStream = analyzer.tokenStream("content", new StringReader(text));
	
	tokenStream.addAttribute(CharTermAttribute.class);
	tokenStream.reset();
	while(tokenStream.incrementToken()){
		 CharTermAttribute charTermAttribute = tokenStream.getAttribute(CharTermAttribute.class);
          System.out.print(new String(charTermAttribute.toString())+"  ");
	}
	System.out.println();
	tokenStream.close();
}

常見的分詞器測試

@Test
public  void test() throws IOException{
	String text = "網友也紛紛吐槽我雖然是中國人,但是操你媽我更愛美國";
	//單字切分
	Analyzer standardAnalyzer = new StandardAnalyzer(Version.LUCENE_44);
	//二分法切分
	Analyzer cjkAnalyzer = new CJKAnalyzer(Version.LUCENE_44);
	//按詞庫切分:庖丁分詞器
	Analyzer ikAnalyzer = new IKAnalyzer();
	testAnalyzer(standardAnalyzer, text);
	System.out.println("---------------------------------------------------");
	testAnalyzer(cjkAnalyzer, text);
	System.out.println("---------------------------------------------------");
	testAnalyzer(ikAnalyzer, text);
} 
其中庖丁分詞器的核心配置IKAnalyzer.cfg.xml如下:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE properties SYSTEM "http://java.sun.com/dtd/properties.dtd">  
<properties>  
	<comment>IK Analyzer 擴充套件配置</comment>
	<!--使用者可以在這裡配置自己的擴充套件字典 可以新增多個擴充套件字典 之間用冒號隔開如 mydict.dic;ext_dict.dic--> 
	<entry key="ext_dict">mydict.dic;</entry> 
	 
	 <!--使用者可以在這裡配置自己的擴充套件停止詞字典 也可以配置多個方式同上-->
	<entry key="ext_stopwords">ext_stopword.dic</entry> 
</properties>
mydict.dic(存放擴充套件詞)
吐槽
操你媽
ext_stopword.dic(存放停用詞)
也
了
仍
從
以
使
則
卻
又
及
對
就
並
很
或
把
是
的
著
給
而
被
讓
在
還
比
等
當
與
於
但
乃
需要注意的是:如果檔案編碼不對 停用詞和擴充套件詞是不會生效的 測試效果如下:

使用分詞器注意事項

建立索引庫和搜尋索引庫都會使用分詞器,所以當改變分詞器的時候,需要重新建立索引庫

文字高亮

1.高亮需要的jar包 Lucene\highlighter\lucene-highlighter-4.4.0.jar
Lucene\memory\lucene-memory-4.4.0.jar

2.高亮的特點 (1) 高亮將文字生成一段摘要,用於搜尋,並把摘要中的關鍵字高亮顯示 (2) 摘要的大小可以配置,預設是100個字元 (3) 文字實現高亮的效果,就是在需要高亮的文字前後加字首和字尾(類似於HTML) 3. 高亮器的使用
IndexSearcher indexSearcher = LuceneUtils.getIndexSearcher();
String keywords = "檢索";
String[] fields = { "content" };
// 構造QueryParser,解析使用者輸入的關鍵字
QueryParser queryParser = new MultiFieldQueryParser(LuceneUtils.getVersion(), fields,
		LuceneUtils.getAnalyzer());
Query query = queryParser.parse(keywords);
TopDocs topDocs = indexSearcher.search(query, 1);
ScoreDoc[] scoreDocs = topDocs.scoreDocs;

// 高亮顯示的格式
Formatter formatter = new SimpleHTMLFormatter("<font color='red'>", "</font>");

// 與query 查詢條件進行關聯,因為query 包含了搜尋的關鍵字
// 只有知道了搜尋的關鍵字,高亮顯示的格式,才能把一段文字進行高亮
Scorer scorer = new QueryScorer(query);

// 建立一個高亮器,使用Lucene自帶的高亮器進行高亮
Highlighter highlighter = new Highlighter(formatter, scorer);
Article article = null;
List<Article> articles = new ArrayList<Article>();
for (ScoreDoc scoreDoc : scoreDocs) {
	article = new Article();
	Document document = indexSearcher.doc(scoreDoc.doc);
	String title = document.get("title");
	String content = document.get("content");
	System.out.println("id==" + document.get("id"));
	System.out.println("title===" + title);
	System.out.println("content===" + content);
	System.out.println("沒有高亮之前的結果....----------------------------------------------------");

	if (content != null) {
		// 返回高亮之後的文字
		String highcontent = highlighter.getBestFragment(LuceneUtils.getAnalyzer(), "content", content);
		System.out.println("高亮過後的highcontent=" + highcontent);
		if (highcontent != null) {
			article.setContent(highcontent);
		} else {
			article.setContent(content);
		}
	}

	if (title != null) {
		String hightitle = highlighter.getBestFragment(LuceneUtils.getAnalyzer(), "title", title);
		//如果我們要對一段文字進行高亮,假設這段文字中不包含關鍵字,對這段文字高亮後返回bull
		System.out.println("高亮過後的hightitle="+hightitle);
		//不能將null返回客戶端,所以我們要進行判斷,如果為null,就返回沒有高亮之前的文字
		if (hightitle !=null) {
			article.setTitle(hightitle);
		}else{
			article.setTitle(title);
		}
	}
}
執行結果如下:
高亮時需要注意的事項:如果對一段文字進行高亮時,如果該文字中不包含搜尋的關鍵字,那麼搜尋後的結果將是null,所以高亮後需要判斷是否為null

相關度排序

(一) 相關度排序得分
(1) 相關度排序得分是在查詢時根據查詢條件實時計算出來的
(2) 如果索引庫資料不變,查詢條件不變,查詢出來的文件得分也不變。 (二) Boost(權值)和Sort(排序) 通過改變文件Boost值來改變排序結果。Boost是指索引建立的過程中,給整篇文章或者文件的某一特定屬性設定的權值因子,在檢索時,優先返回分數高的。通過Document物件的setBoost()方法和Field物件的setBoost()方法,可以分別為Document和Field指定Boost引數。不同在於前者對文件中的每一個域都修改了引數,而後者只對指定域進行修改。預設是1F,一般不做修改。 使用Sort物件定製排序。Sort支援的排序功能以文件當中的域為單位,通過這種方法,可以實現一個或多個域的多形式值排序。 (三)Boost 在新增的時候改變權重值,可以對每個document 的屬性進行新增,
注意:如果新增的索引值沒有進行分詞,則不能改變許可權值.
Document document=new Document();
document.add(new StringField("id",article.getId(),Store.YES));
StringField field=new StringField("title",article.getTitle(),Store.YES);
TextField textField=new TextField("content",article.getContent(),Store.YES);
textField.setBoost(5F);
document.add(field);
document.add(textField);
return document;
測試權重程式碼如下:
IndexSearcher indexSearcher=LuceneUtils.getIndexSearcher();
String keywords="全文檢索";

String fields []={"content"};

QueryParser queryParser=new MultiFieldQueryParser(LuceneUtils.getVersion(),fields,LuceneUtils.getAnalyzer());

//條件
Query query=queryParser.parse(keywords);

TopDocs topDocs=indexSearcher.search(query, 50);
ScoreDoc scoreDocs[]=topDocs.scoreDocs;

for(ScoreDoc scoreDoc :scoreDocs){
	//根據id 去擊中一個文件呢..
	Document document=indexSearcher.doc(scoreDoc.doc);
	//每個文件都有一個得分,這個得分是float 型別,是lucene 自己內部算出來,VSM演算法
	System.out.println("id==="+document.get("id")+"得分===="+scoreDoc.score);
	System.out.println("title==="+document.get("title"));
	System.out.println("content==="+document.get("content"));
	System.out.println("url==="+document.get("url"));
	System.out.println("author==="+document.get("author"));
	
}
測試結果:
其實設定權重值只是影響文件得分的一個重要因素,影響文件得分的因素還有關鍵字在域中的先後順序和出現次數,越在前面,出現次數越多也會提高文件的得分。
(四)Sort 在搜尋索引庫的時候,建立排序
按id升序排列
SortField sortField=new SortField("id",SortField.Type.INT,true);
按id降序排列
SortField sortField=new SortField("id",SortField.Type.STRING);
Sort sort = new Sort(sortField);
TopDocs topDocs = indexSearcher.search(query, 100, sort);
注意:String型別和Int型別在比較排序的時候不同
例如:Int:123>23
     String:”123”<“23”
 測試程式碼:
public class TestSort {
	public static void main(String[] args) throws Exception {
		IndexSearcher indexSearcher=LuceneUtils.getIndexSearcher();
		String keywords="全文檢索";
		
		String fields []={"content"};
		
		QueryParser queryParser=new MultiFieldQueryParser(LuceneUtils.getVersion(),fields,LuceneUtils.getAnalyzer());
		
		//條件
		Query query=queryParser.parse(keywords);
		
		//需要根據哪個欄位進行排序
		// 第一個引數:fieldName 欄位名稱,根據哪個欄位進行排序
		// 第二個引數:type  欄位型別,排序的欄位的型別
		// 第三個引數:reverse 是否逆序排序,true 降序  false 升序
		SortField sortField = new SortField("id", Type.INT, false);
		//設定排序的條件
		Sort sort = new Sort(sortField);
		TopDocs topDocs=indexSearcher.search(query, 10,sort);
		ScoreDoc scoreDocs[]=topDocs.scoreDocs;
		
		for(ScoreDoc scoreDoc :scoreDocs){
			//根據id 去擊中一個文件
			Document document=indexSearcher.doc(scoreDoc.doc);
			System.out.println("id==="+document.get("id")+"得分===="+scoreDoc.score);
			System.out.println("title==="+document.get("title"));
			System.out.println("content==="+document.get("content"));
			System.out.println("url==="+document.get("url"));
			System.out.println("author==="+document.get("author"));
			
		}
	}
}
測試結果:
可以發現按照欄位排序後,就不會再計算文章的得分了。

過濾器

(一) filter說明 使用Filter可以對搜尋結果進行過濾以獲得更小範圍的結果。使用Filter對效能的影響很大(有可能會使查詢慢上百倍)。但也可使用相應的查詢實現一樣的效果
(二) 在搜尋的時候,新增過濾器 indexSearcher.search(query, n);
indexSearcher.search(query, filter, n);
indexSearcher.search(query, filter, n, sort);
Filter filter=NumericRangeFilter.newIntRange("id", 1, 10, true, true);
TopDocs topDocs=indexSearcher.search(query,filter,100);
TopDocs topDocs = indexSearcher.search(query, filter, 100,sort);

測試程式碼如下:
public class TestFilter {
	public static void main(String[] args) throws Exception {
		IndexSearcher indexSearcher=LuceneUtils.getIndexSearcher();
		String keywords="全文檢索";
		
		String fields []={"content"};
		
		QueryParser queryParser=new MultiFieldQueryParser(LuceneUtils.getVersion(),fields,LuceneUtils.getAnalyzer());
		
		//條件
		Query query=queryParser.parse(keywords);
		
		//需要根據哪個欄位進行排序
		// 第一個引數:fieldName 欄位名稱,根據哪個欄位進行排序
		// 第二個引數:type  欄位型別,排序的欄位的型別
		// 第三個引數:reverse 是否逆序排序,true 降序  false 升序
		SortField sortField = new SortField("id", Type.INT, false);
		//設定排序的條件
		Sort sort = new Sort(sortField);
		
		//過濾器
		//NumericRangeFilter.newIntRange(field, min, max, minInclusive, maxInclusive)
		//引數含義:
		// 1.field  需要根據那個欄位進行過濾
		// 2.min    欄位對應範圍的最小值
		// 3.max    欄位對應範圍的最大值
		// 4.minInclusive 是否包含最小值
		// 5.maxInclusive 是否包含最大值
		Filter filter = NumericRangeFilter.newIntRange("id", 1, 3, true, true);
		
		TopDocs topDocs=indexSearcher.search(query,filter, 10,sort);
		ScoreDoc scoreDocs[]=topDocs.scoreDocs;
		
		for(ScoreDoc scoreDoc :scoreDocs){
			//根據id 去擊中一個文件
			Document document=indexSearcher.doc(scoreDoc.doc);
			System.out.println("id==="+document.get("id"));
			System.out.println("title==="+document.get("title"));
			System.out.println("content==="+document.get("content"));
			System.out.println("url==="+document.get("url"));
			System.out.println("author==="+document.get("author"));
			
		}
	}
}
測試結果:

查詢(******)

查詢時Lucene中最重要的一部分,常用的查詢方法一共有8種 :範圍查詢(NumericQuery)、關鍵詞查詢(TermQuery)、萬用字元查詢(WildcardQuery)、查詢所有(MatchAllDocsQuery)、模糊查詢(FuzzyQuery)、短語查詢(PhraseQuery)、布林查詢(BooleanQuery) 以上是直接使用Query物件的子類進行查詢,還有一種就是使用QueryParser或MultiFieldQueryParser生成Query

查詢方式

按照查詢方式分類有兩種: 一是使用QueryParser或MultiFieldQueryParser生成Query物件 二是直接使用Query的各種子類

方式一使用查詢字串

使用查詢字串:QueryParser或 MultiFieldQueryParser生成Query查詢方式
1、QueryParser:只在一個欄位中查詢
2、MultiFieldQueryParser:可以在多個欄位查詢
例如:
QueryParser queryParser = new QueryParser(Version.LUCENE_30, "title", Configuration.getAnalyzer());
QueryParser queryParser = new MultiFieldQueryParser(Version.LUCENE_30, new String[] { "title", "content" }, Configuration.getAnalyzer());
QueryParser介面有兩種方法生成Query Query query =  queryParser.parse("搜尋的內容|查詢字串"); 根據關鍵字或查詢字串生成查詢 注意:查詢字串和搜尋的關鍵字是不一樣的,查詢字串主要指的是檢索的關鍵字 可以是任意內容,而查詢字串是有一定格式的,如果格式錯誤是會報異常的。

方式二:直接使用Query的子類

使用Query的子類其本質也是相當於生成一定規則的"查詢字串", 如果熟悉查詢字串的使用規則,完全可以使用QueryParser.parse(queryString) 進行替代 詳細用法,程式碼如下:
package liuxun.test.lucene;

import java.util.ArrayList;
import java.util.List;

import org.apache.lucene.document.Document;
import org.apache.lucene.index.Term;
import org.apache.lucene.queryparser.classic.MultiFieldQueryParser;
import org.apache.lucene.queryparser.classic.ParseException;
import org.apache.lucene.search.BooleanClause.Occur;
import org.apache.lucene.search.BooleanQuery;
import org.apache.lucene.search.FuzzyQuery;
import org.apache.lucene.search.IndexSearcher;
impor