3.3.12 遍历索引库
IndexReader提供了遍历索引库的接口。在遍历索引库之前可以先看一下索引库中文档的数量。
Directory directory = getDir(); //得到路径对象 IndexReader indexReader = DirectoryReader.open(directory); //取得IndexReader对象 System.out.println(indexReader.maxDoc()); // 打印文档数量
可以根据文档编号遍历索引库中的每个文档。
IndexReader reader = DirectoryReader.open(directory); int totalDocs = reader.numDocs(); //取得所有文档的数量 for(int m=0; m<totalDocs; m++){ Document thisDoc = reader.document(m); //取得索引库中的每个文档 }
检查文档是否已经被删除了,可以调用MultiFields.getLiveDocs(reader)方法。
Bits liveDocs = MultiFields.getLiveDocs(reader); //返回值可能为空 for (int i=0; i<reader.maxDoc(); i++) { if (liveDocs ! = null && ! liveDocs.get(i)) continue; Document doc = reader.document(i); }
文档编号是一个非负整数。在重新做索引后,文档的文档编号可能会改变。所以在重新打开IndexReader以后,原来的文档编号就失效了。
IndexSearcher也是通过IndexReader取得文档对象。
public class IndexSearcher { final IndexReader reader; public Document doc(int docID) throws IOException { return reader.document(docID); //通过IndexReader取得文档对象 } }
IndexSearcher.doc(int)方法只是为了方便从搜索结果中得到文档对象。
下面介绍与遍历索引库相关的几个类。TermEnum用来枚举一个给定的域中的所有项,而不管这个项在哪个文档中。例如:
String indexDir = "D:/test/chatindex"; FSDirectory directory = FSDirectory.open(new File(indexDir)); DirectoryReader reader = DirectoryReader.open(directory); // 读取索引文件里所有的Term Terms terms = SlowCompositeReaderWrapper.wrap(reader).terms("field"); TermsEnum termsEnum = terms.iterator(null); BytesRef term; while ((term = termsEnum.next()) ! = null) { String s = new String(term.bytes, term.offset, term.length); System.out.println("词: "+s); }
TermDocs和TermEnum不同,TermDocs用来识别哪个文档包含指定的项,并且它也会给出该项在文档中的词频。
TermFreqVector(即Term Frequency Vector或者简称Term Vector)是一个数据结构,包含一个指定文档的项和词频信息,并且当在索引期间存储项向量的时候,才能通过IndexReader检索出TermFreqVector。
所谓Term Vector,就是对于文档的某一列,如title、body这种文本类型的列,建立词频的多维向量空间。一个词就是一维,该维的值就是这个词在这个列中的频率。
getTerms()和getTermFrequencies()是并列的数组。也就是说,getTerms()[i]有一个文档频率getTermFrequencies()[i]。
例如,源文本见表3-4。
表3-4 源文本
词排序后的位置见表3-5。
表3-5 排序后位置
用下面的代码发现the出现的位置:
int index = termPositionVector.indexOf("the"); // 7 int positions = termPositionVector.getTermPositions(index); // {0, 6}
这里使用TermEnum按词遍历索引库,代码如下:
public static void getTerms(IndexReader reader) throws IOException { TermEnum terms = reader.terms(); // 读取索引文件里所有的Term while (terms.next()) {// 取出一个Term对象 String field = terms.term().field(); //列名 String text = terms.term().text(); //词 System.out.println(text+":"+field); } }
经常需要统计索引库中哪些词出现的频率最高。例如,需要统计旅游活动索引库中的热门目的地。可以先对目的地列做索引,索引列不分词,然后取得该列中最常出现的几个词,也就是热门目的地。实现方法是:可以用TermEnum遍历索引库中所有的词,取出每个词的文档频率,然后使用优先队列找出频率最高的几个词。
public class TermInfo { public Term term; //索引库中的词 public int docFreq; //文档频率,也就是这个词在多少个文档中出现过 public TermInfo(Term t, int df) { this.term = t; this.docFreq = df; } } //找出频率最高的numTerms个词 public static TermInfo[] getHighFreqTerms(IndexReader reader, int numTerms, String field){ //实例化一个TermInfo的队列 TermInfoQueue tiq = new TermInfoQueue(numTerms); TermEnum terms = reader.terms(); //读取索引文件里所有的term int minFreq = 0; //队列最后一个term的频率即当前最小频率值 while (terms.next()) {//取出一个term对象 String field = terms.term().field(); if (fields ! = null && fields.length > 0) { boolean skip = true; //跳过标识 for (int i = 0; i < fields.length; i++) { //当前Field属于fields数组中的某一个则处理对应的term if (field.equals(fields[i])) { skip = false; break; } } if (skip) continue; } //当前term的内容是过滤词,则直接跳过 if (junkWords ! = null && junkWords.get(terms.term().text()) ! = null) continue; /*获取最高频率的term。基本方法是: 队列底层是最大频率term,顶层是最小频率term, 当插入一个元素后超出初始化队列大小则取出最上面的那个元素, 重新设置最小频率值minFreq*/ if (terms.docFreq() > minFreq) {//当前Term的频率大于最小频率则插入队列 tiq.insertWithOverflow(new TermInfo(terms.term(), terms.docFreq())); if (tiq.size() >= numTerms) { //当队列中的元素个数大于numTerms tiq.pop(); // 取出最小频率的元素,即最上面的一个元素 minFreq = ((TermInfo)tiq.top()).docFreq; //重新设置最小频率 } } } //取出队列元素,最终存放在数组中元素的词频率按从大到小排列 TermInfo[] res = new TermInfo[tiq.size()]; for (int i = 0; i < res.length; i++) { res[res.length - i -1] = (TermInfo)tiq.pop(); } return res; }
四维枚举API指通过列、文档、词、位置四个维度来遍历索引库。