Directory类用来维护索引目录中的索引文件,定义了 创建
、 打开
、 删除
、 读取
、 重命名
、 同步
(持久化索引文件至磁盘)、 校验和
(checksum computing)等抽象方法。
索引目录中不存在多级目录,即不存在子文件夹的层次结构(no sub-folder hierarchy)。
其子类如下图所示, 另外下图中只列出了Lucene7.5.0的core模块中的子类,在其他模块,比如在misc模块中还有很多其他的子类 :
图1:
点击查看大图
接下来一一介绍其子类。
BaseDirectory同样是一个抽象类,提供了其子类共有的 获取索引文件锁
的方法,即维护了一个LockFactory对象,索引文件锁的概念已经在前面的文章中介绍,这里不赘述。 下图中为BaseDirectory类的子类:
图2:
图3:
FSDirectory作为一个抽象类,提供了其子类共有的 创建
、 删除
、 重命名
、 同步
(持久化索引文件至磁盘)、 校验和
(checksum computing)等方法,这些方法在介绍完三个子类后再叙述。
FSDirectory的三个子类主要的不同点在于它们各自实现了 打开
、 读取
索引文件的方法。
打开
一个索引文件,比如说通过DirectoryReader.open(IndexWriter)读取索引文件信息会调用此方法 读取
索引文件,使得可以随机访问索引文件的一块连续数据。 随机访问索引文件的一块连续数据在Lucene中是很重要的,例如图4中画出了.doc索引文件的数据结构,索引文件按照域(field)划分,在读取阶段,Lucene总是按域逐个处理,所以需要获取每一个域在.doc索引文件中的数据区域。
图4:
使用SimpleFSDirectory有以下注意点:
读取
索引文件,因为它是不可打断的(not interruptible)。RAFDirectory已经作为一个旧的API(legacy API)被丢到了misc模块中,它同样不支持并发读取索引文件,所以跟SimpleFSDirectory很类似,不展开介绍 打开
一个索引文件 读取
索引文件,使得可以随机访问索引文件的一块连续数据。 使用NIOFSDirectory有以下注意点:
打开
如果内存映射失败,导致的原因可能是内存中连续的虚拟地址空间的数量(unfragmented virtual address space)不足、操作系统的内存映射大小限制等,更多的原因可以看这里 MapFailed ,Lucene7.5.0中根据不同的情况提供了下面几种出错信息:
以下内容选自<< Linux/UNIX系统编程手册(下册) >>,文字太多,直接上个截图吧:
图5:
内存映射I/O之所以能够带来性能优势的原因如下:
图6:
内存映射I/O优缺点:
图7:
由于JVM的限制,并没有提供取消映射的方法,故在某些JDK版本会存在这么一个问题,即用户调用了FileChannel的close()方法,但是无法关闭操作系统层面的该文件的文件描述符,直到GC回收才能关闭。这样的情况会导致以下的问题,调用了FileChannel的close()方法并且在GC回收前的时间区间内,执行了删除或者覆盖文件的操作,如果是Windows平台,那么会导致抛出异常,不过在其他的平台,基于"delete on last close"的语义,还是能正确的执行,不过会有短暂的额外的磁盘开销(该文件还未被删除),虽然不会有影响但是还要需要知晓这个问题,上述描述的问题可以查看这个 BUG 。
针对无法通过JDK提供的显示方法来取消映射的问题,Lucene提供了一个替代方法(workaround),即unmapHackImpl()方法, 使得可以用户在调用了FileChannel的close()方法后能通过一个native的invokeExact方法来取消映射,unmapHackImpl()的实现不展开,感兴趣的可以 点击这里 查看实现逻辑。
使用unmapHackImpl()方法有以下必须满足的要求:
最后如果出于某些原因,不需要使用unmapHackImpl()方法,那么可以通过setUseUnmap()来取消该功能。
使用MMapDirectory有以下注意点:
由于操作系统的多样性,Lucene无法用一个FSDirectory类来满足所有的平台要求,因此在FSDirectory类中提供了open()方法,让Lucene根据当前的运行平台来选择一个合适的FSDirectory对象,即为用户从SimpleFSDirectory、MMapDirectory、NIOFSDirectory中选出一个合适的FSDirectory对象,当然用户可以通过new的方式直接使用这些FSDirectory对象。
根据不同的条件使用对应的FSDirectory对象:
FSDirectory提供了其子类共有的 创建
、 删除
、 重命名
、 同步
(持久化索引文件至磁盘)、 校验和
(checksum computing)等方法。
创建一个用来存放某个索引文件信息的对象,核心部分即使用 BufferedOutputStream 对象来存放数据。
在一些情况下需要删除索引文件,至少包括以下情况:
另外有一个pendingDeletes的Set对象,当索引文件无法被删除时,pendingDeletes会记录该文件,并且在执行 创建
、 删除
、 重命名
、 同步
时会尝试再次删除这些文件,本该被删除的索引文件如果还留在索引目录中,可能会导致一些问题,比如被错误的合并、被错误的重命名(下文会介绍)。
当删除该索引文件并且失败后,此索引文件会被添加到pendingDeletes中,导致无法被删除的原因,至少包括以下情况:
使用 Files.move() 方法实现重命名。
将内存中的索引文件同步(持久化)到磁盘,使用FileChannel.force()方法,如果同步某个文件抛出I/O异常,那么往上传递,如果同步的是目录,捕获异常,这样区分的目的由于篇幅原因不展开叙述,感兴趣请看这篇 博客 ,这篇博客是Lucene源码中的推荐,博客内容介绍了同步磁盘的知识点。如果链接失效,在附件中可以看到对应的PDF。
校验和的内容在后面介绍CodeUtil类时会详细介绍。
ByteBuffersDirectory使用堆,即通过一个ConcurrentHashMap来存储所有存储索引文件,其实key是索引文件名。
xxxxxxxxxx
private final ConcurrentHashMap<String, FileEntry> files = new ConcurrentHashMap<>();
ByteBuffersDirectory适合用于存储体积较小,不需要持久化的临时索引文件,在这种情况下比MMapDirectory更有优势,因为它没有磁盘同步的开销。
RAMDirectory使用自定义的byte[]数组来存储索引文件信息,并且该数组最多存放1024个字节,所以如果索引大小为indexSize个字节,那么内存中就会有( indexSize / 1024 )个byte[]数组,当indexSize超过hundred megabytes后时会造成资源浪费,比如回收周期(GC cycles)问题。
RAMDirectory已经被置为@Deprecated,所以不详细展开。
本文介绍Lucene7.5.0的core模块中的BaseDirectory类及其子类。
点击下载Markdown文档