FieldInfos是一个容器类,含有如下属性:
static final byte IS_INDEXED = 0x1;
static final byte STORE_TERMVECTOR = 0x2;
static final byte STORE_POSITIONS_WITH_TERMVECTOR = 0x4;
static final byte STORE_OFFSET_WITH_TERMVECTOR = 0x8;
static final byte OMIT_NORMS = 0x10;
通过Field的编号和名字来保存field的信息
private ArrayList byNumber = new ArrayList();
private HashMap byName = new HashMap();
下面将逐个介绍它的方法:
add()方法;
public void add(String name, boolean isIndexed, boolean storeTermVector,
boolean storePositionWithTermVector, boolean storeOffsetWithTermVector, boolean omitNorms) {
FieldInfo fi = fieldInfo(name);
if (fi == null) {
addInternal(name, isIndexed, storeTermVector, storePositionWithTermVector, storeOffsetWithTermVector, omitNorms);
} else {
if (fi.isIndexed != isIndexed) {
fi.isIndexed = true; // once indexed, always index
}
if (fi.storeTermVector != storeTermVector) {
fi.storeTermVector = true; // once vector, always vector
}
if (fi.storePositionWithTermVector != storePositionWithTermVector) {
fi.storePositionWithTermVector = true; // once vector, always vector
}
if (fi.storeOffsetWithTermVector != storeOffsetWithTermVector) {
fi.storeOffsetWithTermVector = true; // once vector, always vector
}
if (fi.omitNorms != omitNorms) {
fi.omitNorms = false; // once norms are stored, always store
}
}
}
从中又可以看到:FieldInfos容器类是通过add方法调用addInterval方法将field信息加入的;代码如下:
private void addInternal(String name, boolean isIndexed,boolean storeTermVector,
boolean storePositionWithTermVector, boolean storeOffsetWithTermVector,
boolean omitNorms) {
FieldInfo fi =
new FieldInfo(name, isIndexed, byNumber.size(), storeTermVector, storePositionWithTermVector,
storeOffsetWithTermVector, omitNorms);
byNumber.add(fi);
byName.put(name, fi);
}
那么FieldInfo是如何构建的呢?
FieldInfo(String na, boolean tk, int nu, boolean storeTermVector,
boolean storePositionWithTermVector, boolean storeOffsetWithTermVector, boolean omitNorms) {
name = na;
isIndexed = tk;
number = nu;
this.storeTermVector = storeTermVector;
this.storeOffsetWithTermVector = storeOffsetWithTermVector;
this.storePositionWithTermVector = storePositionWithTermVector;
this.omitNorms = omitNorms;
}
在这里顺便理一下过程:
首先,在DocumentWriter中的addDocument()方法中构造了一个FieldInfos对象,将传递进来的doc加入到其中。从doc中提取关于Field的信息,将操作频繁的信息提取出来,构造一个个的FieldInfo对象。然后FieldInfos在对FieldInfo进行管理。
其次,在addDocument()方法中利用FieldInfos管理FieldInfo的便利性,再次提取Field的信息,构造Posting类的实例 。在一个FieldInfos写入到索引文件之前,要对doc进行倒排(因为doc已经加入到FieldInfos中了),倒排的过程中对Field进行了分词处理。
再次,对doc倒排之后,形成一个Posting[]数组,接着对它进行排序(使用快速排序),之后FieldInfos才能将各个Field的名称写入索引目录中fieldInfos.write(directory, segment + ".fnm");。如果传递进来的segment值为_ram_1它写到了文件_ram_1.fnm文件中。
也就是说,当postingTable排序完毕,term位置信息生成结束,Lucene会首先调用fieldInfos中的write方法,生成.fnm文件,写入索引目录
FieldInfos中的两个核心write方法如下:
public void write(Directory d, String name) throws IOException {
IndexOutput output = d.createOutput(name);
try {
write(output);
} finally {
output.close();
}
}
@1:在索引目录下中生成.fnm文件;返回了一个有name构造的RAMOutputStream输内存出流对象output。
注:这里面传递进来的 d 是实现类RAMDirectory的实例。
见附录1
@2:向文件中写入数据。调用核心代码write方法;
public void write(IndexOutput output) throws IOException {
output.writeVInt(size()); #1
for (int i = 0; i < size(); i++) {a
FieldInfo fi = fieldInfo(i);
byte bits = 0x0;
if (fi.isIndexed) bits |= IS_INDEXED;
if (fi.storeTermVector) bits |= STORE_TERMVECTOR;
if (fi.storePositionWithTermVector) bits |= STORE_POSITIONS_WITH_TERMVECTOR;
if (fi.storeOffsetWithTermVector) bits |= STORE_OFFSET_WITH_TERMVECTOR;
if (fi.omitNorms) bits |= OMIT_NORMS;
output.writeString(fi.name); #2
output.writeByte(bits); #3
}b
}
#1: output.writeVInt(size());将FieldInfos中FieldInfo的个数的数值写入到当前的缓冲区中了,即写入Field的个数。
public int size() {
return byNumber.size();
}
#2:将FieldInfo的name写入输出流output中,先写入name的长度,再写字符;
public void writeString(String s)throws IOException
{
int length=s.length();
writeVInt(length);
writeChars(s,0,length);
}
#3:将处理过的属性写入输出流output;
a~b:通过一个for循环,根据指定的位置索引(FieldInfo的编号),遍历每个FieldInfo,对其一些属性进行处理后写入,其实就是通过一个字节来识别各个属性(比如是否索引、是否存储词条向量、是否忽略norms、是否存储Payload信息),最后将FieldInfo的name和这些经过处理的属性写入到输出流中。
对FieldInfo类和FieldInfos类进行总结:
1、FieldInfo作为一个实体,保存了Field的一些主要的信息;
2、因为对Field的操作比较频繁,而每次管理都在内存中加载FieldInfo这个轻量级但信息很重要的对象,能够大大提高建立索引的速度;
3、FieldInfos包含的信息比较丰富,通过一个FieldInfo对象,调出Document中的Field到内存中,对每个Field进行详细的管理。
4、FieldInfos支持独立从索引目录中读取Document中的信息(主要根据Document参数,管理其中的Field),然后再写回到索引目录。
综合总结:
FieldInfos是对Document中的Field进行管理的,它主要是在内存中进行管理,然后写入到索引目录中。具体地,它所拥有的信息都被写入了一个与索引段文件相关的segments.fnm文件中 ,在DocumentWriter类的addDocument()方法中可以看到,DocumnetWriter类的实现,是在FieldInfos类的基础上。FieldInfos类对Document的所有的Field的静态信息进行管理,而DocumentWriter类表现出了更强大的管理Document的功能,主要是对Field进行了一些高级的操作,比如使用Analyzer分析器进行分词、对切分出来的词条进行排序(文档倒排)等等。
附录1
根据传进来的name,使用RAMDirectory的createOutput()方法创建一个输出流,即:IndexOutput output = d.createOutput(name);这时,可以看到,接下来的一些操作都转入到RAMDirectory中去了。也就是根据一个name参数,我们看一下name在进入到RAMDirectory中的过程中发生了怎样的变化。
RAMDirectory的createOutput()方法定义如下所示:
public IndexOutput createOutput(String name) {
ensureOpen();
RAMFile file = new RAMFile(this);
synchronized (this) {
RAMFile existing = (RAMFile)fileMap.get(name);
if (existing!=null) {
sizeInBytes -= existing.sizeInBytes;
existing.directory = null;
}
fileMap.put(name, file);
}
return new RAMOutputStream(file);
}
RAMDirectory拥有一个成员变量fileMap(它是一个HashMap),在调用ensureOpen()方法时,通过fileMap来判断,如果fileMap=null,则抛出异常,终止程序流程。所以当fileMap!=null的时候说明RAMDirectory是处于打开状态的。
根据已经处于打开状态的this(即RAMDirectory),以其作为参数构造一个 RAMFile file。接着,以当前打开的RAMDirectory作为同步信号量,从fileMap中获取名称为name的一个RAMFile的实例existing,如果内存中不存在名称为name的RAMFile,则要把该RAMFile添加到fileMap中,即fileMap.put(name, file);,同时直接返回以当前RAMDirectory作为参数构造的RAMFile file;如果RAMFile existing存在,则以该name构造的RAMField作为参数,添加到RAMOutputStream内存输出流中。
这里RAMDirectory的sizeInBytes成员指定了当前RAMDirectory中操作的字节数,而对于每个RAMFile都与一个RAMDirectory相关,所以,当从内存中把一个RAMFile放到内存输出流RAMOutputStream中时,当前内存中的字节数便减少了该RAMFile所具有的字节数,即 sizeInBytes -= existing.sizeInBytes;。同时,这个RAMFile进入到了RAMOutputStream中,即表示该文件已经不与RAMDirectory相关了,所以上面有existing.directory = null;。
附录2
关于IndexOutput的writeVInt()方法
方法定义如下所示:
public void writeVInt(int i) throws IOException {
while ((i & ~0x7F) != 0) {
writeByte((byte)((i & 0x7f) | 0x80));
i >>>= 7;
}
writeByte((byte)i);
}
该方法中,0x7F的值为127,按位取反后~0x7F的值为-128。
当0<=i<=127时,(i & ~0x7F) =0;当128<=i<=255时,(i & ~0x7F) =128;当256<=i<=511时,(i & ~0x7F) =256;以此类推。
也就是说,判断的条件不等于0成立 ,i的取值范围是i>=128。
调用了RAMOutputStream类的writeBytes()方法,写入的字节值为(i & 0x7f) | 0x80,因为,(i & ~0x7F)的值为0,128,256,384,512……,再与0x80(即128)做按位或运算,也就是当(i & 0x7f) 的值为0时,写入的字节是128,从而(i & 0x7f) | 0x80为写入的值:128,256,384,512……,没有0了。
关于RAMOutputStream类的writeByte()方法
public void writeByte(byte b) throws IOException {
if (bufferPosition >= BUFFER_SIZE)
flush();
buffer[bufferPosition++] = b;
}
关于RAMOutputStream类的writeBytes()方法
output是RAMOutputStream类的一个实例,所以在操作字节时使用了RAMOutputStream类的writeBytes()方法,定义如下:
public void writeByte(byte b) throws IOException {
if (bufferPosition == bufferLength) {
currentBufferIndex++;
switchCurrentBuffer();
}
currentBuffer[bufferPosition++] = b;
}
如果bufferPosition == bufferLength,即缓冲区满,需要动态增加当前buffer的容量,索引位置增加1,当前索引位置在buffer的最后位置,然后调用switchCurrentBuffer()方法,实现字节缓冲区的动态分配:
private final void switchCurrentBuffer() throws IOException {
if (currentBufferIndex == file.buffers.size()) {
currentBuffer = file.addBuffer(BUFFER_SIZE);
} else {
currentBuffer = (byte[]) file.buffers.get(currentBufferIndex);
}
bufferPosition = 0;
bufferStart = BUFFER_SIZE * currentBufferIndex;
bufferLength = currentBuffer.length;
}
否则。缓冲区未满,可以写入。
把将待写入的经过处理的FieldInfo的个数数值,以128个字节为最小单位,写入到当前的缓冲区中,不够128个字节,填充到128个字节,不能截断数据。