Lucene全文檢索引擎教你如何查找海量數據

4.Lucene入門
4.1.是什麼
Apache Lucene是一個用Java寫的高性能、可伸縮的全文檢索引擎工具包,它可以方便的嵌入到各種應用中實現針對應用的全文索引/檢索功能。Lucene的目標是爲各種中小型應用程序加入全文檢索功能。
Lucene的核心作者:Doug Cutting是一位資深全文索引/檢索專家。
版本發佈情況:2000年3月,最初版發佈,2001年9月,加入apache;2004年7月,發佈1.4正式版;2009年11月,發佈2.9.1(jdk1.4)及3.0(jdk1.5)版本;2015年3月,發佈4.10.4。2016年2月,發佈5.5.0。
4.2.Helloworld
Lucene的索引庫和數據庫一樣,都提供相應的API來便捷操作。

在這裏插入圖片描述
Lucene中的索引維護使用IndexWriter,由這個類提供添刪改相關的操作;索引的搜索則是使用IndexSearcher進行索引的搜索。HelloWorld代碼如下。
4.2.1.創建索引
步驟:
1、 把文本內容轉換爲Document對象
文本是作爲Document對象的一個字段而存在
2、準備IndexWriter(索引寫入器)
3 、通過IndexWriter,把Document添加到緩衝區並提交
addDocument
commit
close


//創建索引的數據 現在寫死,以後根據實際應用場景
	String doc1 = "hello world";
	String doc2 = "hello java world";
	String doc3 = "hello lucene world";
	private String path ="F:/eclipse/workspace/lucene/index/
            hello";
@Test
	public void testCreate() {
		try {
			//2、準備IndexWriter(索引寫入器)
			//索引庫的位置 FS fileSystem
			Directory d = FSDirectory.open(Paths.get(path ));
			//分詞器
			Analyzer analyzer = new StandardAnalyzer();
			//索引寫入器的配置對象
			IndexWriterConfig conf = new IndexWriterConfig(analyzer);
			IndexWriter indexWriter = new IndexWriter(d, conf);
			System.out.println(indexWriter);
			
			//1、 把文本內容轉換爲Document對象
			//把文本轉換爲document對象
			Document document1 = new Document();
			//標題字段
			document1.add(new TextField("title", "doc1", Store.YES));
			document1.add(new TextField("content", doc1, Store.YES));
			//添加document到緩衝區
			indexWriter.addDocument(document1);
			Document document2 = new Document();
			//標題字段
			document2.add(new TextField("title", "doc2", Store.YES));
			document2.add(new TextField("content", doc2, Store.YES));
			//添加document到緩衝區
			indexWriter.addDocument(document2);
			Document document3 = new Document();
			//標題字段
			document3.add(new TextField("title", "doc3", Store.YES));
			document3.add(new TextField("content", doc3, Store.YES));
			
			//3 、通過IndexWriter,把Document添加到緩衝區並提交
			//添加document到緩衝區
			indexWriter.addDocument(document3);
			indexWriter.commit();
			indexWriter.close();
			
		} catch (Exception e) {
			e.printStackTrace();
		}
	
	}
       // OpenMode=create 每次都會重置索引庫然後重新添加索引文檔
       // 後者覆蓋前者(默認是不覆蓋累加模式)
		conf.setOpenMode(OpenMode.CREATE);

圖形界面客戶端使用
4.2.2.搜索索引
1 封裝查詢提交爲查詢對象
2 準備IndexSearcher
3 使用IndexSearcher傳入查詢對象做查詢-----查詢出來只是文檔編號DocID
4 通過IndexSearcher傳入DocID獲取文檔
5 把文檔轉換爲前臺需要的對象 Docment----> Article

