先理清各个主要类的职责:先从小到大,抽象到具体
org.apache.lucene.index.SegmentInfos
些类封装了某个索引目录下的所有索引段文件信息,主要跟文件系统打交道
org.apache.lucene.index.SegmentReader
此类负责读取某一个索引段的所有文件
org.apache.lucene.index.DirectoryReader
此类负责管理多个索引段org.apache.lucene.index.SegmentReader,可以做为整个索引目录的对名索引段接口。。一般我们的操作都是对该类操作,由该类来管理处理对应的段。
可以将SegmentReader看成是一个只有一个索引段的DirectoryReader,如果文件目录有多个索引段的时候,DirectoryReader以数组方式存储了多个SegmentReader实例,
取数据的时候会将各个SegmentReader比较后的数据。
SegmentReader[] readers = new SegmentReader[sis.size()];
for (int i = sis.size()-1; i >= 0; i--) {
boolean success = false;
try {
readers[i] = SegmentReader.get(readOnly, sis.info(i), termInfosIndexDivisor);
readers[i].readerFinishedListeners = this.readerFinishedListeners;
success = true;
}
构造该类的实例时,会得到该索引目录下的各个索引段对应的SegmentReader,SegmentReader在初始时会加载一些必要的信息:
try {
instance.core = new SegmentCoreReaders(instance, dir, si, readBufferSize, termInfosIndexDivisor);
if (doOpenStores) {
instance.core.openDocStores(si);
}
instance.loadDeletedDocs();
instance.openNorms(instance.core.cfsDir, readBufferSize);
success = true;
}
1.首先会实例一个org.apache.lucene.index.SegmentCoreReaders类,该类构造时会将该索引段的各个元信息加载进来:
1)域信息:org.apache.lucene.index.FieldInfos.FieldInfos(Directory, String)
2)倒排表:org.apache.lucene.index.TermInfosReader.TermInfosReader(Directory, String, FieldInfos, int, int)
2.加载被删除的文档标志文件
3. 实例org.apache.lucene.index.SegmentNorms,主要是加载存储在标准化因子(.nrm)文件
在索引阶段设置Document Boost和Field Boost,存储在(.nrm)文件中
生成的各个索引段SegmentReader后,需要按照一定的关系联系起来构成一个DirectoryReader对外服务。
重新给各个索引段的起始docId计算值,由上一个段的最大id做为本段的起始id相对值。段与段的顺序是由
private void initialize(SegmentReader[] subReaders) throws IOException {
this.subReaders = subReaders;
starts = new int[subReaders.length + 1]; // build starts array
for (int i = 0; i < subReaders.length; i++) {
starts[i] = maxDoc;
maxDoc += subReaders[i].maxDoc(); // compute maxDocs
if (subReaders[i].hasDeletions())
hasDeletions = true;
}
starts[subReaders.length] = maxDoc;
if (!readOnly) {
maxIndexVersion = SegmentInfos.readCurrentVersion(directory);
}
}
这样所有的增删改查文档,都会先读取此文档的信息的对应在哪个索引段的reader里。。采用的是二分查找法查找对应的reader
final static int readerIndex(int n, int[] starts, int numSubReaders) { // find reader for doc n:
int lo = 0; // search starts array
int hi = numSubReaders - 1; // for first element less
while (hi >= lo) {
int mid = (lo + hi) >>> 1;
int midValue = starts[mid];
if (n < midValue)
hi = mid - 1;
else if (n > midValue)
lo = mid + 1;
else { // found a match
while (mid+1 < numSubReaders && starts[mid+1] == midValue) {
mid++; // scan to last match
}
return mid;
}
}
return hi;
}
接下来的操作就是对应于单独的SegmentReader的操作,而主要的计算就是还原要计算的docId的绝对docId值
例如:计算某个词的频率:将各个reader命中的频率总加
@Override
public int docFreq(Term t) throws IOException {
ensureOpen();
int total = 0; // sum freqs in segments
for (int i = 0; i < subReaders.length; i++)
total += subReaders[i].docFreq(t);
return total;
}
先看看查找某个词的时候:
@Override
public TermEnum terms(Term term) throws IOException {
ensureOpen();
if (subReaders.length == 1) {
// Optimize single segment case:
return subReaders[0].terms(term);
} else {
return new MultiTermEnum(this, subReaders, starts, term);
}
}
使用了org.apache.lucene.index.DirectoryReader.MultiTermEnum进行查找
再看看MultiTermEnum的主要结构是:
SegmentMergeQueue:一个保存了SegmentMergeInfo对象的最大堆
将各个reader命中的TermEnum放进这个堆。最后对外就变成按一定字典顺序吐出。
public MultiTermEnum(IndexReader topReader, IndexReader[] readers, int[] starts, Term t)
throws IOException {
this.topReader = topReader;
queue = new SegmentMergeQueue(readers.length);
matchingSegments = new SegmentMergeInfo[readers.length+1];
for (int i = 0; i < readers.length; i++) {
IndexReader reader = readers[i];
TermEnum termEnum;
if (t != null) {
termEnum = reader.terms(t);
} else
termEnum = reader.terms();
SegmentMergeInfo smi = new SegmentMergeInfo(starts[i], termEnum, reader);
smi.ord = i;
if (t == null ? smi.next() : termEnum.term() != null)
queue.add(smi); // initialize queue
else
smi.close();
}
if (t != null && queue.size() > 0) {
next();
}
}
底层实现的管理DirectoryReader的Directory,主要有三个实现类SimpleFSDirectory,NIOFSDirectory,MMapDirectory
/** Just like {@link #open(File)}, but allows you to
* also specify a custom {@link LockFactory}. */
public static FSDirectory open(File path, LockFactory lockFactory) throws IOException {
if ((Constants.WINDOWS || Constants.SUN_OS || Constants.LINUX)
&& Constants.JRE_IS_64BIT && MMapDirectory.UNMAP_SUPPORTED) {
return new MMapDirectory(path, lockFactory);
} else if (Constants.WINDOWS) {
return new SimpleFSDirectory(path, lockFactory);
} else {
return new NIOFSDirectory(path, lockFactory);
}
}
针对64位的操作系统,且通用的一个Directory是新出的MMapDirectory,主要使用java的直接内存操纵索引文件,性能更佳,不过这还没有跟NIOFSDirectory比较过。。