1. 程式人生 > >Lucene+分詞器精確提取使用者自定義關鍵詞(Lucene版本3.6)

Lucene+分詞器精確提取使用者自定義關鍵詞(Lucene版本3.6)

此篇部落格的Lucene版本是3.6.0,若您的Lucene版本為5.X可以移步到此博文http://blog.csdn.net/echoyy/article/details/78468225

在分詞的過程中,有時候需要只提取詞典裡自定義好的關鍵詞,而傳統的分詞工具如(IKanalyzer)好像並不支援此功能

CSDN論壇裡有人給出解決方法,是採用Lucene的TermQuery方法來進行索引的檢索功能

然而我自己在呼叫TermQuery時候反而什麼都檢索不出來

最後發現是索引的建立問題

最開始使用的是流的方式讀詞典檔案就像醬樣doc.add(new Field("contents",new FileReader(files)));

然後使用TermQuery就gg了,因此唯結果論話推測這種方式在建立索引的時候應該是對文字進行了拆解,也就是說比如詞典裡存的是“你好”,這種方式在建立索引時將“你好”拆成了“你”和“好”,導致TermQuery在對“你好”進行檢索的過程中檢索不到值。

解決方案是先用BufferReader來讀取詞典中的文字,將其存到一個List<String> content中,後續迴圈取List中的值呼叫doc.add(new Field("contents",content,Field.Store.YES, Field.Index.NOT_ANALYZED));方法

注意這裡的域索引選項(Field.Index.*)使用的是Index.NOT_ANALYZED引數,這是由於該引數實際上將域值作為單一語彙單元並使之能被搜尋。適用於索引那些不能被分解的域值比如URL、檔案路徑、日期、人名、電話號碼等。

然後,搞定

最後是索引和檢索的程式碼

1、建立索引

import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import java.util.List;

import org.apache.lucene.analysis.Analyzer;
import org.apache.lucene.analysis.standard.StandardAnalyzer;
import org.apache.lucene.document.Document;
import org.apache.lucene.document.Field;
import org.apache.lucene.index.IndexWriter;
import org.apache.lucene.index.IndexWriterConfig;
import org.apache.lucene.store.Directory;
import org.apache.lucene.store.FSDirectory;
import org.apache.lucene.util.Version;

/**
 * 類簡介: ①簡單的向文件裡寫索引; ②在根據索引讀取文件; ③運用路徑來找被索引的文件,找到返回結果
 */
public class Indexer {

	// 寫索引的例項到指定目錄下
	private IndexWriter writer;

	/**
	 * 構造方法:為了例項化IndexWriter
	 */
	public Indexer(String indexDir) throws Exception {

		// 得到索引所在目錄的路徑
		Directory dir = FSDirectory.open(new File(indexDir));

		// 例項化分析器
		Analyzer analyzer = new StandardAnalyzer(Version.LUCENE_35);

		// 例項化IndexWriterConfig
		IndexWriterConfig con = new IndexWriterConfig(Version.LUCENE_35, analyzer);

		// 例項化IndexWriter
		writer = new IndexWriter(dir, con);

	}

	/**
	 * 關閉寫索引
	 * 
	 * @throws Exception
	 */
	public void close() throws Exception {

		writer.close();
	}

	/**
	 * 索引指定目錄的所有檔案
	 * 
	 * @throws Exception
	 */
	public int index(String dataDir) throws Exception {

		// 定義檔案陣列,迴圈得出要加索引的檔案
		File[] file = new File(dataDir).listFiles();

		for (File files : file) {

			// 從這開始,對每個檔案加索引
			indexFile(files);
		}

		// 返回索引了多少個檔案,有幾個檔案返回幾個
		return writer.numDocs();

	}

	/**
	 * 索引指定檔案
	 * 
	 * @throws Exception
	 */
	private void indexFile(File files) throws Exception {

		System.out.println("索引檔案:" + files.getCanonicalPath());

		// 索引要一行一行的找,,在資料中為文件,所以要得到所有行,即文件
		Document document = getDocument(files);

		// 開始寫入,就把文件寫進了索引檔案裡去了;
		writer.addDocument(document);

	}

	/**
	 * 獲得文件,在文件裡在設定三個欄位
	 * 
	 * 獲得文件,相當於資料庫裡的一行
	 * 
	 * @throws Exception
	 */
	private Document getDocument(File files) throws Exception {

		// 例項化Document
		Document doc = new Document();
		// add():把設定好的索引加到Document裡,以便在確定被索引文件。

		List<String> contents = this.getContent(files);
		for (String content : contents) {
			doc.add(new Field("contents", content, Field.Store.YES, Field.Index.NOT_ANALYZED));
		}
		// 返回document
		return doc;
	}

