《Lucene天书》 Lucene的文件系统

本文深入剖析了Lucene的索引机制,包括其在内存中的存储方式及如何持久化到硬盘的过程。从Directory类开始,介绍IndexInput和IndexOutput的作用,并详细分析RAMFile和RAMOutputStream的工作原理。

Lucene的文件系统分为内存和硬盘两个部分,文件逻辑组织方式暂且不提,本文将关注其物理结构,包括它在内存中如何存放,以及如何写入硬盘。

目录
一、相关类
   1.1 Directory
   1.2 IndexInput和IndexOutput
   1.3 RAMFile
二、索引概述
   2.1 IndexOutput
 2.2 RAMOutputStream和RAMFile
   2.3 内存文件是如何写入硬盘的

一、相关类

1.1 Directory
一个Directory在Lucene系统中可以被称为“一份索引”,可以有许多份索引共同提供搜索数据源,而一个Directory就是对其每份索引的描述。它可以存放在内存中,也可以存放于硬盘上。存放在内存中的时候,是使用它子类RAMDirectory的实例,存放在硬盘上的时候是使用FSDirectory的实例。

2009021813335560.gif

图 1.1.1 Directory类图

1.2 IndexInput和IndexOutput
IndexInput负责读取,IndexOutput负责写入。其子类负责具体的工作。这里暂且不多讲。

2009021813394916.gif
图1.2.1 IndexOutput和IndexInput

1.3 RAMFile
RAMFile是Lucene文件系统内存文件的基本单位。一个Directory可以包含N个RAMFile(N >= 2)。

二、索引概述
这里不讨论索引的逻辑结构,而只单纯地看索引是如何在内存中存储,如何持久化到硬盘。

2009021814135985.gif
图 2.1 索引过程

图2.1体现整个过程。首先调用IndexWriter类的AddDocument方法,这个是外部调用,走入内部,是由IndexWriter调用了DocumentWriter的AddDocument方法。然后DocumentWriter完成调用自身的UpdateDocument,ProcessDocument,ProcessField方法,再转入FieldWriter的WriteField方法,这个方法又调用了IndexOutput里WriteInt或者其它一些按类型写入的方法,最终由RAMOutputStream调用自身的WriteByte写入内存。(IndexOutput里的方法实际存在于RAMOutputStream的实例。)

2.1 IndexOutput
IndexOutput主要定义了一些按类型写入的方法,最终都会走向虚方法WriteByte。这些方法在我写的解读Lucene中都有讲到。值得一提的是WriteChars方法,这个方法看似很复杂,但是其完成的功能却很简单,其实是做了.Net Framework中的Encoding.UTF8.GetBytes方法的工作。看下代码2.1.1和2.1.2的相似之处就可以明白。(代码2.1.1摘自Lucene IndexOutput源码,2.1.2摘自.Net Framework UTF8Encoding类源码。其自己实现是因为翻译自Java版本。)

ContractedBlock.gif ExpandedBlockStart.gif 代码 2.1.1
public virtual void  WriteChars(char[] s, int start, int length)
{
    
int end = start + length;
    
for (int i = start; i < end; i++)
    {
        
int code = (int) s[i];
        
if (code >= 0x01 && code <= 0x7F)
            WriteByte((
byte) code);
        
else if (((code >= 0x80&& (code <= 0x7FF)) || code == 0)
        {
            WriteByte((
byte) (0xC0 | (code >> 6)));
            WriteByte((
byte) (0x80 | (code & 0x3F)));
        }
        
else
        {
            WriteByte((
byte) (0xE0 | (SupportClass.Number.URShift(code, 12))));
            WriteByte((
byte) (0x80 | ((code >> 6& 0x3F)));
            WriteByte((
byte) (0x80 | (code & 0x3F)));
        }
    }
}

 

ContractedBlock.gif ExpandedBlockStart.gif 代码 2.1.2
// Count bytes needed 
int bytesNeeded = 1;
if (ch > 0x7F) {
    
if (ch > 0x7FF) {
        
if (ch > 0xFFFF) { 
            bytesNeeded
++;  // 4 bytes (surrogate pair)
        } 
        bytesNeeded
++;      // 3 bytes (800-FFFF) 
    }
    bytesNeeded
++;          // 2 bytes (80-7FF) 
}

if (ch <= 0x7F) { 
    
*pTarget = (byte)ch; 
}
else { 
    
// use separate helper variables for local contexts so that the jit optimizations
    
// won't get confused about the variable lifetimes
    int chb;
    
if (ch <= 0x7FF) { 
        
// 2 byte encoding
        chb = (byte)(unchecked((sbyte)0xC0| (ch >> 6)); 
    } 
    
else
    { 
        
if (ch <= 0xFFFF) {
            chb 
= (byte)(unchecked((sbyte)0xE0| (ch >> 12));
        }
        
else 
        {
            
*pTarget = (byte)(unchecked((sbyte)0xF0| (ch >> 18)); 
            pTarget
++

            chb 
= unchecked((sbyte)0x80| (ch >> 12& 0x3F
        }
        
*pTarget = (byte)chb;
        pTarget
++;

        chb 
= unchecked((sbyte)0x80| (ch >> 6& 0x3F;
    } 
    
*pTarget = (byte)chb; 
    pTarget
++;

    
*pTarget = (byte)(unchecked((sbyte)0x80| ch & 0x3F);
}


2.2 RAMOutputStream和RAMFile
RAMOutputStream是IndexOutput的一个子类,其主要点在于三个元素。
1、BUFFER_SIZE 是其缓冲片段的长度;
2、RAMFile 内存文件实例;
3、它的一些方法。
它的方法最终都是完成把数据写入缓冲片段,再把缓冲片段存入RAMFile的实例。它们之间就是这个关系。
RAMOutputStream需要写入数据,最终会写入一个byte数组,这个数组的长度就是BUFFER_SIZE,由RAMOutputStream调用RAMFile的AddBuffer方法负责创建这个数组,同时把这个数组作为一个节点存入RAMFile的ArrayList。也就是说RAMFile代表的一个内存文件,实际上是一个ArrayList数组。而ArrayList数组的每个索引又都由一个byte数组组成,这个byte数组的长度是BUFFER_SIZE。RAMOutputStream的WriteByte方法负责把数据写入byte数组。(看到这个是不是觉得Lucene的文件系统根本就不是什么天书嘛,也是很好理解的嘛,呵呵。)

2.3 内存文件如何写入硬盘的
Lucene文件系统在硬盘的表现实例就是FSDirectory。这个类会负责创建其文件系统所需的描述性文件和数据文件。而具体写入硬盘的工作是由它的内嵌类FSIndexOutput来完成的。这也就是我们的图1.2.1没有描绘到这个类的原因,这个类继承自BufferedIndexOutput。BufferedIndexOutput大体上与RAMOutputStream差不多,区别在于其Flush方法与会调用到FSIndexOutput的FlushBuffer方法,最终把RAMFile的数据写入硬盘文件。

时间原因,这里就不详细介绍了,感兴趣的朋友可以自己去实验一下。

______________________________________________________________________________________________
2009-2-18 by yurow
@ http://birdshover.cnblogs.com

转载于:https://www.cnblogs.com/birdshover/archive/2009/02/18/1393197.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值