LetsCoding.cn

天地之间有杆秤,拿秤砣砸老百姓。

Lucene源码分析笔记之[org.apache.lucene.store](二)

 IndexInput/IndexOutput类系

综述:Lucene在存储和读取索引的时候,把文件内容都当作字节来对待。Int型拆分成1-5byte分别存储;float则拆分成1-10byte分别存储。Char型拆分成1-3byte来存储。

1. IndexInput/IndexOutput类系的层次图

其中,FSDirectory.FSIndexInputFSDirectory.FSIndexOutputFSDirectory的内部类(protected static)

2.部分代码说明

IndexInput/IndexOutput

在综述里说过,Lucene把文本都以字节为单位进行处理。下面是IndexInput/IndexOutput部分方法的代码清单,从中我们能清楚的理解Lucene的文本处理方式。

writeInt(int)方法把int型变量处理成4个字节,从高位到低位分别存储。

1    public void writeInt(int i) throws IOException {
2        writeByte((byte) (i >> 24));    // 写高8位
3        writeByte((byte) (i >> 16));    // 写次高8位
4        writeByte((byte) (i >> 8));        // 写次次高8位
5        writeByte((byte) i);    //写低8位
6    }

writeVInt(int)方法把int型变量处理成1-5个字节,从低位到高位分别存储。值小的,占用的字节数就少;值大的,占用的字节数就多。这个就是Lucene压缩存储的基石了。

