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的執行個體。
圖 1.1.1 Directory類圖
1.2 IndexInput和IndexOutput
IndexInput負責讀取,IndexOutput負責寫入。其子類負責具體的工作。這裡暫且不多講。
圖1.2.1 IndexOutput和IndexInput
1.3 RAMFile
RAMFile是Lucene檔案系統記憶體檔案的基本單位。一個Directory可以包含N個RAMFile(N >= 2)。
二、索引概述
這裡不討論索引的邏輯結構,而只單純地看索引是如何在記憶體中儲存,如何持久化到硬碟。
圖 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版本。)
代碼 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)));
}
}
}
代碼 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