?
?
?
官網(wǎng): ??? http://lucene.apache.org/
?lucene中國(guó): ?? lucene.com.cn
?
?
1. 簡(jiǎn)介
??Lucene 是 apache 軟件基金會(huì) 4 jakarta 項(xiàng)目組的一個(gè)子項(xiàng)目,是一個(gè)開(kāi)放源代碼的全文檢索引擎工具包,即它不是一個(gè)完整的全文檢索引擎,而是一個(gè)全文檢索引擎的架構(gòu),
? 提供了 ? 完整的查詢(xún)引擎和索引引擎,部分文本分析引擎(英文與德文兩種西方語(yǔ)言)。 Lucene 的目的是為軟件開(kāi)發(fā)人員提供一個(gè)簡(jiǎn)單易用的工具包,以方便的在目標(biāo)系統(tǒng)中實(shí)現(xiàn)
? 全文檢索的功能,或者是以此為基礎(chǔ)建立起完整的全文檢索引擎。
2. 下載
? ? http://labs.renren.com/apache-mirror//lucene/java/3.0.3/
3. 測(cè)試
? 在 eclipse 中邪見(jiàn)項(xiàng)目 Lucence3.0Test, 將一下包導(dǎo)入到 build 路徑中
? ? ?lucene-core-3.0.2.jar
? ? ?lucene-demos-3.0.2.jar
? ? lucene-analyzers-3.0.2.jar
? ? lucene-fast-vector-highlighter-3.0.2.jar
? ? lucene-highlighter-3.0.2.jar
? ? lucene-memory-3.0.2.jar
? ? 在任意目錄下新建兩個(gè)文件夾:
? ? 用來(lái)存放 lucence 進(jìn)行分詞的文件和生成的 index 。如下圖: file1 用來(lái)存放進(jìn)行分詞的文件,里面存放有 N 個(gè) txt 文件, txt 文件的內(nèi)容任意,如:我們都是中國(guó)人, index 文件夾是新建的,里面不要有任何的文件,是用來(lái)存儲(chǔ)生成的 index 的
? ?
4. 代碼
? ? 新建創(chuàng)建索引的類(lèi),以及測(cè)試
import
java.io.BufferedReader;
import
java.io.File;
import
java.io.FileInputStream;
import
java.io.IOException;
import
java.io.InputStreamReader;
import
java.util.Date;
import
org.apache.lucene.analysis.Analyzer;
import
org.apache.lucene.document.DateTools;
import
org.apache.lucene.document.Document;
import
org.apache.lucene.document.Field;
import
org.apache.lucene.index.IndexWriter;
import
org.apache.lucene.store.FSDirectory;
import
org.wltea.analyzer.lucene.IKAnalyzer;
public
class
IndexerOK {
private
static
String INDEX_DIR = "D:\\nutchwork\\LucenceTestDir\\index";
//
索引存放目錄
private
static
String DATA_DIR = "D:\\nutchwork\\LucenceTestDir\\file1";
//
小文件存放的目錄
public
static
void
main(String[] args)
throws
Exception {
??
long
start =
new
Date().getTime();
??
int
numIndexed = index(
new
File(INDEX_DIR),
new
File(DATA_DIR));
//
調(diào)用
index
方法
??
long
end =
new
Date().getTime();
??
System.out.println("Indexing " + numIndexed + " files took "
????
+ (end - start) + " milliseconds");
}
/**
*
索引
dataDir
下的
.txt
文件,并儲(chǔ)存在
indexDir
下,返回索引的文件數(shù)量
*
*
@param
indexDir
*
@param
dataDir
*
@return
int
*
@throws
IOException
*/
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");
??
}
??
Analyzer analyzer =
new
IKAnalyzer();
//
采用的分詞器
??
//
第三個(gè)參數(shù) 為
true
表示新建,
false
表示添加到原有索引中
??
IndexWriter writer =
new
IndexWriter(FSDirectory.open(indexDir),
????
analyzer,
true
, IndexWriter.MaxFieldLength.LIMITED);
??
indexDirectory(writer, dataDir);
//
調(diào)用
indexDirectory
方法
??
int
numIndexed = writer.numDocs();
??
writer.optimize();
??
writer.close();
??
return
numIndexed;
}
/**
*
循環(huán)遍歷目錄下的所有
.txt
文件并進(jìn)行索引
*
*
@param
writer
*
@param
dir
*
@throws
IOException
*/
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[i];
???
if
(f.isDirectory()) {
????
indexDirectory(writer, f);
// recurse
???
}
else
if
(f.getName().endsWith(".txt")) {
????
indexFile(writer, f);
???
}
??
}
}
/**
*
對(duì)單個(gè)
txt
文件進(jìn)行索引
*
*
@param
writer
*
@param
f
*
@throws
IOException
*/
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(new Field("contents", new FileReader(f)));
??
doc.add(
new
Field("filename", f.getCanonicalPath(), Field.Store.YES, Field.Index.ANALYZED));
??
String temp = FileReaderAll(f.getCanonicalPath(), "GBK");
??
System.out.println(temp);
??
doc.add(
new
Field("TTT", temp, Field.Store.YES, Field.Index.ANALYZED));
??
doc.add(
new
Field("path", f.getPath(), Field.Store.YES,
????
Field.Index.ANALYZED));
??
doc.add(
new
Field("modified", DateTools.timeToString(f.lastModified(),
????
DateTools.Resolution.MINUTE), Field.Store.YES,
????
Field.Index.ANALYZED));
??
FileInputStream fis =
new
FileInputStream(f);
??
//
按照
UTF-8
編碼方式將字節(jié)流轉(zhuǎn)化為字符流
??
InputStreamReader isr =
new
InputStreamReader(fis, "utf-8");
??
//
從字符流中獲取文本并進(jìn)行緩沖
??
BufferedReader br =
new
BufferedReader(isr);
??
doc.add(
new
Field("contents", br));
??
writer.setUseCompoundFile(
false
);
??
writer.addDocument(doc);
}
public
static
String FileReaderAll(String FileName, String charset)
???
throws
IOException {
??
BufferedReader reader =
new
BufferedReader(
new
InputStreamReader(
????
new
FileInputStream(FileName), charset));
??
String line =
new
String();
??
String temp =
new
String();
??
while
((line = reader.readLine()) !=
null
) {
???
temp += line;
??
}
??
reader.close();
??
return
temp;
}
}
運(yùn)行結(jié)果:
? ? ??
Indexing D:\nutchwork\LucenceTestDir\file1\1.txt
我們是中國(guó)人
Indexing D:\nutchwork\LucenceTestDir\file1\2.txt
我們是中國(guó)人
Indexing D:\nutchwork\LucenceTestDir\file1\3.txt
我們是中國(guó)人
Indexing D:\nutchwork\LucenceTestDir\file1\4.txt
我們是中國(guó)人
Indexing 4 files took 2293 milliseconds
新建查詢(xún)的類(lèi)以及測(cè)試:
? ?
import
java.io.File;
import
java.io.StringReader;
import
java.util.Date;
import
java.util.List;
import
org.apache.lucene.analysis.Analyzer;
import
org.apache.lucene.analysis.TokenStream;
import
org.apache.lucene.document.Document;
import
org.apache.lucene.document.Fieldable;
import
org.apache.lucene.search.IndexSearcher;
import
org.apache.lucene.search.Query;
import
org.apache.lucene.search.ScoreDoc;
import
org.apache.lucene.search.Sort;
import
org.apache.lucene.search.SortField;
import
org.apache.lucene.search.TopDocs;
import
org.apache.lucene.search.highlight.Highlighter;
import
org.apache.lucene.search.highlight.QueryScorer;
import
org.apache.lucene.search.highlight.SimpleFragmenter;
import
org.apache.lucene.search.highlight.SimpleHTMLFormatter;
import
org.apache.lucene.store.FSDirectory;
import
org.wltea.analyzer.lucene.IKAnalyzer;
import
org.wltea.analyzer.lucene.IKQueryParser;
import
org.wltea.analyzer.lucene.IKSimilarity;
public
class
SearchQueryOK {
private
static
String INDEX_DIR = "D:\\nutchwork\\LucenceTestDir\\index";
//
索引所在的路徑
private
static
String KEYWORD = "
中國(guó)人
";
//
關(guān)鍵詞
private
static
int
TOP_NUM = 100;
//
顯示前
100
條結(jié)果
public
static
void
main(String[] args)
throws
Exception {
??
File indexDir =
new
File(INDEX_DIR);
??
if
(!indexDir.exists() || !indexDir.isDirectory()) {
???
throw
new
Exception(indexDir
?????
+ " does not exist or is not a directory.");
??
}
??
search(indexDir, KEYWORD);
//
調(diào)用
search
方法進(jìn)行查詢(xún)
}
/**
*
查詢(xún)
*
*
@param
indexDir
*
@param
q
*
@throws
Exception
*/
public
static
void
search(File indexDir, String q)
throws
Exception {
??
IndexSearcher is =
new
IndexSearcher(FSDirectory.open(indexDir),
true
);
// read-only
??
String[] field = {"TTT","modified","filename"};
??
long
start =
new
Date().getTime();
// start time
??
//
高亮設(shè)置
??
Analyzer analyzer =
new
IKAnalyzer();
//
設(shè)定分詞器
??
Query query2 = IKQueryParser.parseMultiField(field, KEYWORD);
??
??
??
//
實(shí)例化搜索器
??
IndexSearcher isearcher1 =
new
IndexSearcher(FSDirectory.open(indexDir));
??
//
在索引器中使用
IKSimilarity
相似度評(píng)估器
??
??
isearcher1.setSimilarity(
new
IKSimilarity());
?
??
??
??
??
Sort sort =
new
Sort(
new
SortField("path", SortField.DOC,
false
));
??
//TermQuery q1 = new TermQuery(new Term("filename", "1"));
??
//
搜索相似度最高的記錄
??
TopDocs topDocs1 = isearcher1.search(query2,
null
, TOP_NUM,sort);
?
??
ScoreDoc[] hits3 = topDocs1.scoreDocs;
??
SimpleHTMLFormatter simpleHtmlFormatter =
new
SimpleHTMLFormatter(
????
"<span style='color:#ff0000'>", "</span>");
//
設(shè)定高亮顯示的格式,也就是對(duì)高亮顯示的詞組加上前綴后綴
??
Highlighter highlighter =
new
Highlighter(simpleHtmlFormatter,
????
new
QueryScorer(query2));
??
for
(
int
i = 0; i < hits3.length; i++) {
???
Document doc = is.doc(hits3[i].doc);
???
String docTTT = doc.get("TTT");
???
highlighter.setTextFragmenter(
new
SimpleFragmenter(docTTT.length()));
//
設(shè)置每次返回的字符數(shù)
.
想必大家在使用搜索引擎的時(shí)候也沒(méi)有一并把全部數(shù)據(jù)展示出來(lái)吧,當(dāng)然這里也是設(shè)定只展示部分?jǐn)?shù)據(jù)
???
TokenStream tokenStream = analyzer.tokenStream("",
?????
new
StringReader(docTTT));
???
String str = highlighter.getBestFragment(tokenStream, docTTT);
???
System.out.println("
高亮設(shè)置
: " + str );
??
???
String docModified = doc.get("filename");
???
highlighter.setTextFragmenter(
new
SimpleFragmenter(docModified.length()));
??
???
TokenStream tokenStream2 = analyzer.tokenStream("",
?????
new
StringReader(docModified));
???
String str2 = highlighter.getBestFragment(tokenStream2, docModified);
???
System.out.println("
高亮設(shè)置
: " + str2 );
??
?
??
???
???
???
List<Fieldable> list = doc.getFields();
???
for
(
int
j = 0; j < list.size(); j++) {
????
Fieldable fieldable = list.get(j);
????
System.out.println(fieldable.name() + " : "
??????
+ fieldable.stringValue() + "<br>");
???
}
??
}
??
??
?
?
??
??
long
end =
new
Date().getTime();
// end time
??
System.out.println("Found " + hits3.length
????
+ " document(s) (in " + (end - start)
????
+ " milliseconds) that matched query '" + q + "':");
}
}
對(duì)索引的操作類(lèi):
? ??
import
java.io.File;
import
java.io.IOException;
import
java.sql.Connection;
import
java.sql.SQLException;
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.IndexReader;
import
org.apache.lucene.index.IndexWriter;
import
org.apache.lucene.index.Term;
import
org.apache.lucene.store.Directory;
import
org.apache.lucene.store.FSDirectory;
import
org.apache.lucene.util.Version;
import
org.wltea.analyzer.lucene.IKAnalyzer;
public
class
ManageIndexFile {
private
static
String INDEX_DIR = "D:\\nutchwork\\LucenceTestDir\\index";
//
索引存放目錄
//
刪除索引
public
static
void
DeleteIndex(SearchDocBean bean)
throws
IOException {
??
Directory dir = FSDirectory.open(
new
File(INDEX_DIR));
??
IndexReader reader = IndexReader.open(dir,
false
);
??
Term term =
new
Term("modified", bean.getId());
??
int
count = reader.deleteDocuments(term);
??
reader.close();
??
System.out.println("Successful Delete " + count + " path==" + bean.getId());
}
public
static
void
DeleteIndex(
int
[] posIDS)
throws
IOException {
??
Directory dir = FSDirectory.open(
new
File(INDEX_DIR));
??
IndexReader reader = IndexReader.open(dir,
false
);
??
for
(
int
i = 0; i < posIDS.length; i++) {
???
Term term =
new
Term("posID", Integer.toString(posIDS[i]));
???
reader.deleteDocuments(term);
??
}
??
reader.close();
}
//
更新索引
public
static
void
UpdateIndex(SearchDocBean bean)
throws
IOException {
??
Directory dir = FSDirectory.open(
new
File(INDEX_DIR));
??
IndexReader reader = IndexReader.open(dir,
false
);
??
Term term =
new
Term("modified", bean.getId());
??
reader.deleteDocuments(term);
??
reader.close();
??
IndexWriter writer =
new
IndexWriter(FSDirectory.open(
new
File(
????
INDEX_DIR)),
new
StandardAnalyzer(Version.LUCENE_CURRENT),
????
true
, IndexWriter.MaxFieldLength.LIMITED);
??
Document doc =
new
Document();
??
doc.add(
new
Field("modified", bean.getId(), Field.Store.YES,
????
Field.Index.NOT_ANALYZED));
??
writer.addDocument(doc);
??
writer.optimize();
??
writer.close();
}
//
增加索引
public
static
void
AddIndex(SearchDocBean bean,
???
Connection conn)
throws
IOException, SQLException {
??
Analyzer analyzer =
new
IKAnalyzer();
//
采用的分詞器
??
IndexWriter writer =
new
IndexWriter(FSDirectory.open(
new
File(
????
INDEX_DIR)), analyzer,
false
,
????
IndexWriter.MaxFieldLength.LIMITED);
??
Document doc =
new
Document();
??
doc.add(
new
Field("filename", bean.getFileName(), Field.Store.YES,
????
Field.Index.ANALYZED));
??
doc.add(
new
Field("path", bean.getPath(), Field.Store.YES,
????
Field.Index.ANALYZED));
??
doc.add(
new
Field("dateTime", bean.getId(), Field.Store.YES,
????
Field.Index.ANALYZED));
??
doc.add(
new
Field("TTT", bean.getContents(), Field.Store.YES, Field.Index.ANALYZED));
??
writer.setUseCompoundFile(
false
);
??
writer.addDocument(doc);
??
writer.optimize();
??
writer.close();
}
}
封裝起來(lái)的查詢(xún)結(jié)果:
? ?
public
class
SearchDocBean {
???
private
String id;
???
private
String path;
???
private
String contents;
???
private
String dateTime;
???
public
String getId() {
???????
return
id;
???
}
???
public
void
setId(String id) {
???????
this
.id = id;
???
}
???
public
String getPath() {
???????
return
path;
???
}
???
public
void
setPath(String path) {
???????
this
.path = path;
???
}
???
public
String getContents() {
???????
return
contents;
???
}
???
public
void
setContents(String contents) {
???????
this
.contents = contents;
???
}
???
public
String getDateTime() {
???????
return
dateTime;
???
}
???
public
void
setDateTime(String dateTime) {
???????
this
.dateTime = dateTime;
???
}
???
public
String getFileName() {
???????
return
fileName;
???
}
???
public
void
setFileName(String fileName) {
???????
this
.fileName = fileName;
???
}
???
private
String fileName;
}
? ? 下面是 serach 中國(guó)人的 結(jié)果:
? ?
?
高亮設(shè)置
: <span style='color:#ff0000'>
中國(guó)人
</span>
?
高亮設(shè)置
: null
filename : D:\nutchwork\LucenceTestDir\file1\1.txt<br>
TTT :
我們是中國(guó)人
<br>
path : D:\nutchwork\LucenceTestDir\file1\1.txt<br>
modified : 201107161115<br>
?
高亮設(shè)置
: <span style='color:#ff0000'>
中國(guó)人
</span>
?
高亮設(shè)置
: null
filename : D:\nutchwork\LucenceTestDir\file1\2.txt<br>
TTT :
我們是中國(guó)人
<br>
path : D:\nutchwork\LucenceTestDir\file1\2.txt<br>
modified : 201107161115<br>
?
高亮設(shè)置
: <span style='color:#ff0000'>
中國(guó)人
</span>
?
高亮設(shè)置
: null
filename : D:\nutchwork\LucenceTestDir\file1\3.txt<br>
TTT :
我們是中國(guó)人
<br>
path : D:\nutchwork\LucenceTestDir\file1\3.txt<br>
modified : 201107161115<br>
?
高亮設(shè)置
: <span style='color:#ff0000'>
中國(guó)人
</span>
?
高亮設(shè)置
: null
filename : D:\nutchwork\LucenceTestDir\file1\4.txt<br>
TTT :
我們是中國(guó)人
<br>
path : D:\nutchwork\LucenceTestDir\file1\4.txt<br>
modified : 201107161115<br>
Found 4 document(s) (in 717 milliseconds) that matched query '
中國(guó)人
':
整個(gè)工程:基本上是從網(wǎng)上找到的代碼,運(yùn)行了下,算是有一個(gè)大概的了解。
?
?
?
?
?
?
?
Lucene 簡(jiǎn)介
Lucene 是一個(gè)基于 Java 的全文信息檢索工具包,它不是一個(gè)完整的搜索應(yīng)用程序,而是為你的應(yīng)用程序提供索引和搜索功能。 Lucene 目前是 Apache Jakarta 家族中的一個(gè)開(kāi)源項(xiàng)目。也是目前最為流行的基于 Java 開(kāi)源全文檢索工具包。
目前已經(jīng)有很多應(yīng)用程序的搜索功能是基于 Lucene 的,比如 Eclipse 的幫助系統(tǒng)的搜索功能。 Lucene 能夠?yàn)槲谋绢?lèi)型的數(shù)據(jù)建立索引,所以你只要能把你要索引的數(shù)據(jù)格式轉(zhuǎn)化的文本的, Lucene 就能對(duì)你的文檔進(jìn)行索引和搜索。比如你要對(duì)一些 HTML 文檔, PDF 文檔進(jìn)行索引的話你就首先需要把 HTML 文檔和 PDF 文檔轉(zhuǎn)化成文本格式的,然后將轉(zhuǎn)化后的內(nèi)容交給 Lucene 進(jìn)行索引,然后把創(chuàng)建好的索引文件保存到磁盤(pán)或者內(nèi)存中,最后根據(jù)用戶(hù)輸入的查詢(xún)條件在索引文件上進(jìn)行查詢(xún)。不指定要索引的文檔的格式也使 Lucene 能夠幾乎適用于所有的搜索應(yīng)用程序。
圖 1 表示了搜索應(yīng)用程序和 Lucene 之間的關(guān)系,也反映了利用 Lucene 構(gòu)建搜索應(yīng)用程序的流程:
圖
1.
搜索應(yīng)用程序和
Lucene
之間的關(guān)系
索引是現(xiàn)代搜索引擎的核心,建立索引的過(guò)程就是把源數(shù)據(jù)處理成非常方便查詢(xún)的索引文件的過(guò)程。為什么索引這么重要呢,試想你現(xiàn)在要在大量的文 檔中搜索含有某個(gè)關(guān)鍵詞的文檔,那么如果不建立索引的話你就需要把這些文檔順序的讀入內(nèi)存,然后檢查這個(gè)文章中是不是含有要查找的關(guān)鍵詞,這樣的話就會(huì)耗 費(fèi)非常多的時(shí)間,想想搜索引擎可是在毫秒級(jí)的時(shí)間內(nèi)查找出要搜索的結(jié)果的。這就是由于建立了索引的原因,你可以把索引想象成這樣一種數(shù)據(jù)結(jié)構(gòu),他能夠使你 快速的隨機(jī)訪問(wèn)存儲(chǔ)在索引中的關(guān)鍵詞,進(jìn)而找到該關(guān)鍵詞所關(guān)聯(lián)的文檔。 Lucene 采用的是一種稱(chēng)為反向索引( inverted index )的機(jī)制。反向索引就是說(shuō)我們維護(hù)了一個(gè)詞 / 短語(yǔ)表,對(duì)于這個(gè)表中的每個(gè)詞 / 短語(yǔ),都有一個(gè)鏈表描述了有哪些文檔包含了這個(gè)詞 / 短語(yǔ)。這樣在用戶(hù)輸入查詢(xún)條件的時(shí)候,就能非常快的得到搜索結(jié)果。我們將在本系列文章的第二部分詳細(xì)介紹 Lucene 的索引機(jī)制,由于 Lucene 提供了簡(jiǎn)單易用的 API ,所以即使讀者剛開(kāi)始對(duì)全文本進(jìn)行索引的機(jī)制并不太了解,也可以非常容易的使用 Lucene 對(duì)你的文檔實(shí)現(xiàn)索引。
對(duì)文檔建立好索引后,就可以在這些索引上面進(jìn)行搜索了。搜索引擎首先會(huì)對(duì)搜索的關(guān)鍵詞進(jìn)行解析,然后再在建立好的索引上面進(jìn)行查找,最終返回和用戶(hù)輸入的關(guān)鍵詞相關(guān)聯(lián)的文檔。
Lucene 軟件包分析
Lucene 軟件包的發(fā)布形式是一個(gè) JAR 文件,下面我們分析一下這個(gè) JAR 文件里面的主要的 JAVA 包,使讀者對(duì)之有個(gè)初步的了解。
Package: org.apache.lucene.document
這個(gè)包提供了一些為封裝要索引的文檔所需要的類(lèi),比如 Document, Field 。這樣,每一個(gè)文檔最終被封裝成了一個(gè) Document 對(duì)象。
Package: org.apache.lucene.analysis
這個(gè)包主要功能是對(duì)文檔進(jìn)行分詞,因?yàn)槲臋n在建立索引之前必須要進(jìn)行分詞,所以這個(gè)包的作用可以看成是為建立索引做準(zhǔn)備工作。
Package: org.apache.lucene.index
這個(gè)包提供了一些類(lèi)來(lái)協(xié)助創(chuàng)建索引以及對(duì)創(chuàng)建好的索引進(jìn)行更新。這里面有兩個(gè)基礎(chǔ)的類(lèi): IndexWriter 和 IndexReader ,其中 IndexWriter 是用來(lái)創(chuàng)建索引并添加文檔到索引中的, IndexReader 是用來(lái)刪除索引中的文檔的。
Package: org.apache.lucene.search
這個(gè)包提供了對(duì)在建立好的索引上進(jìn)行搜索所需要的類(lèi)。比如 IndexSearcher 和 Hits, IndexSearcher 定義了在指定的索引上進(jìn)行搜索的方法, Hits 用來(lái)保存搜索得到的結(jié)果。
假設(shè)我們的電腦的目錄中含有很多文本文檔,我們需要查找哪些文檔含有某個(gè)關(guān)鍵詞。為了實(shí)現(xiàn)這種功能,我們首先利用 Lucene 對(duì)這個(gè)目錄中的文檔建立索引,然后在建立好的索引中搜索我們所要查找的文檔。通過(guò)這個(gè)例子讀者會(huì)對(duì)如何利用 Lucene 構(gòu)建自己的搜索應(yīng)用程序有個(gè)比較清楚的認(rèn)識(shí)。
為了對(duì)文檔進(jìn)行索引, Lucene 提供了五個(gè)基礎(chǔ)的類(lèi),他們分別是 Document, Field, IndexWriter, Analyzer, Directory 。下面我們分別介紹一下這五個(gè)類(lèi)的用途:
Document
Document 是用來(lái)描述文檔的,這里的文檔可以指一個(gè) HTML 頁(yè)面,一封電子郵件,或者是一個(gè)文本文件。一個(gè) Document 對(duì)象由多個(gè) Field 對(duì)象組成的。可以把一個(gè) Document 對(duì)象想象成數(shù)據(jù)庫(kù)中的一個(gè)記錄,而每個(gè) Field 對(duì)象就是記錄的一個(gè)字段。
Field
Field 對(duì)象是用來(lái)描述一個(gè)文檔的某個(gè)屬性的,比如一封電子郵件的標(biāo)題和內(nèi)容可以用兩個(gè) Field 對(duì)象分別描述。
Analyzer
在一個(gè)文檔被索引之前,首先需要對(duì)文檔內(nèi)容進(jìn)行分詞處理,這部分工作就是由 Analyzer 來(lái)做的。 Analyzer 類(lèi)是一個(gè)抽象類(lèi),它有多個(gè)實(shí)現(xiàn)。針對(duì)不同的語(yǔ)言和應(yīng)用需要選擇適合的 Analyzer 。 Analyzer 把分詞后的內(nèi)容交給 IndexWriter 來(lái)建立索引。
IndexWriter
IndexWriter 是 Lucene 用來(lái)創(chuàng)建索引的一個(gè)核心的類(lèi),他的作用是把一個(gè)個(gè)的 Document 對(duì)象加到索引中來(lái)。
Directory
這個(gè)類(lèi)代表了 Lucene 的索引的存儲(chǔ)的位置,這是一個(gè)抽象類(lèi),它目前有兩個(gè)實(shí)現(xiàn),第一個(gè)是 FSDirectory ,它表示一個(gè)存儲(chǔ)在文件系統(tǒng)中的索引的位置。第二個(gè)是 RAMDirectory ,它表示一個(gè)存儲(chǔ)在內(nèi)存當(dāng)中的索引的位置。
熟悉了建立索引所需要的這些類(lèi)后,我們就開(kāi)始對(duì)某個(gè)目錄下面的文本文件建立索引了,清單 1 給出了對(duì)某個(gè)目錄下的文本文件建立索引的源代碼。
package TestLucene; import java.io.File; import java.io.FileReader; import java.io.Reader; import java.util.Date; 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; /** * This class demonstrate the process of creating index with Lucene * for text files */ public class TxtFileIndexer { ???? public static void main(String[] args) throws Exception{ ???? //indexDir is the directory that hosts Lucene's index files ???? File ?? indexDir = new File("D:\\luceneIndex"); ???? //dataDir is the directory that hosts the text files that to be indexed ???? File ?? dataDir ? = new File("D:\\luceneData"); ???? Analyzer luceneAnalyzer = new StandardAnalyzer(); ???? File[] dataFiles ? = dataDir.listFiles(); ???? IndexWriter indexWriter = new IndexWriter(indexDir,luceneAnalyzer,true); ???? long startTime = new Date().getTime(); ???? for(int i = 0; i < dataFiles.length; i++){ ????????? if(dataFiles[i].isFile() && dataFiles[i].getName().endsWith(".txt")){ ?????????????? System.out.println("Indexing file " + dataFiles[i].getCanonicalPath()); ?????????????? Document document = new Document(); ?????????? ???? Reader txtReader = new FileReader(dataFiles[i]); ?????????????? document.add(Field.Text("path",dataFiles[i].getCanonicalPath())); ?????????????? document.add(Field.Text("contents",txtReader)); ?????????????? indexWriter.addDocument(document); ???? ????? } ???? } ???? indexWriter.optimize(); ???? indexWriter.close(); ???? long endTime = new Date().getTime(); ??????? ???? System.out.println("It takes " + (endTime - startTime) ???????? + " milliseconds to create index for the files in directory " ???????? + dataDir.getPath()); ??????? ???? } } |
?
在清單 1 中,我們注意到類(lèi) IndexWriter 的構(gòu)造函數(shù)需要三個(gè)參數(shù),第一個(gè)參數(shù)指定了所創(chuàng)建的索引要存放的位置,他可以是一個(gè) File 對(duì)象,也可以是一個(gè) FSDirectory 對(duì)象或者 RAMDirectory 對(duì)象。第二個(gè)參數(shù)指定了 Analyzer 類(lèi)的一個(gè)實(shí)現(xiàn),也就是指定這個(gè)索引是用哪個(gè)分詞器對(duì)文擋內(nèi)容進(jìn)行分詞。第三個(gè)參數(shù)是一個(gè)布爾型的變量,如果為 true 的話就代表創(chuàng)建一個(gè)新的索引,為 false 的話就代表在原來(lái)索引的基礎(chǔ)上進(jìn)行操作。接著程序遍歷了目錄下面的所有文本文檔,并為每一個(gè)文本文檔創(chuàng)建了一個(gè) Document 對(duì)象。然后把文本文檔的兩個(gè)屬性:路徑和內(nèi)容加入到了兩個(gè) Field 對(duì)象中,接著在把這兩個(gè) Field 對(duì)象加入到 Document 對(duì)象中,最后把這個(gè)文檔用 IndexWriter 類(lèi)的 add 方法加入到索引中去。這樣我們便完成了索引的創(chuàng)建。接下來(lái)我們進(jìn)入在建立好的索引上進(jìn)行搜索的部分。
利用 Lucene 進(jìn)行搜索就像建立索引一樣也是非常方便的。在上面一部分中,我們已經(jīng)為一個(gè)目錄下的文本文檔建立好了索引,現(xiàn)在我們就要在這個(gè)索引上進(jìn)行搜索以找到包含某 個(gè)關(guān)鍵詞或短語(yǔ)的文檔。 Lucene 提供了幾個(gè)基礎(chǔ)的類(lèi)來(lái)完成這個(gè)過(guò)程,它們分別是呢 IndexSearcher, Term, Query, TermQuery, Hits. 下面我們分別介紹這幾個(gè)類(lèi)的功能。
Query
這是一個(gè)抽象類(lèi),他有多個(gè)實(shí)現(xiàn),比如 TermQuery, BooleanQuery, PrefixQuery. 這個(gè)類(lèi)的目的是把用戶(hù)輸入的查詢(xún)字符串封裝成 Lucene 能夠識(shí)別的 Query 。
Term
Term 是搜索的基本單位,一個(gè) Term 對(duì)象有兩個(gè) String 類(lèi)型的域組成。生成一個(gè) Term 對(duì)象可以有如下一條語(yǔ)句來(lái)完成: Term term = new Term(“fieldName”,”queryWord”); 其中第一個(gè)參數(shù)代表了要在文檔的哪一個(gè) Field 上進(jìn)行查找,第二個(gè)參數(shù)代表了要查詢(xún)的關(guān)鍵詞。
TermQuery
TermQuery 是抽象類(lèi) Query 的一個(gè)子類(lèi),它同時(shí)也是 Lucene 支持的最為基本的一個(gè)查詢(xún)類(lèi)。生成一個(gè) TermQuery 對(duì)象由如下語(yǔ)句完成: TermQuery termQuery = new TermQuery(new Term(“fieldName”,”queryWord”)); 它的構(gòu)造函數(shù)只接受一個(gè)參數(shù),那就是一個(gè) Term 對(duì)象。
IndexSearcher
IndexSearcher 是用來(lái)在建立好的索引上進(jìn)行搜索的。它只能以只讀的方式打開(kāi)一個(gè)索引,所以可以有多個(gè) IndexSearcher 的實(shí)例在一個(gè)索引上進(jìn)行操作。
Hits
Hits 是用來(lái)保存搜索的結(jié)果的。
介紹完這些搜索所必須的類(lèi)之后,我們就開(kāi)始在之前所建立的索引上進(jìn)行搜索了,清單 2 給出了完成搜索功能所需要的代碼。
? package TestLucene; ? import java.io.File; ? import org.apache.lucene.document.Document; ? import org.apache.lucene.index.Term; ? import org.apache.lucene.search.Hits; ? import org.apache.lucene.search.IndexSearcher; ? import org.apache.lucene.search.TermQuery; ? import org.apache.lucene.store.FSDirectory; ? /** ? * This class is used to demonstrate the ? * process of searching on an existing ? * Lucene index ? * ? */ ? public class TxtFileSearcher { ??????? ? public static void main(String[] args) throws Exception{ ??????? ??? String queryStr = "lucene"; ??????? ??? //This is the directory that hosts the Lucene index ??????? File indexDir = new File("D:\\luceneIndex"); ??????? FSDirectory directory = FSDirectory.getDirectory(indexDir,false); ??????? IndexSearcher searcher = new IndexSearcher(directory); ??????? if(!indexDir.exists()){ ??????? ?????? ? System.out.println("The Lucene index is not exist"); ??????? ?????? ? return; ??????? } ??????? Term term = new Term("contents",queryStr.toLowerCase()); ??????? TermQuery luceneQuery = new TermQuery(term); ??????? Hits hits = searcher.search(luceneQuery); ??????? for(int i = 0; i < hits.length(); i++){ ??????? ?????? ? Document document = hits.doc(i); ??????? ?????? ? System.out.println("File: " + document.get("path")); ? ?????? } ??????? ? } ? } |
?
在清單 2 中,類(lèi) IndexSearcher 的構(gòu)造函數(shù)接受一個(gè)類(lèi)型為 Directory 的對(duì)象, Directory 是一個(gè)抽象類(lèi),它目前有兩個(gè)子類(lèi): FSDirctory 和 RAMDirectory. 我們的程序中傳入了一個(gè) FSDirctory 對(duì)象作為其參數(shù),代表了一個(gè)存儲(chǔ)在磁盤(pán)上的索引的位置。構(gòu)造函數(shù)執(zhí)行完成后,代表了這個(gè) IndexSearcher 以只讀的方式打開(kāi)了一個(gè)索引。然后我們程序構(gòu)造了一個(gè) Term 對(duì)象,通過(guò)這個(gè) Term 對(duì)象,我們指定了要在文檔的內(nèi)容中搜索包含關(guān)鍵詞 ”lucene” 的文檔。接著利用這個(gè) Term 對(duì)象構(gòu)造出 TermQuery 對(duì)象并把這個(gè) TermQuery 對(duì)象傳入到 IndexSearcher 的 search 方法中進(jìn)行查詢(xún),返回的結(jié)果保存在 Hits 對(duì)象中。最后我們用了一個(gè)循環(huán)語(yǔ)句把搜索到的文檔的路徑都打印了出來(lái)。 好了,我們的搜索應(yīng)用程序已經(jīng)開(kāi)發(fā)完畢,怎么樣,利用 Lucene 開(kāi)發(fā)搜索應(yīng)用程序是不是很簡(jiǎn)單。
本文首先介紹了 Lucene 的一些基本概念,然后開(kāi)發(fā)了一個(gè)應(yīng)用程序演示了利用 Lucene 建立索引并在該索引上進(jìn)行搜索的過(guò)程。希望本文能夠?yàn)閷W(xué)習(xí) Lucene 的讀者提供幫助。
?
更多文章、技術(shù)交流、商務(wù)合作、聯(lián)系博主
微信掃碼或搜索:z360901061

微信掃一掃加我為好友
QQ號(hào)聯(lián)系: 360901061
您的支持是博主寫(xiě)作最大的動(dòng)力,如果您喜歡我的文章,感覺(jué)我的文章對(duì)您有幫助,請(qǐng)用微信掃描下面二維碼支持博主2元、5元、10元、20元等您想捐的金額吧,狠狠點(diǎn)擊下面給點(diǎn)支持吧,站長(zhǎng)非常感激您!手機(jī)微信長(zhǎng)按不能支付解決辦法:請(qǐng)將微信支付二維碼保存到相冊(cè),切換到微信,然后點(diǎn)擊微信右上角掃一掃功能,選擇支付二維碼完成支付。
【本文對(duì)您有幫助就好】元