@Test
	public void testSearch() {
		String keyWord = "lucene";
		try {
			// * 1 封裝查詢提交爲查詢對象
		    //通過查詢解析器解析一個字符串爲查詢對象
			String f = "content"; //查詢的默認字段名,
			Analyzer a = new StandardAnalyzer();//查詢關鍵字要分詞,所有需要分詞器
			QueryParser parser = new QueryParser(f, a);
			Query query = parser.parse("content:"+keyWord);
			// * 2 準備IndexSearcher
			Directory d = FSDirectory.open(Paths.get(path ));
			IndexReader r = DirectoryReader.open(d);
			IndexSearcher searcher = new IndexSearcher(r);
			// * 3 使用IndexSearcher傳入查詢對象做查詢-----查詢出來只是文檔編號DocID
			TopDocs topDocs = searcher.search(query, 1000);//查詢ton條記錄 前多少條記錄
			System.out.println("總命中數:"+topDocs.totalHits);
			ScoreDoc[] scoreDocs = topDocs.scoreDocs;//命中的所有的文檔的封裝(docId)
			// * 4 通過IndexSearcher傳入DocID獲取文檔
			for (ScoreDoc scoreDoc : scoreDocs) {
				int docId = scoreDoc.doc;
				Document document = searcher.doc(docId);
				// * 5 把文檔轉換爲前臺需要的對象 Docment----> Article
				System.out.println("=======================================");
				System.out.println("title:"+document.get("title")
								+",content:"+document.get("content"));
			}
		} catch (Exception e) {
			e.printStackTrace();
		}
	}

5.Lucene API詳解
前面已經講了luncene的核心,但還有很多細節,也就是一些LuceneAPI使用,接下來一一講解。
5.1.索引目錄Directory
Directory是一個對索引目錄的一個抽象。索引目錄用於存放lucene索引文件。直接根據一個文件夾地址來創建索引目錄使用SimpleFSDirectory。
MMapDirectory : 針對64系統,它在維護索引庫時,會結合“內存”與硬盤同步來處理索引。
SimpleFSDirectory : 傳統的文件系統索引庫。
RAMDirectory : 內存索引庫
5.2.Document(行)及IndexableField(列)
在這裏插入圖片描述在這裏插入圖片描述
當往索引中加入內容的時候,每一條信息用一個Document來表示,Document的意思表示文檔,也可以理解成記錄,與關係數據表中的一行數據記錄類似;
IndexableField表示字段,與關係數據表中的列類似(列數量不定!!),每個Document也由一系列的IndexableField組成,可以理解爲數據庫的動態列;
在這裏插入圖片描述
Document提供的方法主要包括:
字段添加:add(Fieldable field)
字段刪除:removeField、removeFields
獲取字段或值:get、getBinaryValue、getField、getFields等

IndexableField及Field
Field代表Document中的一列數據,相當於一條表記錄中的一列。
Lucene提供了一個接口IndexableField,其它的API大多針對這個接口編程,因此Lucene中的列對象實際上是由IndexableField來定義。在實際開發中,主要使用的是Field類的子類。
在這裏插入圖片描述

Field的Store方式及Index方式

Lucene中,在創建Field的時候,可以指定Field的store及index屬性;
store屬性:表示字段值是否存儲,Store.YES表示要存儲,而Store.NO則表示不存儲;
index屬性:表示字段的索引方式,
Tokenized表示根據設定的詞法分析器來建立該字段的索引;FALSE,不分詞;true要分詞。
在這裏插入圖片描述
在這裏插入圖片描述
在這裏插入圖片描述

索引庫中實際分爲兩個部分,一個部分佔的空間相對大一些叫做數據區,有Store屬性維護,代表是否把字段的內容存到數據區;另一個部分相對小一些,叫做目錄區,由Index維護,代表是否支持搜索。
Store和Index組合使用的適用情況見下圖:
在這裏插入圖片描述
是否要創建索引: 看是否需要搜索。
是否要分詞 : 看是否是專有名詞。
是否要存儲 : 結果頁面,是否要顯示,看文檔字段的內容能不能鏈接找到。—大字段

