SegmentInfosi=info(i);
output.writeString(si.name);
output.writeInt(si.docCount);
}
}finally{
output.close();
}
//installnewsegmentinfo
directory.renameFile("segments.new",IndexFileNames.SEGMENTS);
}
先看一下第一个函数,它建立了一个segments.new的文件,你如果在调试,就可以看到这个文件产生了,它返回一个IndexOutput对象,用它来写文件。
我们就不去理睬这些有什么用了,第一个FORMAT是-1,第二个version是用System.currentTimeMillis()产生的,目的是产生唯一的一个版本号。
下面counter是0。
SegmentInfos继承自Vector,下面的size()就是它有多少个元素,但是我们没有对任何文档建索引,所以它是空的。
最后一句话是把segments.new文件名重命名为segment。
你可以用UltraEdit或是WinHex打开segments看一下里面的内容。
我这里把它列出来:
FFFFFFFF000001221502072A0000000000000000
writeInt是写入四个字节,writeLong是八个字节,现在可以看到所写入的四个内容分别是什么了。
2.Lucene源代码分析[2]
上次提到了Analyzer类,说它是用于对输入进行过滤,分词等,现在我们详细看一个这个类,Lucene中一个Analyzer通常由Tokenizer和TokenFilter组成,我们先看一下Tokenizer:
publicabstractclassTokenizerextendsTokenStream{
/**ThetextsourceforthisTokenizer.*/
protectedReaderinput;
/**Constructatokenizerwithnullinput.*/
protectedTokenizer(){
}
/**Constructatokenstreamprocessingthegiveninput.*/
protectedTokenizer(Readerinput){
this.input=input;
}
/**Bydefault,closestheinputReader.*/
publicvoidclose()throwsIOException{
input.close();
}
}
只是一个抽象类,而且也没什么值得我们关注的函数,我们看一下他的父类TokenStream:
publicabstractclassTokenStream{
/**Returnsthenexttokeninthestream,ornullatEOS.*/
publicabstractTokennext()throwsIOException;
/**Releasesresourcesassociatedwiththisstream.*/
publicvoidclose()throwsIOException{
}
}
原来值得我们关注的函数在它的父类中,next函数,它会返回流中的下一个token。
其实刚才提到的另一个类TokenFilter也继承自TokenStream:
publicabstractclassTokenFilterextendsTokenStream{
/**Thesourceoftokensforthisfilter.*/
protectedTokenStreaminput;
/**CallTokenFilter(TokenStream)instead.
*@deprecated*/
protectedTokenFilter(){
}
/**Constructatokenstreamfilteringthegiveninput.*/
protectedTokenFilter(TokenStreaminput){
this.input=input;
}
/**ClosetheinputTokenStream.*/
publicvoidclose()throwsIOException{
input.close();
}
}
先写一个依然没有意义的测试类:
packageforfun;
importjava.io.BufferedReader;
importjava.io.File;
importjava.io.FileReader;
importorg.apache.lucene.analysis.LetterTokenizer;
publicclassTokenTest
{
publicstaticvoidmain(String[]args)throwsException
{
Filef=newFile("E:
\\source.txt");
BufferedReaderreader=newBufferedReader(newFileReader(f));
LetterTokenizerlt=newLetterTokenizer(reader);
System.out.println(lt.next());
}
}
Source.txt中我写的helloworld!
。
当然你也可以写别的,我用LetterTokenizer进行分词,最后打印分词后的第一个token。
我们先看一下他是如何分词的,也就是next到底在做什么。
publicclassLetterTokenizerextendsCharTokenizer{
/**ConstructanewLetterTokenizer.*/
publicLetterTokenizer(Readerin){
super(in);
}
/**Collectsonlycharacterswhichsatisfy
*{@linkCharacter#isLetter(char)}.*/
protectedbooleanisTokenChar(charc){
returnCharacter.isLetter(c);
}
}
函数isTokenChar来判断c是不是一个字母,它并没有实现next函数,我们到它的父类看一下,找到了next函数:
/**Returnsthenexttokeninthestream,ornullatEOS.*/
publicfinalTokennext()throwsIOException{
intlength=0;
intstart=offset;
while(true){
finalcharc;
offset++;
if(bufferIndex>=dataLen){
dataLen=input.read(ioBuffer);
bufferIndex=0;
}
;
if(dataLen==-1){
if(length>0)
break;
else
returnnull;
}else
c=ioBuffer[bufferIndex++];
if(isTokenChar(c)){//ifit'satokenchar
if(length==0)//startoftoken
start=offset-1;
buffer[length++]=normalize(c);//bufferit,normalized
if(length==MAX_WORD_LEN)//bufferoverflow!
break;
}elseif(length>0)//atnon-Letterw/chars
break;//return'em
}
returnnewToken(newString(buffer,0,length),start,
start+length);
}
看起来很长,其实很简单,至少读起来很简单,其中isTokenChar就是我们刚才在LetterTokenizer中看到的,代码中用start记录一个token的起始位置,用length记录它的长度,如果不是字符的话,就break;,我们看到一个新的类Token,这里它的构造参数有字符串,起始位置,结束位置。
看一下Token的源代码:
StringtermText;//thetextoftheterm
intstartOffset;//startinsourcetext
intendOffset;//endinsourcetext
Stringtype="word";//lexicaltype
privateintpositionIncrement=1;
/**ConstructsaTokenwiththegiventermtext,andstart&endoffsets.
Thetypedefaultsto"word."*/
publicToken(Stringtext,intstart,intend){
termText=text;
startOffset=start;
endOffset=end;
}
/**ConstructsaTokenwiththegiventext,startandendoffsets,&type.*/
publicToken(Stringtext,intstart,intend,Stringtyp){
termText=text;
startOffset=start;
endOffset=end;
type=typ;
}
和我们刚才用到的构造函数对应一下,就知道三个成员变量的意思了,type和positionIncrement我还是引用一下别的人话,Type主要用来表示文本编码和语言类型,single表示单个ASCII字符,double表示non-ASCII字符,Word是默认的不区分的字符类型。
而positionIncrement表示位置增量,用于处理拼音之类的情况(拼音就在那个词的上方)。
3.Lucene源代码分析[3]
关于TokenFilter我们先看一个最简单的LowerCaseFilter,它的next函数如下:
publicfinalTokennext()throwsIOException{
Tokent=input.next();
if(t==null)
returnnull;
t.termText=t.termText.toLowerCase();
returnt;
}
没什么意思,就是把Token对象中的字符串换成了小写,你想看有意思的可以看PortStemFilter,剑桥大学出的那本Introductiontoinformationretrieval中也提到过这种方法,34页。
再看一个稍有一点意义的TokenFilter,StopFilter,我们看一下
publicstaticfinalSetmakeStopSet(String[]stopWords){
returnmakeStopSet(stopWords,false);
}
publicstaticfinalSetmakeStopSet(String[]stopWords,booleanignoreCase){
HashSetstopTable=newHashSet(stopWords.length);
for(inti=0;istopTable.add(ignoreCase?
stopWords[i].toLowerCase()
:
stopWords[i]);
returnstopTable;
}
publicfinalTokennext()throwsIOException{
//returnthefirstnon-stopwordfound
for(Tokentoken=input.next();token!
=null;token=input.next())
{
StringtermText=ignoreCase?
token.termText.toLowerCase()
:
token.termText;
if(!
stopWords.contains(termText))
returntoken;
}
//reachedEOS--returnnull
returnnull;
}
makeStopSet是把所有要过滤的词加到stopTable中去(不清楚为什么不用HashSet呢),在next函数中,它过滤掉stopTable有的字符串。
再来看一个简单的Analyzer,StopAnalyzer的next函数:
publicTokenStreamtokenStream(StringfieldName,Readerreader){
returnnewStopFilter(newLowerCaseTokenizer(reader),stopWords);
}
记得这句话吗?
Lucene中一个Analyzer通常由Tokenizer和TokenFilter组成,这里就是这句话的证据,我们先对reader传进来的字符串进行分词,再对它进行过滤。
而其中的tokenStream当然就是我们在分词时要调用的那个函数了。
4.Lucene源代码分析[4]
写一个略有一点意义的例子,我们把”HelloWorld”加入索引:
packageforfun;
importorg.apache.lucene.