	private List<String> getContent(File files) {
		List<String> strList = new ArrayList<String>();
		try {
			InputStream stream = new FileInputStream(files);
			String code = "UTF-8";
			BufferedReader br = new BufferedReader(new InputStreamReader(stream, code));
			String str = br.readLine();
			while (str != null) {
				strList.add(str);
				str = br.readLine();
			}
			br.close();
		} catch (FileNotFoundException e) {
			e.printStackTrace();
		} catch (UnsupportedEncodingException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		}
		return strList;
	}

	// 開始測試寫入索引
	public static void main(String[] args) {
		// 索引指定的文件路徑
		String indexDir = "./Index/Keyword";
		// 被索引資料的路徑
		File dataDir = new File("D:\\workspace\\IKTest\\src\\ext.dic");
		// 寫索引
		Indexer indexer = null;
		try {
			// 通過索引指定的路徑,得到indexer
			indexer = new Indexer(indexDir);
			// 將要索引的資料路徑(int:因為這是要索引的資料,有多少就返回多少數量的索引檔案)
			indexer.indexFile(dataDir);
		} catch (Exception e) {
			e.printStackTrace();
		} finally {
			try {
				indexer.close();
			} catch (Exception e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
	}
}
2、搜尋關鍵字
import java.io.File;

import org.apache.lucene.analysis.Analyzer;
import org.apache.lucene.analysis.standard.StandardAnalyzer;
import org.apache.lucene.document.Document;
import org.apache.lucene.index.IndexReader;
import org.apache.lucene.index.Term;
import org.apache.lucene.queryParser.QueryParser;
import org.apache.lucene.search.IndexSearcher;
import org.apache.lucene.search.Query;
import org.apache.lucene.search.ScoreDoc;
import org.apache.lucene.search.TermQuery;
import org.apache.lucene.search.TopDocs;
import org.apache.lucene.store.Directory;
import org.apache.lucene.store.FSDirectory;
import org.apache.lucene.util.Version;

/**
 * 
 * 通過索引欄位來讀取文件
 *
 */
public class SearchKeyword {
	
	private  static final String indexDir = "./Index/Keyword";

	public boolean search(String keyword)throws Exception{
		
		//得到讀取索引檔案的路徑
		Directory dir=FSDirectory.open(new File(indexDir));
		
		//通過dir得到的路徑下的所有的檔案
		IndexReader reader=IndexReader.open(dir);
		
		//建立索引查詢器
		IndexSearcher is=new IndexSearcher(reader);
		
		//例項化分析器
		Analyzer analyzer=new StandardAnalyzer(Version.LUCENE_35); 
		//建立查詢解析器
		/**
		 * 第一個引數是要查詢的欄位;
		 * 第二個引數是分析器Analyzer
		 * */
		QueryParser parser=new QueryParser(Version.LUCENE_35, "contents", analyzer);
		//根據傳進來的p查詢
//		Query query=parser.parse(keyword);
		TermQuery query = new TermQuery(new Term("contents",keyword));
		//開始查詢
		/**
		 * 第一個引數是通過傳過來的引數來查詢得到的query;
		 * 第二個引數是要出查詢的行數
		 * */
		TopDocs hits=is.search(query, 10);
		boolean flag = false;
		if(hits.totalHits>0) {
			flag = true;
		}
		reader.close();
		return flag;
	}
}
3、main函式
import java.io.StringReader;

import org.apache.lucene.analysis.Analyzer;
import org.apache.lucene.analysis.TokenStream;
import org.apache.lucene.analysis.tokenattributes.CharTermAttribute;
import org.wltea.analyzer.lucene.IKAnalyzer;

public class Main {
	public static void main(String[] args) {
		String keyWord = "這是一整個關鍵詞德瑪西亞";
		IKAnalyzer analyzer = new IKAnalyzer(true);
		try {
			printAnalysisResult(analyzer, keyWord);
		} catch (Exception e) {
			e.printStackTrace();
		}
	}

	/**
	 * 打印出給定分詞器的分詞結果
	 * 
	 * @param analyzer
	 *            分詞器
	 * @param keyWord
	 *            關鍵詞
	 * @throws Exception
	 */
	private static void printAnalysisResult(Analyzer analyzer, String keyWord)
			throws Exception {
		System.out.println("["+keyWord+"]分詞效果如下");
		TokenStream tokenStream = analyzer.tokenStream("content",
				new StringReader(keyWord));
		tokenStream.addAttribute(CharTermAttribute.class);
		SearchKeyword sk = new SearchKeyword();
		while (tokenStream.incrementToken()) {
			CharTermAttribute charTermAttribute = tokenStream
					.getAttribute(CharTermAttribute.class);
			if(sk.search(charTermAttribute.toString())==true) {
				System.out.println(charTermAttribute.toString());
			}

		}
	}
}
4、使用者自定義關鍵詞


5、執行結果


可以看到“這是一整個關鍵詞”被精確索引出來了,

而“德瑪西亞”關鍵詞由於在字典中儲存的是“*德瑪西亞*”,不滿足精確搜尋所以並沒有被切分出來