5.3.分詞Analyzer(詞法分析器)
分詞器是Lucene中非常重要的一個知識點,如果你面試時說你用過Lucene面試官一定會問你用的什麼分詞器。
分詞,也稱詞法分析器(或者叫語言分析器),就是指索引中的內容按什麼樣的方式來建立,這在全文檢索中非常關鍵,是按英文單詞建立索引,還是按中文詞意建立索引;這些需要由Analyzer來指定。
對於中文,需要採用字典分詞,也叫詞庫分詞;把中文件的詞全部放置到一個詞庫中,按某種算法來維護詞庫內容;如果匹配到就切分出來成爲詞語。通常詞庫分詞被認爲是最理想的中文分詞算法。如:“我們是中國人”,效果爲:“我們”、“中國人”。(可以使用SmartChineseAnalyzer,“極易分詞” MMAnalyzer ,或者是“庖丁分詞”分詞器、IKAnalyzer。推薦使用IKAnalyzer )
在這裏我們推薦IKAnalyzer。使用時需導入IKAnalyzer.jar,並且拷貝IKAnalyzer.cfg.xml,ext_stopword.dic文件,分詞器測試代碼如下:


public class AnalyzerTest {
	//創建索引的數據 現在寫死,以後根據實際應用場景
	private String en = "oh my lady gaga"; // oh my god
	private String cn = "迅雷不及掩耳盜鈴兒響叮噹仁不讓";
	private String str = "源代碼教育FullText Search Lucene框架的學習";
	
	
	/**
	 * 把特定字符串按特定的分詞器來分詞
	 * @param analyzer
	 * @param str
	 * @throws Exception
	 */
	public void testAnalyzer(Analyzer analyzer,String str) throws Exception {
		TokenStream tokenStream = analyzer.tokenStream("content", new StringReader(str));
		// 在讀取詞元流後,需要先重置/重加載一次
		tokenStream.reset();
		while(tokenStream.incrementToken()){
			System.out.println(tokenStream);
		}
	}
	
	//標準分詞:不支持中文
	@Test
	public void testStandardAnalyzer() throws Exception {
		
		testAnalyzer(new StandardAnalyzer(), cn);
	}
	
	//簡單分詞:不支持中文
	@Test
	public void testSimpleAnalyzer() throws Exception {
		testAnalyzer(new SimpleAnalyzer(), cn);
	}
	
	//二分分詞:兩個字是一個詞
	@Test
	public void testCJKAnalyzer() throws Exception {
		testAnalyzer(new CJKAnalyzer(), cn);
	}
	
	//詞典分詞:從詞典中查找
	@Test
	public void testSmartChineseAnalyzer() throws Exception {
		testAnalyzer(new SmartChineseAnalyzer(), str);
	}
	
	//IK分詞:從詞典中查找
	// 簡單使用:拷貝兩個配置文件,IKAnalyzer.cfg.xml,stopword.dic拷貝一個jar包 
    IKAnalyzer2012_V5.jar
	//       擴展詞,停止詞
	//  注意:打開方式,不要使用其他的,
//直接使用eclipse的text Editor, 
修改以後要刷新一下讓項目重新編譯(有時候需要有時候不需要刷新)
	
	@Test
	public void testIKAnalyzer() throws Exception {
		//true 粗密度分詞(智能分詞)  false 細密度分詞
		testAnalyzer(new IKAnalyzer(true), str);
	}
}

5.4.索引的添刪改
經過之前的分析,我們知道對索引的操作統一使用IndexWriter。測試代碼如下:

