解读Lucene.Net —— 一、 Directory 之二
MakeLock方法在Java代码里是这样的:
代码 1-12
public final Lock makeLock(final String name) {
return new Lock() {
public boolean obtain() throws IOException {
synchronized (files) {
if (!fileExists(name)) {
createFile(name).close();
return true;
}
return false;
}
}
public void release() {
deleteFile(name);
}
public boolean isLocked() {
return fileExists(name);
}
};
}
实际上是返回了一个Lock类的实例。而这种写法很像.Net 3.X里的类初始化。不同的是,这里提供了3个方法,而.Net里只能提供属性或者字段。在Lock类中,这里提供的三个方法的申明是抽象方法:
public abstract boolean obtain() throws IOException;
public abstract void release();
public abstract boolean isLocked();
这种写法在.Net里不受支持,这里要么把这三个方法换成委托,要么做个内嵌类比较适合。当然,写Lock的子类,然后聚合进来也可以。这里先略过,等到看到Lock类的时候再来详细分析。
以上就将RAMDirectory类基本解读完了。
接着分析Directory的另一个子类——FSDirectory。
依照对RAMDirectory的理解,分析FSDirectory自然要容易一些。还是先来看构造函数。它只有一个私有的构造函数,这意味着,只能用静态方法创建该对象的实例。
代码 1-13
private FSDirectory(System.IO.FileInfo path, bool create)
{
directory = path;
if (LOCK_DIR == null)
{
lockDir = directory;
}
else
{
lockDir = new System.IO.FileInfo(LOCK_DIR);
}
if (create)
{
Create();
}
if (!System.IO.Directory.Exists(directory.FullName))
throw new System.IO.IOException(path + " not a directory");
}
看看,怎么才能建立这个对象。它有两个重载的静态方法,可以返回实例。
public static FSDirectory GetDirectory(System.String path, bool create)
public static FSDirectory GetDirectory(System.IO.FileInfo file, bool create)
而实际操作的则是第二个。
代码 1-14
public static FSDirectory GetDirectory(System.IO.FileInfo file, bool create)
{
file = new System.IO.FileInfo(file.FullName);
FSDirectory dir;
lock (DIRECTORIES.SyncRoot)
{
dir = (FSDirectory) DIRECTORIES[file];
if (dir == null)
{
dir = new FSDirectory(file, create);
DIRECTORIES[file] = dir;
}
else if (create)
{
dir.Create();
}
}
lock (dir)
{
dir.refCount++;
}
return dir;
}
这个静态方法,会先在它的哈希表里查找,如果没有,才会创建新的FSDirectory实例(注意,在最新的版本中,2.3.1版本,加了一个无参数的构造方法,但是不执行任何动作)。创建完实例,就会把该实例扔到哈希表里,而哈希表的键就是文件储存的路径。最后,会让私有变量refCount执行加一操作。细细体会一下,和内存存储做个对比,就会发现,内存存储的哈希表存储的是文件名。因为建立了虚拟文件,如果不把名字保持下来,无法找到这个名字。而这里的哈希表保存的是一个FSDirectory实例。实际上FSDirectory代表了一个目录下的索引文件。Lucene的索引分目录,就是这么被缓存在系统中的。而实例中的refCount私有变量,就像是一个计数器,表明这个目录被读写一共多少次。而这个静态方法的第二个参数是设置这个目录下的索引是否要重建。在看Create方法之前,先分析完FSDirectory的构造函数。
构造函数需要传入一个路径,还有一个是否创建。这是一个控制上的逻辑。如果在静态方法中查找DIRECTORIES里,没有找到,则通过构造函数来决定是否创建,而如果存在了,则在静态方法里直接决定。这里有个对LOCK_DIR的判断。LOCK_DIR实际上是去读取配置文件的节点,然后返回一个字符串。事实上解读这段代码,发现LOCK_DIR不可能为null值,操作系统的临时目录不可能是一个null值吧?那这地方Lucene.Net的做法有点昏。
代码 1-15
public static System.String Get(System.String key, System.String defValue)
{
System.String theValue = System.Configuration.ConfigurationSettings.AppSettings.Get(key);
if (theValue == null)
{
return defValue;
}
return theValue;
}
以上代码,如果读取到节点是null值(就是没有设置配置文件,如果用System.Configuration.ConfigurationSettings.AppSettings[key]的方式,如果没有设置,会出错,这里使用了Get方法保证不会出现错误),就返回defValue。
而public static readonly System.String LOCK_DIR = SupportClass.AppSettings.Get("Lucene.Net.lockdir", System.IO.Path.GetTempPath());这个调用,赋予了defValue是操作系统的临时目录,这个目录不会是null吧?这个构造函数确实是昏掉了。所以总是lockDir = new System.IO.FileInfo(LOCK_DIR);这句话被执行。(注:最新版的代码已经做过调整)。
接下来就说到Create方法了。Create方法是先删除目录,然后再建立目录的一个逻辑,看上去很复杂,流程上还是很简单的。只有
System.String lockPrefix = GetLockPrefix().ToString(); // clear old locks
这行,是删除该目录下的lock文件的。其他都是用dotnet框架API完成的。看看GetLockPrefix方法干了点什么。这个方法先是拿到了对象的目录,然后赋给dirName变量。这时候感觉奇怪了,DIGESTER好像没赋值啊,怎么锁定个null值呢?仔细一看,原来,FSDirectory还有个静态构造函数(静态构造函数只有当对象第一次实例化的时候被执行)。
代码 1-16
static FSDirectory()
{
DISABLE_LOCKS = System.Configuration.ConfigurationSettings.AppSettings.Get("disableLuceneLocks") != null;
{
try
{
DIGESTER = System.Security.Cryptography.MD5.Create();
}
catch (System.Exception e)
{
throw new System.SystemException(e.ToString());
}
}
}
DISABLE_LOCKS 的赋值不用管它,现在还没用到。在这个静态构造函数里DIGESTER 被实例化了。锁定了DIGESTER,GetLockPrefix方法,开始计算目录名转换为byte数组后的哈希值。相当于想取一个不重复的散列,这个就是一个算法而已,不要当作天书。要问为什么这么做,目的就是为了得到散列,让后面从HEX_DIGITS数组中取出来的值不会重复。
int b = digest[i];这句话,把byte变成int,没啥用?确实是!然后接着执行HEX_DIGITS[(b >> 4) & 0xf],b的值是从byte转过去的,怎么也不会大于255,如果是255,也就是0xFF,二进制就是 1111 1111,右移四位,就变成1111,假如不是255呢?b >> 4 实际上就是得到一个值,在 0 -15 之间,包括0和15.和&做运算,这个是Lucene翻译过来的问题,因为java的byte是 -127 - 128,和C#的sbyte是一个类型,而dotnet的byte则是 0 -255,所以这个地方,无论byte怎么运算,总是不肯为负值。这个与操作实际没意义。(我在这篇文章中讲到过数据溢出的问题 谈谈C#的数值类型——顺便给for循环做个补充)。Lucene.Net的又一个不对的地方,嘿嘿,看到了吧?执行了无意义的运算,也不能说不对啦。而下面那句话,buf.Append(HEX_DIGITS[b & 0xf]); 这个是有意义的,相当于什么呢,因为b的二进制是8位,要忽略它是int,实际上它还是byte的值,HEX_DIGITS[(b >> 4) & 0xf]实际上是取高位,也就是8个二进制符的前四个,HEX_DIGITS[b & 0xf]而这句则是取后四个,然后这个值总是不会超过15.所以,这种从15个元素的数组取值总能取到。
其他的方法,参考关于RAMDirectory的分析,没什么难度,就不多讲了。