解读Lucene.Net —— 一、 Directory 之一

在使用Lucene.Net中,第一个接触的类一般是Directory。它是Lucene存储的一个抽象,由此派生了两个类:FSDirectoryRAMDirectory,用于控制索引文件的存储位置。使用FSDirectory类,就是存储到硬盘;使用RAMDirectory类,则是存储到内存。

 

 

 

图1-1 存储抽象实现UML
 
如图1-1,显示了这种关系。而看Lucene代码会发现,RAMDirectoryFSDirectory还分别有一个内嵌类。这个内嵌类实际上是通过工具从Java版本转移过来,工具自动产生的。那就先看看Java代码的结构,然后再来看转移过来生成的内嵌类是干什么用的。
 
Directory类一共有11个方法,看看类的注释就知道,翻译过来也是没有做非常细致的调整。比如,注释上有这句话“Java's i/o APIs not used directly, but rather all i/o is through this API. ”。还保留着Java的解释了,虽然没有人会认为在dotNet平台会采用Java的API,但是,这句话确实够昏的。Directory的注释原文:

A Directory is a flat list of files.  Files may be written once, when they are created.  Once a file is created it may only be opened for read, or
deleted.  Random access is permitted both when reading and writing.
Java's i/o APIs not used directly, but rather all i/o is through this API.  This permits things such as:

implementation of RAM-based indices;
implementation indices stored in a database, via JDBC;
implementation of an index as a single file;

 

意思就是:一个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类。RAMDirectoryDirectory的内存操作实现。RAMDirectory类有5个重载构造函数。

RAMDirectory() 构造函数无操作;

RAMDirectory(Directory dir)   允许把硬盘上的索引载入内存,这个操作只适用于可以被载入内存的索引。(注:文件结构不对或者索引大小超出内存肯定就不行了。)这个构造函数只调用了RAMDirectory(Directory dir, boolean closeDir)构造函数,并未做其他动作。

再来看看RAMDirectory(Directory dir, boolean closeDir)构造函数。除了默认构造函数,其他3个构造函数都是调用的这个构造函数做处理的。

 

 代码 1-2:

            string[] files = dir.List();
            
for (int i = 0; i < files.Length; i++)
            {
                
// make place on ram disk
                OutputStream os = CreateFile(System.IO.Path.GetFileName(files[i]));
                
// read current file
                InputStream is_Renamed = dir.OpenFile(files[i]);
                
// and copy to ram disk
                int len = (int) is_Renamed.Length();
                
byte[] buf = new byte[len];
                is_Renamed.ReadBytes(buf, 
0, len);
                os.WriteBytes(buf, len);
                
// graceful cleanup
                is_Renamed.Close();
                os.Close();
            }
            
if (closeDir)
                dir.Close();

 

其他两个构造函数用到了FSDirectory类把文件构造成Directory对象。看看代码就行了:

 代码 1-3:


        
public RAMDirectory(System.IO.FileInfo dir) : this(FSDirectory.GetDirectory(dir, false), true)
        {
        }
        
public RAMDirectory(System.String dir) : this(FSDirectory.GetDirectory(dir, false), true)
        {
        }
 
这两个构造函数第二个参数都是true,和
 
代码 1-4:
        public RAMDirectory(Directory dir) : this(dir, false)
        {
        }
这个不一样,那是因为,这个参数是控制是否关闭传入的对象构建或者直接创建的Directory对象。对于传入的,也就是代码1-4,因为这个传入的对象是个引用类型,这个如果被关闭,将影响到传递来的对象状态。而代码1-3是有它自身创建的Directory,关闭它并不会影响到RAMDirectory的外部。代码1-2的功能就是实现把硬盘上的索引按字节转存到内存中。
 
首先,会创建一个文件,调用的是RAMDirectory自身的CreateFile方法:
 
代码1-5
        public override OutputStream CreateFile(System.String name)
        {
            RAMFile file 
= new RAMFile();
            files[name] 
= file;
            
return new RAMOutputStream(file);
        }
 
这个方法调用了两个还没讲的类。实现的功能就是创建一个内存的文件映像。
 
RAMDirectory定义了一个Hashtable,这个哈希表会在CreateFile被调用时,往里面填充创建的文件。所以,从硬盘往内存拷贝文件的过程中,这个哈希表就记录下了内存中所有被创建的文件。在List方法,就可以通过枚举的方式来获取内存中文件的数量。
 