// 數據源
	private String doc1 = "hello world";
	private String doc2 = "hello java world";
	private String doc3 = "hello lucene world";

	// 索引庫目錄
	private String indexPath = "F:\\ecworkspace\\lucene\\indexCRUD";
	@Test
	public void createIndex() throws IOException, ParseException {
		/**
		 * 準備工作
		 */
		// 索引目錄
		Directory d = FSDirectory.open(Paths.get(indexPath));
		// 詞法分析器
		Analyzer analyzer = new StandardAnalyzer();
		// 寫操作核心配置對象
		IndexWriterConfig conf = new IndexWriterConfig(analyzer);
		conf.setOpenMode(OpenMode.CREATE);
		// 寫操作核心對象
		IndexWriter indexWriter = new IndexWriter(d, conf);
		System.out.println(indexWriter);

		/**
		 * 操作
		 */
		Document document1 = new Document();
		document1.add(new TextField("id", "1", Store.YES));
		document1.add(new TextField("name", "doc1", Store.YES));
		document1.add(new TextField("content", doc1, Store.YES));
		indexWriter.addDocument(document1);

		Document document2 = new Document();
		document2.add(new TextField("id", "2", Store.YES));
		document2.add(new TextField("name", "doc2", Store.YES));
		document2.add(new TextField("content", doc2, Store.YES));
		indexWriter.addDocument(document2);
		Document document3 = new Document();
		document3.add(new TextField("id", "3", Store.YES));
		document3.add(new TextField("name", "doc3", Store.YES));
		document3.add(new TextField("content", doc3, Store.YES));
		indexWriter.addDocument(document3);
		/**
		 * 收尾
		 */
		indexWriter.commit();
		indexWriter.close();
		
		searchIndex();
	}

	@Test
	public void del() throws IOException, ParseException{
		/**
		 * 準備工作
		 */
		// 索引目錄
		Directory d = FSDirectory.open(Paths.get(indexPath));
		// 詞法分析器
		Analyzer analyzer = new StandardAnalyzer();
		// 寫操作核心配置對象
		IndexWriterConfig conf = new IndexWriterConfig(analyzer);
		// 寫操作核心對象
		IndexWriter indexWriter = new IndexWriter(d, conf);
		System.out.println(indexWriter);
		
		
		//刪除所有
		//indexWriter.deleteAll();
		//第一種
//		QueryParser qpParser = new QueryParser("id", analyzer);
//		Query query = qpParser.parse("1");
//		indexWriter.deleteDocuments(query);
		
		//第二種
		indexWriter.deleteDocuments(new Term("id", "1"));
		
		indexWriter.commit();
		indexWriter.close();
		
		searchIndex();
	}
	
	@Test
	public void update() throws IOException, ParseException{
		/**
		 * 準備工作
		 */
		// 索引目錄
		Directory d = FSDirectory.open(Paths.get(indexPath));
		// 詞法分析器
		Analyzer analyzer = new StandardAnalyzer();
		// 寫操作核心配置對象
		IndexWriterConfig conf = new IndexWriterConfig(analyzer);
		// 寫操作核心對象
		IndexWriter indexWriter = new IndexWriter(d, conf);
		System.out.println(indexWriter);
		
		
		Document doc = new Document();
		doc.add(new TextField("id", "2", Store.YES));
		doc.add(new TextField("name", "doc2", Store.YES));
		doc.add(new TextField("content", "修改後 -的doc2", Store.YES));
		
		indexWriter.updateDocument(new Term("id","2"), doc );
		/*等價於
		 indexWriter.deleteDocuments(new Term("id", "2"));
		 indexWriter.addDocument(doc);
		 */
		indexWriter.commit();
		indexWriter.close();
		
		searchIndex();
	}
	
	
	@Test
	public void searchIndex() throws IOException, ParseException {
		// 索引目錄
		Directory d = FSDirectory.open(Paths.get(indexPath));
		// 詞法分析器
		Analyzer analyzer = new StandardAnalyzer();
		// 創建索引的讀寫對象
		IndexReader r = DirectoryReader.open(d);
		// 創建核心對象
		IndexSearcher indexSearcher = new IndexSearcher(r);

		// 查詢解析器
		// 參數1:默認查詢的字段
		// 參數2:分詞器
		QueryParser queryParser = new QueryParser("content", analyzer);
		String queryString = "*:*";

		Query query = queryParser.parse(queryString);
		// 調用核心對象的search方法
		// 參數query: 查詢對象
		// 參數 n : 前n條
		TopDocs topDocs = indexSearcher.search(query, 50);
		System.out.println("一共查詢到的數量:" + topDocs.totalHits);

		// 獲得數據集合
		ScoreDoc[] scoreDocs = topDocs.scoreDocs;
		for (ScoreDoc scoreDoc : scoreDocs) {
			// 獲取文檔ID
			int docId = scoreDoc.doc;
			// 通過docId獲取Document
			Document doc = indexSearcher.doc(docId);

			System.out.println("id="+doc.get("id")+",name=" + doc.get("name") + ",content=" + doc.get("content"));
		}
	}

