Lucene in Action中文版

Lucene in Action
中文版
 第一部分 Lucene核心
1.      接觸Lucene 
2.      索引
3.      爲程序添加搜索
4.      分析
5.      高極搜索技術
6.      擴展搜索
第二部分 Lucene應用
7.      分析常用文檔格式
8.      工具和擴充
9.      Lucene其它版本
10.   案例學習
 


 

 
Lucene開始是做爲私有項目。在1997年末,因爲工作不穩定,我尋找自己的一些東西來賣。Java是比較熱門的編程語言,我需要一個理由來學習它。我已經瞭解如何來編寫搜索軟件,所以我想我可以通過用Java寫搜索軟件來維持生計。所以我寫了Lucene。
幾年以後,在2000年,我意識到我沒有銷售天賦。我對談判許可和合同沒有任何興趣,並且我也不想僱人開一家公司。我喜歡做軟件,而不是出售它。所以我把Lucene放在SourceForge上,看看是不是開源能讓我繼續我想做的。
有些人馬上開始使用Lucene。大約一年後,在2001年,Apache提出要採納Lucene。Lucene郵件列表中的消息每天都穩定地增長。也有人開始貢獻代碼,大多是圍繞Lucene的邊緣補充:我依然是僅有的理解它的核心的開發者。儘管如些,Lucene開始成爲真正的合作項目。
現在,2004年,Lucene有一羣積極的深刻理解其核心的開發者。我早已不再每天作開發,這個強有力的工作組在進行實質性的增加與改進。
這些年來,Lucene已經翻譯成很多其它的語言包括C++、C#、Perl和Python。在最開始的Java和其它這些語言中,Lucene的應用比我預想的要廣泛地多。它爲不同的應用(如財富100公司討論組、商業Bug跟蹤、Microsoft提供的郵件搜索和100頁面範圍的Web搜索引擎)提供搜索動力。在業內,我被介紹爲“Lucene人”。很多人告訴我他們在項目中使用到Lucene。我依然認爲我只聽說了使用Lucene的程序的小部分。
如果我當初只是出售它,Lucene應用得不會這麼廣泛。程序開發人員看來更喜歡開源。他們在有問題時不用聯繫技術支持而只需查看一下源代碼。如果這還不夠,郵件列表中的免費支持比大多商業支持要好得多。類似Lucene的開源項目使得程序開發人員更加有效率。
Lucene通過開源已經變得比我想象的偉大的多。我見證了它的發展,是Lucene社區的努力才使得它如此興旺。
Lucene的未來怎樣?我無法回答。有了這本書,你現在也是Lucene社區的一員,現在由您將Lucene帶往新的高地。旅途順利!
 
DOUG CUTTING
Lucene和Nutch的作者


