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

其中,FSDirectory.FSIndexInput和FSDirectory.FSIndexOutput是FSDirectory的内部类(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).读取下个字节存入b,b往左偏移7*(n-1)位后与i拼接后存入i,转到(2)
注:
A.n为循环次数
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拆成2个int来处理;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,它最终补充实现了该类系所需提供服务的全部实现。
FSIndexOutput的flushBuffer(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类型的参数。实现了IndexOutput的writeByte()方法,也提供了在索引间拷贝数据用的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
}