IndexReader reader;
問題出來了,IndexReader是壹個abstract類,不能實例化。那好,換派生類試試看。找到IndexReader的兩個孩子——SegmentReader和MultiReader。用哪個呢?無論是哪個都需要壹大堆參數(我是頗費了周折才搞清楚它們的用途,後面再解釋),似乎想用Lucene的索引數據不是那麽容易啊。通過跟蹤代碼和查閱文檔,我終於找到使用IndexReader的鑰匙。原來IndexReader有壹個“工廠模式”的static interface——IndexReader.Open。定義如下:
#0001 public static IndexReader Open(System.String path)
#0002 public static IndexReader Open(System.IO.FileInfo path)
#0003 public static IndexReader Open(Directory directory)
#0004 private static IndexReader Open(Directory directory, bool closeDirectory)
其中有三個是public的接口,可供調用。打開壹個索引,就是這麽簡單:
#0001 IndexReader reader = IndexReader.Open(index);
實際上,這個打開索引經歷了這樣的壹個過程:
#0001 SegmentInfos infos = new SegmentInfos();
#0002 Directory directory = FSDirectory.GetDirectory(index, false);
#0003 infos.Read(directory);
#0004 bool closeDirectory = false;
#0005 if (infos.Count == 1)
#0006 {
#0007 // index is optimized
#0008 return new SegmentReader(infos, infos.Info(0), closeDirectory);
#0009 }
#0010 else
#0011 {
#0012 IndexReader[] readers = new IndexReader[infos.Count];
#0013 for (int i = 0; i < infos.Count; i++)
#0014 readers[i] = new SegmentReader(infos.Info(i));
#0015 return new MultiReader(directory, infos, closeDirectory, readers);
#0016 }
首先要讀入索引的段信息(segment information, #0001~#0003),然後看壹下有幾個段:如果只有壹個,那麽可能是優化過的,直接讀取這壹個段就可以(#0008);否則需要壹次讀入各個段(#0013~#0014),然後再拼成壹個MultiReader(#0015)。打開索引文件的過程就是這樣。
接下來我們要看看如何讀取信息了。用下面這段代碼來說明。
#0001 public static void PrintIndex(IndexReader reader)
#0002 {
#0003 //顯示有多少個document
#0004 System.Console.WriteLine(reader + "\tNumDocs = " + reader.NumDocs());
#0005 for (int i = 0; i < reader.NumDocs(); i++)
#0006 {
#0007 System.Console.WriteLine(reader.Document(i));
#0008 }
#0009
#0010 //枚舉term,獲得<document, term freq, position* >信息
#0011 TermEnum termEnum = reader.Terms();
#0012 while (termEnum.Next())
#0013 {
#0014 System.Console.Write(termEnum.Term());
#0015 System.Console.WriteLine("\tDocFreq=" + termEnum.DocFreq());
#0016
#0017 TermPositions termPositions = reader.TermPositions(termEnum.Term());
#0018 int i = 0;
#0019 int j = 0;
#0020 while (termPositions.Next())
#0021 {
#0022 System.Console.WriteLine((i++) + "->" + " DocNo:" + termPositions.Doc() + ", Freq:" + termPositions.Freq());
#0023 for (j = 0; j < termPositions.Freq(); j++)
#0024 System.Console.Write("[" + termPositions.NextPosition() + "]");
#0025 System.Console.WriteLine();
#0026 }
#0027
#0028 //直接獲取 <term freq, document> 的信息
#0029 TermDocs termDocs = reader.TermDocs(termEnum.Term());
#0030 while (termDocs.Next())
#0031 {
#0032 System.Console.WriteLine((i++) + "->" + " DocNo:" + termDocs.Doc() + ", Freq:" + termDocs.Freq());
#0033 }
#0034 }
#0035
#0036 // FieldInfos fieldInfos = reader.fieldInfos;
#0037 // FieldInfo pathFieldInfo = fieldInfos.FieldInfo("path");
#0038
#0039 //顯示 term frequency vector
#0040 for (int i = 0; i < reader.NumDocs(); i++)
#0041 {
#0042 //對contents的token之後的term存於了TermFreqVector
#0043 TermFreqVector termFreqVector = reader.GetTermFreqVector(i, "contents");
#0044
#0045 if (termFreqVector == null)
#0046 {
#0047 System.Console.WriteLine("termFreqVector is null.");
#0048 continue;
#0049 }
#0050
#0051 String fieldName = termFreqVector.GetField();
#0052 String[] terms = termFreqVector.GetTerms();
#0053 int[] frequences = termFreqVector.GetTermFrequencies();
#0054
#0055 System.Console.Write("FieldName:" + fieldName);
#0056 for (int j = 0; j < terms.Length; j++)
#0057 {
#0058 System.Console.Write("[" + terms[j] + ":" + frequences[j] + "]");
#0059 }
#0060 System.Console.WriteLine();
#0061 }
#0062 System.Console.WriteLine();
#0063 }
#0004 計算document的個數
#0012~#0034 枚舉collection中所有的term
其中#0017~#0026 枚舉每個term在出現的document中的所有位置(第幾個詞,從1開始計數);#0029~#0033 計算每個term出現在哪些文檔和相應的出現頻度(即DF和TF)。
#0036~#0037在reader是SegmentReader類型的情況下有效。
#0040~#0061可以快速的讀取某篇文檔中出現的term和相應的頻度。但是這部分需要在建索引時,設置storeTermVector為true。比如
doc.Add(Field.Text("contents", reader, true));
其中的第三項即是。默認為false。
有了這些數據,就可以統計我需要的數據了。以後我會介紹如何建立索引,如何應用Lucene。