前言
來自Erik Hatcher
在Internet早期我就對搜索和索引感興趣。我已經建立了用majordomo、MUSH(Mail User’s Shell)和少量Perl、awk及shell腳本來管理郵件列表的存儲結構。我實現了一個CGI的web接口,允許用戶搜索這個列表和其它用戶的信息,其內部使用了grep。然後相繼出現了Yahoo!、AltaVista和Excite,這些我都經常訪問。
在我有了第一個兒子Jakob之後,我開始了數字照片檔案的設計。我想開發一套管理圖片的系統,可以給圖片附加元數據,如關鍵字、拍攝日期。當然用我選擇的尺寸定位圖片是很容易的。在19世紀90年代末,我構建了基於文件系統的原型,使用了Microsoft的技術,包括Microsoft Index Server、Action Server Pages及處理圖片的第三方COM組件。從那時起,我的職業生涯都消耗在這些類似的技術上了。I was able to cobble together a compelling application in a couple of days of spare-time hacking.
我的職業轉向Java技術,並且我越來越少地利用Microsoft Windows。爲了以系統無關的方式用Java技術重新實現我的個人照片檔案系統及搜索引擎,我使用了Lucene。Lucene的簡單易用遠遠超過了我的期望—我所期望的其它開源庫或工具在概念上簡單,但是卻難以使用。
在2001年,Steve Loughran和我開始編寫Java Development with Ant(Manning)。我們採用圖片搜索引擎的思想,並把它推廣爲一個文檔搜索引擎。這個程序示例在那本Ant書中使用,而且可被定製爲圖片搜索引擎。Ant的責任不僅來自於簡單的編譯打包的構建過程,也來自於定製的任務,<index>,我們在構建過程中使用Lucene創建索引文件。Ant任務現在生存在Lucene的Sandbox(沙箱)中,將在本書8.4節描述。
Ant已經應用在我的博客系統中,我稱爲BlogScene(http://www.blogscene.org/erik)。在建立一個博客實體之後,我運行一個Ant構建過程,索引新的實體並將它們上傳到我的服務器上。我的博客服務器由一個Servlet、一些驗證模板和一個Lucene索引組成,允許(rich)查詢,甚至聯合查詢。與其它博客系統相比,BlogScene在特色和技巧上差很多,但是它的全文檢索能力非常強大。
我現在效力於維吉尼亞大學對Patacriticism的應用研究小組(http://www.patacriticism.org)。我用對文本分析、索引和搜索的經驗通過討論量子力學與藝術的關係來測試及拓展我的思路。“詩人是世界上不被認可的最偉大的工程師”。
來自Otis Gospodnetic
我對信息搜索與管理的興趣和熱情開始於在Middlebury大學的學生時代。那時候,我發現了信息的廣大資源,即Web。儘管Web仍然剛開始發展,但是對收集、分析、索引和搜索的長期需求是很明顯的。我開始對建立來自Web的信息庫感到困惑,開始編寫Web爬行器夢想有種方法可以對這些收集的信息進行搜索。我認爲在巨大的未知領域中搜索是殺手級軟件。有了這種思想以後,我開始了一系列收集和搜索項目。
在1995年,和同學Marshall Levin一起創建了WebPh,一個用來收集和找出個人聯繫信息的開源程序。基本上,這是一個簡單的具有Web接口(CGI)的電話本,那時排在首位的類型。(實際上,它在19世紀90年代末的案例學習中被引用爲一個示例。)大學和政府機構是這個程序的主要用戶,現在還有很多在使用它。在1997年使用我的WebPh,我繼續創建了Populus,一個當時很流行的白頁。儘管技術(與WebPh類似)很普通,但是Populus有很重的負擔,並且能夠與WhoWhere、Bigfoot和Infospace等大角色相媲美。
在兩個關於個人聯繫信息的項目之後,是該探索新的領域了。我開始了下一個冒險,Infojump,用來在網上時事通訊、雜誌、報紙中選擇高質量的信息。我擁有的軟件由大量的Perl模塊和腳本組成,Infojump利用一個稱作Webinator的Web爬行器和一個全文搜索的產品叫作Texis。在1998年Infojump提供的服務很像今天的FindArticles.com。
儘管WebPh、Populus和Infojump達到了它們的目的並是功能很完善,但它們都有技術的侷限性。它們缺少的是一個用反向索引來支持全文搜索強大的信息搜索庫。爲了不重複相同的工作,我開始搜尋一個我認爲不可能存在的解決方案。在2000年早期,我發現了Lucene,我正在尋找的缺少的部分,並且我一下子就喜歡上了它。
我在Lucene還在SourceForge的時候就加入了這個項目,後來2002年Lucene轉移到Apache軟件基金會。我對Lucene的熱愛是因爲這些年來它已經成爲我很多思想的核心組件。這些思想中的一個是Simpy,我最近的一個項目。Simpy是個有許多特點的個性Web服務,可以讓用戶加標籤、索引、搜索和共享在網上找到的信息。它主要使用了Lucene,上千條索引,由Doug Cutting的另一個項目Nutch(見第10章)提供動力支持。我對Lucene的積極參與導致我被邀請與Erik Hatcher共同編寫Lucene in Action。
Lucene In Action有關於Lucene最全面的信息。接下來的10章包含的信息圍繞你使用Lucene創建優秀程序所需的所有主題。這是它平坦且輕快的協作過程的結果,就像Lucene社區一樣。Lucene和Lucene in Action證明了有類似興趣的人們可以完成什麼,不管在人生中會碰到什麼情況,都會積極地爲全球知識的共享做出貢獻。

 

 
致謝
 
首先並且是最重要的,我們感謝我們的妻子Carole(Erik)和Margaret(Otis),一直支持這本書的寫作。沒有她們的支持,這本書就不可能出版。Erik感謝他的兩個兒子,Ethan和Jakob,因爲他們的忍耐和理解,Erik寫這本書時沒有時間陪他們玩耍。
我們真誠感謝Doug Cutting。沒有Doug的貢獻,就不可能有Lucene。沒有其他Lucene的貢獻者,Lucene就會少很多特徵、更多的Bug,Lucene的成長就會花更長的時間。感謝所有的貢獻者,包括Peter Carlson、Tal Dayan、Scott Ganyo、Eugene Gluzberg、Brian Goetz、Christoph Goller、Mark Harwook、Tim Jones、Daniel Naber、Andrew C. Oliver、Dmitry Serebrennikov、Kelvin Tan和Matt Tucher。同時,我們感謝所有貢獻在第10章的案例的人:Dion Almaer、Michael Cafarella、Bob Carpenter、Karsten Konrad、Terence Parr、Robert Selvaraj、Ralf Steinbach、Holger Stenzhorn和Craig Walls。

 

 
本書簡介
 
Lucene in Action爲使用最好的Java開源搜索引擎的用戶提供所有細節、最好的實踐、警告、技巧。
本書假設讀者熟悉基本的Java編程。Lucene本身是個Java檔案(JAR)文件並能集成到簡單的命令行程序和大型企業級應用程序中。
Roadmap
我們在本書第1部分覆蓋Lucene核心編程接口(API)使你在將Lucene整合到你的程序中時願意使用它:
n        第1章,接觸Lucene。我們介紹了一些基本的信息搜索術語和Lucene的主要競爭對手。我們很快地構建了一個你馬上能用或修改以適應需要的簡單索引和搜索程序。這個示例程序向你打開了探索Lucene其它能力的大門。
n        第2章使你熟悉Lucene基本的索引操作。我們描述了索引數值和日期的不同字段類型和各種技術。包括調整索引過程、優化索引以及如何處理線程安全。
n        第3章向你介紹基本的搜索,包括Lucene如何根據查詢來排列文檔的細節。我們討論基礎的查詢類型及它們如何通過用戶輸入的查詢表達式創建。
n        第4章深入研究Lucene的索引核心,分析過程。分析器創建塊及單詞、單詞流和單詞過濾器。我們創建了一些定製的分析器,showcasing synonym injection and metaphone(like soundex) replacement.也分析了非英語語言,典型的分析漢字文本的示例。
n        第5章講述搜索章節剩餘的。我們描述了一些高級的搜索特徵,包括排序、過濾及使用詞向量。高級的查詢類型在此出現,包括SpanQuery家族。最後,我們討論了Lucene對查詢多索引的內建支持,並行的及遠程的。
n        第6章超越高級搜索,向你展示瞭如何擴展Lucene的搜索能力。你將學到如何定製搜索結果的排序、擴展查詢表達式分析、實現Hit收集和調整查詢性能。
第2部分超越Lucene內建的工具並向你展示圍繞Lucene可以做什麼。
n        第7章,我們創建了可重用、可擴展的用來分析Word、HTML、XML、PDF及其它格式文檔的框架。
n        第8章包括圍繞Lucene的擴展和工具。我們描述了一些Lucene的索引查看和開發工具以及Lucene沙箱中的好東西。高亮搜索項就是這種你想要的沙箱擴展,還有在Ant構建過程中創建索引的其它工具。使用noncore分析器,並使用類似WordNet的索引。
n        第9章描述Lucene翻譯成其它各種語言的版本,如C++、C#、Perl和Python。
n        第10章將Lucene的技術細節帶到大量優秀的案例學習中。這些案例由那些創建了以Lucene爲核心的有趣的、快速的、可升級的程序的開發者提供。
誰應該閱讀本書?
在程序中需要強大搜索能力的開發人員需要閱讀這本書。Lucene in Action也適合於那些對Lucene或索引和搜索技術好奇的開發人員,他們可能不會馬上就用到它。把Lucene添加到你的工具箱對以後的項目來說是值得的—搜索是個熱門的話題並且將來也會是。
這本書主要使用Java版的Lucene(來自Apache Jakarta),並且大多數示例使用Java。最適合熟悉Java的讀者。Java經驗是很有幫助的,然而Lucene已經翻譯成很多其它的語言包括C++、C#、Python和Perl。概念、技術甚至API本身都和Java版Lucene差不多。
代碼示例
本書的源代碼可以從Manning的網站http://www.manning.com/hatcher2上下載。代碼的使用說明包含在代碼包的README文件。
書中出現的大多數代碼是由我們編寫幷包含在代碼包中。某些代碼(尤其是案例代碼)不在我們的代碼包中提供。書中的代碼片斷歸貢獻者所有。同時,我們包含了Lucene代碼庫的部分代碼,基於Apache軟件許可協議(http://www.apache.org/licenses/LICENSE-2.0)。
代碼示例不包括package 和import 語句,以節省空間;具體請參照實際代碼。
爲什麼是JUnit?
我們相信書中的代碼示例應該都是高質量的。典型的“hello world”例子經常幫助讀者測試他們的環境。
我們使用獨特的方法來使用書中的代碼示例。大部分示例是實際的JUnit測試用例(http://www.junit.org)。JUnit,是Java單元測試框架,可以斷言一個特殊情況是否能以可重複的方式出現。通過IDE或Ant進行自動JUnit測試用例可以一步一步地構築系統。我們在本書用使用JUnit是因爲平時都在其它項目中使用,並想讓你看看我們如何編碼。測試驅動開發(Test Driven Development, TDD)是我們強烈推薦的開發模式。
如果你對JUnit不熟,請閱讀以下基礎。我們也建議你閱讀Dave Thomas和Andy Hunt編著的《Pragmatic Unit Testing in Java with JUnit》,還有Vincent Massol和Ted Husted編著的《JUnit in Action》。
JUnit基礎
這部分是對JUnit快速但當然不完整的介紹。我們將提供理解我們示例代碼所需的基礎知識。首先,我們的JUnit測試用例繼承junit.framework.TestCase並且很多通過部LiaTestCase基類間接繼承它。我們的具體測試類附合這個命名習慣:給類名加後綴Test。例如,我們的QueryParser的測試是QueryParserTest.java。
JUnit自動執行所有類似public void testXXX()的方法,此處XXX是個任意有意義的名稱。JUnit測試方法必須簡潔,保持好的設計。(例如創建可重複的功能模塊等等)
斷言
JUnit建立在一組assert語句上,使你自由編寫簡潔的測試代碼並使JUnit框架處理失敗狀態及指出細節。最常用的assert語句是assertEquals;一些是爲不同的數據類型而重載的assertEquals方法。一個示例測試方法如下:
public void testExample() {
    SomeObject obj = new SomeObject();
    assertEqueals(10, obj.someMethod());
}
如果指定的值(在本例中的10)不等於真實值(本例中是調用obj的someMethod的返回值),assert方法拋出運行時異常。除了assertEquals,爲了方便還有一些其他assert方法。我們也使用assertTrue(expression)、assertFalse(expression)和assertNull(expression)語句。這些測試分別判斷這個表達式是否是true、false和null。
assert語句有個接受一個附加的String參數的重載表示。String參數都是用來彙報的,在測試失敗時向開發人員指出更多信息。我們使用這個String消息參數以更好的描述。
通過以這種風格編寫我們的測試用例,可以從我們構建大系統的複雜中解放出來,而且可以每次只關注更少的細節。利用合適的測試用例,我們能夠增強信心和靈活性。信心來自於我們知道代碼的變化如優化算法不會破壞系統的其它部分,因爲出現這種情況的話,自動測試組件能讓我們在它影響產品之前發現。重構是一種改變代碼內部結構的藝術(或者說科學),所以它能夠適應變化的需求而又不影響系統的對外接口。
在上下文中的JUnit
讓我們看一下到目前爲止談論的JUnit並把它放到本書的上下文中。JUnit測試用例繼承於junit.framework.TestCase,且測試方法都類似public void testXXX()形式。我們的測試用例之一(第3章)如下:
public class BasicSearchingTest extends LiaTestCase {
    public void testTerm() throws Exception {
        IndexSearcher searcher = new IndexSearcher(directory);
        Term t = new Term(“subject”, “ant”);
        Query query = new TermQuery(t);
        Hits hits = searcher.search(query);
        assertEquals(“JDwA”, 1, hits.length());
                                              One hit expected for
                                          search for “ant”
        t = new Term(“subject”, “junit”);
        hits = searcher.search(new TermQuery(t));
        assertEquals(2, hits.length());
                                           Two hits expected for “junit”
        searcher.close();
    }
}
當然,我們將在之後解釋這個測試用例中使用的Lucene API。現在我們只關注JUnit的細節。testTerm方法中的directory變量沒在此類中定義。JUnit提供一個在執行每個測試方法之前的初始化鉤子;這個鉤子是名爲public void setUp()的方法。我們的LiaTestCase基類以這種方式實現setUp:
public abstract class LiaTestCase extends TestCase {
    private String indexDir = System.getProperty(“index.dir”);
    protected Directory directory;
 
    protected void setUp() throws Exception {
        directory = FSDirectory.getDirectory(indexDir, false);
    }
}
如果testTerm中的第一個斷言失敗,我們會得到一個異常:
junit.framework.AssertionFalsedError: JDwA expected:<1> but was:<0>
    at lia.searching.BasicSearchingTest.
→  testTerm(BasicSearchingTest.java:20)
這個失敗指出我們的測試數據與預期的結果不同。
測試Lucene
本書中的大部分測試都是測試Lucene本身的。實際上,這是否現實呢?難道要測的不是我們自己寫的代碼而是庫本身?有個Test Driven Development的姊妹篇是用來學習API的:Test Driven Learning。它爲新API寫測試以瞭解它是如何工作以及你能從中得到什麼時非常有幫助。這正是我們在大部分代碼示例中所做的,所以測試都是測試Lucene它本身。但是不要把這些爲學習而做的測試拋開。保留它們以確保你在升級到新版的API或因API改變而重構時,它們能夠保持真值。
模型對象
在一些用例中,我們使用模型對象來測試。模型對象用來作爲探測器傳入真實的業務邏輯,以判斷這個業務邏輯是否正常工作。例如,第4章中有個SynonymEngine接口(4.6節)。使用這個接口的真實業務邏輯是個分析器。當我們想測試這個分析器本身時,SynonymEngine使用什麼類型就不重要,我們只想使用一個定義良好並有可預見行爲的對象。模型對象可以使得測試用例儘可能簡單,這樣它們每次只測試系統的一個方面,在測試失敗要修正什麼錯誤時沒有糾纏的依賴。使用模型對象的一個好處來自於設計的變動,例如關係的分離和設計使用接口代替直接具體實現。
我們的測試數據
爲了避免每個小節都要用完全不同的數據,書中大部多都是圍繞一組公共的示例數據以提供一致性。這些示例數據由書籍詳細資料組成。表1顯示了這些數據,你可以參考它來理解我們的例子。
表1 本書中用到的示例數據
Title / Author Category Subject
A Modern Art of Education
Rudoif Steiner /education/pedagogy education philosophy
psychology practice Waldorf
Imperial Secrets of Health
and Logevity
Bob Flaws /health/alternative/Chinese diet chinese medicine qi
gong health herbs
Tao Te Ching 道德經
Stephen Mitchell /philosophy/eastern taoism
Gödel, Escher, Bach:
an Eternal Golden Braid
Douglas Hofstadter /technology/computers/ai artificial intelligence number theory mathematics music
Mindstorms
Seymour Papert /technology/computers/programming/eduction children computers powerful
ideas LOGO eduction
Java Development with Ant
Erik Hatcher,
Steve Loughran /technology/computers/programming apache jakarta ant build tool
junit java development
JUnit in Action
Vincent Massol, Ted Husted /technology/computers/programming junit unit testing mock
objects
Lucene in Action
Otis Gospodnetic,
Erik Hatcher /technology/computers/programming lucene search
Extreme Programming
Explained
Kent Beck /technology/computers/programming/methodology extreme programming agile
test driven development
methodology
Tapestry in Action
Howard Lewis-Ship /technology/computers/programming tapestry web user interface
components
The Pragmatic Programmer
Dave Thomas, Andy Hunt /technology/computers/programming pragmatic agile methodology
developer tools

 
除了表中所顯示的數據字段,還有ISBN、URL和出版日期。種類和標題字段是我們主觀值,但是另一些都是有關這些書的真實客觀值。
代碼約定和下載
列表和正文中的代碼都以等寬字體的形式出現以與普通文本區分。在正文中Java方法名稱通常都不包含完整聲明。
 
 
 
 
 

第一部分
Lucene核心
本書的前半部分覆蓋了“盒子外的”(嗯…,JAR外的)Lucene。你將以整體的觀點接觸Lucene並開發一個完整的索引和搜索程序。每個後續的章節系統地深入研究特定的內容。“索引”數據和文檔和後來的對它們的“搜索”是使用Lucene的第一步。在從索引過程返回後,“分析”將使你深入理解在使用Lucene索引文本時發生了什麼。搜索是Lucene真正的亮點:本部分以僅使用內建特徵的“高級搜索”技術結束,“擴展搜索”顯示了Lucene針對定製目的的擴展性。
 
第一章 接觸Lucene
本章包括
n        理解Lucene
n        使用基本的索引API
n        使用搜索API
n        考慮可替換產品
 
Lucene流行和成功的一個關鍵因素是它的簡單。… …
1.1 信息組織和訪問的演化
 
 
 
 
 
 
 
 
 
 
 
 
 
1.2 理解Lucene
不同的人使用不同的方法解決相同的問題—即信息超負荷問題。一些人使用新的用戶接口來工作,一些使用智能代理,還有一些使用發展較爲成熟的搜索工具如Lucene。本章稍後我們展示代碼示例之前,我們將提供給你一張高層次的圖來說明Lucene是什麼,它不是什麼和它以後會變得怎樣。
1.2.1 Lucene是什麼
Lucene是一個高性能、可伸縮的信息搜索(IR)庫。它使你可以爲你的應用程序添加索引和搜索能力。Lucene是用java實現的成熟的、免費的開源項目,是著名的Apache Jakarta大家庭的一員,並且基於在Apache軟件許可 [ASF, License]。同樣,Lucene是當前與近幾年內非常流行的免費的Java信息搜索(IR)庫。
 
注意   貫穿這本書,我們將使用術語IR(Information Retrieval)來描述像Lucene這樣的搜索工具。人們常常將IR庫歸諸於搜索引擎,但是一定不要將IR庫與web搜索引擎混爲一談。
 
正如你馬上就會發現的,Lucene提供了一組簡單卻足夠強大的核心API,只需要最小限度地理解全文索引和搜索。你只須學習它的幾個類從而把Lucene集成到一個應用程序中。因爲Lucene是一個Java庫,它並不限定要索引和搜索的內容,這使得它比其它一些搜索程序更具有優勢。
剛接觸Lucene的人經常把它誤解爲一個現成的程序,類似文件搜索程序或web網絡爬行器或是一個網站的搜索引擎。那些都不是Lucene:Lucene是一個軟件庫,一個開發工具包(如果你想這樣稱呼),而不是一個具有完整特徵的搜索應用程序。它本身只關注文本的索引和搜索,並且這些事它完成的非常好。Lucene使得你的應用程序只針對它的問題域來處理業務規則,而把複雜的索引和搜索實現隱藏在一組簡單易用的API之後。你可以把Lucene認爲成一層,應用程序位於它之上,如圖1.5所示。
 
圖1.5 一個集成Lucene的典型應用
 
大量基於Lucene的完整的搜索程序已經構建出來。如果你正在尋找預創建的東西或是一個抓取、文檔處理和搜索的框架,請參考Lucene Wiki 的“powered by”頁(http://wiki.apache.org/jakarta-lucene/PoweredBy)以獲得更多選擇:Zilverling、SearchBlox、Nutch、LARM和jSearch,還有其它一部分的命名。Nutch和SearchBlox的案例研究包含在第10章。
1.2.2 Lucene能做什麼
Lucene使你可以爲你的應用程序添加索引和搜索能力(這些功能將在1.3節中描述)。Lucene可以索引並能使得可以轉換成文本格式的任何數據能夠被搜索。在圖1.5可以看出,Lucene並不關心數據的來源、格式甚至它的語言,只要你能將它轉換爲文本。這就意味着你可經索引並搜索存放於文件中的數據:在遠程服務器上的web頁面,存於本地文件系統的文檔,簡單的文本文件,微軟Word文檔,HTML或PDF文件或任何其它能夠提取出文本信息的格式。
同樣,利用Lucene你可以索引存放於數據庫中的數據,提供給用戶很多數據庫沒有提供的全文搜索的能力。一旦你集成了Lucene,你的應用程序的用戶就能夠像這樣來搜索:+George +Rice –eat –pudding, Apple –pie +Tiger, animal:monkey AND food:banana等等。利用Lucene,你可以索引和搜索email郵件,郵件列表檔案,即時聊天記錄,你的Wiki頁面……等等更多。
 
1.2.3 Lucene的歷史
Lucene最初是由Doug Cutting開發的,在SourceForge的網站上提供下載。在2001年9月做爲高質量的開源Java產品加入到Apache軟件基金會的Jakarta家族中。隨着每個版本的發佈,這個項目得到明顯的增強,也吸線了更多的用戶和開發人員。2004年7月,Lucene1.4版正式發佈,10月的1.4.2版本做了一次bug修正。表1.1顯示了Lucene的發佈歷史。
 
表1.1 Lucene的發佈歷史
版本 發佈日期 里程碑
0.01 2000年3月 第一個開源版本(SourceForge)
1.0 2000年10月  
1.01b 2001年7月 最後的SourceForge版本
1.2 2002年6月 第一個Apache Jakarta版本
1.3 2003年12月 複合索引格式,查詢分析器增加,遠程搜索,token定位,可擴展的API
1.4 2004年7月 Sorting, span queries, term vectors
1.4.1 2004年8月 排序性能的bug修正
1.4.2 2004年10月 IndexSearcher optimization and misc. fixes
1.4.3 2004年冬 Misc. fixes


 
注意      Lucene的創建者,Doug Cutting,在信息搜索領域有很強的理論和實踐經驗。他發表過許多IR主題相關的研究論文並曾在Excite、Apple和Grand Central等公司工作。最近,考慮到web搜索引擎數目的減少和這個領域的潛在壟斷,他創建了Nutch,第一個開源的萬維網搜索引擎(http://www.nutch.org),它用來處理抓取、索引和搜索數十億時常更新的網頁。毫不奇怪,Lucene是Nutch的核心,10.1節包括Nutch如何使用Lucene的案例研究。
Doug Cutting 仍然是Lucene的後臺主力,但是自從Lucene加入到Apache Jakarta的庇護之後,更多的聰明智慧注入進來。在本書寫作時,Lucene的核心工作組有數個積極的開發者,其中兩位就是本書的作者。除了官方的項目開發人員,Lucene擁有大量積極的技術用戶羣,經常貢獻補丁,Bug修復和新的特徵。
1.2.4 誰在使用Lucene
誰不使用呢?除了在Lucene Wiki的Powered by Lucene頁提到的那些組織外,還有大量的知名的跨圖組織正在使用Lucene。它爲Eclipse IDE、Encyclopedia Britannica CD-ROM/DVD、FedEx、Mayo Clinic、Hewlett-Packard、New Scientist雜誌、Epiphany、MIT的OpenCourseware和Dspace、Akamai的EdgeComputing平臺等等提供搜索能力。你的名字也將會出現在這個列表中。
1.2.5 Lucene其它版本:Perl, Python, C++, .NET, Ruby
判斷一個開源軟件是否成功的一種方法是通過它被改編爲其它語言版本的數量。使用這個標準,Lucene是非常成功的!儘管開始時Lucene是用Java寫的,Lucene已經有很多其它語言的版本了:Perl,Python,C++和.NET,並且一些基礎已經用Ruby實現了。這對於那些需要訪問用不同的語言寫成的應用程序所得到的Lucene索引的開發者來說是個好消息。在第9章你將瞭解更多關於這方面的東西。
1.3 索引和搜索
所有搜索引擎的核心就是索引的概念:將原始數據處理成一個高效的交差引用的查找結構以便於快速的搜索。讓我們對索引和搜索過程做一次快速的高層次的瀏覽。
1.3.1 什麼是索引,爲什麼它很重要?
想像一下,你需要搜索大量的文件,並且你想找出包含一個指定的詞或短語的文件。你如何編寫一個程序來做到這個?一個幼稚的方法是針對給定的詞或短語順序掃描每個文件。這個方法有很多缺點,最明顯的就是它不適合於大量的文件或者文件非常巨大的情況。這時就出現了索引:爲了快速搜索大量的文本,你必須首先索引那個文本然後把它轉化爲一個可以讓你快速搜索的格式,除去緩慢的順序地掃描過程。這個轉化過程稱爲索引,它的輸出稱爲一條索引。
你可以把索引理解爲一個可以讓你快速隨機訪問存於其內部的詞的數據結構。它隱含的概念類似於一本書最後的索引,可以讓你快速找到討論指定主題的頁面。在Lucene中,一個索引是一個精心設計的數據結構,在文件系統中存儲爲一組索引文件。我們在附錄B中詳細地說明了索引文件的結構,但是目前你只須認爲Lucene的索引是一個能快速的詞彙查找的工具。
1.3.2 什麼是搜索?
搜索是在一個索引中查找單詞來找出它們所出現的文檔的過程。一個搜索的質量用精確度和召回率來描述。召回率衡量搜索系統搜索到相關文檔的能力,精確度衡量系統過濾不相關文檔的能力。然而,在考慮搜索時你必須考慮其它一些因素。我們已經提到速度和快速搜索大量文本的能力。支持單個和多個詞彙的查詢,短語查詢,通配符,結果分級和排序也是很重要的,在輸入這些查詢的時候也是友好的語法。Lucene強大的軟件庫提供了大量的搜索特徵、bells和whistles,所以我們不得不把關於搜索的討論展開爲三章(第3、5、6章)。
1.4 Lucene實戰:一個簡單的程序
讓我們來實戰Lucene。首先回憶在1.3.1節描述的索引和搜索文件的問題。此外,假設你要索引和搜索存放於一個目錄樹中的文件,並不只在一個目錄中。爲了向你展示Lucene的索引和檢索能力,我們將用到兩個命令行程序:Indexer和Searcher。首先我們將索引一個包含文本文件的目錄樹,然後我們搜索創建的索引。
這個示例程序將使你熟悉Lucene的API,簡單易用而功能強大。代碼清單是完整的,現成的命令行程序。如果文件索引/搜索是你要解決的問題,那你可複製一份代碼,用它來適應你的需要。在接下來的章節中,我們將更深入的描述Lucene使用中的每個方面。
在我們可以利用Lucene搜索之前,需要創建一個索引,所以我們開始Indexer程序。
1.4.1 創建一個索引
在本節中,你將看到一個名爲Indexer的類和它的四個靜態方法。它們共同遞歸遍歷文件系統目錄並索引所有具有.txt擴展名的文件。當Indexer執行完畢時,爲它的後續Searcher(在1.4.2小節中介紹)留下一個創建好的Lucene索引。
我們不期望你熟悉例子中用到的幾個Lucene類和方法,我們馬上就會解釋它們。在有註釋的代碼列表之後,我們向你展示瞭如何使用Indexer。如果你感覺在看到編碼之前學習Indexer如何使用很有幫助,直接跳到代碼後面的用法討論部分。
使用Indexer來索引文本文件
列表1.1展示了Indexer命令行程序。它用到兩個參數:
n        我們存放Lucene索引的路徑
n        包含我們要索引的文本文件的路徑
 
列表 1.1 Indexer:遍歷文件系統並且索引.txt文件
/**
 * This code was originally written for
 * Erik’s Lucene intro java.net article
 */
public class Indexer {
    public static void main(String[] args) throws Exception {
        if (args.length != 2) {
            throw new Exception(“Usage: java ” + Indexer.class.getName()
                + “ <index dir> <data dir>”);
        }
        File indexDir = new File(args[0]);
        File dataDir = new File(args[1]);
 
        long start = new Data().getTime();
        int numIndexed = index(indexDir, dataDir);
        long end = new Date().getTime();
 
        System.out.println(“Indexing ” + numIndexed + “ files took ”
            + (end - start) + “ milliseconds”);
    }
   
    // open an index and start file directory traversal
    public static int index(File indexDir, File dataDir)
        throws IOException {
        if (!dataDir.exists() || !dataDir.isDirectory()) {
            throw new IOException(dataDir
                + “ does not exist or is not a directory”);
        }
 
        IndexWriter writer = new IndexWriter(indexDir,   ① 創建Lucene索引
            new StandardAnalyzer(), true);
        writer.setUseCompoundFile(false);
 
        indexDirectory(writer, dataDir);
 
        int numIndexed = writer.docCount();
        writer.optimize();
        writer.close();
        return numIndexed;
    }
 
    // recursive method that calls itself when it finds a directory
    private static void indexDirectory(IndexWriter writer, File dir)
        throws IOException {
 
        File[] files = dir.listFiles();
        for (int i = 0; i < files.length; i++) {
            File f = files;
            if (f.isDirectory()) {
                indexDirectory(writer, f);    ② 遞歸
            } else if (f.getName().endsWith(“.txt”)) {
                indexFile(writer, f);
            }
        }
    }
 
    // method to actually index file using Lucene
    private static void indexFile(IndexWriter writer, File f)
        throws IOException {
 
        if (f.isHidden() || !f.exists() || !f.canRead()) {
            return;
        }
        System.out.println(“Indexing ” + f.getCanonicalPath());
 
        Document doc = new Document();
        doc.add(Field.Text(“contents”, new FileReader(f)));  ③ 索引文件
                                                          內容
        doc.add(Field.Keyword(“filename”, f.getCannicalPath()));④ 索引
                                                          文件名稱
        writer.addDocument(doc);  ⑤ 添加片段到Lucene索引
    }
}
有趣的是,代碼的大部分是執行目錄的遍歷(②)。只有IndexWriter的創建和關閉(①)和IndexFile方法中的四行(③,④,⑤)使用了Lucene API—有效的6行代碼。
這個示例只關注.txt擴展名的文本文件是爲了在說明Lucene的用法和強大功能時保持儘量簡單。在第7章,我們將向你展示如何處理非文本文件,並且我們開發了一個現成的小框架來分析和索引幾種常見的格式的文檔。
運行Indexer
在命令行中,我們針對包含Lucene本身的源文件的本地工作目錄運行Indexer。我們使Indexer索引/lucene目錄下的文件並將Lucene 索引保存在build/index目錄中。
% java lia.meetlucene.Indexer build/index /lucene
 
Indexing /lucene/build/test/TestDoc/test.txt
Indexing /lucene/build/test/TestDoc/test2.txt
Indexing /lucene/BUILD.txt
Indexing /lucene/CHANGES.txt
Indexing /lucene/LICENSE.txt
Indexing /lucene/README.txt
Indexing /lucene/src/jsp/README.txt
Indexing /lucene/src/test/org/apache/lucene/analysis/ru/
→  stemsUnicode.txt
Indexing /lucene/src/test/org/apache/lucene/analysis/ru/
→  test1251.txt
Indexing /lucene/src/test/org/apache/lucene/analysis/ru/
→  testKOI8.txt
Indexing /lucene/src/test/org/apache/lucene/analysis/ru/
→  testUnicode.txt
Indexing /lucene/src/test/org/apache/lucene/analysis/rn/
→  wordsUnicode.txt
Indexing /lucene/todo.txt
Indexing 13 files took 2205 milliseconds
 
Indexer打印出索引的文件名稱,你可以看出它只索引擴展名爲.txt的文本文件。
 
注意      如果你在Windows平臺的命令行中運行這個程序,你需要調整命令行的目錄和路徑分割符。Windows命令行是java build/index c:/lucene。
 
當索引完成後,Indexer輸出它索引的文件數目和所花費的時間。因爲報告的時間包含文件目錄遍歷和索引,你不能把它做爲一個正式的性能衡量依據。在我們的示例中,每個索引的文件都很小,但只有了2秒索引這些文件還是不錯的。
索引速度是要關注的,我們將在第2章中討論它。但是通常,搜索更加重要。
1.4.2 在索引中搜索
在Lucene中搜索和索引一樣高效和簡單。它的功能驚人地強大,在第3章和第5章你將看到。現在,讓我們看一下Searcher,一個我們用來搜索Indexer創建的索引的命令行程序。(記住我們的Seacher只是用來示範Lucene的搜索API的用法。你的搜索程序也可以是網頁或帶有GUI的桌面程序或EJB等形式。)
在上一部分,我們索引了一個目錄中的文本文件。在本例中的索引,放在文件系統的一個目錄中。我們讓Indexer在build/index目錄中創建Lucene索引,這個目錄和我們調用Indexer的目錄相關。在列表1.1中看出,這個索引包含被索引的文件和它們的絕對路徑。現在我們要用Lucene來搜索這個索引以找出包含指定文本片段的文件。例如,我們可能想找出包含關鍵字java或Lucene的所有文件,或者可能想找出包含短語“system requirements”的所有文件。
使用Searcher實現搜索
Searcher程序和Indexer相輔相成並提供命令行搜索的能力。列表1.2展示了Searcher的全部代碼。它接受兩個命令行參數:
n        Indexer創建的索引的路徑
n        搜索索引的查詢
 
列表 1.2 Searcher:爲參數傳來的查詢搜索Lucene索引
/**
 * This code was originally written for
 * Erik’s Lucene intro java.net article
 */
public class Searcher {
    public static void main(String[] args) throws Exception {
        if (args.length != 2) {
            throw new Exception(“Usage: java ” + Searcher.class.getName()
                + “ <index dir> <auery>”);
        }
        File indexDir = new File(args[0]);
        String q = args[1];
       
        if (!indexDir.exists() || !indexDir.isDirectory()) {
            throw new Exception(indexDir +
                “ does not exist or is not a directory.”);
        }
        search(indexDir, q);
    }
    public static void search(File indexDir, String q)
        throws Exception {
        Directory fsDir = FSDirectory.getDirectory(indexDir, false);
        IndexSearcher is = new IndexSearcher(fsDir); ① 打開索引
 
        Query query = QueryParser.parse(q, “contents”,  ② 分析查詢
            new StandardAnalyzer());
        long start = new Date().getTime();
        Hits hits = is.search(query);      ③ 搜索索引
        long end = new Date().getTime();
 
        System.err.println(“Found ” + hits.length() +
            “ document(s) (in ” + (end - start) +
            “ milliseconds) that matched query ‘” +
               q + “’:”);
 
        for (int i = 0; i < hits.length(); i++) {
            Document doc = hits.doc(i);        ④ 得到匹配的文檔
            System.out.println(doc.get(“filename”));
        }
    }
}
Searcher類似於Indexer,只有幾行代碼與Lucene相關。在search方法中出現了幾種特別的事物,
① 我們使用Lucene的IndexSearcher和FSDirectory類來打開我們的索引以進行搜索。
② 我們使用QueryParser來把human-readable查詢分析成Lucene的查詢類。
③ 搜索以一個Hits對象的形式返回結果集。
④ 注意Hits對象包含的僅僅是隱含的文檔的引用。換句話說,不是在搜索的時候立即加載,而是採用從索引中惰性加載的方式—僅當調用hits.doc(int)時。
運行Searcher
讓我們運行Searcher並用‘lucene’查詢在索引中找出幾個文檔:
%java lia.meetlucene.Searcher build/index ‘lucene’
Found 6 document(s) (in 66 milliseconds) that matched query ‘lucene’:
/lucene/README.txt
/lucene/src/jsp/README.txt
/lucene/BUILD.txt
/lucene/todo.txt
/lucene/LICENSE.txt
/lucene/CHANGES.txt
輸出顯示我們用Indexer索引的13個文檔中的6個含有lucene這個單詞,而且這次搜索花費66毫秒。因爲Indexer在索引中存放了文件的絕對路徑,Searcher可以輸出它們。在這個例子中,我們決定把文件和路徑存爲一個字段並沒有考慮什麼,但是以Lucene的觀點,可以給要索引的文檔附加任意元數據。
當然,你可以使用更多複雜的查詢,例如‘lucene AND doug’或者‘lucene AND NOT slow’或‘+lucene +book’等等。第3、5和第6章所有搜索的不同方面,包括Lucene的查詢語法。
使用xargs工具
Searcher類對Lucene的搜索特徵的非常簡化的示例。所以,它僅僅把匹配結果輸出到標準輸出上。然而,Searcher還有另一個技巧。考慮你需要找出含有指定的關鍵詞或短語的文件,並且你想以某種方式處理這些匹配文件。爲了保持簡單性,讓我們考慮你想使用UNIX命令ls列出每個匹配的文件,或許看看該文件的大小、許可位或擁有者。既然已經簡單地把匹配文檔的路徑寫到標準輸出上,又把統計輸出寫到了標準錯誤上,你可以利用UNIX的xargs工具來處理匹配文件,如下:
%java lia.meetlucene.Searcher build/index
→ ‘lucene AND NOT slow’ | xargs ls –l
 
Found 6 document(s) (in 131 milliseconds) that
→  matched query ‘lucene AND NOT slow’:
-rw-r--r-- 1 erik staff 4215 10 Sep 21:51 /lucene/BUILD.txt
-rw-r--r-- 1 erik staff 17889 28 Dec 10:53 /lucene/CHANGES.txt
-rw-r--r-- 1 erik staff 2670 4 Nov 2001 /lucene/LICENSE.txt
-rw-r--r-- 1 erik staff 683 4 Nov 2001 /lucene/README.txt
-rw-r--r-- 1 erik staff 370 26 Jan 2002 /lucene/src/jsp/
→  README.txt
-rw-r--r-- 1 erik staff 943 18 Sep 21:27 /lucene/todo.txt
在這個例子中,我們選擇布爾查詢‘lucene AND NOT slow’來找出所有含有單詞lucene但不含有單詞slow的文件。這個查詢花費了131毫秒並找出6個匹配文件。我們把Searcher的輸入傳遞給xargs命令,它將會依次使用ls –l命令來列出每個匹配的文件。與之類似,這些匹配文件可以被複制、連接、email或打印到標準輸出。
我們的索引和搜索應用程序示例展示了Lucene的優點。它的API使用簡潔。代碼的大小(並且這適用於所有使用Lucene的程序)與業務目的密切相關--在這個示例中,是Indexer中負責尋找文本文件的文件系統爬行器和Searcher中打印某一查詢的匹配文件名稱到標準輸出的代碼。但是別讓這個事實或者這個示例的簡單讓你覺得自滿:在Lucene的表面下有相當多的事情發生,而且我們還用到了一些最好的實踐經驗。爲了更好的使用Lucene,更深入地理解它如何工作及如何在需要的時候擴展它是很重要的。本書作者毫無保留地給出了這些。
1.5 理解核心索引類
在Indexer類中可見,你需要以下類來執行這個簡單的索引過程:
n        IndexWriter
n        Directory
n        Analyzer
n        Document
n        Field
接下來是對這些類的一個簡短的瀏覽,針對它們在Lucene的角色,給出你粗略的概念。我們將在整本書中使用這些類。
1.5.1 IndexWriter
IndexWriter是在索引過程中的中心組件。這個類創建一個新的索引並且添加文檔到一個已有的索引中。你可以把IndexWriter想象成讓你可以對索引進行寫操作的對象,但是不能讓你讀取或搜索。不管它的名字,IndexWriter不是唯一的用來修改索引的類,2.2小節描述瞭如何使用Lucene API來修改索引。
1.5.2 Directory
Directory類代表一個Lucene索引的位置。它是一個抽象類,允許它的子類(其中的兩個包含在Lucene中)在合適時存儲索引。在我們的Indexer示例中,我們使用一個實際文件系統目錄的路徑傳遞給IndexWriter的構造函數來獲得Directory的一個實例。IndexWriter然後使用Directory的一個具體實現FSDirectory,並在文件系統的一個目錄中創建索引。
在你的應用程序中,你可能較喜歡將Lucene索引存儲在磁盤上。這時可以使用FSDirectory,一個包含文件系統真實文件列表的Driectory子類,如同我們在Indexer中一樣。
另一個Directory的具體子類是RAMDirectory。儘管它提供了與FSDirectory相同的接口,RAMDirectory將它的所有數據加載到內存中。所以這個實現對較小索引很有用處,可以全部加載到內存中並在程序關閉時銷燬。因爲所有數據加載到快速存取的內存中而不是在慢速的硬盤上,RAMDirectory適合於你需要快速訪問索引的情況,不管是索引或搜索。做爲實例,Lucene的開發者在所有他們的單元測試中做了擴展使用:當測試運行時,快速的內存駐留索引被創建搜索,當測試結束時,索引自動銷燬,不會在磁盤上留下任何殘餘。當然,在將文件緩存到內存的操作系統中使用時RAMDirectory和FSDirectory之間的性能差別較小。你將在本書的代碼片斷中看到Directory的兩個實現的使用。
1.5.3 Analyzer
在文本索前之前,它先通過Analyzer。Analyzer在IndexWriter的構造函數中指定,司職對文本內容提取關鍵詞併除去其它的。如果要索引的內容不是普通的文本,首先要轉化成文本,如果2.1所示。第7章展示瞭如何從常見的富媒體文檔格式中提取文本。Analyzer是個抽象類,但是Lucene中有幾個它的實現。有的處理的時候跳過終止詞(不能用來把某個文件與其它文件區分開的常用的詞);有的處理時把關鍵字轉化爲小寫字母,所以這個搜索不是大小寫敏感等等。Analyzer是Lucene的一個重要的部分並且不只是在輸入過濾中使用。對一個將Lucene集成到應用程序中的開發者來說,對Analyzer的選擇在程序設計中是重要元素。你將在第4章學到更多有關的知識。
1.5.4 Document
一個Document代表字段的集合。你可以把它想象爲以後可獲取的虛擬文檔—一塊數據,如一個網頁、一個郵件消息或一個文本文件。一個文檔的字段代表這個文檔或與這個文檔相關的元數據。文檔數據的最初來源(如一條數據庫記錄、一個Word文檔、一本書的某一章等等)與Lucene無關。元數據如作者、標題、主題、修改日期等等,分別做爲文檔的字段索引和存儲。
 
注意      當我們在本書中提到一個文檔,我們指一個Microsoft Word、RTF、PDF或其它文檔類型;我們不是談論Lucene的Document類。注意大小寫和字體的區別。
 
Lucene只用來處理文本。Lucene的核心只能用來處理java.lang.String和java.io.Reader。儘管很多文檔類型都能被索引並使之可搜索,處理它們並不像處理可以簡單地轉化爲java的String或Reader類型的純文本內容那樣直接。你將在第7章學到處理非文本文檔。
在我們的Indexer中,我們處理文本文件,所以對我們找出的每個文本文件,創建一個Document類的實例,用Field(字段)組裝它,並把這個Document添加到索引中,完成對這個文件的索引。
1.5.5 Field
在索引中的每個Document含有一個或多個字段,具體化爲Field類。每個字段相應於數據的一個片段,將在搜索時查詢或從索引中重新獲取。
Lucene提供四個不同的字段類型,你可以從中做出選擇:
n        Keyword—不被分析,但是被索引並逐字存儲到索引中。這個類型適合於原始值需要保持原樣的字段,如URL、文件系統路徑、日期、個人名稱、社會安全號碼、電話號碼等等。例如,我們在Indexer(列表1.1)中把文件系統路徑作爲Keyword字段。
n        UnIndexed—不被分析也不被索引,但是它的值存儲到索引中。這個類型適合於你需要和搜索結果一起顯示的字段(如URL或數據庫主鍵),但是你從不直接搜索它的值。因爲這種類型字段的原始值存儲在索引中,這種類型不適合於存放比較巨大的值,如果索引大小是個問題的話。
n        UnStored—和UnIndexed相反。這個字段類型被分析並索引但是不存儲在索引中。它適合於索引大量的文本而不需要以原始形式重新獲得它。例如網頁的主體或任休其它類型的文本文檔。
n        Text—被分析並索引。這就意味着這種類型的字段可以被搜索,但是要小心字段大小。如果要索引的數據是一個String,它也被存儲;但如果數據(如我們的Indexer例子)是來自一個Reader,它就不會被存儲。這通常是混亂的來源,所以在使用Field.Text時要注意這個區別。
所有字段由名稱和值組成。你要使用哪種字段類型取決於你要如何使用這個字段和它的值。嚴格來說,Lucene只有一個字段類型:以各自特徵來區分的字段。有些是被分析的,有些不是;有些是被索引,然面有些被逐字地存儲等等。
表1.2提供了不同字段特徵的總結,顯示了字段如何創建以及基本使用示例。
 
表1.2 不同字段類型的特徵和使用方法
Fied method/type Analyzed Indexed Stored Example usage
Field.Keyword(String,String)
Field.Keyword(String,Date)   ✔ ✔ Telephone and Social Security numbers, URLs, personal names, Dates
Field.UnIndexed(String,
String)     ✔ Document type (PDF, HTML, and so on), if not used as search criteria
Field.UnStored(String,String) ✔ ✔   Document titles and content
Field.Text(String,String) ✔ ✔ ✔ Document titles and content
Field.Text(String,Reader) ✔ ✔   Document titles and content


 
注意所有字段類型都能用代表字段名稱和它的值的兩個String來構建。另外,一個Keyword字段可以接受一個String和一個Date對象,Text字段接受一個String和一個Reader對象。在所有情況下,這些值在被索引之前都先被轉化成Reader,這些附加方法的存在可以提供比較友好的API。
 
注意      注意Field.Text(String, String)和Field.Text(String, Reader)之間的區別。String變量存儲字段數據,而Reader變量不存儲。爲索引一個String而又不想存儲它,可以用Field.UnStored(String, String)。
 
最後,UnStored和Text字段能夠用來創建詞向量(高級的話題,在5.7節中描述)。爲了讓Lucene針對指定的UnStored或Text字段創建詞向量,你可以使用Field.UnStored(String, String, true),Field.Text(String, String, true)或Field.Text(String, Reader, true)。
在使用Lucene來索引時你會經常用到這幾個類。爲了實現基本的搜索功能,你還需要熟悉同樣簡單的幾個Lucene搜索類。
1.6 理解核心搜索類
Lucene提供的基本搜索接口和索引的一樣直接。只需要幾個類來執行基本的搜索操作:
n        IndexSearcher
n        Term
n        Query
n        TermQuery
n        Hits
接下來的部分對這些類提供一個簡要的介紹。我們將在深入更高級主題之前,在接下來的章節中展開這些解釋。
1.6.1 IndexSearcher
IndexSearcher用來搜索而IndexWriter用來索引:暴露幾個搜索方法的索引的主要鏈接。你可以把IndexSearcher想象爲以只讀方式打開索引的一個類。它提供幾個搜索方法,其中一些在抽象基類Searcher中實現;最簡單的接受單個Query對象做爲參數並返回一個Hits對象。這個方法的典型應用類似這樣:
IndexSearcher is = new IndexSearcher(
FSDirectory.getDirectory(“/tmp/index”, false));
Query q = new TermQuery(new Term(“contents”, “lucene”));
Hits hits = is.search(q);
我們將在第3章中描述IndexSearcher的細節,在第5、6章有更多信息。
1.6.2 Term
Term是搜索的基本單元。與Field對象類似,它由一對字符串元素組成:字段的名稱和字段的值。注意Term對象也和索引過程有關。但是它們是由Lucene內部生成,所以在索引時你一般不必考慮它們。在搜索時,你可能創建Term對象並TermQuery同時使用。
Query q = new TermQuery(new Term(“contents”, “lucene”));
Hits hits = is.search(q);
這段代碼使Lucene找出在contents字段中含有單詞lucene的所有文檔。因爲TermQuery對象繼承自它的抽象父類Query,你可以在等式的左邊用Query類型。
1.6.3 Query
Lucene中包含一些Query的具體子類。到目前爲止,在本章中我們僅提到過最基本的Lucene Query:TermQuery。其它Query類型有BooleanQuery,PhraseQuery, PrefixQuery, PhrasePrefixQuery, RangeQuery, FilteredQuery和SpanQuery。所有這些都在第3章描述。Query是最基本的抽象父類。它包含一些通用方法,其中最有趣的是setBoost(float),在第3.5.9小節中描述。
1.6.4 TermQuery
TermQuery是Lucene支持的最基本的查詢類型,並且它也是最原始的查詢類型之一。它用來匹配含有指定值的字段的文檔,這在前幾段只已經看到。
1.6.5 Hits
Hits類是一個搜索結果(匹配給定查詢的文檔)文檔隊列指針的簡單容器。基於性能考慮,Hits的實例並不從索引中加載所有匹配查詢的所有文檔,而是每次一小部分。第3章描述了其中的細節。
1.7 其它類似的搜索產品
在你選擇Lucene做爲你的IR庫之前,你可能想看看相同領域中的其它方案。我們對你可能相考慮的其它方案做了研究,這個小節對我們的發現做了總結。我們將這些產品分成兩大類:
n        信息搜索(IR, Information Retrieval)庫
n        索引和搜索程序
第一組比較小;它由一些比Lucene小的全文索引和搜索庫組成。你可以把這個組的產品嵌入到你的程序中,如前面的圖1.5所示。
第二組,比較大的組由一些現成的索引的搜索軟件組成。這個軟件一般設計爲針對某種特定的數據,如網頁,不如第一組的軟件靈活。然而,其中一些產品也提供了它們的底層API,所以有時你也可以把它們當做IR庫。
1.7.1 IR庫
在我們對本章的研究中,我們發現兩個IR庫—Egothor和Xapian—提供了差不多的特徵集合並且基本上都是輔助開發者的。我們也發現了MG4J,它並不是一個IR庫而是一套創建IR庫的有用工具;我們認爲使用IR的開發者應該瞭解它。這裏是我們對這三種產品的評論。
Egothor
一個全文索引和搜索的Java庫,Egothor的核心算法與Lucene類似。它已經存在了很多年並擁有少量積極的開發者和用戶團體。領頭人是捷克工程師Leo Galambos,一個在IR領域有深厚理論背景的博士研究生。他時常參與Lucene用戶和開發者郵件列表的討論。
Egothor提供一個擴展的Boolean模塊,使得它起到純Boolean模塊和Vector模塊的作用。你可以通過一個查詢時參數來選擇使用哪個模塊。這個軟件有大量不同的查詢類型,支持類似的查詢語法,並允許多線程查詢,如果你工作在多CPU計算機或搜索遠程索引時是相當簡單的。
Egothor多以現成的程序如網絡爬行器… …
 
 
1.7.2 索引和搜索程序
另一組可用的軟件,包括免費的和商業的,包裝成打包好的產品。這些軟件通常不暴露大量的API且不讓你基於它構建定製的軟件。其中大部分提供了一種機制使你控制有限的參數集合,但卻不能以預期的方法來使用這個軟件。(當然,也有些特殊情況。)
這樣,我們不能把這種軟件直接和Lucene相比。然而,其中一些產品可能對你的需求來說是足夠的,並能讓你的工作運轉起來,儘量Lucene或其它的IR庫以長期角度來說是個不錯的選擇。這裏是此類產品中比較流行的幾種:
n        SWISH,SWISH-E和SWISH++ -- http://homepage.mac.com/pauljlucas/software/swish/, http://swish-e.org/
n        Glimpse和Webglimpse—http://webglimpse.net/
n        Namazu—http://www.namazu.org/
n        ht://Dig—http://www.htdig.org/
n        Harvest和Harvest-NG—http://www.sourceforge.net/projects/harvest/,http://
webharvest.sourceforge.net/ng/
n        Microsoft Index Server—http://www.microsoft.com/NTServer/techresources/webserv/
IndxServ.asp
n        Verity—http://www.verity.com/
1.7.3 在線資源
上一小節只是對相關產品的總的看法。很多資源可以幫你找到的其它的IR庫和產品:
n        DMOZ—在DMOZ Open Directory Project(ODP)中,你將發現http://dmoz.org/Computers/
Software/Information_Retrieval/和它的子版塊有大量的信息。
n        Google—儘管Google Directory是基於Open Directory的數據,這兩個目錄確實不一樣。所以你也應該訪問http://directory.google.com/Top/Computers/Software/
Information_Retrival/。
n        Searchtools—有個搜索工具的專門的網站http://www.searchtools.com/。這個網站並不時常更新,但是它已經很多年了並且相當廣泛。軟件根據操作系統、編程語言、許可證等等進行分類。如果你僅僅對用Java寫的搜索軟件感興趣,訪問http://www.searchtools.com/toos
/tools-java.html。
我們提供了一些Lucene的替代品的正面評論,但是我們確信你的工作會讓你覺得Lucene纔是最好的選擇。
1.8 總結
在本章中,你獲得了Lucene的一些基本知識。現在你知道了Lucene是一個IR庫而不是現成的產品,當然也不是Lucene的初識者常常認爲的web爬行器。你也瞭解了Lucene是如何產生的以及在Lucene背後的關鍵人物和組織。
根據Manning’s in Action的思想,我們先向你展示了兩個獨立的程序,Indexer和Searcher,它們可以對存儲於文件系統的文本文件進行索引和搜索。然後我們主要描述了在那兩個程序中用到每個Lucene類。最後貢獻出我們對類似於Lucene的一些產品的研究。
搜索無處不在,在你閱讀本書時也可能發生,你對你程序中不可或缺的搜索感興趣。基於你的需求,集成Lucene可能是微不足道的,或者它可能會包含在架構層的設計中。
我們像本章一樣組織了後面兩章的內容。首先我們需要做的是索引一些文檔;在第2章詳細討論這個過程。
 
 

第二章 索引
 
 
本章包括
n        執行基本索引操作
n        在索引時添加Document和Field
n        索引日期、數值和用來排序搜索結果的字段
n        使用影響Lucene索引性能和資料消耗的參數
n        優化索引

 

 
你可能想搜索存儲在硬盤上的文件或者搜索你的郵件、網頁甚至數據庫中的數據。Lucene能夠幫助你。然頁,在你能夠搜索之前,你應該對它進行索引,這也是本章你所要學習的內容。
在第1章中,你看到了一個簡單的索引示例。本章將深入並都你有關索引更新、調整索引過程的參數和更多高級索引技術以幫助你更加了解Lucene。此處你也會發現Lucene索引的結構、當以多線程或多進程訪問Lucene索引時要注意的重要問題和Lucene提供的防止併發修改索引的鎖機制。
2.1 理解索引過程
正如你在第1章中所見,爲索引一個文檔只需調用Lucene API的幾個方法。所以,表面看來,用Lucene進行索引是個簡單的操作。然而在這些簡單API的背後隱藏了一些有趣且相當複雜的操作集合。我們可以將這個集合分爲三個主要的功能,如圖2.1所示,在隨後的幾個小節中描述。
圖2.1 使用Lucene索引分爲三個主要步驟:
將數據轉化爲文本,分析,將它保存至索引
2.1.1 轉化爲文本
爲用Lucene索引數據,你必須首先將它轉化爲純文本單詞流,Lucene能消化的格式。在第1 章中,我們限制示例索引和搜索.txt文件,這允許我們分析它的內容並使它來生成Field的實例。然而,事情並不總是那麼簡單。
假設你要索引一些PDF格式的手冊。爲了準備這些手冊以進行索引,你必須首先提取出PDF文檔中的文本信息並使用這些提取的數據來創建Lucene Document及其Field。你回顧21頁的表1.2,你會發現Field方法總是接受String值,有時是Date和Reader值。沒有哪個方法接受PDF的類型,既使這種類型存在。在索引Microsoft Word文檔或其它任何非純文本格式的文檔時你都會遇到這個問題。甚至你在處理使用純文本的符號的XML或HTML文檔時,你仍然需要足夠的智能來準備這些數據來進行索引,避免索引類似XML元素或HTML標籤的東西,而要索引這些文檔中的真實數據。
文本提取的細節在第7章,我們構建了一個小但是完整的框架以索引圖2.1所示的所有文檔格式及其它幾種格式。實際上,你會發現圖2.1和圖7.3很類似。
2.1.2 分析
一旦你準備好了要索引的數據並創建了由Field組成的Lucene Document,你就可以調用IndexWriter的addDocument(Document)方法,把你的數據送入Lucene索引。當你做這些時,Lucene首先分析這些數據以使它更適合索引。它將文本數據分割成塊或單詞,並執行一些可選擇的操作。例如,單詞可以在索引之前轉化爲小寫,以保證搜索是大小寫無關的。典型的,它也有可能排除轉入中所有經常出現但無意義的詞,例如英語終止詞(a, an, the, in, on等等)。類似的,通常分析輸入單詞以獲取它的本質。
這個非常重要的步驟稱爲分析。Lucene的輸入能夠以很多有趣且有用的方法進行分析,所以我們將在第4章詳細分析這個過程。目前,把這個步驟想像爲一個過濾器。
2.1.3 寫索引
在輸入被分析完後,就可以添加到索引中了。Lucene將輸入存儲在一個反向索引的數據結構中。這個數據結構在允許快速關鍵字查詢的同時有效地利用了磁盤空間。這個結構反向是因爲它使用從輸入中提取的單詞做爲查詢鍵值而不是用處理的文檔做爲中樞入口。換句話說,代替嘗試回答這個問題“這個文檔中含有哪些單詞?”,這個結構爲提供快速回答“哪篇文檔含有單詞X?”做了優化。
如果你想一下你常用的Web搜索引擎和你典型的查詢格式,你會發現你想得到的精確的查詢。當今所有的Web搜索引擎的核心都是反向索引。使得各搜索引擎不同的是一組嚴格保密的附加參數來改進這個索引結構。例如Goolge知名的級別(PageRank, PR)因素。Lucene也有它自己的一套技術,你可以在附錄B中學到其中一些。
2.2 基本索引操作
在第1章中,你看到了如何向索引中添加文檔。但是我們將在此總結這個過程,同時描述刪除和更新操作,以給你一個方便的參數點。
2.2.1 向索引中索加文檔
爲了總結你已知的,讓我們來看一下在本章中作爲單元測試基類的代碼片斷。代碼列表2.1創建一個複合的索引稱做index-dir(索引目錄),存儲於系統臨時目錄:UNIX的/tmp,或使用Windows的C:/TEMP。(複合索引在附錄B中描述)我們使用SimpleAnalyzer來分析輸入文本,然後我們索引兩個簡單的Document,每個都包含四種類型的Field:Keyword、UnIndexed、UnStored和Text。
 
列表2.1 在基本測試類的每個測試之前準備一個新的索引
public abstract class BaseIndexingTestCase extends TestCase {
    protected String[] keywords = {“1”, “2”};
    protected String[] unindexed = {“Netherlands”, “Italy”};
    protected String[] unstored = {“Amsterdam has lots of bridges”, “Venice has lots of canals”};
    protected String[] text = {“Amsterdam”, “Venice”};
    protected Directory dir;
 
    protected void setUp() throws IOException {
        String indexDir =
            System.getProperty(“java.io.tmpdir”, “tmp”)  +
            System.getProperty(“file.separator”) + “index-dir”;
        dir = FSDirectory.getDirectory(indexDir, true);
        addDocuments(dir);
    }
 
    protected void addDocuments(Directory dir)
        throws IOException {
        IndexWriter writer = new IndexWriter(dir, getAnalyzer(), true);
        writer.setUseCompoundFile(isCompound());
        for (int i = 0; i < keywords.length; i++) {
            Document doc = new Document();
            doc.add(Field.Keyword(“id”, keywords));
            doc.add(Field.UnIndexed(“country”, unindexed));
            doc.add(Field.UnStored(“contents”, unstored));
            doc.add(Field.Text(“city”, text));
            writer.addDocument(doc);
        }
        writer.optimize();
        writer.close();
    }
 
    protected Analyzer getAnalyzer() {
        return new SimplyAnalyzer();
    }
    protected boolean isCompound() {
        return true;
    }
}
因爲BaseIndexingTestCase類要被本章的其它單元測試類繼承,我們將指出幾個重要的細節。BaseIndexingTestCase每次setUp()方法調用時創建相同的索引。因爲setUp()在測試執行之前被調用,每個測試都是針對新創建的索引運行。儘管基類使用SimpleAnalyzer,子類可以覆蓋getAnalyzer()方法以返回不同的Analyzer類型。
不同的Document
Lucene的一個重要特徵是它允許有不同Field的Document在同一索引中共存。這就意味着你可以用一個索引來保存代表不同實體的Document。例如,你可以存放代表零售產品,有名稱和價格字段的Document和代表people,有名稱、年齡和性別字段的Document。
附加字段
假設你有個生成給定單詞的同意詞數組的程序,並且你想用Lucene來索引基本詞和所有它的同意詞。實現的一個方法是遍歷所有的同意詞並把它們添加到一個String中,然後你可以用它來創建Lucene字段。索引所有同意詞和基本詞另一個方法可能是更好的方法是把不同的值添加到相同的字段,如下:
String baseWord = “fast”;
String synonyms[] = String {“quick”, “rapid”, “speedy”};
Document doc = new Document();
doc.add(Field.Text(“word”, baseWord));
for (int i = 0; i < synonyms.length; i++) {
    doc.add(Field.Text(“word”, synonyms));
}
其中,Lucene添加所有的單詞並把它們索引在同一個字段word中,允許你在搜索時使用其中任何一個。
2.2.2 在索引中清除Document
儘管大多程序關心的是添加Document到Lucene索引中,一些也需要清除它們。例如,報紙出版社可能只想在可搜索的索引中保留最近一個周的有價值的新聞。另外的程序可能想清除所有包含特定單詞的Document。
Document的刪除是由IndexReader來完成的。這個類並不立即從索引中刪除Document。它只做個刪除的標誌,等待IndexReader的close()方法調用時真正的Document刪除。理解了這些之後,讓我們看一下列表2.2:它繼承BaseIndexingTestCase類,這意味着在每次測試方法運行之前,基類重建兩個文檔的索引,在2.2.1小節中描述。
 
列表2.2 根據內部文檔號刪除Document
public class DocumentDeleteTest extends BaseIndexingTestCase {
    public void testDeleteBeforeIndexMerge() throws IOException {
        IndexReader reader = IndexReader.open(dir);
        assertEquals(2, reader.maxDoc());            ① 下一個Document號是2
        assertEquals(2, reader.numDocs());   ② 索引中有兩個Document
        reader.delete(1);    ③ 刪除號碼爲1的Document
 
        assertTrue(reader.isDeleted(1));         ④ 刪除Document
        assertTrue(reader.hasDeletions());             ⑤ 包含刪除的索引
        assertEquals(2, reader.maxDoc());            ⑥ 1個索引的Document,下一個Document號是2
        reader.close();
        reader = IndexReader.open(dir);
 
        assertEquals(2, reader.maxDoc());            ⑦ 在IndexReader重新打開後,
        assertEquals(1, reader.numDocs());      下一個Document號是2
        reader.close();
    }
 
    public void testDeleteAfterIndexMerge() throws IOException {
        IndexReader reader = IndexReader.open(dir);
        assertEquals(2, reader.maxDoc());
        assertEquals(2, reader.numDocs());
        reader.delete(1);
        reader.close();
 
        IndexWriter writer = new IndexWriter(dir, getAnalyzer(), false);
        writer.optimize();
        writer.close();
 
        reader = IndexReader.open(dir);
        assertFalse(reader.isDeleted(1));
        assertFalse(reader.hasDeletions());            ⑧ Optimizing
        assertEquals(1, reader.maxDoc());              renumbers
        assertEquals(1, reader.numDocs());     Documents
 
        reader.close();
    }

①②③ 列表2.2的代示展示瞭如何指定Document的內部編號來刪除Document。它也展示了IndexReader經常混淆的兩個方法的不同:maxDoc()和numDocs()。前者返回下一個可用的內部Document號,後者返回索引中的Document的數目。因爲我們的索引只含有兩個Document,numDocs()返回2;又因爲Document號從0開始,maxDoc()也返回2。
 
注意      每個Lucene的Document有個唯一的內部編號。這些編碼不是永久分配的,因爲Lucene索引分配時在內部重新分配Document的編號。因此,你不能假定一個給定的Document總是擁有同一個Document編號。
 
④⑤ 在testDeleteBeforeIndexMerge()方法中的測試也示範了IndexReader的hasDeletions()方法以檢查一個索引是否包含有刪除標誌的Document和isDeleted(int)方法以檢查指定編號的Document的狀態。
⑥⑦ 可見,numDocs()能夠立即感知到Document的刪除,而maxDoc()不能。
⑧ 此外,在testDeleteAfterIndexMerge()方法中,我們關閉IndexReader並強制Lucene優化索引以合併索引的各片斷。然後我們用IndexReader打開索引,maxDoc()方法返回1而不是2,因爲在刪除和合並後,Lucene對剩餘的Document重新編號。索引中只有一個Document,所以下一下可能Document編號是1。
除了我們通過指定Document編號來刪除單個Document之外,你可以用IndexReader的delete(Term)方法刪除多個Document。使用這個刪除方法,允許你刪除所有包含指定Term的Document。例如,爲了刪除city字段中包含單詞Amsterdam的Document,你可以這樣用IndexReader:
IndexReader reader = IndexReader.open(dir);
reader.delete(new Term(“city”, “Amsterdam”));
reader.close();
你在使用這個方法時要特別小心,因爲在所有索引的Document中指定一個term將會擦除整個索引。這個方法的使用類似於基於Document編號的刪除方法;將在2.2.4小節中描述。
你可能奇怪爲什麼Lucene在IndexReader中執行Document刪除而不是IndexWriter中。這個問題在Lucene社區中每幾個月就問一次,大概因爲有缺點或者可能是容易讓人誤解的類名。Lucene的用戶經常認爲IndexWriter是唯一可以修改索引的類,且IndexReader以只讀的形式訪問索引。實際上,IndexWriter只接觸索引片斷列表和合並片斷時的一小部分索引文件。另一方面,IndexReader知道如何解析所有索引文件。當一個Document刪除時,IndexReader在標記它被刪除之前首先需要定位包含指定Document的片斷。目前還沒有計劃改變這兩個Lucene類的名稱或行爲。
2.2.3 恢復Document
因爲Document的刪除延遲到IndexReader實例關閉時才執行,Lucene允許程序改變想法並恢復已做刪除標記的Document。對IndexReader的undeleteAll()方法的調用通過清除索引目錄中的.del文件來恢復所有刪除的Document。所以在關閉IndexReader實例關閉之後Document就保留在索引中了。只能使用與刪除Document時同一個IndexReader實例,才能調用undeleteAll()來恢復Document。
2.2.4 更新索引中的Document
“如何才能更新索引中的文檔?”是一個在Lucene用戶郵件列表中經常問的問題。Lucene並沒有提供更新方法;Document必須首先從索引中刪除然後再重新添加它,如列表2.3所示。
 
列表2.3 通過刪除再添加的方法更新索引的Document
public class DocumentUpdateTest extends BaseIndexingTestCase {
    public void testUpdate() throws IOException {
        assertEquals(1, getHitCount(“city”, “Amsterdam”));
        IndexReader reader = IndexReader.open(dir);
        reader.delete(new Term(“city”, “Amsterdam”));
        reader.close();
 
        assertEquals(0, getHitCount(“city”, “Amsterdam”));
        IndexWriter writer = new IndexWriter(dir, getAnalyzer(), false);
        Document doc = new Document();
        doc.add(Field.Keyword(“id”, “1”));
        doc.add(Field.UnIndexed(“country”, “Netherlands”));
        doc.add(Field.UnStored(“contents”,
            “Amsterdam has lots of bridges”));
        doc.add(Field.Text(“city”, “Haag”));
        writer.addDocument(doc);
        writer.optimize();
        writer.close();
        assertEquals(1, getHitCount(“city”, “Haag”));
    }
    protected Analyzer getAnalyzer() {
        return new WhitespaceAnalyzer();
    }
    private int getHitCount(String fieldName, String searchString) throws IOException {
        IndexSearcher searcher = new IndexSeracher(dir);
        Term t = new Term(fieldName, searchString);
        Query query = new TermQuery(t);
        Hits hits = searcher.search(query);
        int hitCount = hits.length();
        searcher.close();
        return hitCount;
    }
}
我們首先刪除了city字段含有Amsterdam的所有Document;然後添加一個字段與刪除的Document相同的新Document,除了把city字段設了一個新值。新的Document的city字段是Haag而不是Amsterdam。我們正確地更新了索引中的一個Document。
通過定量刪除來更新
我們的例子刪除和添加單個Document。如果你需要刪除和添加多個Document,最好是進行批操作。按以下步驟:
1.       打開IndexReader。
2.       刪除所有你要刪除的Document。
3.       關閉IndexReader。
4.       打開IndexWriter。
5.       添加你要添加的所有Document。
6.       關閉IndexWriter。
要記住:批量Document刪除和索引總是比交叉刪除和添加操作快。
懂得了更新和刪除操作,讓我們討論如何提升索引的性能並儘可能好地利用硬件資源。
 
        技巧        當刪除和添加Document時,批量進行。這樣總是要比交叉刪除和添加操作快。
 
2.3 Document和Field增量
並不是所有的Document和Field是平等創建的――或者至少你能確定選擇性的Document或Field增量的情況。假設你要寫一個索引和搜索公司Email的程序。可能需求是給公司員工的Email比其它Email消息更多的重要性。你會如何做呢?
Document增量是個使得這種需求能夠簡單實現的一個特徵。默認情況下,所有的Document都沒有增量――或者更恰當地說,它們都有相同的增量因數1.0。通過改變某個Document的增量因數,你可能讓Lucene認爲它比索引中的其他Document更重要或不重要。執行這些的API只需一個方法,setBoost(float),可以這樣用:
public static final String COMPANY_DOMAIN = “example.com”;
public static final String BAD_DOMAIN = “yucky-domain.com”;
 
Document doc = new Document();
String senderEmail = getSenderEmail();
String senderName = getSenderName();
String subject = getSubject();
String body = getBody();
doc.add(Field.Keywork(“senderEmail”, senderEmail));
doc.add(Field.Text(“senderName”, senderName));
doc.add(Field.Text(“subject”, subject));
doc.add(Field.UnStored(“body”, body));
if (getSenderDomain().endsWithIgnoreCase(COMPANY_DOMAIN)) {
    doc.setBoost(1.5);        ① 員工增量因數:1.5
} else if (getSenderDomain().endsWithIgnoreCase(BAD_DOMAIN)) {
    doc.setBoost(0.1);        ② Bad域增量因數:0.1
}
writer.addDocument(doc);
在本例中,我們檢查郵件域名來決定發件人是不是公司的員工。
①    當我們索引由公司員工發送的消息時,我們把他們的增量因數設爲1.5,這默認的因數1.0大。
②    當我們碰到來自虛構的不可信域的發件人發送的消息時,我們通過把它們的增量因數隱爲0.1把它們歸類爲不重要的。
就象你可以增量Document一樣,你也可以增量個別的字段。當你增量Document時,Lucene內部使用相同的增量因數增量它的每個字段。假設Email索引程序的另一個需求是考慮標題字段比發件人的名稱字段更重要。換句話說,搜索時對標題字段的匹配比同樣對senderName字段的匹配更有價值。爲了完成這個計劃,我們使用Field類的setBoost(float)方法:
Field senderNameField = Field.Text(“senderName”, senderName);
Field subjectField = Field.Text(“subject”, subject);
subjectField.setBoost(1.2);
在本例中,我們隨便選了一個增量因數1.2,就像我們隨便爲Document選了一個增量因數1.5和0.1一樣。你要用的增量因數值取決於你要達到什麼目的;你可能需要做一些實驗和調整來達到預期的目標。
值得注意的是字段可以有和它們相關聯的固定增量,是由於Lucene的算分方式。增量總得來說是個高級特徵,沒有它很多程序也能工作得很好。
Document和Field增量在搜索時起作用,你將在3.5.9小節中學到。Lucene的搜索結果根據每個Document與查詢的接近程度來分級,每個匹配的Document分被賦於一個分值。Lucene的評分規則受許多因素影響,增量因數是其中之一。
2.4 索引日期
郵件含有發送和接收日期,文件有很多相關的日期,HTTP呼應有一個包含請求頁面最後修改日期Last-Modified頭。像很多其他Lucene用戶一樣,你可能需要索引日期。Lucene帶有一個Field.Keyword(String, Date)方法,還有DateField類,這使得索引日期很簡單。例如,爲了索引當前日期,可以這樣:
Document doc = new Document();
doc.add(Field.Keyword(“indexDate”, new Date()));
在內部,Lucene使用DateField類把給定的日期轉化成適於索引的字符串。這樣處理日期比較簡單,但是你在使用這個方法時必須小心:使用DateField把日期轉化成可以索引的String,包括日期的所有部分,甚至毫秒。你將在6.5節中看到,這可能會導致某些查詢的性能問題。實際上,你很少需要精確到毫秒的日期,至少對查詢來說是這樣。通常,你可以把日期近似到小時或着甚至天。
因爲所有Field值最後都會轉化成文本,你可以像String一樣索引日期。例如,如果你把日期近似到天,以YYYYMMDD格式的String索引日期,可以使用Field.Keyword(String, String)方法。運用這種方法的原因是你能夠索引在Unix Epoch(1970年1月1日)之前的日期,DateField不能處理。儘管一些解決這個限制的修補在近幾年來被很多人提出,但是沒有一個很完美。所以他們只能在Lucene補丁列表中找到,而沒有包含在Lucene中。根據Lucene用戶提到這個限制的頻率,不能索引1970年之前的日期通常不是個問題。
 
           注意           如果你僅需要對日期進行搜索,而不是時間,用Field.Keyword(“date”,
                             “YYYYMMDD”)。如果你要取得完整的時間,用Field.Keyword(“timestamp”,
                             <java.util.Date>)索引另一個Field。
 
如果你想把日期或時間格式化爲其它形式,一定注意String是以字典順序排序的;這樣做允許期間(date-range)查詢。以YYYYMMDD格式索引日期的一個好處是能夠僅用年來查詢,或用年和月,或者精確地年和月和日。僅用年查詢,使用PrefixQuery。我們將在3.4.3小節中深入討論PrefixQuery。
2.5 索引數值
有兩種常見的情況,數值索引非常重要。第一種,數值包含在要索引的文本中,並且你想確定這些數值被索引了,這樣你就能在後來的搜索中使用它們。例如,你的文檔可能包含“珠穆朗瑪峯高8848米。”你想像搜索珠穆朗瑪峯時找到含有這個句子的文檔一樣搜索數字8848。
第二種情況,有隻含有數值的Field,並且你想索引和搜索它們。此外,你可能想利用這些Field來執行範圍內搜索(range queries)。例如,如果你要索引郵件消息,有個字段保存郵件大小,你可能想找出所有指定大小的消息;或者,你可能想使用範圍搜索來找出所有大小在一個特定範圍內的消息。你可能也要根據大小來排序結果。
Lucene能夠通過在內部把它們處理成字符串來索引數值。如果你需要索引自由格式文本中的數值,你要做的第一件事就是選擇不過濾數字的Analyzer。在第4.3節中將討論到的,WhiteSpaceAnalyzer和StandardAnalyzer是兩個可能的選擇。如果你傳遞給他們一個類似“珠穆朗瑪峯高8848米”的句子,他們把8848提取爲一個單詞,並把它傳到索引過程,允許稍後搜索8848。另外,SimpleAnalyzer和StopAnalyzer將把數字從單詞流中分離出來,這意味着搜索8848不會有任何匹配的文檔。

本文來自CSDN博客,轉載請標明出處:http://blog.csdn.net/shilibo/archive/2007/02/01/1499806.aspx

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