lucene原理及源碼解析--核心類

馬雲說:你們還沒搞清PC時代的時候,移動互聯網來了,還沒搞清移動互聯網的時候,大數據時代來了。java

然而,我看到的是:在PC時代搞PC的,移動互聯網時代搞移動互聯網的,大數據時代搞大數據的,都是同一夥兒人。數據庫

我就是一個作業務方向的,而回憶起真正作技術的時光,也就是大數據時代剛來臨的時候作搜索了。設計模式

搜索用的是solr框架,solr就是包裝了lucene實現了近實時索引。因此源頭仍是lucene。並且lucenejava寫的全文檢索庫,源碼是必定要研究一下的。緩存

剛纔提到全文檢索,要說它的概念先來談談數據。數據分爲結構化數據和全文數據。對於沒有什麼固定格式的全文數據的檢索就是全文檢索了。安全

對全文數據的搜索最原始的作法是順序掃描。就是找一個開始從頭至尾的匹配,匹配對了就記錄一下,而後繼續掃直到結束。Windows的搜索功能就是這麼作的。數據結構

順序掃描很慢,想要加快其搜索速度就要讓數據高度有序,即結構化。這種從全文中提取從新組織的信息稱爲索引。而lucene作的只有兩件事:創建索引和搜索索引。目前lucene的最新版是6.4.1,但本文的版本是我當初使用的使用的版本4.10.1. 記得那時每個月都有新版,我跟追劇似的追了很久。
多線程

首先說創建索引:打開文件目錄,這個目錄能夠是磁盤或者是內存。遍歷裏面的文件,將每一個文件的一個字段存成一個Field對象(至關於關係型數據庫的列),一條記錄存成一個Document(至關於關係型數據庫的行),寫入IndexWriter裏。併發

打開目錄用的FSDirectory對象的open函數。這個函數是一個Directory的工廠(工廠模式),其實打開類型的文件也是和環境進行了適配的,也能夠理解爲適配器模式。研究任何一種源碼都離不開其設計模式的。app

今天先介紹幾個核心的類。由於放在冰箱裏的水果被人偷吃了好多,寶寶不開心。下次記得必定要把袋子繫好口,儘可能往裏面放。因此今天多打算多些幾行代碼,忘掉我那可愛的蘋果,桂圓和小金桔。蘋果總共就四個,是誰那麼狠心忍心拿走一個啊[大哭][大哭]拿走個人橙子覺得我數不出來嗎,不知道我記性好啊,看一眼就知道幾個了啊。下次能不能拿點我看不出來的啊?你吃了個人東西給我留個條啊?沒準我知道你來不及吃早飯還給你送吃的過去呢,不帶這麼不客氣的!世界上最遙遠的距離,是你我同在一個辦公室裏,你偷吃了個人水果,我殊不知道你是誰。框架

Lucene核心類

     

lucene-core裏這幾個package列出了lucene核心作的幾件事情:解析,解碼,存儲格式,索引,搜索,存儲和工具類。

這裏面最好理解的是document包的內容:

這裏面全部以Field結尾的類就是定義字段格式的。除了各類Field,包裏就剩下Document用於存放Field和幾個字段格式的工具類。值得注意的是裏面有一個DocumentStoredFieldVisitor。咱們通常類命名除了類的功能屬性外,還有一種常見的結尾,就是設計模式名。這個類一看就知道是運用了訪問者模式。源碼很簡單:

public class DocumentStoredFieldVisitor extends StoredFieldVisitor {

private final Document doc = new Document();
private final Set<String> fieldsToAdd;


public DocumentStoredFieldVisitor(Set<String> fieldsToAdd) {
this.fieldsToAdd = fieldsToAdd;
}

/** Load only fields named in the provided fields. */
public DocumentStoredFieldVisitor(String... fields) {
fieldsToAdd = new HashSet<>(fields.length);
for(String field : fields) {
fieldsToAdd.add(field);
}
}

/** Load all stored fields. */
public DocumentStoredFieldVisitor() {
this.fieldsToAdd = null;
}

@Override
public void binaryField(FieldInfo fieldInfo, byte[] value) throws IOException {
doc.add(new StoredField(fieldInfo.name, value));
}

@Override
public void stringField(FieldInfo fieldInfo, String value) throws IOException {
final FieldType ft = new FieldType(TextField.TYPE_STORED);
ft.setStoreTermVectors(fieldInfo.hasVectors());
ft.setIndexed(fieldInfo.isIndexed());
ft.setOmitNorms(fieldInfo.omitsNorms());
ft.setIndexOptions(fieldInfo.getIndexOptions());
doc.add(new Field(fieldInfo.name, value, ft));
}

@Override
public void intField(FieldInfo fieldInfo, int value) {
doc.add(new StoredField(fieldInfo.name, value));
}

@Override
public void longField(FieldInfo fieldInfo, long value) {
doc.add(new StoredField(fieldInfo.name, value));
}

@Override
public void floatField(FieldInfo fieldInfo, float value) {
doc.add(new StoredField(fieldInfo.name, value));
}

@Override
public void doubleField(FieldInfo fieldInfo, double value) {
doc.add(new StoredField(fieldInfo.name, value));
}

@Override
public Status needsField(FieldInfo fieldInfo) throws IOException {
return fieldsToAdd == null || fieldsToAdd.contains(fieldInfo.name) ? Status.YES : Status.NO;
}

public Document getDocument() {
return doc;
}
}

 訪問者模式把數據結構和做用於結構上的操做解耦合,使得操做集合可相對自由地演化。具體到這個類就是不論是什麼類型的數據,我都把它添加到Document對象裏。

 store這個包主要是作IO的:

