我正在尝试创建Lucene IndexWriter
和DirectoryReader
并保持打开以供将来使用。问题 - 如果在提交之前实例化DirectoryReader,则DirectoryReader不会看到提交给索引的任何更改。
请参阅以下代码:
public class SearchLayer1 {
private final RAMDirectory directory;
private final IndexWriter indexWriter;
private final DirectoryReader directoryReader;
public SearchLayer1() throws IOException {
this.directory = new RAMDirectory();
IndexWriterConfig config = new IndexWriterConfig(new SimpleAnalyzer());
this.indexWriter = new IndexWriter(directory, config);
this.directoryReader = DirectoryReader.open(indexWriter, false);
}
public void add() throws IOException, InterruptedException {
Document doc = new Document();
String text = "This is the text to be indexed.";
doc.add(new StringField("fieldname", text, Field.Store.YES));
indexWriter.addDocument(doc);
indexWriter.commit();
doc = new Document();
doc.add(new StringField("fieldname", text, Field.Store.YES));
indexWriter.addDocument(doc);
indexWriter.commit();
}
public void experiment() throws IOException, ParseException {
//IT WORKS IF THE DirectoryReader IS OPENED AFTER SOME DOCUMENTS ARE ADDED TO THE INDEX
//DirectoryReader directoryReader = DirectoryReader.open(indexWriter, false);
IndexSearcher isearcher = new IndexSearcher(directoryReader);
Query query = new TermQuery(new Term("fieldname", "This is the text to be indexed."));
ScoreDoc[] hits = isearcher.search(query, null, 1000).scoreDocs;
for (int i = 0; i < hits.length; i++) {
Document hitDoc = isearcher.doc(hits[i].doc);
System.out.println("==========> " + hitDoc.get("fieldname"));
}
directoryReader.close();
}
public void close() throws IOException {
indexWriter.close();
directory.close();
}
}
然后在单元测试中执行:
@Test
public void experiment() throws Exception {
SearchLayer1 searchLayer1 = new SearchLayer1();
searchLayer1.add();
searchLayer1.experiment();
searchLayer1.close();
}
我希望“==========&gt;这是要编入索引的文字。”要打印两次,但除非我将DirectoryReader实例化向下移动到experiment()
方法,否则它不会打印。
那么为什么DirectoryReader看不到任何提交? 我正在使用lucene 5.3.1
P.S 是的我知道某些方法的弃用以及QueryParsers的存在,请不要对此发表评论。
答案 0 :(得分:2)
基本上,这就是Lucene的工作原理。
如果您打开IndexReader(例如,通过DirectoryReader.open
),您将获得该特定时刻存在的索引的时间点视图,并且在您再次打开IndexReader之前它将不会更改,无论索引活动如何在IndexWriter上。
但是,不要再关闭并再次打开IndexReader。你想要做的是重新打开现有的阅读器。这样,只有新的段可以打开,现有的段可以重用,而不是总是读取完整的索引(这是一个昂贵的操作)。
重新打开这样的(省略代码没有改变):
public class SearchLayer1 {
// ...
private DirectoryReader directoryReader;
private IndexSearcher indexSearcher;
public SearchLayer1() throws IOException {
// ...
this.directoryReader = DirectoryReader.open(indexWriter, false);
this.indexSearcher = new IndexSearcher(directoryReader);
}
// ...
private void refreshReader() throws IOException {
DirectoryReader newReader = DirectoryReader.openIfChanged(this.directoryReader);
if (newReader != null && newReader != this.directoryReader) {
this.directoryReader.close();
this.directoryReader = newReader;
this.indexSearcher = new IndexSearcher(this.directoryReader);
}
}
public void experiment() throws IOException {
refreshReader();
IndexSearcher isearcher = this.indexSearcher;
// ...
}
public void close() throws IOException {
directoryReader.close();
// ...
}
}
在每次搜索之前刷新确保您始终可以看到 最新的更改,但刷新操作可能非常昂贵,特别是如果合并已经发生并且必须打开大段。 通常情况下,您有一个计划的线程,以某个间隔(例如每秒)运行刷新。
此外,处理刷新操作本身相当低级。
我建议的代码在这种情况下不涉及任何异常处理
必须关闭旧阅读器或新阅读器的正确实例
如果您有专用的刷新线程,则可能不会关闭decRef
而是关闭。建议使用SearcherManager
代码更容易,建议使用
import org.apache.lucene.search.SearcherManager;
// ...
public class SearchLayer1 {
// ...
private final SearcherManager searcherManager;
public SearchLayer1() throws IOException {
// ...
this.searcherManager = new SearcherManager(indexWriter, false, null);
}
// ...
public void experiment() throws IOException {
searcherManager.maybeRefresh();
IndexSearcher isearcher = searcherManager.acquire();
try {
// ...
} finally {
searcherManager.release(isearcher);
}
}
public void close() throws IOException {
searcherManager.close();
// ...
}
}
同样,最好使用单独的线程进行定期刷新。 无论哪种方式都可以为您提供预期的输出。