对使用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();
}