1    public void writeVInt(int i) throws IOException {
2        while ((i & ~0x7F!= 0{    // 当最高位不为0,执行循环体
3            writeByte((byte) ((i & 0x7f| 0x80));    // 写入低7位,最高位置1
4            i >>>= 7;    // 向右偏移7位,也就是往高位移动7位
5        }

6        writeByte((byte) i);    // 写入数据最高位(肯定不足7位了),最高位显然是0
7    }

IndexInput中的readInt()readVInt()用来读取文件内容。

readInt()在读取时,把读取的4个字节从高位到底位依次拼接。这一点在下面的代码中可以很容易看出来。

1    public int readInt() throws IOException {
2        return ((readByte() & 0xFF<< 24| ((readByte() & 0xFF<< 16)
3                | ((readByte() & 0xFF<< 8| (readByte() & 0xFF);
4    }

readVInt()稍微有点复杂,它的读取顺序是由低位到高位,步骤如下:

(1).读入一个字节存入变量b
(2).b取后7位,存入变量i;若b首位是0,则返回i
(3).读取下个字节存入bb往左偏移7*(n-1)位后与i拼接后存入i,转到(2)

注:
An为循环次数
B.其实只要理解了writeVInt(int)的写入方式后,readVInt()就不难理解了。

下面是readVInt()的代码清单:

1    public int readVInt() throws IOException {
2        byte b = readByte(); // 读取第一个字节
3        int i = b & 0x7F// 取后7位
4        for (int shift = 7; (b & 0x80!= 0; shift += 7// 当该字节首位不为0,执行循环体
5            b = readByte(); // 读取下个字节
6            i |= (b & 0x7F<< shift; // 取该字节后7位,偏移到高位,跟原i值拼接
7        }

8        return i;
9    }

至于writeLong(long),它在形式上把long拆成2int来处理;writeVLong(long)/readVLong()思路(代码)跟writeVInt(int)/readVLong()除了方法名之外,完全一样;realLong()通过两次readInt(),第一个值偏移32位后拼接第二个值。

writeChars(char[],int,int)用来把一个符合UTF-8编码的字符数组写入文件,它同样把字符拆分成字节来对待。对每个字符,按照其有效位数n(去掉高位的0)的不同,采用有三种不同的写入方法:

(1).0< n <=7,取后7位,首位置0写入文件
(2).7< n <=11或者n=0,取高1-5位,首3位置110;取后6位,首2位置10;写入文件
(3).11< n <=16,取高0-4位,首4位置1110;取中6位,首2位置10;取后6位,首2位置10;写入文件

其代码及注释如下:

 1    public void writeChars(char[] s, int start, int length) throws IOException {
 2        // start为开始的字符在char[]中的位置,length为需要写的字符的个数
 3        final int end = start + length;
 4        for (int i = start; i < end; i++{    // 循环遍历char[]中从start到end的字符
 5            final int code = (int) s[i];
 6            if (code >= 0x01 && code <= 0x7F
 7                // code值在0x01-0x7F,直接写入
 8                // code的有效位数为1-7位
 9                writeByte((byte) code);
10            else if (((code >= 0x80&& (code <= 0x7FF)) || code == 0{
11                // code值在0x80-0x7FF或者为0,则分两个字节写入
12                // code的有效位数8-11位
13                writeByte((byte) (0xC0 | (code >> 6)));     // 写高2-5位,首3位置110
14                writeByte((byte) (0x80 | (code & 0x3F)));    // 写低6位,首2位置10
15            }
 else {
16                //0x7FF之后的用3个字节写入,code有效位数12-16位
17                writeByte((byte) (0xE0 | (code >>> 12)));    // 写高0-4位,首4位置1110
18                writeByte((byte) (0x80 | ((code >> 6& 0x3F)));    //写此高位6位,首2位置10
19                writeByte((byte) (0x80 | (code & 0x3F)));        //写低6位,首2位置10
20            }

21        }

22    }

writeChars(String, int, int)思路(代码)跟上面是一样的。

writeChars(char[], int, int)对应的readChars(char[], int, int)代码及注释如下:

 1    public void readChars(char[] buffer, int start, int length)
 2            throws IOException {
 3        final int end = start + length;
 4        for (int i = start; i < end; i++{
 5            byte b = readByte(); // 读取一个字节
 6            if ((b & 0x80== 0// 如果首位不为1,说明该字节单独为一字符
 7                buffer[i] = (char) (b & 0x7F);
 8            else if ((b & 0xE0!= 0xE0// 首4位不为1110
 9                buffer[i] = (char) (((b & 0x1F<< 6| (readByte() & 0x3F));
10            }
 else {
11                buffer[i] = (char) (((b & 0x0F<< 12)
12                        | ((readByte() & 0x3F<< 6| (readByte() & 0x3F));
13            }

14        }

15    }

 writeString(String)用来写入字符串。它先写入该字符串的长度,然后调用writeChars(String, int, int)写入字符串。代码及注释如下:

1    public void writeString(String s) throws IOException {
2        int length = s.length();    // 字符串长度
3        writeVInt(length);    // 写入字符串长度
4        writeChars(s, 0, length);    //写入字符串
5    }

 readString()在读取的时候利用了IndexInput类的私有变量(private char[] chars)来缓存字符串,唯一需要注意的就是在需要时,要给char扩充容量。代码及注释如下:

    public String readString() throws IOException {
        
int length = readVInt();
       
if (chars == null || length > chars.length)    // 需要时,给chars扩容
            chars = new char[length];
        readChars(chars, 
0, length);
        
return new String(chars, 0, length);
    }

BufferedIndexInput/BufferedIndexOutput

BufferedIndexInput/BufferedIndexOutput依然是抽象类,它们给出了部分IndexInput/IndexOutput未曾实现的抽象方法,如getFilePointer()writeByte()/readByte()等等。还提供了writeBytes()/readBytes()这样的在索引优化合并时使用的方法。

BufferedIndexOutput中的变量说明:

1    static final int BUFFER_SIZE = 16384;    // buffer的总容量
2
3    private final byte[] buffer = new byte[BUFFER_SIZE];    // 用于写文件时的缓冲区
4    private long bufferStart = 0// position in file of buffer:  buffer 在文件中的偏移量
5    private int bufferPosition = 0// position in buffer : 在buffer中的偏移量


writeByte(byte)
为往buffer中写入byte,代码比较简单,如下:

1    public void writeByte(byte b) throws IOException {
2        if (bufferPosition >= BUFFER_SIZE)    // 注意buffer装满时需要flush()
3            flush();
4        buffer[bufferPosition++= b;    
5    }

 writeBytes(byte[], int, int)从名字就知道是存储一个byte数组。代码及注释如下:

 1    public void writeBytes(byte[] b, int offset, int length) throws IOException {    // 该方法在索引优化合并时使用
 2        // offset: 首个字节在b中的位置;    length: 序列长度(字节数)
 3        int bytesLeft = BUFFER_SIZE - bufferPosition;    // bytesLeft: buffer剩余容量
 4        // is there enough space in the buffer?
 5        if (bytesLeft >= length) {    // 剩余容量可以放下长度length的字节数
 6            // we add the data to the end of the buffer
 7            System.arraycopy(b, offset, buffer, bufferPosition, length);
 8            bufferPosition += length;
 9            // if the buffer is full, flush it
10            if (BUFFER_SIZE - bufferPosition == 0)
11                flush();
12         }
 else {    // 剩余容量放不下
13            // is data larger then buffer?
14             if (length > BUFFER_SIZE) {    // BUFFER_SIZE < length 时
15                // we flush the buffer
16                if (bufferPosition > 0)
17                    flush();
18                // and write data at once
19                flushBuffer(b, offset, length);
20                bufferStart += length;
21             }
 else {    // bytesLeft < length < BUFFER_SIZE 时,分2次写入
22                // we fill/flush the buffer (until the input is written)
23                int pos = 0;     // position in the input data
24                int pieceLength;    // 一次往buffer中写入的字节数
25                while (pos < length) {    // 我仰天狂吼:为什么?为什么要用循环?!为什么?天呀,为什么你如此偏爱它?
26                    // 剩余字节数(length - pos)小于bytesLeft,pieceLength = lenght - pos,否则,pieceLength = bytesLeft
27                    pieceLength = (length - pos < bytesLeft) ? length - pos
28                            : bytesLeft;
29                    System.arraycopy(b, pos + offset, buffer, bufferPosition, pieceLength);
30                    pos += pieceLength;
31                    bufferPosition += pieceLength;    // 改变bufferPosiotion
32                    // if the buffer is full, flush it
33                    bytesLeft = BUFFER_SIZE - bufferPosition;    // 计算剩余容量
34                    if (bytesLeft == 0{    // b装满则flush()
35                        flush();
36                        bytesLeft = BUFFER_SIZE;    // flush()后bytesLeft自然就要跟 BUFFER_SIZE 一样了
37                    }

38                }

39            }

40        }

41    }

 flush():把buffer中内容写入文件,清空buffer。代码及注释如下:

1    public void flush() throws IOException {
2        flushBuffer(buffer, bufferPosition);    // buffer中的内容写入文件
3        bufferStart += bufferPosition;    // 更改buffer在文件中的偏移量
4        bufferPosition = 0;    // buffer为空,则 bufferPosition = 0
5    }

 readByte(): buffer中读取一个字节。

1    public byte readByte() throws IOException {
2        if (bufferPosition >= bufferLength)    //当前读取位置超过buffer中内容有效长度,refill()
3            refill();
4        return buffer[bufferPosition++];
5    }

refill():重新装填buffer

 1    private void refill() throws IOException {
 2        long start = bufferStart + bufferPosition; // 计算在文件中的偏移位置
 3        long end = start + bufferSize;    // 结束位置
 4        if (end > length()) // don't read past EOF: 超出文件大小
 5            end = length();
 6        int newLength = (int) (end - start);    // 能读取的长度
 7        if (newLength <= 0)
 8            throw new IOException("read past EOF");
 9
10        if (buffer == null{    // 需要初始化buffer
11            buffer = new byte[bufferSize]; // allocate buffer lazily
12            seekInternal(bufferStart);
13        }

14        readInternal(buffer, 0, newLength);    // 这里才是真正的装填buffer
15        bufferLength = newLength;    // 设置buffer中有效字节数
16        bufferStart = start;    // 设置buffer在文件中的偏移量
17        bufferPosition = 0;    // 当前buffer中的偏移量
18    }

readBytes(byte[], int, int, boolean):读取字节数组,跟writeBytes()一样,在索引优化合并时使用。源码中的注释本身已足够清晰了,我就偷了回懒,没写自己的注释,就粘过来了。

 1    public void readBytes(byte[] b, int offset, int len, boolean useBuffer)
 2            throws IOException {
 3
 4        if (len <= (bufferLength - bufferPosition)) {
 5            // the buffer contains enough data to satisfy this request
 6            if (len > 0// to allow b to be null if len is 0
 7                System.arraycopy(buffer, bufferPosition, b, offset, len);
 8            bufferPosition += len;
 9        }
 else {
10            // the buffer does not have enough data. First serve all we've got.
11            int available = bufferLength - bufferPosition;
12            if (available > 0{
13                System.arraycopy(buffer, bufferPosition, b, offset, available);
14                offset += available;
15                len -= available;
16                bufferPosition += available;
17            }

18            // and now, read the remaining 'len' bytes:
19            if (useBuffer && len < bufferSize) {
20                // If the amount left to read is small enough, and
21                // we are allowed to use our buffer, do it in the usual
22                // buffered way: fill the buffer and copy from it:
23                refill();
24                if (bufferLength < len) {
25                    // Throw an exception when refill() could not read len
26                    // bytes:
27                    System.arraycopy(buffer, 0, b, offset, bufferLength);
28                    throw new IOException("read past EOF");
29                }
 else {
30                    System.arraycopy(buffer, 0, b, offset, len);
31                    bufferPosition = len;
32                }

33            }
 else {
34                // The amount left to read is larger than the buffer
35                // or we've been asked to not use our buffer -
36                // there's no performance reason not to read it all
37                // at once. Note that unlike the previous code of
38                // this function, there is no need to do a seek
39                // here, because there's no need to reread what we
40                // had in the buffer.
41                long after = bufferStart + bufferPosition + len;
42                if (after > length())
43                    throw new IOException("read past EOF");
44                readInternal(b, offset, len);
45                bufferStart = after;
46                bufferPosition = 0;
47                bufferLength = 0// trigger refill() on read
48            }

49        }

50    }

 FSIndexInput/FSIndexOutput

FSIndexInput/FSIndexOutput继承自BufferedIndexInput/BufferedIndexOutput,它最终补充实现了该类系所需提供服务的全部实现。

FSIndexOutputflushBuffer(byte[], int, int)方法,它的功能在于真正的完成buffer到文件的数据存储。

1        public void flushBuffer(byte[] b, int offset, int size)
2                throws IOException {
3            file.write(b, offset, size);    // 写文件
4        }

flushBuffer()对应的,readInternal(byte[], int, int)从底层真正的把数据从文件提取到buffer中。

 1        protected void readInternal(byte[] b, int offset, int len)    // 从文件中读取内容到buffer
 2                throws IOException {
 3            synchronized (file) {    // file需要同步访问
 4                long position = getFilePointer();    // 获取当前文件读取位置
 5                if (position != file.position) {    // file定位到当前读取位置
 6                    file.seek(position);
 7                    file.position = position;
 8                }

 9                int total = 0;
10                do {    
11                    /* 一般情况下,此循环体只会执行一次,只有在第一次循环时,file中内容不能使b全部装满,
12                     * 这时,total < len,而下次循环,已读到文件尾部,i = -1,抛出异常。
13                     * 也就是说,当b不能读满时,此方法必会抛出异常 
14                     */

15                    int i = file.read(b, offset + total, len - total);
16                    if (i == -1)
17                        throw new IOException("read past EOF");
18                    file.position += i;
19                    total += i;
20                }
 while (total < len);
21            }

22        }

 

RAMOutputStream

RAMOutputStream继承自IndexOutput,是用于处理在内存中建索引时的写数据类,它在实例化是需要RAMFile类型的参数。实现了IndexOutputwriteByte()方法,也提供了在索引间拷贝数据用的writeBytes()writeTo()方法。

writeByte():往内存缓冲区中写一个字节数据。

1    public void writeByte(byte b) throws IOException {    // 写单个字节到buffer,如果当前buffer已满,则切换到下个buffer
2        if (bufferPosition == bufferLength) {
3            currentBufferIndex++;
4            switchCurrentBuffer();    // 切换buffer
5        }

6        currentBuffer[bufferPosition++= b;    // 写入 b
7    }

 writeBytes():索引间拷贝数据用。

 1    public void writeBytes(byte[] b, int offset, int len) throws IOException {
 2        while (len > 0{    // 
 3            if (bufferPosition == bufferLength) {    // 如果buffer装满,切换下个buffer
 4                currentBufferIndex++;
 5                switchCurrentBuffer();    // 切换buffer
 6            }

 7            
 8            int remainInBuffer = currentBuffer.length - bufferPosition;    // buffer中剩余容量
 9            int bytesToCopy = len < remainInBuffer ? len : remainInBuffer;    // 实际拷贝长度
10            System.arraycopy(b, offset, currentBuffer, bufferPosition,
11                    bytesToCopy);    // 拷贝
12            offset += bytesToCopy;    // 调整偏移量
13            len -= bytesToCopy;    // 调整长度
14            bufferPosition += bytesToCopy;    // 调整buffer中当前位置
15        }

16    }

writeTo(IndexOutput):把数据从当前内存缓冲区写到参数指定的IndexOutput中。 

 1   public void writeTo(IndexOutput out) throws IOException {    // 拷贝整个缓冲区数据到out
 2        flush();
 3        final long end = file.length;    // file总长度
 4        long pos = 0;    // 开始偏移位置
 5        int buffer = 0;    // buffer索引
 6        while (pos < end) {
 7            int length = BUFFER_SIZE;
 8            long nextPos = pos + length;
 9            if (nextPos > end) // at the last buffer
10                length = (int) (end - pos);
11            }

12            out.writeBytes((byte[]) file.getBuffer(buffer++), length);    // 拷贝数据
13            pos = nextPos;    // 更改偏移位置
14        }

15    }

posted on 2008-11-13 21:25 Rolandz 阅读(1606) 评论(1)  编辑  收藏

评论

# re: Lucene源码分析笔记之[org.apache.lucene.store](二) 2009-10-20 12:08 bbmonkey62笨笨猴

写得很好  回复  更多评论   


只有注册用户登录后才能发表评论。


网站导航:
 

导航

统计

留言簿(1)

随笔分类(12)

随笔档案(19)

积分与排名

最新评论

阅读排行榜

评论排行榜