解读Lucene.Net ——二、 InputStream 之一

其他文章:解读Lucene.Net 阅读索引

 

InputStream这个类在Java基本类库里就有,但是Lucene选择了自己来实现,翻译到dotnet版本,名称保持没变。InputStream实现了ICloneable接口,就是能支持拷贝出新对象。

 

代码2-1

        public virtual System.Object Clone()

        {

            InputStream clone = null;

            try

            {

                clone = (InputStream) this.MemberwiseClone();

            }

            catch (System.Exception e)

            {

                throw new Exception("Can't clone InputStream.", e);

            }

            if (buffer != null)

            {

                clone.buffer = new byte[BUFFER_SIZE];

                Array.Copy(buffer, 0, clone.buffer, 0, bufferLength);

            }

            clone.chars = null;

            return clone;

        }

 

从2-1代码可以看出,显示定义了一个InputStream 变量,然后用MemberwiseClone方法拷贝一个浅表副本,然后,判断如果buffer 内有数据,就把数据拷贝到浅副本里。而这个过程呢,实际上就是深拷贝。从中可以看出,浅拷贝就是克隆出新对象,但是不带数据,深拷贝呢就是浅副本带上数据。

 

InputStream 类有一个静态构造函数,给BUFFER_SIZE赋了一下值,这个值是默认buffer的大小。由OutputStream类提供,初始值是1024大小。下来在看看每个方法是干嘛的。

代码 2-2

private void  Refill()
{
    long start = bufferStart + bufferPosition;
    long end = start + BUFFER_SIZE;
    if (end > length)
    // don't read past EOF
        end = length;
    bufferLength = (int) (end - start);
    if (bufferLength == 0)
        throw new System.IO.IOException("read past EOF");
    if (buffer == null)
        buffer = new byte[BUFFER_SIZE]; // allocate buffer lazily
    ReadInternal(buffer, 0, bufferLength);
    bufferStart = start;
    bufferPosition = 0;
}

要理解Refill方法一定要看看另外三个变量。

private long bufferStart = 0; // position in file of buffer
private int bufferLength = 0; // end of valid bytes
private int bufferPosition = 0; // next byte to read

从注释上可以看到,bufferStart 代表文件缓冲区的位置,bufferLength 代表文件缓冲区的结束点,bufferPosition 表示当前读取到文件缓冲区的位置。而Refill方法也只在ReadByte方法中被使用了。

代码2-3

public byte ReadByte()
{
    if (bufferPosition >= bufferLength)
        Refill();
    return buffer[bufferPosition++];
}

可以看出,是当读取的指针达到了文件缓冲区的最大长度,才调用了Refill方法。大体上就可以猜测,Refill是用来把下一段数据载入缓冲区的。Refill为什么要在第一个语句中计算

long start = bufferStart + bufferPosition;

这个start 变量到底代表什么呢?InputStream类有一个自身的缓冲区域,private byte[] buffer;在静态构造函数中设置了这个区域的大小。而用这个类去读取文件,文件的大小一般都是大于1024字节,所以,InputStream每次读取最多1024个字节的话,在InputStream读取文件的方式就是不连续读取的,是一段一段的载入的,这个start就代表了当前读取到了文件流的位置。long end = start + BUFFER_SIZE;语句则是计算出缓冲区域相对于文件流的位置。然后判断当前缓冲区相对于文件流位置如果大于length,那么就做一个调整。在InputStream的子类RAMInputStream中,可以看到length代表了文件的总长度。这个判断就是为了限制不会超出。

bufferLength = (int) (end - start);计算出了当前缓冲区域的长度。因为读入的字节如果不满1024字节的话,实际上在任何地方都没有对buffer进行清空,因此,只能用这种方式来处理在读取的字节数小于缓冲区大小的时候,防止读取超出文件实际长度。接着用到一个ReadInternal方法,这个方法在InputStream类中是抽象方法。等讲到子类的时候再来看。

看完了Refill方法,继续关注一下ReadByte,可以看到ReadByte方法相对于是做遍历操作的,只要调用ReadByte方法,就是获取下一个字节。

代码2-4

public void  ReadBytes(byte[] b, int offset, int len)
    {
        if (len < BUFFER_SIZE)
        {
            for (int i = 0; i < len; i++)
            // read byte-by-byte
                b[i + offset] = (byte) ReadByte();
        }
        else
        {
            // read all-at-once
            long start = GetFilePointer();
            SeekInternal(start);
            ReadInternal(b, offset, len);
            bufferStart = start + len; // adjust stream variables
            bufferPosition = 0;
            bufferLength = 0; // trigger refill() on read
        }
    }

而ReadBytes方法则是一次读取指定数量的字节。如果读取的字节小于缓冲区域,则按字节读取,而如果超出了,则会先计算当前缓冲区相对文件缓冲区的位置。