代码 1-6
        public override System.String[] List()
        {
            System.String[] result 
= new System.String[files.Count];
            
int i = 0;
            System.Collections.IEnumerator names 
= files.Keys.GetEnumerator();
            
while (names.MoveNext())
            {
                result[i
++= ((System.String) names.Current);
            }
            
return result;
        }
 
FileExistsFileModified 都比较简单,对照下CreateFile的代码很容易读懂:
代码 1-7
        /// <summary>Returns true iff the named file exists in this directory. </summary>
        public override bool FileExists(System.String name)
        {
            RAMFile file 
= (RAMFile) files[name];
            
return file != null;
        }
        
        
/// <summary>Returns the time the named file was last modified. </summary>
        public override long FileModified(System.String name)
        {
            RAMFile file 
= (RAMFile) files[name];
            
return file.lastModified;
        }
 
TouchFile的代码比较长,需要看一下
 
代码1-8
 
        /// <summary>Set the modified time of an existing file to now. </summary>
        public override void  TouchFile(System.String name)
        {
            
//     final boolean MONITOR = false;
            
            RAMFile file 
= (RAMFile) files[name];
            
long ts2, ts1 = (System.DateTime.Now.Ticks - 621355968000000000/ 10000;
            
do 
            {
                
try
                {
                    System.Threading.Thread.Sleep(
new System.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("SLEEP COUNT: " + count);
        }

通过文件名,可以从哈希表中还原出一个RAMFile对象,但是下面的代码比较难懂,为什么要减去个那数值呢?看看Java版的代码:

 

代码1-9

 

    /** Set the modified time of an existing file to now. */
    
public void touchFile(String name) throws IOException {
        
// final boolean MONITOR = false;

        RAMFile file 
= (RAMFile) files.get(name);
        
long ts2, ts1 = System.currentTimeMillis();
        
do {
            
try {
                Thread.sleep(
01);
            } 
catch (InterruptedException e) {
            }
            ts2 
= System.currentTimeMillis();
            
// if (MONITOR) {
            
// count++;
            
// }
        } while (ts1 == ts2);

        file.lastModified 
= ts2;

        
// if (MONITOR)
        
// System.out.println("SLEEP COUNT: " + count);
    }

 

看出来了,这段只是想计算时间的。为什么要这么计算时间呢?

FSDirectory类看到,他的方法就要简单很多

 

代码1-10

        /// <summary>Set the modified time of an existing file to now. </summary>
        public override void  TouchFile(System.String name)
        {
            System.IO.FileInfo file 
= new System.IO.FileInfo(System.IO.Path.Combine(directory.FullName, name));
            file.LastWriteTime 
= System.DateTime.Now;
        }

 

为什么在RAMDirectory需要这么来计算,那是为了取得一种随机数的效果,尽量不会产生重复的。因为内存操作文件,而且载入内存的一般文件比较小,对文件时间的更新如果是用DateTime.Now的话就会产生误差。通过这种算法,用Thread.Sleep的方式,然后通过循环对比,让两个时间产生差距,否则很可能会一样。要是一样,对文件版本的控制就不是很好。而这个睡眠0.01ms,速度很快,不会影响性能。实际上采用下面的方式也是一样的:

 

代码1-11

            long ts2, ts1 = System.DateTime.Now.Ticks;
            
do
            {
                
try
                {

                    System.Threading.Thread.Sleep(
new System.TimeSpan((System.Int64)10000 * 0 + 100 * 1));
                }
                
catch (System.Threading.ThreadInterruptedException)
                {
                }
                ts2 
= System.DateTime.Now.Ticks;
                
//       if (MONITOR) {
                
//         count++;
                
//       }
            }
            
while (ts1 == ts2);

 

 

new System.TimeSpan((System.Int64)10000 * 0 + 100 * 1)取0.01毫秒,那是因为Thread.Sleep的另外一个重载函数,只能放入int值,即使放1,也会是1毫秒,扩大了100倍的时间。经过我的测试,这个循环需要执行几千次,这里暂且不评论这个用法的好坏。

 

其他方法都比较简单,很容易看懂,还要注意的就是一个MakeLock方法。转换工具导致了一个内嵌类,要分析这个方法的操作,还要看Java的源码。

 

posted @ 2008-08-07 02:35  Birdshover  阅读(3478)  评论(4编辑  收藏  举报