5.5.Query及Searcher
搜索是全文檢索中最重要的一部分,前面HelloWorld中也發現,Query對象只是一個接口,他有很多子類的實現。在前面直接使用QueryParser的Parse方法來創建Query對象的實例,實際他會根據我們傳入的搜索關鍵字自動解析成需要的查詢類型,索引在這裏我們也可以直接new一個Query實例來達到不同的搜索效
抽取結構:

// 先做一個準備工作,提供兩個search方法 
//一個傳入搜索關鍵字進行搜索
public void search(String keyword) throws Exception {
		Directory directory = FSDirectory.open(Paths.get("E:\\tools\\eclipse\\workspace\\lucene\\helloIndex"));
		;
		// 索引的和讀取對象
		IndexReader reader = DirectoryReader.open(directory);
		// 搜索文檔通過核心搜索類IndexSearcher來查詢
		IndexSearcher indexSearcher = new IndexSearcher(reader);

		// 先創建一個QueryParse對象
		QueryParser queryParser = new QueryParser("content", new StandardAnalyzer());
		// 通過queryParse對象解析關鍵字並創建對應的查詢對象
		Query query = queryParser.parse(keyword);

		// 通過search方法返回前n個文檔的封裝對象
		TopDocs topDocs = indexSearcher.search(query, 5);
		// 總共找到的相關的文檔數
		int totalHits = topDocs.totalHits;
		System.out.println("總條數:" + totalHits);
		// 獲取查詢的結果(並不包含文檔本身)
		ScoreDoc[] scoreDocs = topDocs.scoreDocs;
		for (ScoreDoc scoreDoc : scoreDocs) {
			int documentId = scoreDoc.doc;
			Document document = indexSearcher.doc(documentId);
			float score = scoreDoc.score;

			// 獲取文檔的字段值
			String docId = document.get("docId");
			String content = document.get("content");

			System.out.println("ID:" + documentId + ",score:" + score + ",docId:" + docId + ",content:" + content);
		}
	}



// 傳入一個查詢對象
	public static void testSearch(Query q) throws Exception {
        // 索引庫地址
		String path = "E:\\work\\eclipse4.7_project\\Luncene-demo\\index";
		System.out.println("對應的查詢語句爲:" + q);
		// 獲取索引庫的目錄
		Directory d = FSDirectory.open(Paths.get(path));
		// 獲取索引讀取對象
		IndexReader reader = DirectoryReader.open(d);
		// 創建索引查詢器
		IndexSearcher searcher = new IndexSearcher(reader);
		// 執行查詢
		TopDocs td = searcher.search(q, 10);
		// 遍歷結果
		for (int i = 0; i < td.scoreDocs.length; i++) {
			// 得到符合條件的內部文檔對象
			ScoreDoc doc = td.scoreDocs[i];
			// 得到文檔對象
			Document d1 = searcher.doc(doc.doc);
			System.out.println("title: " + d1.get("title") + "     content:" + d1.get("content"));
		}
	}

1)單詞查詢
在這裏插入圖片描述
2)段落搜索, 要想把多個單詞當成一個整體進行搜索,使用雙引號包裹
在這裏插入圖片描述
3)通配符搜索
在這裏插入圖片描述
4)模糊搜索 最多允許 2個錯誤
在這裏插入圖片描述
5)臨近查詢,在段落查詢的基礎上用“~”後面跟一個1到正無窮的正整數。代表段落中,單詞與單詞之間最大的間隔數
在這裏插入圖片描述
6)組合查詢
// + (must) : 對應的單詞必須出現
// - (must_not): 不能出現
// 不寫 (should): 可能出現
// 關鍵字之間的邏輯計算是 AND
在這裏插入圖片描述

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章