高毅,任洪敏
(上海海事大學(xué)信息工程學(xué)院,上海 201306)
Git版本庫(kù)全文檢索系統(tǒng)的設(shè)計(jì)與實(shí)現(xiàn)
高毅,任洪敏
(上海海事大學(xué)信息工程學(xué)院,上海 201306)
隨著Git版本控制系統(tǒng)應(yīng)用的日益廣泛,Git版本庫(kù)中的文檔資料也越來(lái)越多?;贚ucene的Git版本庫(kù)搜索引擎針對(duì)Git版本庫(kù)中的文件建立索引并進(jìn)行搜索。該搜索系統(tǒng)通過(guò)Git的鉤子機(jī)制異步地建立索引,在檢索時(shí)使用了基于J2EE的架構(gòu)。首先對(duì)Lucene、jGit等相關(guān)工具包進(jìn)行研究,然后對(duì)索引模塊、檢索模塊等進(jìn)行設(shè)計(jì),最后編程實(shí)現(xiàn)和測(cè)試分析。系統(tǒng)實(shí)現(xiàn)的Git版本庫(kù)中文件的全文檢索,為Git版本庫(kù)提供Web化的檢索方式,同時(shí)也是對(duì)Lucene應(yīng)用領(lǐng)域的探索。
Git;Lucene;全文檢索;搜索引擎
Git是Linus開(kāi)發(fā)的一個(gè)用于Linux內(nèi)核代碼管理的分布式版本控制工具[1]?;贕it的客戶端管理工具如Git bash能用命令的形式對(duì)版本庫(kù)中文件進(jìn)行查找,但這種方式使用不方便、也不直觀。為版本庫(kù)提供一種Web化的檢索方式,將提升用戶檢索的體驗(yàn)。
針對(duì)版本庫(kù)中文件數(shù)據(jù)的特征,全文檢索技術(shù)是管理這些數(shù)據(jù)的有效方式,它能根據(jù)文件的內(nèi)容進(jìn)行處理和定位。Lucene是實(shí)現(xiàn)全文檢索的工具包,為實(shí)現(xiàn)Git版本庫(kù)的檢索提供了可能[2]。
本文通過(guò)研究Lucene在各個(gè)領(lǐng)域的應(yīng)用,結(jié)合開(kāi)源框架Spring和Spring MVC[3],設(shè)計(jì)了Java EE架構(gòu)的全文檢索系統(tǒng)。改進(jìn)了Lucene的分詞器,更好地支持中文分詞;使用開(kāi)源的文本抽取工具Tika為文本內(nèi)容抽取提供了統(tǒng)一的編程接口;將Git鉤子和Java異步編程結(jié)合,實(shí)現(xiàn)了異步索引;設(shè)計(jì)并實(shí)現(xiàn)了針對(duì)Git版本庫(kù)中的海量數(shù)據(jù)的全文檢索系統(tǒng)。
1.1 系統(tǒng)結(jié)構(gòu)
全文檢索是一種將文件中的所有文本與檢索項(xiàng)匹配的文字資料檢索方法[4]。一般包括索引程序根據(jù)文件內(nèi)容建立索引、檢索程序根據(jù)用戶的搜索關(guān)鍵字對(duì)已建立的索引進(jìn)行查找并反饋查找結(jié)果等步驟。
本系統(tǒng)按照全文檢索理論針對(duì)Git版本庫(kù)為用戶提供全文檢索服務(wù)。在系統(tǒng)結(jié)構(gòu)上有索引數(shù)據(jù)庫(kù)數(shù)據(jù)搜集、處理查詢及展示結(jié)果集等全文檢索核心功能。Git倉(cāng)庫(kù)及訪問(wèn)、索引引擎、查詢引擎、文本分析引擎及Web相關(guān)功能構(gòu)成了整個(gè)系統(tǒng)。本系統(tǒng)結(jié)構(gòu)如圖1所示。
圖1 系統(tǒng)結(jié)構(gòu)
1.2 功能分析
本系統(tǒng)主要實(shí)現(xiàn)了Git倉(cāng)庫(kù)的異步索引和檢索兩大主要功能。其中,異步索引功能是針對(duì)用戶的提交操作觸發(fā)Git鉤子,由鉤子腳本調(diào)用索引模塊完成Git倉(cāng)庫(kù)的索引的創(chuàng)建。在異步索引的過(guò)程中,要經(jīng)過(guò)數(shù)據(jù)提取與轉(zhuǎn)換模塊、文本分析模塊才能建立索引。這是由于在Git倉(cāng)庫(kù)中可能包含多種格式的文件,如txt、html、word、excel、pdf等,而Lucene的索引機(jī)制只能對(duì)文本文件建立索引[5],所以在做索引之前必須做數(shù)據(jù)轉(zhuǎn)換,即提取各種格式文件的文本信息,這一功能由數(shù)據(jù)提取與轉(zhuǎn)換模塊完成。在提取文本數(shù)據(jù)之后,要由分詞器對(duì)文本信息做分詞處理,該功能由文本分析模塊完成。
檢索功能是本系統(tǒng)的核心功能。用戶通過(guò)檢索頁(yè)面輸入檢索關(guān)鍵字提交檢索請(qǐng)求后,經(jīng)過(guò)文本分析模塊、查詢模塊后將匹配結(jié)果集返回。各模塊流程如圖2所示。
圖2 模塊流程圖
2.1 Git鉤子和異步索引機(jī)制【6】
在Git版本庫(kù)的版本庫(kù)目錄下有一個(gè)hooks目錄,該目錄是Git版本庫(kù)的鉤子腳本目錄,也是實(shí)現(xiàn)所有與鉤子相關(guān)功能的關(guān)鍵。這些鉤子腳本能在特定的條件下被觸發(fā),從而可以利用該機(jī)制設(shè)計(jì)基于鉤子的異步索引。在共享版本庫(kù)中我們使用的是“update”腳本,要使該腳本生效只要去掉其文件的擴(kuò)展名.sample即可。
在本系統(tǒng)中在共享版本庫(kù)中設(shè)置該鉤子,在本地的版本庫(kù)向其推送時(shí)觸發(fā)腳本,執(zhí)行索引程序。工作流程如圖3所示。
圖3 基于鉤子的異步索引機(jī)制
2.2 增量化索引
在Lucene中增量索引是指每次有新的數(shù)據(jù)源要索引時(shí)只對(duì)新增的數(shù)據(jù)做索引而不需改變?cè)瓉?lái)的索引數(shù)據(jù)。根據(jù)Git版本庫(kù)的特點(diǎn),本系統(tǒng)采用增量索引的方式做索引。在一次提交中可能包含若干發(fā)生變化的文件,采用增量索引,只對(duì)這些發(fā)生變化的文件做索引。這種索引方式不僅提高了索引的效率,對(duì)于檢索的查全率也有重要影響[7]。
做增量索引時(shí),首先要獲取在一次提交中發(fā)生變化的文檔的集合,然后遍歷該集合,刪除與每一個(gè)文檔相關(guān)聯(lián)的對(duì)象塊。下一步又分為兩種情況,若發(fā)生變化的文檔的變化類型是被刪除的,則不做任何索引處理;若是其他變化類型,如修改,則對(duì)該文檔重新建立索引。總之索引數(shù)據(jù)庫(kù)中保存的總是文檔的最新修改狀態(tài)的數(shù)據(jù)形式。邏輯流程如圖4所示。
2.3 文檔解析模塊設(shè)計(jì)
文檔解析是指通過(guò)相關(guān)的工具對(duì)各種格式的文檔進(jìn)行轉(zhuǎn)換并解析出文本內(nèi)容。文檔解析模塊的意義在于為索引文檔提供統(tǒng)一的數(shù)據(jù)格式。在Git版本庫(kù)中可能存在有各種格式的文檔如word、excel、pdf及普通的txt文檔,使用jGit工具讀取Git版本庫(kù)中的數(shù)據(jù)后,可以得到字節(jié)數(shù)組形式的數(shù)據(jù)。Lucene內(nèi)部只能對(duì)純文本格式的數(shù)據(jù)建立文檔,而對(duì)于其他類型的文檔,不能建立索引,這也是做文檔解析的必要性所在。故在做索引前必須將各種類型的文檔轉(zhuǎn)換成文本文檔。通過(guò)分析Lucene的API發(fā)現(xiàn)其提供了文檔類型的描述類Document類和關(guān)于域信息的描述類 Field類,其中Document代表被索引的一個(gè)文檔,F(xiàn)ield代表文檔的一個(gè)域信息,如文檔內(nèi)容等。也就是說(shuō)在實(shí)際使用時(shí)我們不必將各種格式的文檔轉(zhuǎn)換為文本文檔,只需提取出各類型文檔的文本內(nèi)容即可。文檔解析模塊是索引的前提和基礎(chǔ),如果不能順利提取文本內(nèi)容索引也將無(wú)法建立。
圖4 增量索引邏輯流程圖
文檔解析的實(shí)現(xiàn)邏輯[8]:
文檔解析模塊使用解析框架Tika,Tika是使用Java語(yǔ)言實(shí)現(xiàn)的并且是Apache下的開(kāi)源項(xiàng)目。它能探測(cè)并從各種常見(jiàn)的文件中提取包括文本和元信息在內(nèi)的數(shù)據(jù)。多文檔解析的示意圖如圖5所示。
圖5 文檔解析流程
3.1 異步索引的實(shí)現(xiàn)
“update”腳本在整個(gè)推送過(guò)程完成后運(yùn)行。修改該腳本后大致內(nèi)容如下:
#!/bin/sh
refname="$1"
cd$GIT_DIR/tool
uname=$(git config--get user.name)
java-Duser=$uname -Dbranch=$refname -Dfile. encoding=utf-8-jar index.jar
該腳本能在本地倉(cāng)庫(kù)向遠(yuǎn)程倉(cāng)庫(kù)推送完成后執(zhí)行,執(zhí)行過(guò)程中首先接收參數(shù)refname,其代表推送的分支。然后調(diào)用索引模塊index.jar完成索引的構(gòu)建。
在索引時(shí)使用了Java線程池和FutureTask類:
ExecutorService executorService = Executors.new-FixedThreadPool(5);
Task task=new Task();
FutureTask<Integer>futureTask=new FutureTask<>(task);
executorService.submit(futureTask);executorService.shutdown();
3.2 增量索引的實(shí)現(xiàn)
通過(guò)分析增量索引的邏輯流程,可知要實(shí)現(xiàn)增量索引首先要獲取在一次提交中發(fā)生變化的文檔集,獲取文檔集的核心代碼如下:
//the list of files changed in a commit
List<PathChangeModel>list=new ArrayList<PathChange-Model>();
......
List<DiffEntry>diffs=df.scan(parent.getTree(),commit. getTree();
for(DiffEntry diff:diffs){
//create the path change model
PathChangeModel pcm=PathChangeModel.from(diff,commit.getName();
if(calculateDiffStat){
//update file diffstats
df.format(diff);
PathChangeModel pathStat=df.getDiffStat(). getPath(pcm.path);if(pathStat!=null){
pcm.insertions=pathStat.insertions;pcm.deletions=pathStat.deletions;}
}
list.add(pcm);
}
然后遍歷該文檔集,刪除每個(gè)文檔對(duì)應(yīng)的索引:
//delete the indexed blob
deleteBlob(repositoryName,branch,path.name){
......
}
接下來(lái)判斷變化文件的變化類型,若是刪除則什么也不做;若不是則重建索引。
//re-index the blob
if(!ChangeType.DELETE.equals(path.changeType){
//the file deleted is not indexed
result.blobCount++;
Document doc=new Document();
doc.add(new Field(FIELD_OBJECT_TYPE,SearchObjectType.blob.name(),StringField.TYPE_STORED);
......
//file name
doc.add(new Field(FIELD_NAME,path.name,TextField. TYPE_STORED);
//determine extension to compare to the extension //blacklist
String ext=null;
String name=path.name.toLowerCase();if(name.indexOf('.')>-1){
ext=name.substring(name.lastIndexOf('.')+1);}
if(StringUtils.isEmpty(ext)||!excludedExtensions.contains(ext){
//read the blob content
Stringstr= JGitUtils.getStringContent(repository,commit.getTree(),path.path,encodings);
if(str!=null){doc.add (new Field (FIELD_CONTENT,str,TextField.TYPE_STORED);writer.addDocument(doc);}
}
}
3.3 文檔解析的實(shí)現(xiàn)
通過(guò)在設(shè)計(jì)章節(jié)對(duì)文檔解析模塊的分析,本系統(tǒng)使用開(kāi)源的文本提取工具Tika提取各種格式文檔的文本數(shù)據(jù),它為文本抽取提供了統(tǒng)一的編程接口。實(shí)現(xiàn)文本抽取的核心代碼如下:
①ByteArrayInputStream in=new ByteArrayInput-Stream(content);
②ContentHandler contentHandler=new BodyContentHandler();
③Metadata metadata=new Metadata();
④Parser parser=new AutoDetectParser();
①中是讀取被解析的文件保存在字節(jié)數(shù)組content中,這時(shí)在程序內(nèi)部會(huì)建立XHTML形式的數(shù)據(jù)結(jié)構(gòu),然后②中創(chuàng)建一個(gè)ContentHandler的子類的實(shí)例,本系統(tǒng)使用的是BodyContentHandler類,它能讀?。糱ody></ body>標(biāo)簽之間的文本內(nèi)容并保存在 contentHandler中。③中是創(chuàng)建了一個(gè)默認(rèn)設(shè)置的元信息對(duì)象。④中創(chuàng)建了一個(gè)自動(dòng)探測(cè)解析類型的解析器,它會(huì)探測(cè)文檔的類型然后調(diào)用相應(yīng)的解析器。
3.4 索引的實(shí)現(xiàn)
在實(shí)現(xiàn)時(shí)具體的過(guò)程如下:
(1)搜集用戶輸入的查詢關(guān)鍵詞及過(guò)濾條件,并作初步的客戶端驗(yàn)證。
(2)創(chuàng)建查詢解析器QueryParser對(duì)象以解析用戶輸入,在創(chuàng)建對(duì)象時(shí)傳入解析器作為構(gòu)造的參數(shù)。
(3)創(chuàng)建BooleanQuery對(duì)象以封裝多個(gè)子查詢。(4)創(chuàng)建IndexSearcher對(duì)象和TopScoreDocCollector容器對(duì)象,并執(zhí)行查詢方法,將結(jié)果集寫(xiě)入容器。
(5)以分頁(yè)顯示的方式從容器中取得本頁(yè)數(shù)據(jù),并放入ScoreDoc類型的數(shù)組中。
(6)遍歷數(shù)組,返回結(jié)果集。
檢索功能的核心代碼如下:
//result set
Set<SearchResult> results= new LinkedHashSet<SearchResult>();
IKAnalyzer analyzer=new IKAnalyzer();try{
BooleanQuery query=new BooleanQuery();
QueryParser qp;
if(StringUtils.isEmpty(fname){
//search by content
//default search checks content qp=new QueryParser(LUCENE_VERSION,F(xiàn)IELD_CONTENT,analyzer);
qp.setAllowLeadingWildcard(true);
query.add(qp.parse(text),Occur.SHOULD);}else if(StringUtils.isEmpty(text){
//search by filename…………}else{
//search by filename and content…………}
//searcher
IndexSearcher searcher=getIndexSearcher(repositories[0]);
Query rewrittenQuery=searcher.rewrite(query);
TopScoreDocCollector collector=TopScoreDocCol lector. create(5000,true);
searcher.search(rewrittenQuery,collector);
int offset=Math.max(0,(page-1)*pageSize);
ScoreDoc[]hits=collector.topDocs(offset,pageSize).score-Docs;
int totalHits=collector.getTotalHits();for(int i=0;i<hits.length;i++){
int docId=hits[i].doc;
Document doc=searcher.doc(docId);
//search result
SearchResult result=createSearchResult(doc,hits[i]. score,docId,offset+i+1,totalHits);
result.repository=repositories[0];
String content=doc.get(FIELD_CONTENT);if(content.length()>50){
//the number of display
content=content.substring(0,50);
}
result.fragment=getHighlightedFragment(analyzer,query,content,result);
results.add(result);
}
系統(tǒng)實(shí)現(xiàn)的檢索結(jié)果頁(yè)面如圖6所示。
圖6 檢索界面
系統(tǒng)測(cè)試環(huán)境中CPU為Intel Core i3-3110M CPU @2.40GHz,內(nèi)存為4GB。以txt、docx、pdf三種文檔格式作測(cè)試,數(shù)據(jù)大小級(jí)別分別為10KB、100KB、1000KB。測(cè)試結(jié)果如表1所示。測(cè)試結(jié)果表明,文件類型和大小對(duì)索引的建立速度有直接的影響,而對(duì)檢索速度影響不大。由于在實(shí)際環(huán)境中,更頻繁的操作是檢索,故能基本滿足用戶對(duì)Git倉(cāng)庫(kù)的檢索需求。
表1 系統(tǒng)測(cè)試結(jié)果
本文在研究了Git的工作原理和開(kāi)源檢索框架Lucene的基礎(chǔ)上,對(duì)多格式文檔內(nèi)容進(jìn)行提取,設(shè)計(jì)并實(shí)現(xiàn)了一個(gè)基于Git倉(cāng)庫(kù)的全文檢索系統(tǒng)。 系統(tǒng)能根據(jù)用戶輸入的關(guān)鍵字和文件名定位索引庫(kù)中的文件。系統(tǒng)主要在兩方面需要改進(jìn),其一對(duì)中文分詞的進(jìn)一步優(yōu)化,提高分詞精度;其二在面對(duì)海量數(shù)據(jù)檢索時(shí),檢索的準(zhǔn)確性和效率。
[1]蔣鑫.Git權(quán)威指南[M].北京:機(jī)械工業(yè)出版社,2011.
[2]Apache Software Foundation.Lucene Java Documentation[DB/OL].http://lucene.apache.org/core/3_0_3/index.html,2013-06-21.
[3]王福強(qiáng).Spring揭秘[M].北京:人民郵電出版社,2009.438-580.
[4]周敬才.基于Lucene全文檢索系統(tǒng)的設(shè)計(jì)與實(shí)現(xiàn)[J].計(jì)算機(jī)工程與科學(xué),2015,37(2):2-3.
[5]牛長(zhǎng)流.Lucene實(shí)戰(zhàn)[M].北京:人民郵電出版社,2011.32-73.
[6]王文龍.分布式軟件開(kāi)發(fā)平臺(tái)的設(shè)計(jì)與實(shí)施[D].北京郵電大學(xué),2011.
[7]江毅銘.專業(yè)搜索引擎技術(shù)的研究與實(shí)現(xiàn)[D].北京化工大學(xué),2005.
[8]Apache Software Foundation.Apache Tika Document[DB/OL].http://tika.apache.org/1.12/parser.html,2016.
[9]O'Reilly Taiwan公司譯.Shell腳本學(xué)習(xí)指南[M].北京:機(jī)械工業(yè)出版社,2009.
Design and Implement of Git Repository Search Engine
GAO Yi,REN Hong-min
(College of Information Engineering,Shanghai Maritime University,Shanghai 201306)
With Git version control system applications increasingly widespread,Git repository documentation is also increasing.Lucene-based Git repository search engine indexing for Git repository file and search.The search system indexed by the hook mechanism Git asynchronously,when used in the retrieval based on J2EE architecture.First,Lucene,jGit kits and other related research,and then indexing module,searching module design,programming and final test analysis.System to achieve full-text search Git repository file with a Git repository provides a Web-based retrieval methods,but also the exploration of Lucene applications.
1007-1423(2016)21-0071-06
10.3969/j.issn.1007-1423.2016.21.017
高毅(1987-),男,山東青島人,碩士研究生,研究方向?yàn)樾畔⑾到y(tǒng)與電子商務(wù)任紅敏(1969-),男,上海人,博士,副教授,研究方向?yàn)檐浖w系結(jié)構(gòu)、構(gòu)件技術(shù)、軟件復(fù)用、過(guò)程工程、軟件項(xiàng)目管理,基于社會(huì)計(jì)算、群體智能、人本計(jì)算、社會(huì)網(wǎng)絡(luò)、輿情分析等技術(shù)的軟件資產(chǎn)管理、船舶協(xié)同設(shè)計(jì)知識(shí)管理等
2016-05-06
2016-07-15