3. //读取一个文件的所有内容--重载
public static String getText(String s){
String t = "";
try{
File f = new File(s);
t = getText(f);
}catch(Exception e){
t = "";
}
return t;
}
//主函数,测试
public static void main(String[] args) throws IOException{
String s = FileText.getText("doc/test.htm");
System.out.println(s);
}
}
4. 上面的例子是对文件的读取,对于luncen读取文件是为创建索引获取内容的,有了内容才可以创建索引,下面再看个例子就是创建索引。
package luceneindexer;
import org.apache.lucene.document.Document;
import org.apache.lucene.document.Field;
import org.apache.lucene.index.IndexWriter;
import org.apache.lucene.analysis.standard.StandardAnalyzer;
import java.io.*;
import jeasy.analysis.MMAnalyzer;
public class FileIndexer{
public static void main(String[] args) throws java.io.IOException{
String indexPath = "file";
IndexWriter writer = new IndexWriter(indexPath,new StandardAnalyzer(),true);//创建索引的构造方法
Document doc = new Document();//创建索引的主要元素
5. File f = new File("doc/黑帝.htm");
String name = f.getName();
Field field = new Field("name",name ,Field.Store.YES, Field.Index.TOKENIZED);
doc.add(field);
String content = FileText.getText(f);
field = new Field("content", content ,Field.Store.YES, Field.Index.TOKENIZED);
doc.add(field);
String path = f.getPath();
field = new Field("path", path ,Field.Store.YES, Field.Index.NO);
doc.add(field);
writer.addDocument(doc,new MMAnalyzer());
6. doc = new Document();
f = new File("doc/China.htm");
name = f.getName();
field = new Field("name",name ,Field.Store.YES, Field.Index.TOKENIZED);
doc.add(field);
content = FileText.getText(f);
field = new Field("content", content ,Field.Store.YES, Field.Index.TOKENIZED);
doc.add(field);
path = f.getPath();
field = new Field("path", path ,Field.Store.YES, Field.Index.NO);
doc.add(field);
writer.addDocument(doc,new StandardAnalyzer());
writer.close();
System.out.println("File Index Created!");
}
}
7. 好,现在已经把索引建成。通过这两个小例子让大家对lucene的第一部分有个感性的认识。
Lucene做搜索引擎不可能是从一个文件里来搜索,应该是从多个文件来查找想要的东西,现在就对上面的代码做一个修改,在创建索引时从一个文件夹里读取多个文件,然后创建索引。
package indexfiles;
import java.io.*;
public class FileList{
private static final String SEP = "/";
private static StringBuffer sb = new StringBuffer("");
//返回数组,把一个文件夹下面的所有文件都读取进来
public static String[] getFiles(File f) throws IOException{
if(f.isDirectory()){
File[] fs=f.listFiles();
for(int i=0;i
8. public static String[] getFiles(String t) throws IOException
File f = new File(t);
if(f.isDirectory()){
File[] fs=f.listFiles();
for(int i=0;i
13. public class Searcher {
//执行搜索 -- 搜索的关键词,索引的路径
public static void search(String phrase,String indexPath) throws IOException{
IndexSearcher searcher = new IndexSearcher(indexPath);//建立搜索器
Term t = new Term("text", phrase);//搜索 text 字段
Query q = new TermQuery(t);//生成Query对象
Hits hs = searcher.search(q);//获得搜索结果对象
int num = hs.length();//搜索到的结果数量
//输出搜索结果
for(int i = 0; i < num; i++){
Document doc = hs.doc(i);//获得Document
if(doc == null){
continue;
}
14. Field field = doc.getField("filename");//获得filename
String filename = field.stringValue();
field = doc.getField("uri");//uri
String uri = field.stringValue();
field = doc.getField("cdate");
String cdate = field.stringValue();
field = doc.getField("digest");
String digest = field.stringValue();
StringBuffer sb=new StringBuffer();
sb.append("URI:" + uri +"\n");
sb.append("filename:" + filename + "\n");
sb.append("cdate:" + cdate +"\n");
sb.append("digest:" + digest + "\n");
sb.append("------------------------------------" + "\n");
System.out.println(sb);
}
//关闭搜索器
searcher.close();
}
public static void main(String[] args) throws IOException {
search("史记中称伏牺","E:/indexer");
}
}
15. 好,现在就可以看到整个lucene的运行流程。但是lucene的东西是很多的还有排序,高亮,这些在后面的学习中给大家做介绍。现在我想先带大家入门,先做简单的,大家在用的时候可以深入的学习。现在我把主要的知识点讲解一下,lucene提供了五个基础类,Document 、Field、indexWriter、Analyzer、Directory。
Document 就相当一张表里的一条记录。
Field 就相当一这个表中的字段。
Analyzer 就是在文档被索引之前首先要对文档的内容进行分词处理。Analyzer是一个抽象类,它有多个实现方法,针对不同的语言和应用需要选择适当的Analyzer.具体的实现大家要参阅API。
IndexWriter是lucene创建索引的核心类,它的作用是封装Document对象。如果把Document理解成一条记录的话IndexWriter就相当于存储这条记录的表。
IndexWriter 的构造函数有六个。分别是:
IndexWriter(Directory d, Analyzer a) IndexWriter(Directory d, Analyzer a, boolean create) IndexWriter(File path, Analyzer a) IndexWriter(File path, Analyzer a, boolean create) IndexWriter(String path, Analyzer a) IndexWriter(String path, Analyzer a, boolean create)
17. 现在大家多Lucene创建索引器的各个元素都了解了,现在我和大家共同理一下创建索引的思路。创建索引首先的有内容,先到获取内容,如果是文件就要读取文件,如果是数据库里的内容就要查找要建立索引的内容。有了内容就要对内容进行包装,刚说了Filed就相当一字段,咱们对表的操作也是把内容放到对应的字段里,Lucene也一样把要建立索引的内容要放到Field里。在上面代码里大家看到了这样的代码
Field field = new Field("filename", filename ,Field.Store.YES, Field.Index.TOKENIZED);
//创建索引的内容都必须以字符形式来存储的,大家看Field的构造方法就知道
Filename 就相当于在表里是字段名,这个大家可以随便起名, filename就是要建立索引的内容, Field.Store.YES这个位置 大家可以选择有Field.Store.YES、 Field.Store.NO、Field.Store.COMPRESS。分别代表的意思是建立索引并存储、建立索引不存储、建立索引压缩存储。 Field.Index.TOKENIZED这个位置也是有多个选择的,有Field.Index.NO、 Field.Index.TOKENIZED……;这个大家在开发是都有提示,我在这稍做解释,这些都是索引方式,就是索引和分词的组合。 Field.Index.NO是不索引, Field.Index.TOKENIZED是分词并建立索引。其他的大家参阅API。
18. 索引内容封装好了就要创建Document来存储Field。Document只有一个无参构造方法,Document document = new Document();这样就可以了。
Document的方法很多,大家可以看文档,在常见索引的时候会用到add()方法,一个Document 可以封装多个Field。准备工作都做好了,最后用IndexWriter把他存储到指定的地方
执行语句writer.addDocument(doc);最后别忘了写writer.close();
创建索引就说这么多了,以大家聪明的脑袋我估计能写出了更好的程序了!呵呵
19. 现在讲Searcher,Searcher的作用是从索引文件里根据我们的条件来查找。使用Lucene进行搜索,首先要创建IndexSearcher对象,然后要通过Term和Query对象来封装用户输入的搜索条件,最后,将结果封装在Hits对象中,返回给用户。下面对Searcher的这个类的用法进行讲解:
创建IndexSearcher对象的方法
IndexSearcher is = new IndexSearcher(“索引存放的路径”);这句代码的意思就是读取索引文件,IndexSearcher有好多方法,我们常用的方法是searcher
它的构造函数也很多,大家可以查看API。
使用IndexSearcher搜索需要对查询条件进行封装,Lucene提供了Term和Query对象来封装搜索条件。首先是Term对搜索条件封装成Term对象,然后再封装成Query对象,
20. Term t = new Term(“索引里的Field名字”,”搜索的关键字”);
Query q = new Query(t);
Searcher的搜索条件必须的封装成Query;现在可以执行搜索了,
is.search(q);执行这条代码来查询最后它要返回一个结果集,这个结果集Lucene也进行 了封装,封装成Hits。Hits和List差不多,也有自己的索引,Hits里存放的是Document,取出Doc然后再取出Field,最后把Field转化成String就OK了。
Field field = doc.getField(“filename”);//获得
filenameString filename = field.stringValue();
21. 好,到现在就可以做简单的搜索引擎了,下面我再讲解一下中文分词,现在主流的中文分词器有单字切分、二分法、词典法、语义法。
Lucene默认分词很多,支持中文的只有StandardAnalyzer,大家可以先看一下Lucene自带的分词器的效果。现在流行的分词方法都有开源的大家可以直接用。如果这还不满足的话,咱们自己可以写分析器。只需要继承Analyzer就可以了,具体代码在项目中可以看到。
在开发中大家用Lucene默认的分词法就够用了,下面我给大家介绍几个网上传说好的中文分词器。Lucene的各种包都分着的,大家用什么分词法就的下载相应的jar包。
二分法:CJKAnalyzer
public class UseCJK{
public static void main(String[] args) throws java.io.IOException{
String s = "我爱我伟大的老爸老妈,我爱我壮丽的中华!";
StringReader sr = new StringReader(s);
CJKTokenizer cjk = new CJKTokenizer(sr);
org.apache.lucene.analysis.Token t = null;
while( (t=cjk.next()) !=null ){
System.out.print(t.termText()+"|");
}
}
}
运行结果:我爱|爱我|我伟|伟大|大的|的老|老爸|爸老|老妈|我爱|爱我|我壮|壮丽|丽的|的中|中华|
22. Lucene自带的中文分词法ChineseAnalyzer,他是单字分词,大家可以看一下分词效果。
public class UseCN{
public static void main(String[] args) throws java.io.IOException{
String s = "我爱我伟大的老爸老妈,我爱我壮丽的中华!";
StringReader sr = new StringReader(s);
ChineseTokenizer cn = new ChineseTokenizer(sr);
org.apache.lucene.analysis.Token t = null;
while( (t=cn.next()) !=null ){
System.out.print(t.termText()+"|");
}
}
}
运行结果:我|爱|我|伟|大|的|老|爸|老|妈|我|爱|我|壮|丽|的|中|华|
JE分词器:
是基于词库,可以向词库中增加新词,现在用的挺多的。
看一下分词效果:
public class UseJE{
public static void main(String[] args) throws java.io.IOException{
String s = "我爱我伟大的老爸老妈,我爱我壮丽的中华!";
MMAnalyzer mm = new MMAnalyzer();
System.out.print(mm.segment(s,"|"));
}
}
运行结果:我|爱我|伟大|老爸|老妈|我|爱我|壮丽|中华|
JE分词是单双结合的,有点按语义分词的效果。
在网上还有好多免费的分词方法,大家可以试着使用一下,看看效果怎么。
24. public class Test2{
public static void main(String[] args) throws java.io.IOException{
RAMDirectory rd = new RAMDirectory();
//RAMDirectory是把索引建到内存中,下面就是创建索引,这没的说吧,大家都会哦
IndexWriter writer = new IndexWriter(rd,new StandardAnalyzer());
Document doc = null;
Field field = null;
String id = null;
String text = null;
String time = null;
//doc 0
doc = new Document();
id = "0";
field = new Field("id",id ,Field.Store.YES, Field.Index.UN_TOKENIZED);
doc.add(field);
text = "i love you, my mother land! ";
field = new Field("text", text ,Field.Store.YES, Field.Index.TOKENIZED);
doc.add(field);
time = "12:00 2007-05-28";
field = new Field("time", time ,Field.Store.YES, Field.Index.UN_TOKENIZED);
doc.add(field);
writer.addDocument(doc);
25. //doc 1
doc = new Document();
id = "1";
field = new Field("id",id ,Field.Store.YES, Field.Index.UN_TOKENIZED);
doc.add(field);
text = "i love you, xiaoyue,honey! ";
field = new Field("text", text ,Field.Store.YES, Field.Index.TOKENIZED);
doc.add(field);
time = "10:00 2007-05-29";
field = new Field("time", time ,Field.Store.YES, Field.Index.UN_TOKENIZED);
doc.add(field);
writer.addDocument(doc);
//doc 2
doc = new Document();
id = "2";
field = new Field("id",id ,Field.Store.YES, Field.Index.UN_TOKENIZED);
doc.add(field);
text = "people all love Genius! ";
field = new Field("text", text ,Field.Store.YES, Field.Index.TOKENIZED);
doc.add(field);
time = "22:00 2007-06-01";
field = new Field("time", time ,Field.Store.YES, Field.Index.UN_TOKENIZED);
doc.add(field);
writer.addDocument(doc);
//close
writer.close();
/**********************************************************/
26. //这里就是搜索了,排序就是在对搜索结果进行的操作
IndexSearcher searcher = new IndexSearcher(rd);
//Term & Query
String searchField = "text";
String searchPhrase = "love";
Term t = new Term(searchField, searchPhrase);
TermQuery q = new TermQuery(t);
//搜索条件封装好了就要执行搜索了,在搜索的过程中就要加上排序了,这是咱们现在学习的一个知识点
//Hits
//SortField sf = new SortField("time",SortField.LONG,false);
//Sort sort = new Sort(sf);
//可以和下面的进行对比,要排序的字段能按照大家想要的类型进行比较排序的,在这里要注意core包的版本,在1.4以前的类型转化是很少的,现在我用的是2.9.0所有基本去有了
//先用这个对排序条件不加任何处理的字段来做为排序字段,就像怎么写的SQL语句中的order by
Sort sort = new Sort("time",true);
//按照时间排序,这没有把时间字符串转化成long型进行比较,而是把它作为字符串来比较的,大家可以参阅Lucene-API,在建立索引时都以字符串来存储的
//如果把true换做false就是从小到大了
Hits hs = searcher.search(q,sort);
27. int num = hs.length();
StringBuffer sb = new StringBuffer("");
for(int i=0;i
28. 排序说完了,就对高亮做一个介绍吧,HighLight。
大家先看例子,然后对照例子进行讲解
public class Test2
{
public static void main(String[] args) throws java.io.IOException
{
//create index
RAMDirectory rd = new RAMDirectory();
IndexWriter writer = new IndexWriter(rd,new StandardAnalyzer());
Document doc = null;
Field field = null;
String id = null;
String text = null;
String time = null;
//doc 0
doc = new Document();
id = "0";
field = new Field("id",id ,Field.Store.YES, Field.Index.UN_TOKENIZED);
doc.add(field);
text = "i love you, my mother land!i love you, my mother land!i love you, my mother land!i love you, my mother land!i love you, my mother land!i love you, my mother land!i love you, my mother land!i love you, my mother land!i love you, my mother land! ";
field = new Field("text", text ,Field.Store.YES, Field.Index.TOKENIZED,Field.TermVector.WITH_POSITIONS_OFFSETS);
doc.add(field);
time = "2007-05-28";
field = new Field("time", time ,Field.Store.YES, Field.Index.UN_TOKENIZED);
doc.add(field);
writer.addDocument(doc);
29. //doc 1
doc = new Document();
id = "1";
field = new Field("id",id ,Field.Store.YES, Field.Index.UN_TOKENIZED);
doc.add(field);
text = "i love you, xiaoyue,honey!i love you, xiaoyue,honey!i love you, xiaoyue,honey! i love you, xiaoyue,honey! i love you, xiaoyue,honey! i love you, xiaoyue,honey! i love you, xiaoyue,honey! i love you, xiaoyue,honey! i love you, xiaoyue,honey! ";
field = new Field("text", text ,Field.Store.YES, Field.Index.TOKENIZED,Field.TermVector.WITH_POSITIONS_OFFSETS);
doc.add(field);
time = "2007-05-29";
field = new Field("time", time ,Field.Store.YES, Field.Index.UN_TOKENIZED);
doc.add(field);
writer.addDocument(doc);
//doc 2
doc = new Document();
id = "2";
field = new Field("id",id ,Field.Store.YES, Field.Index.UN_TOKENIZED);
doc.add(field);
text = "people all love Genius!people all love Genius!people all love Genius!people all love Genius!people all love Genius!people all love Genius!people all love Genius!people all love Genius!people all love Genius! ";
field = new Field("text", text ,Field.Store.YES, Field.Index.TOKENIZED,Field.TermVector.WITH_POSITIONS_OFFSETS);
doc.add(field);
time = "2007-06-01";
field = new Field("time", time ,Field.Store.YES, Field.Index.UN_TOKENIZED);
doc.add(field);
writer.addDocument(doc);
//close
writer.close();
/**********************************************************/
30. IndexSearcher searcher = new IndexSearcher(rd);
//Term & Query
String searchField = "text";
String searchPhrase = "love";
Term t = new Term(searchField, searchPhrase);
TermQuery q = new TermQuery(t);
Hits hs = searcher.search(q,Sort.RELEVANCE);
SimpleHTMLFormatter shf = new SimpleHTMLFormatter("","");
Highlighter highlighter =new Highlighter(shf,new QueryScorer(q));
SimpleFragmenter sf = new SimpleFragmenter(60);
highlighter.setTextFragmenter( sf );
for (int i = 0; i < hs.length(); i++){
text = hs.doc(i).get("text");
int maxNumFragmentsRequired = 3;
String fragmentSeparator = "!!!";
TermPositionVector tpv = (TermPositionVector)searcher.getIndexReader().getTermFreqVector(hs.id(i),"text");
TokenStream tokenStream=TokenSources.getTokenStream(tpv);
String result = highlighter.getBestFragments( tokenStream, text, maxNumFragmentsRequired, fragmentSeparator);
System.out.println(result);
System.out.println("----------------------------------");
}
searcher.close();
}
}
31. 通过上面的例子,大家注意到在增加高亮这一功能时创建索引是否发生变化呢,实现高亮的原理是(1)建立索引时,在文档的相关字段(需要高亮处理的字段,被搜索的字段)中记录词条(Term)的位置。
(2)搜索时,利用字段中记录的词条的位置信息。将修饰符号添加进去,从而改变了搜索关键字的显示格式,达到了突出显示的目的。
在创建索引是怎么记录词条的位置呢,这就的靠Field的构造方法的第五个参数了。看上面例子有这样的Field构造方法
field = new Field("text", text ,Field.Store.YES, Field.Index.TOKENIZED,Field.TermVector.WITH_POSITIONS_OFFSETS);
最后一个参数就是来记录词条的位置的。要实现高亮先的构造Highlighter对象,我先介绍一种构造方法,Highlighter hl = new Highlighter (Scorer fragmentScorer);
其参数类型是Scorer,Scorer是一个接口。它的一个常用子类就是QueryScorer,这个类可以由QueryScorer (Query query)方法来构造。
32. 下面看具体的构造Highlighter的步骤。
Term t = new Term(“id”,”1”);//这是封装查询条件
Query q = new TermQuery(t);
QueryScorer qs = new QueryScorer(q);
Highlighter hl = new Highlighter(qs);
如果搜索出来一个很大的文章,我们不想把整个文章的内容都返回去,那么我没就可一通过设定文本块的大小来实现返回部分文字,但命中的内容都能够返回。就需要下面的代码来实现了
SimpleFragmenter sf = new SimpleFragmenter(20);//设置文本块的大小 hl.setTextFragmenter( sf );
现在已经对Highlighter对象的创建完成,但还没有对搜索结果进行高亮处理呢,参看代码再做讲解。
33. 现在大家就可以简单的用Lucene来走搜索了,下面我再给大家介绍一下Lucene索引的管理,这里的管理也就是对索引的增加、删除和修改。
在原有索引上再继续添加索引,这就要在索引器上添加第三个参数了 ;
IndexWriter iw = new IndexWriter(indexPath,new StandardAnalyzer(),false);
其他的和新建索引是一样的,第三个参数设置为false就是在原有的索引上继续添加新的索引。
对索引的删除,需要先把索引的索引都读取出来,然后再做删除,IndexReader就能够完成读取索引,大家可以参阅API,我在这就不详细介绍了,我只说一下IndexReader常用的方法,open()就是常用的一个,用法如下:IndexReader ir = IndexReader.open(indexPath);IndexPath就是存放索引文件的路径。IndexReader的其他方法在代码中做介绍.索引文件的修改,是要先删除已经存在的索引文件,然后再把新的索引文件添加上去。删除和添加都会了那更新也就会了吧。在简单的说一下索引的优化,建立索引的目的是为了搜索,搜索实际上是I/O操作,但索引数量增加,文件增大的时候,I/O操作就会变慢的,搜索速度也就会变慢的,索引对索引的优化是很有必要的。
(1)使用复合式索引文件,这样索引文件就不会太零散了。使用复合式索引文件可以通过设置IndexWriter的useCompoundFile参数,方法如下:
writer.setUseCompoundFile(true);参数设置为true就是使用复合式索引文件,如果设置为false就不使用复合式索引文件,这个方法是索引优化的重要方法。