8) 第二章 索引:基本索引操作
?
? ? 先上示例代码,原意看的就看,不愿意看的先略过,回头再对照着看也行:
?
import java.io.IOException;import junit.framework.TestCase;import org.apache.lucene.analysis.WhitespaceAnalyzer;import org.apache.lucene.document.Document;import org.apache.lucene.document.Field;import org.apache.lucene.index.IndexReader;import org.apache.lucene.index.IndexWriter;import org.apache.lucene.index.Term;import org.apache.lucene.search.IndexSearcher;import org.apache.lucene.search.Query;import org.apache.lucene.search.TermQuery;import org.apache.lucene.store.Directory;import org.apache.lucene.store.RAMDirectory;/** * 示例代码: * Lucene中document的基本操作 * (新增、删除、更新) */public class IndexingTest extends TestCase {protected String[] ids = { "1", "2" };protected String[] unindexed = { "Netherlands", "Italy" };protected String[] unstored = { "Amsterdam has lots of bridges","Venice has lots of canals" };protected String[] text = { "Amsterdam", "Venice" };private Directory directory;//---------------------------【新增】protected void setUp() throws Exception {// A setUp()方法会在每一个测试之前运行,它先创建了一个RAMDirectory用来存储索引信息directory = new RAMDirectory();// B 参见 D#IndexWriter writer = getWriter(); // C 遍历所有内容,创建Document及Fields并将Document加入索引for (int i = 0; i < ids.length; i++) {Document doc = new Document();doc.add(new Field("id", ids[i], Field.Store.YES,Field.Index.NOT_ANALYZED));doc.add(new Field("country", unindexed[i], Field.Store.YES,Field.Index.NO));doc.add(new Field("contents", unstored[i], Field.Store.NO,Field.Index.ANALYZED));doc.add(new Field("city", text[i], Field.Store.YES,Field.Index.ANALYZED));writer.addDocument(doc);}writer.close();}// D 在RAMDirectory上创建IndexWriterprivate IndexWriter getWriter() throws IOException { return new IndexWriter(directory, new WhitespaceAnalyzer(),IndexWriter.MaxFieldLength.UNLIMITED);}protected int getHitCount(String fieldName, String searchString)throws IOException {// E 创建SearcherIndexSearcher searcher = new IndexSearcher(directory); Term t = new Term(fieldName, searchString);// F 创建简单的单一Term的查询Query query = new TermQuery(t); // G 获取结果集的数量int hitCount = TestUtil.hitCount(searcher, query); searcher.close();return hitCount;}public void testIndexWriter() throws IOException {IndexWriter writer = getWriter();// H 验证writer中document的数量assertEquals(ids.length, writer.numDocs()); writer.close();}public void testIndexReader() throws IOException {IndexReader reader = IndexReader.open(directory);// I 验证searcher中document的数量assertEquals(ids.length, reader.maxDoc());assertEquals(ids.length, reader.numDocs());reader.close();}//---------------------------【删除】public void testDeleteBeforeIndexMerge() throws IOException {IndexWriter writer = getWriter();// 1 索引中有2个documentassertEquals(2, writer.numDocs());// 2 删除第一个documentwriter.deleteDocuments(new Term("id", "1"));writer.commit();// 3 验证索引中包含了删除操作assertTrue(writer.hasDeletions()); // 4 一个被索引的document 一个被删除的documentassertEquals(2, writer.maxDoc());// 5 仅有一个被索引的documentassertEquals(1, writer.numDocs());writer.close();}public void testDeleteAfterIndexMerge() throws IOException {IndexWriter writer = getWriter();assertEquals(2, writer.numDocs());writer.deleteDocuments(new Term("id", "1"));// 5 优化压缩索引writer.optimize(); writer.commit();assertFalse(writer.hasDeletions());// 6 合并后仅剩一个被索引的documentassertEquals(1, writer.maxDoc()); assertEquals(1, writer.numDocs());writer.close();}//---------------------------【更新】public void testUpdate() throws IOException {assertEquals(1, getHitCount("city", "Amsterdam"));IndexWriter writer = getWriter();// 1 创建新的document用来更新, 其city域为HaagDocument doc = new Document();doc.add(new Field("id", "1", Field.Store.YES, Field.Index.NOT_ANALYZED));doc.add(new Field("country", "Netherlands", Field.Store.YES, Field.Index.NO));doc.add(new Field("contents", "Amsterdam has lots of bridges", Field.Store.NO, Field.Index.ANALYZED));doc.add(new Field("city", "Haag", Field.Store.YES, Field.Index.ANALYZED));// 2 用新创建的document替换之前的writer.updateDocument(new Term("id", "1"), doc);writer.close();// 3 验证原来的document不存在了assertEquals(0, getHitCount("city", "Amsterdam"));// 4 验证新的document被加入索引中assertEquals(1, getHitCount("city", "Haag")); }}
?
?还需要一个辅助类上面的代码才能够正确运行:
?
import java.io.IOException;import org.apache.lucene.search.IndexSearcher;import org.apache.lucene.search.Query;public class TestUtil {public static int hitCount(IndexSearcher searcher, Query query)throws IOException {return searcher.search(query, 1).totalHits;}}
?
?
?
1. 新增Document
? ? 新增方法有两个:
addDocument(Document) - 采用默认的分析器(analyzer)添加文档addDocument(Document, Analyzer) - 采用指定的分析器添加文档
?? ?需要注意的是,搜索阶段所使用的分析器必须与索引阶段使用的一致,才能正确的搜索到相应结果。
? ? 为了简化问题,示例代码中,待索引的内容为String型,而在实际应用中,它们可能来自PDF等,需要先解析哦!
? ? 在我们的例子中,构造IndexWriter共传入了三个参数:第一个参数Directory表示索引存储的位置(这里是内存);第二个参数是索引过程中所使用的分析器;第三个参数 MaxFieldLength.UNLIMITED 示意IndexWriter要索引文档中所有的词元。如果IndexWriter检测到指定的Directory中并没有index文件存在,它会先创建一个,否则,IndexWriter会仅为其增加新的document.
? ? IndexWriter还有其它许多构造方法,其中一些接受String或File参数以创建FSDirectory, 还有一些允许你指定是想强制性地创建新索引还是覆盖之前的。更高级的构造器,则允许你设计自己的索引删除策略或是提交策略等。
?
2. 删除Document
? ? 删除方法有四个:? ? ??
deleteDocuments(Term);deleteDocuments(Term[]);deleteDocuments(Query);deleteDocuments(Query[]);
? ? 从它们的参数中你也许已经猜出个大概。你可以删掉包含了特定Term的文档,或是符合Query查询条件的。通常,为你的document设计一个名为"id"的Field是个不错的办法,这样就你可以为每个document设置唯一的id,然后通过如下方式删除它:
? ? ? writer.deleteDocument(new Term("id", documentID));
? ? 同新增方法一样,删除操作不会被立即写入磁盘中,你需要手动调用commit()以提交所有变化或者调用close()关闭writer.
? ? 示例代码中调用的两个方法 maxDoc() 和 numDocs(), 它们的区别在于:
? ? ? maxDoc() ? 会返回索引中的文档(document)总数,包括删除的和未删除的
? ? ? numDocs() ?仅返回索引中未被删除的文档数
?
3. 更新Document
? ? 更新方法有两个:
updateDocument(Term, Document);updateDocument(Term, Document, Analyzer);
?? ?很明显它们的区别在于是否指定特定分析器。
? ? 需要指出的是,更新操作实质上仅仅是删除和新增的加合。该操作会先依据指定Term删除相应的document,然后依据第二个参数为索引增加新的document. 遗憾的是,即便你仅修改了document中的某个field,你也需要删除整个document,再为其增加新的document,也就是说,此操作的最小粒度是document而非field.
?
?
?
?
?
?