這裏面值得注意是在作IO的時候用到了LockFactory,一個鎖工廠。這是典型的工廠模式自沒必要說。它的主要做用是防止讀寫Directory的併發。下面是BaseDirectory的源碼:

public abstract class BaseDirectory extends Directory {

volatile protected boolean isOpen = true;

protected LockFactory lockFactory;

/** Sole constructor. */
protected BaseDirectory() {
super();
}

@Override
public Lock makeLock(String name) {
return lockFactory.makeLock(name);
}

@Override
public void clearLock(String name) throws IOException {
if (lockFactory != null) {
lockFactory.clearLock(name);
}
}

@Override
public void setLockFactory(LockFactory lockFactory) throws IOException {
assert lockFactory != null;
this.lockFactory = lockFactory;
lockFactory.setLockPrefix(this.getLockID());
}

@Override
public LockFactory getLockFactory() {
return this.lockFactory;
}

@Override
protected final void ensureOpen() throws AlreadyClosedException {
if (!isOpen)
throw new AlreadyClosedException("this Directory is closed");
}

}

BaseDirectory基本上就作了一件事:就是管理鎖工廠。下面重點來講一下Directory相關的類:

 

仍是Type Hierarchy更直觀些:

從上面兩個圖能夠看出Directory的直接子類就只有BaseDirectoy和FilterDirectory兩個。
其中FilterDirectory的子類都是給Directory添加附加的功能:TrackingDirectoryWrapper是記錄文件的寫入或刪除;NRTCachingDirectory是提供對RAMDirectory的緩存,達到近實時的效果(NRT:near real time);RateLimitedDirecotyWrapper是經過IOContext來限制讀寫速率。這些都是裝飾器模式的表明。

BaseDirectory的子類中,FileSwitchDirectory針對lucene的不一樣的索引文件使用不一樣的Directory;CompoundFileDirectory用於訪問一個組合的數據流;RAMDirectory是常駐內存的Directory實現。FSDirectory是文件系統的Directory(FS:File System ),它的三種實現:SimpleFSDirectory,它的併發支持能力有限;NIOFSDirectory支持默認線程安全的多線程讀取;MMapDirectory是經過內存映射讀取的Directory。

下面是analysis包,這個是用來分詞的,我在當初作項目的時候用的是IK分詞器。由於歪果仁的分詞器對中文的支持不太好,什麼自帶的標準分詞器啥的都是一個個字就是一個分詞了。中國人比較人性化的是基於詞典的分詞器。標準分詞器在對姓名這種組合詞組沒有什麼意義的時候有用。因此在作垂直搜索的時候,能夠對不一樣的列採用不一樣的分詞器。好比一篇文章存成一個Document,裏面有不一樣的Field。標題和內容是TextField,能夠用基於詞典的分詞器拆分。發表時間是一個LongField(我記得我當初用的時候沒有專門的時間Field)不可拆分,能夠排序。標題能夠是StringField不可拆分,也能夠用TextField標準分詞器來拆分分詞,看業務需求。

 由於如今看的建立索引的過程,那麼index那個包裏先只看IndexWriter。代碼比較多,我就不貼源碼了。

  一個IndexWriter對象只建立並維護一個索引。IndexWriter經過Directory以及Analyzer來構建,給它畫個類圖仍是頗有意義的:

 

話說咱也是會用UML建模工具的,只是以爲手繪比較快,顯然歪果仁和我有一樣的看法,他們還專門起個名字叫:freehand

 Lucene打開一個IndexWriter就會把它Lock住,不釋放,再次訪問就會拋出Lock obtain timed out異常。Document寫入索引文件的時候也是先寫入小的segment分段索引而後再按必定機制合併的。我在運行的時候,會觀察磁盤的變化。好比:能發如今IndexWriter在打開後磁盤會多出一個write.lock文件。IndexWriter關閉時文件被刪除。你還能夠看到分段文件到大索引文件的合併過程。還能夠對比全文數據到索引文件大小的變化。

相關文章
相關標籤/搜索