對使用Lucene的一點點認識
就我而言我使用Lucene就是為了搜索而已,所以我就以這個目的來描述這個筆記。我使用Lucene的步驟如下:
1:創建索引
2:學習有哪些過濾器
3:學習有哪些查詢器(很多)
4:學習有哪些分詞器(很多,很重要)
(1)創建索引
/**
* @param args
* 這個是在文檔目錄中增加
這個例子是把docs目錄下的文件都添加所以到index文件里面
*/
public static void main(String[] args) throws Exception{
String indexPath="C:\\Users\\Administrator\\Workspaces\\MyEclipse9\\Lucene01\\index";
String docsPath="C:\\Users\\Administrator\\Workspaces\\MyEclipse9\\Lucene01\\docs";
//這段代碼我們注意一下,因為我們創建所以的目錄有兩種方式,一種是真實的文件目錄,一種是內存目錄,一般內存目錄是為了我們方便測試用的,我們這兒用的是真實的目錄
Directory dir=FSDirectory.open(new File(indexPath));
Analyzer analyzer=new StandardAnalyzer(Version.LUCENE_45);
IndexWriterConfig iwc=new IndexWriterConfig(Version.LUCENE_45,analyzer);
IndexWriter writer=new IndexWriter(dir,iwc);
File docDir=new File(docsPath);
indexDocs(writer,docDir);
writer.close();
dir.close();
}
public static void indexDocs(IndexWriter writer,File file) throws Exception{
if(file.canRead()){
if(file.isDirectory()){
String[] files=file.list();
if(files!=null){
for (int i = 0; i < files.length; i++) {
System.out.println(files[i]+"2");
//根據 parent 抽象路徑名和 child 路徑名字符串創建一個新 File 實例。
//File(File parent, String child)
indexDocs(writer,new File(file,files[i]));
}
}
}else{
FileInputStream fis ;
try {
fis = new FileInputStream(file);
} catch (FileNotFoundException e) {
return;
}
Document doc = new Document();
Field PathField = new StringField("path",file.getPath(),Field.Store.YES);
doc.add(PathField);
doc.add(new LongField("modified",file.lastModified(),Field.Store.NO));
try {
doc.add(new TextField("contents",new BufferedReader(new InputStreamReader(fis,"UTF-8"))));
writer.addDocument(doc);
} catch (UnsupportedEncodingException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} finally {
fis.close();
}
System.out.println("writer.addDocument(doc)-file.getPath():"+file.getPath());
System.out.println("writer.numDocs():"+writer.numDocs());
System.out.println("writer.maxDoc():"+writer.maxDoc());
System.out.println("writer.numRamDocs():"+writer.numRamDocs());
}
}
}
(2)過濾器
我感覺不是很重要,所以這兒就不提了。
(3)查詢器
很多,但是也好理解,我的有很多代碼的例子,所以這兒也不說了。
(4)分詞器
我們的分詞器有很多,但是常用的就有一下幾種(針對的是西文的分詞器)
,我們可以到文檔之中去查找。
1:StandardAnalyzer:支持漢語,混合分詞。
2:StopAnalyzer:去掉一些連接詞,is,or啊之類的無意義的詞,還有空格之類的連接詞。
3:SimpleAnalyzer:就是分割一些空格,連接符之類的。
如何使用呢?
public static void main(String[] args) throws Exception{
Directory dir=new RAMDirectory();
//想用成什么樣的分詞器就寫什么樣的分詞器,包括寫成后面說的中文分詞器
Analyzer analyzer=new StandardAnalyzer(Version.LUCENE_45);
IndexWriterConfig iwriter=new IndexWriterConfig(Version.LUCENE_45,analyzer);
IndexWriter writer=new IndexWriter(dir,iwriter);
Document doc=new Document();
Field f=new TextField("content","xy&z mail is - xyz@hello.com,中文",Store.YES);
doc.add(f);
writer.addDocument(doc);
writer.close();
DirectoryReader dr= DirectoryReader.open(dir);
IndexSearcher is=new IndexSearcher(dr);
QueryParser parser=new QueryParser(Version.LUCENE_45,"content",analyzer);
Query query=parser.parse("\"xyz@hello.com,中文\"");
ScoreDoc[] hits=is.search(query, 100).scoreDocs;
System.out.println(query.toString());
for (int i = 0; i < hits.length; i++) {
Document document=is.doc(hits[i].doc);
System.out.println(hits[i]+"content:"+document.get("content"));
}
dr.close();
dr.close();
}
下面這段代碼是驗證上面提的這些分詞器是如何分詞的,打印分詞的效果。
public static void main(String[] args) throws Exception {
//Analyzer analyzer=new StandardAnalyzer(Version.LUCENE_45);
Analyzer analyzer=new SimpleAnalyzer(Version.LUCENE_45);
//獲取lucene的tokenstream對象
TokenStream ts=analyzer.tokenStream("content", new StringReader("xy&z mail is - xyz@hello.com,中文"));
//獲取詞源位置屬性
OffsetAttribute offset=ts.addAttribute(OffsetAttribute.class);
CharTermAttribute term=ts.addAttribute(CharTermAttribute.class);
//獲取詞源文本屬性
TypeAttribute type=ts.addAttribute(TypeAttribute.class);
ts.reset();
while(ts.incrementToken()){
System.out.println(offset.startOffset()+"-"+offset.endOffset()+":"+term.toString());
}
ts.end();
ts.close();
}
打印結果:
0-2:xy|word
3-4:z|word
5-9:mail|word
10-12:is|word
15-18:xyz|word
19-24:hello|word
25-28:com|word
29-31:中文|word
重點介紹的是中文的分詞器:
IK Analyzer
它的安裝部署十分簡單,將IKAnalyzer2012.jar部署亍項目的lib目彔中;IKAnalyzer.cfg.xml和 stopword.dic文件放置在class根目彔(對亍web項目,通常是WEB-INF/classes目彔,同hibernate、log4j等配置文件相同)下即可。
使用方法跟上面是一模一樣的,具體效果是什么樣,可以自己測試,也可以看它的文檔。
綜合使用
思考1:
我這兒創建索引的時候針對的都是txt文檔,要是還有其他的比如視頻格式,PDF,XML等等之類的格式呢?
好辦,我們用tika這個包。使用代碼如下:
/**初始化給目錄下的所有文件都建索引
* @param args
*/
public static void main(String[] args) throws Exception {
String indexPath=INDEX;
String docsPath=INDEX_PATH.replace(".","\\");
Directory dir=FSDirectory.open(new File(indexPath));
Analyzer analyzer=new IKAnalyzer(true);
IndexWriterConfig iwc=new IndexWriterConfig(Version.LUCENE_43,analyzer);
iwc.setOpenMode(OpenMode.CREATE_OR_APPEND);
IndexWriter writer=new IndexWriter(dir,iwc);
File file=new File(docsPath);
indexDocs(writer,file);
writer.close();
dir.close();
}
public static void indexDocs(IndexWriter writer,File file) throws IOException {
if(file.canRead()){
if(file.isDirectory()){//如果是文件夾
String[] filelist=file.list();
if(filelist!=null){
for (int i = 0; i < filelist.length; i++) {
indexDocs(writer,new File(file,filelist[i]));
}
}
}else{
//如果不是文件夾
FileInputStream fis;
try {
fis = new FileInputStream(file);
} catch (FileNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
return ;
}
Document doc=new Document();
Field PathField = new StringField(PATH_FIELD_NAME,file.getPath(),Field.Store.YES);
doc.add(PathField);
doc.add(new LongField(MODIFIED_FIELD_NAME,file.lastModified(),Field.Store.NO));
//我們用tika來解析我們的流
try{
ContentHandler handler = new BodyContentHandler();
Parser parser = new AutoDetectParser();
Metadata meta = new Metadata();
parser.parse(fis, handler, meta, new ParseContext());
doc.add(new TextField(CONTENTS_FIELD_NAME,"《"+file.getName()+"》"+handler.toString(),Field.Store.YES));
writer.updateDocument(new Term(PATH_FIELD_NAME, file.getPath()),doc);
writer.commit();
}catch (Exception e){
e.printStackTrace();
}finally{
fis.close();
}
System.out.println("%%%%%%%%%%%%%%%%%%%%");
System.out.println("writer.updateDocument(doc)-file.getPath():"+file.getPath());
System.out.println("writer.numDocs():"+writer.numDocs());
System.out.println("writer.maxDoc():"+writer.maxDoc());
System.out.println("writer.numRamDocs():"+writer.numRamDocs());
}
}
}
}
Tika相當于是一個萬能的解析工具了。
思考2
百度文庫人家查出來你要搜索的詞包含在文中,文中使用高亮顯示的啊,我能嗎?
能!
代碼如下:
public void Searcher(String danci,Integer pageno,Integer pagesize)throws Exception{
String indexPath = INDEX;
Directory dir = FSDirectory.open(new File(indexPath));
Analyzer analyzer = new IKAnalyzer(true);
DirectoryReader ireader = DirectoryReader.open(dir);
IndexSearcher searcher = new IndexSearcher(ireader);
QueryParser parser = new QueryParser(Version.LUCENE_43,CONTENTS_FIELD_NAME,analyzer);
Query query = parser.parse(danci);
System.out.println("QueryParser:"+query.toString());
if(pageno*pagesize>querysize){
querysize=pageno*pagesize;
}
ScoreDoc[] hits = searcher.search(query, querysize).scoreDocs;
//高亮設置
SimpleHTMLFormatter simpleHtmlFormatter = new SimpleHTMLFormatter("<font color=red>","</font>");
Highlighter highlighter = new Highlighter(simpleHtmlFormatter,new QueryScorer(query));
highlighter.setTextFragmenter(new SimpleFragmenter(150));
Integer start=(pageno-1)*pagesize;
Integer end=pageno*pagesize;
hitslength=hits.length;
if(hits.length>start){
if(pagesize*pageno>hits.length) end=hits.length;
for (Integer i=start;i<end;i++){
fileatr=new FileAtr();
Document doc = searcher.doc(hits[i].doc);
String strpath = doc.get(PATH_FIELD_NAME);
fileatr.setLujing(strpath);//添加文件的路徑
fileatr.setFileName(getFileNameByPath(strpath));
//這兒也是為了高亮顯示
TokenStream tokenStream = analyzer.tokenStream(CONTENTS_FIELD_NAME, new StringReader(doc.get(CONTENTS_FIELD_NAME)));
String str = highlighter.getBestFragment(tokenStream, doc.get(CONTENTS_FIELD_NAME));
fileatr.setContents(str);
filelist.add(fileatr);
}
}
System.out.println(hits.length+"這是多少");
pagecount=hits.length/pagesize;
if( pageno >= pagecount) pagecount = pageno;
// System.out.prIntegerln("ireader.numDeletedDocs()"+ireader.numDeletedDocs());
// System.out.prIntegerln("ireader.numDocs()"+ireader.numDocs());
// System.out.prIntegerln("ireader.maxDoc()"+ireader.maxDoc());
ireader.close();
dir.close();
}