解读Lucene.Net——一、Directory之一在使用Lucene.Net中,第一个接触的类一般是Directory。它是Lucene存储的一个抽象,由此派生了两个类:FSDirectory和RAMDirectory,用于控制索引文件的存储位置。使用FSDirectory类,就是存储到硬盘;使用RAMDirectory类,则是存储到内存。图1-1存储抽象实现UML图如图1-1,显示了这种关系。而看Lucene代码会发现,RAMDirectory和FSDirectory还分别有一个内嵌类。这个内嵌类实际上是通过工具从Java版本转移过来,工具自动产生的。那就先看看Java代码的结构,然后再来看转移过来生成的内嵌类是干什么用的。Directory类一共有11个方法,看看类的注释就知道,翻译过来也是没有做非常细致的调整。比如,注释上有这句话“Java'si/oAPIsnotuseddirectly,butratheralli/oisthroughthisAPI.”。还保留着Java的解释了,虽然没有人会认为在dotNet平台会采用Java的API,但是,这句话确实够昏的。Directory的注释原文:ADirectoryisaflatlistoffiles.Filesmaybewrittenonce,whentheyarecreated.Onceafileiscreateditmayonlybeopenedforread,ordeleted.Randomaccessispermittedbothwhenreadingandwriting.Java'si/oAPIsnotuseddirectly,butratheralli/oisthroughthisAPI.Thispermitsthingssuchas:implementationofRAM-basedindices;implementationindicesstoredinadatabase,viaJDBC;implementationofanindexasasinglefile;意思就是:一个Directory对象是一份文件的清单。文件可能只在被创建的时候写一次。一旦文件被创建,它将只被读取或者删除。在读取的时候进行写入操作是允许的。Java的I/O库没有被直接使用,所以的I/O操作都通过这个API。这些存储可以允许:实现基于内存的索引;实现索引存入数据库,通过JDBC;实现一个索引是一个文件。而Directory的11个方法分别是:1、list把一个Directory对象下的文件,按字符串数组的方式返回;2、fileExists给定一个文件名,如果存在,就返回true;3、fileModified返回给定文件名被修改的时间;4、touchFile设置给定文件名文件的更新时间为现在;5、deleteFile删除当前directory对象下一个给定文件名的文件,该文件必须存在;6、renameFile重命名当前directory一个文件的文件名,如果新的名字在directory里已经存在,将会更换。这个要更换原子;7、fileLength返回文件的长度;8、createFile创建一个空文件,并且返回该文件的写入流;9、openFile返回一个存在文件的读取流;10、makeLock锁定该directory对象;11、close关闭该对象。而在Directory类中,使用的都是抽象方法,把这个类换成接口也可以。然后再来看看RAMDirectory类。RAMDirectory是Directory的内存操作实现。RAMDirectory类有5个重载构造函数。RAMDirectory()构造函数无操作;RAMDirectory(Directorydir)允许把硬盘上的索引载入内存,这个操作只适用于可以被载入内存的索引。(注:文件结构不对或者索引大小超出内存肯定就不行了。)这个构造函数只调用了RAMDirectory(Directorydir,booleancloseDir)构造函数,并未做其他动作。再来看看RAMDirectory(Directorydir,booleancloseDir)构造函数。除了默认构造函数,其他3个构造函数都是调用的这个构造函数做处理的。代码1-2:string[]files=dir.List();for(inti=0;ifiles.Length;i++){//makeplaceonramdiskOutputStreamos=CreateFile(System.IO.Path.GetFileName(files[i]));//readcurrentfileInputStreamis_Renamed=dir.OpenFile(files[i]);//andcopytoramdiskintlen=(int)is_Renamed.Length();byte[]buf=newbyte[len];is_Renamed.ReadBytes(buf,0,len);os.WriteBytes(buf,len);//gracefulcleanupis_Renamed.Close();os.Close();}if(closeDir)dir.Close();其他两个构造函数用到了FSDirectory类把文件构造成Directory对象。看看代码就行了:代码1-3:publicRAMDirectory(System.IO.FileInfodir):this(FSDirectory.GetDirectory(dir,false),true){}publicRAMDirectory(System.Stringdir):this(FSDirectory.GetDirectory(dir,false),true){}这两个构造函数第二个参数都是true,和代码1-4:publicRAMDirectory(Directorydir):this(dir,false){}这个不一样,那是因为,这个参数是控制是否关闭传入的对象构建或者直接创建的Directory对象。对于传入的,也就是代码1-4,因为这个传入的对象是个引用类型,这个如果被关闭,将影响到传递来的对象状态。而代码1-3是有它自身创建的Directory,关闭它并不会影响到RAMDirectory的外部。代码1-2的功能就是实现把硬盘上的索引按字节转存到内存中。首先,会创建一个文件,调用的是RAMDirectory自身的CreateFile方法:代码1-5publicoverrideOutputStreamCreateFile(System.Stringname){RAMFilefile=newRAMFile();files[name]=file;returnnewRAMOutputStream(file);}这个方法调用了两个还没讲的类。实现的功能就是创建一个内存的文件映像。在RAMDirectory定义了一个Hashtable,这个哈希表会在CreateFile被调用时,往里面填充创建的文件。所以,从硬盘往内存拷贝文件的过程中,这个哈希表就记录下了内存中所有被创建的文件。在List方法,就可以通过枚举的方式来获取内存中文件的数量。代码1-6publicoverrideSystem.String[]List(){System.String[]result=newSystem.String[files.Count];inti=0;System.Collections.IEnumeratornames=files.Keys.GetEnumerator();while(names.MoveNext()){result[i++]=((System.String)names.Current);}returnresult;}FileExists和FileModified都比较简单,对照下CreateFile的代码很容易读懂:代码1-7///summaryReturnstrueiffthenamedfileexistsinthisdirectory./summarypublicoverrideboolFileExists(System.Stringname){RAMFilefile=(RAMFile)files[name];returnfile!=null;}///summaryReturnsthetimethenamedfilewaslastmodified./summarypublicoverridelongFileModified(System.Stringname){RAMFilefile=(RAMFile)files[name];returnfile.lastModified;}TouchFile的代码比较长,需要看一下代码1-8///summarySetthemodifiedtimeofanexistingfiletonow./summarypublicoverridevoidTouchFile(System.Stringname){//finalbooleanMONITOR=false;RAMFilefile=(RAMFile)files[name];longts2,ts1=(System.DateTime.Now.Ticks-621355968000000000)/10000;do{try{System.Threading.Thread.Sleep(newSystem.TimeSpan((System.Int64)10000*0+100*1));}catch(System.Threading.ThreadInterruptedException){}ts2=(System.DateTime.Now.Ticks-621355968000000000)/10000;//if(MONITOR){//count++;//}}while(ts1==ts2);file.lastModified=ts2;//if(MONITOR)//System.out.println(SLEEPCOUNT:+count);}通过文件名,可以从哈希表中还原出一个RAMFile对象,但是下面的代码比较难懂,为什么要减去个那数值呢?看看Java版的代码:代码1-9/**Setthemodifiedtimeofanexistingfiletonow.*/publicvoidtouchFile(Stringname)throwsIOException{//finalbooleanMONITOR=false;RAMFilefile=(RAMFile)files.get(name);longts2,ts1=System.currentTimeMillis();do{try{Thread.sleep(0,1);}catch(InterruptedExceptione){}ts2=System.currentTimeMillis();//if(MONITOR){//count++;//}}while(ts1==ts2);file.lastModified=ts2;//if(MONITOR)//System.out.println(SLEEPCOUNT:+count);}看出来了,这段只是想计算时间的。为什么要这么计算时间呢?去FSDirectory类看到,他的方法就要简单很多代码1-10///summarySetthemodifiedtimeofanexistingfiletonow./summarypublicoverridevoidTouchFile(System.Stringname){System.IO.FileInfofile=newSystem.IO.FileInfo(System.IO.Path.Combine(directory.FullName,name));file.LastWriteTime=Sys