關(guān)于Lucene以及索引和搜索的流程
Lucene的普及和成功的背后是因?yàn)樗暮唵巍?/p>
因此,你不需要深入理解Lucene的信息索引和檢索工作方面的知識就可以開始使用。
Lucene提供了簡單但是強(qiáng)大的核心API去實(shí)現(xiàn)全文索引和檢索,你只需要掌握少數(shù)的類就能將Lucene整合到應(yīng)用中。
剛接觸Lucene的人可能會誤認(rèn)為Lucene是一個文件搜索工具、網(wǎng)絡(luò)爬蟲、或者網(wǎng)頁搜索引擎。實(shí)際上Lucene是一個軟件庫,而不是一個全功能的搜索應(yīng)用程序。它涉及全文索引和搜索,而且做得非常好。Lucene可以讓你的應(yīng)用程序隱藏起復(fù)雜的索引和搜索背后的操作,而使用簡單的API處理特定的問題領(lǐng)域和業(yè)務(wù)規(guī)則。你可以想象Lucene就是像一個層,你的應(yīng)用就在層的上面。
Lucene允許你添加索引和搜索功能到應(yīng)用程序中。Lucene不關(guān)心數(shù)據(jù)的來源,Lucene可以索引和搜索任何可以轉(zhuǎn)換成文本格式的數(shù)據(jù)。這意味著你可以用Lucene索引和搜索數(shù)據(jù):遠(yuǎn)程web服務(wù)器上的網(wǎng)頁、存儲在本地文件系統(tǒng)的文檔、簡單的文本文件、Microsoft Word文檔、HTML或PDF文件,或者其他任何可以從中提取文本信息的格式文件。
所有搜索引擎的核心就是索引的概念:把原始數(shù)據(jù)處理成一個高效的交叉引用查找,以便快速檢索。讓我們看看快速高效的索引和搜索過程。
1.索引是什么,為什么它這么重要?
假如你需要搜索大量的文件,你希望找到那些包含某個單詞或詞組的文件。你將如何去寫一個程序?qū)崿F(xiàn)這個功能?一個做法就是按順序掃描每一個文件,搜索是否包含給定的單詞或詞組。但是這樣的做法有很多缺陷的,其中最明顯的就是在大量的文件存在的情況下,速度是令人無法接受的。這種情況下,索引產(chǎn)生了。為了搜索大量的文本,你首先要對這些文本以特定的結(jié)構(gòu)存儲,這種存儲結(jié)構(gòu)可以讓你迅速的搜索,消除慢的順序掃描的過程。這種存儲結(jié)構(gòu)就叫索引,將文本轉(zhuǎn)換成特定結(jié)構(gòu)存儲的過程,就叫建立索引。
索引作為一種數(shù)據(jù)結(jié)構(gòu),允許你快速隨機(jī)的訪問存儲在里面的詞。類似于字典的目錄,某個詞對應(yīng)到某一頁,查找的時候直接定位到那一頁,速度就非常快,不用一頁一頁的翻去查找。Lucene的索引是一種專門設(shè)計(jì)的數(shù)據(jù)結(jié)構(gòu),通常作為一組索引文件存儲在文件系統(tǒng)上。
2.什么是搜索?
在索引中搜索關(guān)鍵詞,找到包含關(guān)鍵詞的文檔的過程就是搜索。搜索質(zhì)量通常使用準(zhǔn)確度和召回率來描述。所謂召回率是指一次搜索結(jié)果集合中符合用戶要求的數(shù)目與和用戶查詢相關(guān)的總數(shù)之比,而準(zhǔn)確率是指一次搜索結(jié)果集合中符合用戶要求的數(shù)目與該次搜索結(jié)果總數(shù)之比。我們也需要考慮其他有關(guān)搜索的因素,比如速度和快速搜索大量文本的能力,單個和多項(xiàng)查詢、 短語查詢、 通配符、 結(jié)果的排名和排序的支持也很重要。
3.Lucene in Action
假如我們需要索引和搜索存儲在一個目錄下的文件。
在我們使用Lucene進(jìn)行搜索之前,我們需要先建立索引。使用的Lucene的版本是3.6。
3.1建立索引
1)創(chuàng)建存放索引的目錄Directory
2)創(chuàng)建索引器配置管理類IndexWriterConfig
3)使用索引目錄和配置管理類創(chuàng)建索引器
4)使用索引器將Document寫到索引文件中
索引器類:
- /**
- * 索引器
- * @author Luxh
- */
- public class Indexer {
- /**
- * 建立索引
- * @param filePath 需要建立索引的文件的存放路徑
- * @throws IOException
- */
- public static void createIndex(String filePath) throws IOException {
- //在當(dāng)前路徑下創(chuàng)建一個叫indexDir的目錄
- File indexDir = new File("./indexDir");
- //創(chuàng)建索引目錄
- Directory directory = FSDirectory.open(indexDir);
- //創(chuàng)建一個分詞器
- Analyzer analyzer = new StandardAnalyzer(Version.LUCENE_36);
- //創(chuàng)建索引配置器
- IndexWriterConfig indexWriterConfig = new IndexWriterConfig(Version.LUCENE_36,analyzer);
- LogMergePolicy mergePolicy = new LogByteSizeMergePolicy();
- //設(shè)置segment添加文檔(Document)時的合并頻率
- //值較小,建立索引的速度就較慢
- //值較大,建立索引的速度就較快,>10適合批量建立索引
- mergePolicy.setMergeFactor(50);
- //設(shè)置segment最大合并文檔(Document)數(shù)
- //值較小有利于追加索引的速度
- //值較大,適合批量建立索引和更快的搜索
- mergePolicy.setMaxMergeDocs(5000);
- //啟用復(fù)合式索引文件格式,合并多個segment
- mergePolicy.setUseCompoundFile(true);
- indexWriterConfig.setMergePolicy(mergePolicy);
- //設(shè)置索引的打開模式
- indexWriterConfig.setOpenMode(OpenMode.CREATE_OR_APPEND);
- //創(chuàng)建索引器
- IndexWriter indexWriter = new IndexWriter(directory,indexWriterConfig);
- File fileDir = new File(filePath);
- for(File file : fileDir.listFiles()) {
- //Document是Lucene的文檔結(jié)構(gòu),需要索引的對象都要轉(zhuǎn)換為Document
- Document document = new Document();
- //文件名,可查詢,分詞,存儲到索引庫記錄中
- document.add(new Field("name",getFileName(file),Store.YES,Index.ANALYZED));
- //文件路徑,可查詢,不分詞,存儲到索引庫記錄中
- document.add(new Field("path",file.getAbsolutePath(),Store.YES,Index.NOT_ANALYZED));
- //大文本內(nèi)容,可查詢,不存儲,實(shí)際上可根據(jù)文件路徑去找到真正的文本內(nèi)容
- //document.add(new Field("content",new FileReader(file)));
- //小文本內(nèi)容,可以存儲到索引記錄庫
- document.add(new Field("content",getFileContent(file),Store.YES,Index.ANALYZED));
- //把文檔添加到索引庫
- indexWriter.addDocument(document);
- }
- //提交索引到磁盤上的索引庫,關(guān)閉索引器
- indexWriter.close();
- }
- /**
- * 獲取文件名
- */
- public static String getFileName(File file) {
- String fileName = "";
- if(file != null) {
- fileName = file.getName().substring(0, file.getName().lastIndexOf("."));
- }
- return fileName;
- }
- /**
- * 獲取文本
- * @param file
- */
- public static String getFileContent(File file) {
- FileReader fr = null;
- BufferedReader br = null;
- String content = "";
- try {
- fr = new FileReader(file);
- br = new BufferedReader(fr);
- StringBuffer sb = new StringBuffer();
- String line = br.readLine();
- while(null != line){
- sb.append(line);
- line = br.readLine();
- }
- content = sb.toString();
- }catch(Exception e) {
- e.printStackTrace();
- }finally {
- try {
- if(fr != null)
- fr.close();
- if(br != null)
- br.close();
- } catch (IOException e) {
- e.printStackTrace();
- }
- }
- return content;
- }
- }
IndexWriter:索引器,負(fù)責(zé)創(chuàng)建和維護(hù)一條索引。
在Lucene3.6版本,只推薦使用一個構(gòu)造方法IndexWriter(Directory d,IndexWriterConfig conf),其他的構(gòu)造方法都已經(jīng)過時。所有關(guān)于IndexWriter的配置都是通過IndexWriterConfig來進(jìn)行管理。
IndexWriterConfig:索引器配置類,管理所有有關(guān)索引器的配置。只有一個構(gòu)造方法IndexWriterConfig(Version matchVersion,Analyzer analyzer),構(gòu)造方法中的參數(shù)matchVersion是Lucene的版本,analyzer是分詞器。
接下來我們運(yùn)行索引器創(chuàng)建索引。
- public class TestIndexer {
- /**
- * 創(chuàng)建索引
- * @throws IOException
- */
- @Test
- public void testCreateIndex() throws IOException{
- //存放需要建立索引的文件的目錄路徑
- String filePath = "./fileDir";
- //調(diào)用索引器的創(chuàng)建索引方法
- Indexer.createIndex(filePath);
- }
- }
這樣我們就對當(dāng)前路徑下fileDir中的文件創(chuàng)建了索引。
3.2執(zhí)行搜索
在Lucene中搜索像建立索引一樣簡單、快速。現(xiàn)在,我們建立一個搜索器,搜索包含特定文本的文件。
1)使用QueryParser將查詢的關(guān)鍵詞解析成Lucene的查詢對象Query。創(chuàng)建QueryParser的時候我們需要用到分詞器,這個分詞器要和前面創(chuàng)建索引的時候使用的分詞器一致。
2)使用FSDirectory打開索引所在的目錄。
3)使用IndexReader讀取索引目錄和使用IndexSearcher進(jìn)行搜索。
4)返回搜索結(jié)果對象TopDocs。TopDocs包含搜索到結(jié)果總數(shù)和結(jié)果的集合ScoreDocs數(shù)組
5)遍歷結(jié)果的集合ScoreDocs數(shù)組,根據(jù)每一個ScoreDoc的文檔編號獲取Document
看看搜索器的代碼:
- /**
- * 搜索器
- * @author Luxh
- */
- public class Searcher {
- /**
- * 搜索
- * @param keyWord 搜索的關(guān)鍵詞
- * @param indexDir 索引目錄所在路徑
- * @throws ParseException
- * @throws IOException
- * @return List<Document>
- */
- public static List<Document> search(String keyWord,String indexDirPath) throws ParseException, IOException {
- String[] fields = {"name","content"};
- //創(chuàng)建一個分詞器,和創(chuàng)建索引時用的分詞器要一致
- Analyzer analyzer = new StandardAnalyzer(Version.LUCENE_36);
- //創(chuàng)建查詢解析器
- QueryParser queryParser = new MultiFieldQueryParser(Version.LUCENE_36,fields,analyzer);
- //將查詢關(guān)鍵詞解析成Lucene的Query對象
- Query query = queryParser.parse(keyWord);
- //打開索引目錄
- File indexDir = new File(indexDirPath);
- Directory directory = FSDirectory.open(indexDir);
- //獲取訪問索引的接口,進(jìn)行搜索
- IndexReader indexReader = IndexReader.open(directory);
- IndexSearcher indexSearcher = new IndexSearcher(indexReader);
- //TopDocs 搜索返回的結(jié)果
- TopDocs topDocs = indexSearcher.search(query, 100);//只返回前100條記錄
- int totalCount = topDocs.totalHits; // 搜索結(jié)果總數(shù)量
- System.out.println("搜索到的結(jié)果總數(shù)量為:" + totalCount);
- ScoreDoc[] scoreDocs = topDocs.scoreDocs; // 搜索的結(jié)果集合
- List<Document> docs = new ArrayList<Document>();
- for(ScoreDoc scoreDoc : scoreDocs) {
- //文檔編號
- int docID = scoreDoc.doc;
- //根據(jù)文檔編號獲取文檔
- Document doc = indexSearcher.doc(docID);
- docs.add(doc);
- }
- indexReader.close();
- indexSearcher.close();
- return docs;
- }
- }
接下來我們運(yùn)行搜索器:
- public class TestSearcher {
- /**
- * 搜索
- */
- @Test
- public void testSearch() throws IOException, ParseException{
- //搜索關(guān)鍵詞
- String keyWord = "Java";
- //索引目錄路徑
- String indexDirPath = "./indexDir";
- //調(diào)用搜索器進(jìn)行搜索
- List<Document> docs = Searcher.search(keyWord, indexDirPath);
- for(Document doc : docs) {
- System.out.println("文件名 : "+doc.get("name"));
- System.out.println("路徑 : "+doc.get("path"));
- System.out.println("內(nèi)容 : "+doc.get("content"));
- }
- }
- }
如果有包含關(guān)鍵詞的文件,就會被搜索出來了。
原文鏈接:http://www.cnblogs.com/luxh/archive/2012/06/20/2554095.html