如果在任何写入之前打开,Lucene DirectoryReader为什么看不到IndexWriter所做的任何更改?

时间:2015-12-13 13:05:08

标签: java lucene

我正在尝试创建Lucene IndexWriterDirectoryReader并保持打开以供将来使用。问题 - 如果在提交之前实例化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的存在,请不要对此发表评论。

1 个答案:

答案 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();
    // ...
  }
}

同样,最好使用单独的线程进行定期刷新。 无论哪种方式都可以为您提供预期的输出。