代码 2-5

public long GetFilePointer()
{
    return bufferStart + bufferPosition;
}

然后SeekInternal方法也是一个抽象方法,是把读取位置跳转到指定的位置。然后用抽象方法ReadInternal实际读取。读取出的b变量因为是引用类型,所以值直接就发生了变化。

bufferLength = 0; // trigger refill() on read

把bufferLength 设置为0,则ReadByte方法一定会触发refill方法,这个注释也是这么写的。

 

代码2-6

public int ReadInt()
{
    return ((ReadByte() & 0xFF) << 24) | ((ReadByte() & 0xFF) << 16) | ((ReadByte() & 0xFF) << 8) | (ReadByte() & 0xFF);
}

ReadInt方法,用读出的每个字节和0xFF做与操作。这个用法前面也介绍到过,在.net中实际上是无必要的,这是Java和dotnet的差异造成的,翻译过来的时候译者可能并未深究。事实上在Java中也只需要 0x7F就够了,不需要0xFF。
假如ReadByte得出的值是 byte b = 244 那么 做了与操作之后还是244。左移24位,244的2进制是1111 1010,就是在后面加 24个0,位数就变成了32位。后面的操作和这个类似,做了或操作,相当于是把ReadByte读取到的4个byte值,排列得到一个数字。因为数字是32位,所以只要这个byte值是大于127的,都会产生负数。因为int类型的最高位是代表符号的,127的2进制是0111 1111,左移24位,最高位是0,而如果是128,就会变成 1111 1111    1000 0000,自然就变成了负数了。这个方法读取了4个字节。

代码2-7

public int ReadVInt()
{
    byte b = ReadByte();
    int i = b & 0x7F;
    for (int shift = 7; (b & 0x80) != 0; shift += 7)
    {
        b = ReadByte();
        i |= (b & 0x7F) << shift;
    }
    return i;
}

和ReadInt相比,运算的方式和ReadInt相似,但也有差别,每次位移是7位。

 

代码2-8

public long ReadLong()
{
    return (((long) ReadInt()) << 32) | (ReadInt() & 0xFFFFFFFFL);
}

ReadLong方法读取了8个字节。对比看看ReadLong方法和ReadVInt方法有什么区别。ReadVInt方法每次位移的是7位的整数倍,那是因为

int i = b & 0x7F;

操作得到的值不会大于127,而这个值得2进制是0111 1111,把最高位的0省略,那么就是7个1。这样第一次没有位移,而第二次位移7位,相当于在第一次读取字节二进制的高位加上了第二读取的字节。一直循环N次,加上第一次,就是N+1次。而ReadLong则是和ReadInt方法一样的,把后面得到的字节放在低位。ReadVInt方法相当于把ReadLong方法的高位和低位调换了,这在系统中比较常见,Socket就是这么做的,做过游戏外挂的肯定都知道这个的。

 

代码 2- 9

public long ReadVLong()
{
    byte b = ReadByte();
    long i = b & 0x7F;
    for (int shift = 7; (b & 0x80) != 0; shift += 7)
    {
        b = ReadByte();
        i |= (b & 0x7FL) << shift;
    }
    return i;
}

对比以上的理解就很容易理解ReadVLong方法了,和ReadVInt类似。ReadVLong,ReadVInt循环中的条件是(b & 0x80) != 0,0x80就是 1000 0000,所以一旦读取到b是1000 0000以上的数值,这个循环就终止了。也就是当b >= 128 。当然在java版本中和这个有差异,因为java的byte是 -128 - 127,所以,byte的最高位是符号,那么这个地方就是小于0的意思。

 

代码 2- 10

public System.String ReadString()
    {
        int length = ReadVInt();
        if (chars == null || length > chars.Length)
            chars = new char[length];
        ReadChars(chars, 0, length);
        return new System.String(chars, 0, length);
    }

ReadString方法,在第一次运行时候,会设置为整个文件的长度,然后读取出了整个文件的字符。

代码 2-11

public void  ReadChars(char[] buffer, int start, int length)
    {
        int end = start + length;
        for (int i = start; i < end; i++)
        {
            byte b = ReadByte();
            if ((b & 0x80) == 0)
                buffer[i] = (char) (b & 0x7F);
            else if ((b & 0xE0) != 0xE0)
            {
                buffer[i] = (char) (((b & 0x1F) << 6) | (ReadByte() & 0x3F));
            }
            else
                buffer[i] = (char) (((b & 0x0F) << 12) | ((ReadByte() & 0x3F) << 6) | (ReadByte() & 0x3F));
        }
    }

ReadChars方法在ReadString方法中就是用来读取文件的全部字符的。

其他方法都比较简单,就不用细看了。至于这里为什么要这样来读取,那就要看OutputStream类了。呵呵。

你可能感兴趣的:(Inputstream)