解读Lucene.Net ——二、 InputStream 之二
InputStream类有两个子类,RAMInputStream和FSInputStream。
RAMInputStream这个类比较简单,由于父类已经实现了ICloneable接口,这里只是一个形式上的实现。RAMInputStream内聚了一个RAMFile对象。它最复杂的部分,就是重写抽象方法ReadInternal。
代码 2-12
public override void ReadInternal(byte[] dest, int destOffset, int len)
{
int remainder = len;
int start = pointer;
while (remainder != 0)
{
int bufferNumber = start / BUFFER_SIZE;
int bufferOffset = start % BUFFER_SIZE;
int bytesInBuffer = BUFFER_SIZE - bufferOffset;
int bytesToCopy = bytesInBuffer >= remainder?remainder:bytesInBuffer;
byte[] buffer = (byte[]) file.buffers[bufferNumber];
Array.Copy(buffer, bufferOffset, dest, destOffset, bytesToCopy);
destOffset += bytesToCopy;
start += bytesToCopy;
remainder -= bytesToCopy;
}
pointer += len;
}
ReadInternal的主要逻辑在循环里边。ReadInternal方法在InputStream类中两个方法中被调用,分别是ReadBytes方法和Refill方法。在Refill方法中把buffer传入了ReadInternal方法,传入的另一个重要参数是bufferLength。可以看出,在Refill的调用中传入ReadInternal的就是InputStream的缓冲区和缓冲区的结尾。ReadInternal的循环就是遍历缓冲区的一个过程。如果是第一次调用Refill方法,
int bufferNumber = start / BUFFER_SIZE;
在这次调用中得到的值是0,bufferOffset 也是0。bytesInBuffer 就是1024。
int bytesToCopy = bytesInBuffer >= remainder?remainder:bytesInBuffer;
则是计算应该读取的实际长度。
byte[] buffer = (byte[]) file.buffers[bufferNumber];这里用到了RAMFile对象,RAMFile实际上是一个内存文件映像,它的buffers存储了文件的分段,感觉就像是一个文件写在不同的硬盘扇区上。这里得到了起始段。然后把这个段写入InputStream的缓冲区buffer。
destOffset += bytesToCopy;计算了缓冲区的结束点,start += bytesToCopy;则是计算了当前文件指针指向读取点的地址。最后remainder -= bytesToCopy;计算出还剩余多少字节需要拷贝。循环结束后把指针指向了结束点。
如果第二次调用Refill方法,那么pointer已经改变了,通过计算,就得到现在应该读取文件的哪个地方,然后重复上述过程。
ReadBytes则是通过SeekInternal方法主动调整了pointer的值。过程和上面的一样。
而FSInputStream类则相对复杂,这个类写在了FSDirectory.cs文件中。它是和FSDirectory类配合使用的一组处理方法。它里面有一个内嵌类Descriptor,这个类继承自BinaryReader,是用来读取传进来的文件的。在构造函数中传入一个文件:
代码 2-13
public FSInputStream(System.IO.FileInfo path)
{
file = new Descriptor(this, path, System.IO.FileAccess.Read);
length = file.BaseStream.Length;
}
代码 2-14
internal class Descriptor : System.IO.BinaryReader
{
private void InitBlock(FSInputStream enclosingInstance)
{
this.enclosingInstance = enclosingInstance;
}
private FSInputStream enclosingInstance;
public FSInputStream Enclosing_Instance
{
get
{
return enclosingInstance;
}
}
public long position;
public Descriptor(FSInputStream enclosingInstance, System.IO.FileInfo file, System.IO.FileAccess fileAccess)
: base(new System.IO.FileStream(file.FullName, System.IO.FileMode.Open, fileAccess, System.IO.FileShare.ReadWrite))
{
}
}
file = new Descriptor(this, path, System.IO.FileAccess.Read);实际上就是打开该文件。2-14的代码是通过转换器转换过来的,和下面代码效果一样:
代码 2-15
internal class Descriptor : System.IO.BinaryReader
{
public Descriptor1(FSInputStream enclosingInstance, System.IO.FileInfo file, System.IO.FileAccess fileAccess)
: base(new System.IO.FileStream(file.FullName, System.IO.FileMode.Open, fileAccess, System.IO.FileShare.ReadWrite))
{
}
}
取得当前位置值需要用Descriptor 的BaseStream.Position。
代码 2-16
public override void ReadInternal(byte[] b, int offset, int len)
{
lock (file)
{
long position = GetFilePointer();
if (position != file.position)
{
file.BaseStream.Seek(position, System.IO.SeekOrigin.Begin);
file.position = position;
}
int total = 0;
do
{
int i = file.Read(b, offset + total, len - total);
if (i <= 0)
throw new System.IO.IOException("read past EOF");
file.position += i;
total += i;
}
while (total < len);
}
}
代码2-16重写父类ReadInternal方法,先是锁定了一个文件读取对象,long position = GetFilePointer();则是得到了当前指针,如果计算出的指针和实际指针不一致,则把指针定位到计算出的指针。然后会通过Read方法把字节读入缓冲区。
该类实现了自己的深拷贝,在拷贝后,拷贝出的对象会有一个标记clone.isClone = true;完整代码:
代码 2- 17
public override System.Object Clone()
{
FSInputStream clone = (FSInputStream) base.Clone();
clone.isClone = true;
return clone;
}
在Close方法中,则判断了,如果打开了文件,并且该对象不是拷贝,则关闭文件读取。最后让GC不回收该类。
代码 2- 18
public override void Close()
{
if (!isClone && file != null)
file.Close();
System.GC.SuppressFinalize(this);
}
因为这个类的读取,指针由BinaryReader维护,所以SeekInternal并没有任何动作。在RAMDirectory和FSDirectory类的OpenFile方法中分别使用了RAMInputStream和FSInputStream这两种文件操作方式。