jhengfei
          愛(ài)JAVA,愛(ài)生活

          摘要:

          在這篇文章中,我推薦使用Lucene,它是基于Java的開(kāi)源搜索引擎,通過(guò)提取和索引相關(guān)的源碼元素來(lái)搜索源代碼。這里,我僅限定搜索Java源代碼。然而,Lucene同樣可以做到對(duì)其他編程語(yǔ)言的源代碼的搜索。
          某些網(wǎng)站允許軟件開(kāi)發(fā)社團(tuán)通過(guò)發(fā)布開(kāi)發(fā)者指南、白皮書(shū)、FAQs【常見(jiàn)問(wèn)題解答】和源代碼以實(shí)現(xiàn)信息的共享。隨著信息量的增長(zhǎng),和幾個(gè)開(kāi)發(fā)者貢獻(xiàn)出自己的知識(shí)庫(kù),于是網(wǎng)站提供搜索引擎來(lái)搜索站點(diǎn)上現(xiàn)有的所有信息。雖然這些搜索引擎對(duì)文本文件的搜索可以做的很好,但對(duì)開(kāi)發(fā)者搜索源代碼做了比較嚴(yán)格的限制。搜索引擎認(rèn)為源代碼就是純文本文件,因此,在這一點(diǎn)上,與成熟的可以處理大量源文件的工具――grep相比沒(méi)有什么不同。

          在這篇文章中,我推薦使用Lucene,它是基于Java的開(kāi)源搜索引擎,通過(guò)提取和索引相關(guān)的源碼元素來(lái)搜索源代碼。這里,我僅限定搜索Java源代碼。然而,Lucene同樣可以做到對(duì)其他編程語(yǔ)言的源代碼的搜索。

          文章給出了在Lucene環(huán)境下搜索引擎重點(diǎn)方面的簡(jiǎn)短概述。要了解更多細(xì)節(jié)信息,參考Resources部分。

          版權(quán)聲明:任何獲得Matrix授權(quán)的網(wǎng)站,轉(zhuǎn)載時(shí)請(qǐng)務(wù)必保留以下作者信息和鏈接
          作者:Renuka;Knightchen(作者的blog:http://blog.matrix.org.cn/page/Knightchen)
          原文:http://www.matrix.org.cn/resource/article/44/44362_Lucene+Java.html
          關(guān)鍵字:Lucene;Java

          概述
          Lucene是最流行的開(kāi)源搜索引擎庫(kù)之一。它由能文本索引和搜索的核心API組成。Lucene能夠?qū)o出一組文本文件創(chuàng)建索引并且允許你用復(fù)雜的查詢來(lái)搜索這些索引,例如:+title:Lucene -content:Search、search AND Lucene、+search +code。在進(jìn)入搜索細(xì)節(jié)之前,先讓我來(lái)介紹一下Lucene的一些功能。

          在Lucene中索引文本

          搜索引擎對(duì)所有需要被搜索的數(shù)據(jù)進(jìn)行掃描并將其存儲(chǔ)到能有效獲取的一個(gè)結(jié)構(gòu)里。這個(gè)最有名的結(jié)構(gòu)被稱(chēng)為倒排索引。例如,現(xiàn)在考慮對(duì)一組會(huì)議記錄進(jìn)行索引。首先,每個(gè)會(huì)議記錄的文件被分為幾個(gè)獨(dú)立的部分或者域:如標(biāo)題、作者、email、摘要和內(nèi)容。其次,每一域的內(nèi)容被標(biāo)記化并且提取出關(guān)鍵字或者術(shù)語(yǔ)。這樣就可以建立如下表所示會(huì)議記錄的倒排索引。

          ????????....???????? ????????

          對(duì)于域中的每一術(shù)語(yǔ)而言,上圖存儲(chǔ)了兩方面的內(nèi)容:該術(shù)語(yǔ)在文件中出現(xiàn)的數(shù)量(即頻率【DF】)以及包含該術(shù)語(yǔ)的每一文件的ID。對(duì)于每個(gè)術(shù)語(yǔ)保存的其它細(xì)節(jié):例如術(shù)語(yǔ)在每個(gè)文件中出現(xiàn)的次數(shù)以及出現(xiàn)的位置也被保存起來(lái)。無(wú)論如何,對(duì)于我們非常重要的一點(diǎn)是要知道:利用Lucene檢索文件意味著將其保存為一種特定格式,該格式允許高效率查詢及獲取。

          分析被索引的文本

          Lucene使用分析器來(lái)處理被索引的文本。在將其存入索引之前,分析器用于將文本標(biāo)記化、摘錄有關(guān)的單詞、丟棄共有的單詞、處理派生詞(把派生詞還原到詞根形式,意思是把bowling、bowler和bowls還原為bowl)和完成其它要做的處理。Lucene提供的通用分析器是:
          ????????SimpleAnalyzer:用字符串標(biāo)記一組單詞并且轉(zhuǎn)化為小寫(xiě)字母。
          ????????StandardAnalyzer:用字符串標(biāo)記一組單詞,可識(shí)別縮寫(xiě)詞、email地址、主機(jī)名稱(chēng)等等。并丟棄基于英語(yǔ)的stop words (a, an, the, to)等、處理派生詞。

          檢索(搜索索引)
          索引結(jié)構(gòu)建立后,可以通過(guò)指定被搜索的字段和術(shù)語(yǔ)構(gòu)造復(fù)雜的查詢來(lái)對(duì)索引進(jìn)行檢索。例如,用戶查詢abstract:system AND email:abc@mit.edu得到的結(jié)果是所有在摘要中包含system、在email地址中有abc@mit.edu的文件。也就是說(shuō),如果在前面倒排索引表的基礎(chǔ)上搜索就返回Doc15。與查詢匹配的文件是按照術(shù)語(yǔ)在文件中出現(xiàn)的次數(shù)以及包含該術(shù)語(yǔ)的文檔的數(shù)量進(jìn)行排列的。Lucene執(zhí)行一種順序排列機(jī)制并且提供了給我們更改它的彈性。

          源代碼搜索引擎

          現(xiàn)在我們知道了關(guān)于搜索引擎的基本要點(diǎn),下面讓我們看一看用于搜索源代碼的搜索引擎應(yīng)如何實(shí)現(xiàn)。下文中展示在搜索Java示例代碼時(shí),開(kāi)發(fā)者主要關(guān)注以下Java類(lèi):
          繼承一個(gè)具體類(lèi)或?qū)崿F(xiàn)一個(gè)接口。
          調(diào)用特定的方法。
          使用特定的Java類(lèi)。

          綜合使用上述部分的組合可以滿足開(kāi)發(fā)者獲取他們正在尋找相關(guān)代碼的需要。因此搜索引擎應(yīng)該允許開(kāi)發(fā)者對(duì)這些方面進(jìn)行單個(gè)或組合查詢。IDEs【集成開(kāi)發(fā)環(huán)境】有另一個(gè)局限性:大部分可使用的工具僅僅基于上述標(biāo)準(zhǔn)之一來(lái)支持搜索源代碼。在搜索中,缺乏組合這些標(biāo)準(zhǔn)進(jìn)行查詢的靈活性。

          現(xiàn)在我們開(kāi)始建立一個(gè)支持這些要求的源代碼搜索引擎。

          編寫(xiě)源代碼分析器
          第一步先寫(xiě)一個(gè)分析器,用來(lái)提取或去除源代碼元素,確保建立最佳的索引并且僅包含相關(guān)方面的代碼。在Java語(yǔ)言中的關(guān)鍵字--public,null,for,if等等,在每個(gè).java文件中它們都出現(xiàn)了,這些關(guān)鍵字類(lèi)似于英語(yǔ)中的普通單詞(the,a,an,of)。因而,分析器必須把這些關(guān)鍵字從索引中去掉。

          我們通過(guò)繼承Lucene的抽象類(lèi)Analyzer來(lái)建立一個(gè)Java源代碼分析器。下面列出了JavaSourceCodeAnalyzer類(lèi)的源代碼,它實(shí)現(xiàn)了tokenStream(String,Reader)方法。這個(gè)類(lèi)定義了一組【stop words】,它們能夠在索引過(guò)程中,使用Lucene提供的StopFilter類(lèi)來(lái)被去除。tokenStream方法用于檢查被索引的字段。如果該字段是“comment”,首先要利用LowerCaseTokenizer類(lèi)將輸入項(xiàng)標(biāo)記化并轉(zhuǎn)換成小寫(xiě)字母,然后利用StopFilter類(lèi)除去英語(yǔ)中的【stop words】(有限的一組英語(yǔ)【stop words】),再利用PorterStemFilter移除通用的語(yǔ)形學(xué)以及詞尾后綴。如果被索引的內(nèi)容不是“comment”,那么分析器就利用LowerCaseTokenizer類(lèi)將輸入項(xiàng)標(biāo)記化并轉(zhuǎn)換成小寫(xiě)字母,并且利用StopFilter類(lèi)除去Java關(guān)鍵字。

          package com.infosys.lucene.code JavaSourceCodeAnalyzer.;

          import java.io.Reader;
          import java.util.Set;
          import org.apache.lucene.analysis.*;

          public class JavaSourceCodeAnalyzer extends Analyzer {
          ??????private Set javaStopSet;
          ??????private Set englishStopSet;
          ??????private static final String[] JAVA_STOP_WORDS = {
          ???????? "public","private","protected","interface",
          ????????????"abstract","implements","extends","null""new",
          ?? ????????"switch","case", "default" ,"synchronized" ,
          ????????????"do", "if", "else", "break","continue","this",
          ?? ????????"assert" ,"for","instanceof", "transient",
          ????????????"final", "static" ,"void","catch","try",
          ????????????"throws","throw","class", "finally","return",
          ????????????"const" , "native", "super","while", "import",
          ????????????"package" ,"true", "false" };
          ???? private static final String[] ENGLISH_STOP_WORDS ={
          ????????????"a", "an", "and", "are","as","at","be" "but",
          ????????????"by", "for", "if", "in", "into", "is", "it",
          ????????????"no", "not", "of", "on", "or", "s", "such",
          ????????????"that", "the", "their", "then", "there","these",
          ????????????"they", "this", "to", "was", "will", "with" };
          ???? public SourceCodeAnalyzer(){
          ????????????super();
          ????????????javaStopSet = StopFilter.makeStopSet(JAVA_STOP_WORDS);
          ????????????englishStopSet = StopFilter.makeStopSet(ENGLISH_STOP_WORDS);
          ???? }
          ???? public TokenStream tokenStream(String fieldName, Reader reader) {
          ????????????if (fieldName.equals("comment"))
          ???????????????????? return?? new PorterStemFilter(new StopFilter(
          ????????????????????????new LowerCaseTokenizer(reader),englishStopSet));
          ????????????else
          ???????????????????? return?? new StopFilter(
          ?????????????????? new LowerCaseTokenizer(reader),javaStopSet);
          ???? }
          }


          編寫(xiě)類(lèi)JavaSourceCodeIndexer
          第二步生成索引。用來(lái)建立索引的非常重要的類(lèi)有IndexWriter、Analyzer、Document和Field。對(duì)每一個(gè)源代碼文件建立Lucene的一個(gè)Document實(shí)例。解析源代碼文件并且摘錄出與代碼相關(guān)的語(yǔ)法元素,主要包括:導(dǎo)入聲明、類(lèi)名稱(chēng)、所繼承的類(lèi)、實(shí)現(xiàn)的接口、實(shí)現(xiàn)的方法、方法使用的參數(shù)和每個(gè)方法的代碼等。然后把這些句法元素添加到Document實(shí)例中每個(gè)獨(dú)立的Field實(shí)例中。然后使用存儲(chǔ)索引的IndexWriter實(shí)例將Document實(shí)例添加到索引中。

          下面列出了JavaSourceCodeIndexer類(lèi)的源代碼。該類(lèi)使用了JavaParser類(lèi)解析Java文件和摘錄語(yǔ)法元素,也可以使用Eclipse3.0 ASTParser。這里就不探究JavaParser類(lèi)的細(xì)節(jié)了,因?yàn)槠渌馕銎饕部梢杂糜谔崛∠嚓P(guān)源碼元素。在源代碼文件提取元素的過(guò)程中,創(chuàng)建Filed實(shí)例并添加到Document實(shí)例中。
          import org.apache.lucene.document.*;
          import org.apache.lucene.index.*;
          import com.infosys.lucene.code.JavaParser.*;

          public class JavaSourceCodeIndexer {
          ????private static JavaParser parser = new JavaParser();
          ????????private static final String IMPLEMENTS = "implements";
          ????????private static final String IMPORT = "import";
          ????????...
          ????????public static void main(String[] args) {
          ????????????????File indexDir = new File("C:\\Lucene\\Java");
          ????????????????File dataDir = new File("C:\\JavaSourceCode ");
          ????????????????IndexWriter writer = new IndexWriter(indexDir,
          ????????????????????new JavaSourceCodeAnalyzer(), true);
          ????????????????indexDirectory(writer, dataDir);
          ????????????????writer.close();
          ????????}
          ????????public static void indexDirectory(IndexWriter writer, File dir){
          ????????????File[] files = dir.listFiles();
          ????????????for (int i = 0; i < files.length; i++) {
          ????????????????????File f = files[i];
          ????????????????// Create a Lucene Document
          ????????????????Document doc = new Document();
          ????????????????//??Use JavaParser to parse file
          ????????????????parser.setSource(f);
          ????????????????addImportDeclarations(doc, parser);
          ?????? ???????? ????????addComments(doc, parser);
          ???????? ????????// Extract Class elements Using Parser
          ????????????????JClass cls = parser.getDeclaredClass();
          ????????????????addClass(doc, cls);
          ???????? ????????// Add field to the Lucene Document
          ?????? ????????????????doc.add(Field.UnIndexed(FILENAME, f.getName()));
          ????????????????writer.addDocument(doc);
          ?? ???????? ????????}
          ????????}
          ????????private static void addClass(Document doc, JClass cls) {
          ?? ????????????????//For each class add Class Name field
          ????????????doc.add(Field.Text(CLASS, cls.className));
          ????????????String superCls = cls.superClass;
          ????????????if (superCls != null)
          ?? ????????????????//Add the class it extends as extends field
          ????????doc.add(Field.Text(EXTENDS, superCls));
          ????????????// Add interfaces it implements
          ????????????ArrayList interfaces = cls.interfaces;
          ????????????for (int i = 0; i < interfaces.size(); i++)
          ????????????????doc.add(Field.Text(IMPLEMENTS, (String) interfaces.get(i)));
          ?? ???????? ????????//Add details??on methods declared
          ????????????addMethods(cls, doc);
          ????????????ArrayList innerCls = cls.innerClasses;
          ?? ????????????????for (int i = 0; i < innerCls.size(); i++)
          ????????????????addClass(doc, (JClass) innerCls.get(i));
          ????????}
          ????????private static void addMethods(JClass cls, Document doc) {
          ????????????ArrayList methods = cls.methodDeclarations;
          ????????????for (int i = 0; i < methods.size(); i++) {
          ?????? ????????????????JMethod method = (JMethod) methods.get(i);
          ????????????????// Add method name field
          ????????????????doc.add(Field.Text(METHOD, method.methodName));
          ????????????????// Add return type field
          ????????????????doc.add(Field.Text(RETURN, method.returnType));
          ????????????????ArrayList params = method.parameters;
          ????????????????for (int k = 0; k < params.size(); k++)
          ????????????????// For each method add parameter types
          ????????????????????doc.add(Field.Text(PARAMETER, (String)params.get(k)));
          ????????????????String code = method.codeBlock;
          ????????????????if (code != null)
          ????????????????//add the method code block
          ????????????????????doc.add(Field.UnStored(CODE, code));
          ????????????}
          ????????}
          ????????private static void addImportDeclarations(Document doc, JavaParser parser) {
          ?? ????????????????ArrayList imports = parser.getImportDeclarations();
          ????????????if (imports == null)???? return;
          ????????????for (int i = 0; i < imports.size(); i++)
          ????????????????????//add import declarations as keyword
          ????????????????doc.add(Field.Keyword(IMPORT, (String) imports.get(i)));
          ????????}
          }


          Lucene有四種不同的字段類(lèi)型:Keyword,UnIndexed,UnStored和Text,用于指定建立最佳索引。
          &#61548;????????Keyword字段是指不需要分析器解析但需要被編入索引并保存到索引中的部分。JavaSourceCodeIndexer類(lèi)使用該字段來(lái)保存導(dǎo)入類(lèi)的聲明。
          &#61548;????????UnIndexed字段是既不被分析也不被索引,但是要被逐字逐句的將其值保存到索引中。由于我們一般要存儲(chǔ)文件的位置但又很少用文件名作為關(guān)鍵字來(lái)搜索,所以用該字段來(lái)索引Java文件名。
          &#61548;????????UnStored字段和UnIndexed字段相反。該類(lèi)型的Field要被分析并編入索引,但其值不會(huì)被保存到索引中。由于存儲(chǔ)方法的全部源代碼需要大量的空間。所以用UnStored字段來(lái)存儲(chǔ)被索引的方法源代碼??梢灾苯訌腏ava源文件中取出方法的源代碼,這樣作可以控制我們的索引的大小。
          &#61548;????????Text字段在索引過(guò)程中是要被分析、索引并保存的。類(lèi)名是作為T(mén)ext字段來(lái)保存。下表展示了JavaSourceCodeIndexer類(lèi)使用Field字段的一般情況。



          1.
          ?? 用Lucene建立的索引可以用Luke預(yù)覽和修改,Luke是用于理解索引很有用的一個(gè)開(kāi)源工具。圖1中是Luke工具的一張截圖,它顯示了JavaSourceCodeIndexer類(lèi)建立的索引。

          resized image
          圖1:在Luke中索引截圖

          如圖所見(jiàn),導(dǎo)入類(lèi)的聲明沒(méi)有標(biāo)記化或分析就被保存了。類(lèi)名和方法名被轉(zhuǎn)換為小寫(xiě)字母后,才保存的。

          查詢Java源代碼
          建立多字段索引后,可以使用Lucene來(lái)查詢這些索引。它提供了這兩個(gè)重要類(lèi)分別是IndexSearcher和QueryParser,用于搜索文件。QueryParser類(lèi)則用于解析由用戶輸入的查詢表達(dá)式,同時(shí)IndexSearcher類(lèi)在文件中搜索滿足查詢條件的結(jié)果。下列表格顯示了一些可能發(fā)生的查詢及它的含義:


          用戶通過(guò)索引不同的語(yǔ)法元素組成有效的查詢條件并搜索代碼。下面列出了用于搜索的示例代碼。
          public class JavaCodeSearch {
          public static void main(String[] args) throws Exception{
          ????File indexDir = new File(args[0]);
          ????String q =??args[1]; //parameter:JGraph code:insert
          ????Directory fsDir = FSDirectory.getDirectory(indexDir,false);
          ????IndexSearcher is = new IndexSearcher(fsDir);

          ????PerFieldAnalyzerWrapper analyzer = new
          ????????PerFieldAnalyzerWrapper( new
          ????????????????JavaSourceCodeAnalyzer());

          ????analyzer.addAnalyzer("import", new KeywordAnalyzer());
          ????Query query = QueryParser.parse(q, "code", analyzer);
          ????long start = System.currentTimeMillis();
          ????Hits hits = is.search(query);
          ????long end = System.currentTimeMillis();
          ????System.err.println("Found " + hits.length() +
          ????????????????" docs in " + (end-start) + " millisec");
          ????for(int i = 0; i < hits.length(); i++){
          ????Document doc = hits.doc(i);
          ????????System.out.println(doc.get("filename")
          ????????????????+ " with a score of " + hits.score(i));
          ????}
          ????is.close();
          }
          }


          IndexSearcher實(shí)例用FSDirectory來(lái)打開(kāi)包含索引的目錄。然后使用Analyzer實(shí)例分析搜索用的查詢字符串,以確保它與索引(還原詞根,轉(zhuǎn)換小寫(xiě)字母,過(guò)濾掉,等等)具有同樣的形式。為了避免在查詢時(shí)將Field作為一個(gè)關(guān)鍵字索引,Lucene做了一些限制。Lucene用Analyzer分析在QueryParser實(shí)例里傳給它的所有字段。為了解決這個(gè)問(wèn)題,可以用Lucene提供的PerFieldAnalyzerWrapper類(lèi)為查詢中的每個(gè)字段指定必要的分析。因此,查詢字符串import:org.w3c.* AND code:Document將用KeywordAnalyzer來(lái)解析字符串org.w3c.*并且用JavaSourceCodeAnalyzer來(lái)解析Document。QueryParser實(shí)例如果查詢沒(méi)有與之相符的字段,就使用默認(rèn)的字段:code,使用PerFieldAnalyzerWrapper來(lái)分析查詢字符串,并返回分析后的Query實(shí)例。IndexSearcher實(shí)例使用Query實(shí)例并返回一個(gè)Hits實(shí)例,它包含了滿足查詢條件的文件。

          結(jié)束語(yǔ)

          這篇文章介紹了Lucene——文本搜索引擎,其可以通過(guò)加載分析器及多字段索引來(lái)實(shí)現(xiàn)源代碼搜索。文章只介紹了代碼搜索引擎的基本功能,同時(shí)在源碼檢索中使用愈加完善的分析器可以提高檢索性能并獲得更好的查詢結(jié)果。這種搜索引擎可以允許用戶在軟件開(kāi)發(fā)社區(qū)搜索和共享源代碼。

          資源
          這篇文章的示例Sample code
          Matrix:http://www.matrix.org.cn
          Onjava:http://www.onjava.com/
          Lucene home page
          "Introduction to Text Indexing with Apache Jakarta Lucene:" 這是篇簡(jiǎn)要介紹使用Lucene的文章。
          Lucene in Action: 在使用Lucene方面進(jìn)行了深入地講解。

          Renuka Sindhgatta 是一位資深的構(gòu)架師,現(xiàn)在在印度班加羅爾市【 in the Software Engineering and Technology labs of Infosys Technologies Limited 】工作。
          posted on 2006-04-21 13:53 點(diǎn)滴鑄就輝煌 閱讀(203) 評(píng)論(0)  編輯  收藏 所屬分類(lèi): 技術(shù)點(diǎn)滴
           
          主站蜘蛛池模板: 瓮安县| 文安县| 兰坪| 兰西县| 灵寿县| 乌鲁木齐市| 饶平县| 华蓥市| 尼勒克县| 敦化市| 惠水县| 凤城市| 望城县| 榆林市| 铜鼓县| 太原市| 兴城市| 乌拉特中旗| 南宫市| 明溪县| 正安县| 呼图壁县| 出国| 肇州县| 闵行区| 新绛县| 鲁甸县| 无棣县| 牡丹江市| 惠东县| 汪清县| 湄潭县| 鹰潭市| 潜江市| 玛纳斯县| 平乐县| 远安县| 昭通市| 宁明县| 温州市| 南部县|