- 浏览: 899485 次
- 性别:
- 来自: 武汉
文章分类
最新评论
-
小宇宙_WZY:
膜拜一下大神,解决了我一个大问题,非常感谢 orz
【解惑】深入jar包:从jar包中读取资源文件 -
JKL852qaz:
感谢,遇到相同的问题!
【解惑】深入jar包:从jar包中读取资源文件 -
lgh1992314:
为什么java中调用final方法是用invokevirtua ...
【解惑】Java动态绑定机制的内幕 -
鲁曼1991:
说的都有道理,protected只能被同一级包的类所调用
【解惑】真正理解了protected的作用范围 -
鲁曼1991:
...
【总结】String in Java
1.5 IndexWriter的关闭细节
IndexWriter索引器创建内存索引的整体流程在前几篇文章中已经详细阐述了,当我们利用IndexWriter创建完内存索引表之后,剩下的工作就只剩下关闭IndexWriter了。IndexWriter在关闭的时候除了清理内存中的对象之外,还有一个非常重要的工作,就是把内存要存储的信息(需要保存的Fields信息,倒排索引表等)写入Lucene的磁盘索引文件。 关于Lucene的每个磁盘索引文件我们会用一个专题来系列阐述,这里只是了解一下写文件的流程。
◆ IndexWriter. optimize()
在《索引创建(1):IndexWriter索引器 》1.1节索引创建的代码中,当对所有的Document对象建立索引之后,我们会调用IndexWriter.optimize();来关闭索引器。 调用源代码的流程如下:
IndexWriter.optimize()
---> IndexWriter.optimize(boolean doWait)
---> IndexWriter.optimize(int maxNumSegments, boolean doWait)
public void optimize(int maxNumSegments, boolean doWait){ //....... flush(true, false, true); //将索引信息由内存写入到磁盘文件 //...... }
IndexWriter.flush(boolean triggerMerge, boolean flushDocStores, boolean flushDeletes)
--> IndexWriter.doFlush(boolean flushDocStores, boolean flushDeletes)
--> IndexWriter.doFlushInternal(boolean flushDocStores, boolean flushDeletes)
private synchronized final boolean doFlushInternal(boolean flushDocStores, boolean flushDeletes) { //待索引的文档数量 final int numDocs = docWriter.getNumDocsInRAM(); //存储域和词向量所要要写入的段名,"_0" String docStoreSegment = docWriter.getDocStoreSegment(); //存储域和词向量要写入的段中的偏移量 int docStoreOffset = docWriter.getDocStoreOffset(); //是否使用复合索引文件存储 boolean docStoreIsCompoundFile = false; //得到要写入的段名:"0" String segment = docWriter.getSegment(); //开始将缓存的索引信息写入段 flushedDocCount = docWriter.flush(flushDocStores);
◆ DocumentsWriter. flush()
接上面的源码,IndexWriter会调用DocumentsWriter的flush()方法来进一步完成关闭工作。像创建索引的时候DocumentsWriter会调用一个索引链(加工车间)来完成创建工作一样,关闭的时候DocumentsWriter也会通过索引链一步步关闭,并将创建索引时每一步产生的指定信息写入相应的磁盘文件中。
先来看看DocumentsWriter.flush(boolean closeDocStore)的主要工作:
1、 closeDocStore(); 按照基本索引链关闭存储域和词向量信息
其主要是根据基本索引链结构,关闭存储域和词向量信息
consumer(DocFieldProcessor ).closeDocStore(flushState);
consumer(DocInverter ).closeDocStore(state);
consumer(TermsHash ).closeDocStore(state);
consumer(FreqProxTermsWriter ).closeDocStore(state);
if (nextTermsHash != null) nextTermsHash.closeDocStore(state);
consumer(TermVectorsTermsWriter ).closeDocStore(state);
endConsumer(NormsWriter ).closeDocStore(state);
fieldsWriter(StoredFieldsWriter ).closeDocStore(state);
其中有实质意义的是以下两个closeDocStore:
(1)词向量的关闭:TermVectorsTermsWriter.closeDocStore(SegmentWriteState)
void closeDocStore(final SegmentWriteState state) throws IOException { if (tvx != null) { //为不保存词向量的文档在tvd文件中写入零。即便不保存词向量,在tvx, tvd中也保留一个位置 fill(state.numDocsInStore - docWriter.getDocStoreOffset()); //关闭tvx, tvf, tvd文件的写入流 tvx.close(); tvf.close(); tvd.close(); tvx = null; //记录写入的文件名,为以后生成cfs文件的时候,将这些写入的文件生成一个统一的cfs文件。 state.flushedFiles.add(state.docStoreSegmentName + "." + IndexFileNames.VECTORS_INDEX_EXTENSION); state.flushedFiles.add(state.docStoreSegmentName + "." + IndexFileNames.VECTORS_FIELDS_EXTENSION); state.flushedFiles.add(state.docStoreSegmentName + "." + IndexFileNames.VECTORS_DOCUMENTS_EXTENSION); //从DocumentsWriter的成员变量openFiles中删除,未来可能被IndexFileDeleter删除 docWriter.removeOpenFile(state.docStoreSegmentName + "." + IndexFileNames.VECTORS_INDEX_EXTENSION); docWriter.removeOpenFile(state.docStoreSegmentName + "." + IndexFileNames.VECTORS_FIELDS_EXTENSION); docWriter.removeOpenFile(state.docStoreSegmentName + "." + IndexFileNames.VECTORS_DOCUMENTS_EXTENSION); lastDocID = 0; } }
(2) 存储域的关闭:StoredFieldsWriter.closeDocStore(SegmentWriteState)
public void closeDocStore(SegmentWriteState state) throws IOException { //关闭fdx, fdt写入流 fieldsWriter.close(); --> fieldsStream.close(); --> indexStream.close(); fieldsWriter = null; lastDocID = 0; //记录写入的文件名 state.flushedFiles.add(state.docStoreSegmentName + "." + IndexFileNames.FIELDS_EXTENSION); state.flushedFiles.add(state.docStoreSegmentName + "." + IndexFileNames.FIELDS_INDEX_EXTENSION); state.docWriter.removeOpenFile(state.docStoreSegmentName + "." + IndexFileNames.FIELDS_EXTENSION); state.docWriter.removeOpenFile(state.docStoreSegmentName + "." + IndexFileNames.FIELDS_INDEX_EXTENSION); }
2、consumer.flush(threads, flushState); 按照基本索引链结构将索引结果写入指定段名的磁盘文件
其写入磁盘的调用顺序也按照基本索引链,如下:
DocFieldProcessor.flush // 将Fields信息写入.fdx和.fdt
--> DocInverter.flush //将倒排索引表写入
--> TermsHash.flush
--> FreqProxTermsWriter.flush
--> TermVectorsTermsWriter.flush()
--> NormsWriter.flush
第一步: 首先调用 DocFieldProcessor.flush(Collection<DocConsumerPerThread> threads, SegmentWriteState state) 将Fields信息写入.fdx和.fdt
public void flush(Collection<DocConsumerPerThread> threads, SegmentWriteState state){ //回收fieldHash,以便用于下一轮的索引,为提高效率,索引链中的对象是被复用的。 Map<DocFieldConsumerPerThread, Collection<DocFieldConsumerPerField>> childThreadsAndFields = new HashMap<DocFieldConsumerPerThread, Collection<DocFieldConsumerPerField>>(); for ( DocConsumerPerThread thread : threads) { DocFieldProcessorPerThread perThread = (DocFieldProcessorPerThread) thread; childThreadsAndFields.put(perThread.consumer, perThread.fields()); perThread.trimFields(state); } //将Fields信息写入.fdx和.fdt文件 fieldsWriter.flush(state); //调用索引链的第二步DocInverter将索引表写入磁盘文件 consumer.flush(childThreadsAndFields, state); //写入域元数据信息,并记录写入的文件名,以便以后生成cfs文件 final String fileName = state.segmentFileName(IndexFileNames.FIELD_INFOS_EXTENSION); fieldInfos.write(state.directory, fileName); state.flushedFiles.add(fileName); }
在这一步中,主要作用就是调用StoredFieldsWriter将Document对象中所需要存储的Field信息写入.fdx和.fdt文件《索引文件格式(3):Field数据[.fdx/.fdt/.fnm] 》。其调用流程如下:
StoredFieldsWriter.flush(SegmentWriteState state)
---> FieldsWriter.flush()
void flush() throws IOException { indexStream.flush(); //写入.fdx文件 fieldsStream.flush(); //写入.fdt文件 }
--->BufferedIndexOutput.flush()
--->BufferedIndexOutput.flushBuffer(byte[] b, int len)
--->SimpleFSDirectory.flushBuffer(byte[] b, int offset, int size)
public void flushBuffer(byte[] b, int offset, int size) throws IOException { file.write(b, offset, size); //JDK RandomAccessFile.write }
第二步 再调用 DocInverter.flush(Map<DocFieldConsumerPerThread, Collection<DocFieldConsumerPerField>> threadsAndFields, SegmentWriteState state) 将倒排索引表信息写入文件.tii、.tis、.frq、.prx文件。
//DocInverter.flush() void flush(Map<DocFieldConsumerPerThread, Collection<DocFieldConsumerPerField>> threadsAndFields, SegmentWriteState state) { //写入倒排表及词向量信息(TermHash.flush()) consumer.flush(childThreadsAndFields, state); //写入标准化因子 endConsumer.flush(endChildThreadsAndFields, state); }
DocInverter.flush --> TermsHash.flush
//TermsHash.flush() flush(Map<InvertedDocConsumerPerThread,Collection<InvertedDocConsumerPerField>>, SegmentWriteState){ //写入倒排表信息(FreqProxTermsWriter.flush()) consumer.flush(childThreadsAndFields, state); //回收RawPostingList shrinkFreePostings(threadsAndFields, state); //写入词向量信息(TermVectorsTermsWriter.flush()) if (nextTermsHash != null) nextTermsHash.flush(nextThreadsAndFields, state); consumer.flush(childThreadsAndFields, state); }
-->(1) FreqProxTermsWriter.flush() 将倒排索引表的词典信息写入tii和tis文件《 索引文件格式(4):Terms数据[.tii/.tis] 》,将posting的docID和position分别写入frq和prx文件。
public void flush(Map<TermsHashConsumerPerThread,Collection<TermsHashConsumerPerField>> threadsAndFields, final SegmentWriteState state) { // 搜集所有线程已经建立索引的Field List<FreqProxTermsWriterPerField> allFields = new ArrayList<FreqProxTermsWriterPerField>(); for(Map.Entry<TermsHashConsumerPerThread,Collection<TermsHashConsumerPerField>> entry : threadsAndFields.entrySet()) { Collection<TermsHashConsumerPerField> fields = entry.getValue(); for (final TermsHashConsumerPerField i : fields) { final FreqProxTermsWriterPerField perField = (FreqProxTermsWriterPerField) i; //当前Field有posting,表明此域已经被建立了索引结构 if (perField.termsHashPerField.numPostings > 0) allFields.add(perField); } } //所有Field按名称排序,使得同名域能够一起处理(因为多线程处理,每个Document对象都会有相同的域) Collections.sort(allFields); final int numAllFields = allFields.size(); //①生成倒排表的写对象 final FormatPostingsFieldsConsumer consumer = new FormatPostingsFieldsWriter(state, fieldInfos); int start = 0; //对于每一个域 while(start < numAllFields) { //将所有线程处理的名字相同的域放在FreqProxTermsWriterPerField[]数组中准备一起写入文件 final FieldInfo fieldInfo = allFields.get(start).fieldInfo; final String fieldName = fieldInfo.name; int end = start+1; while(end < numAllFields && allFields.get(end).fieldInfo.name.equals(fieldName)) end++; FreqProxTermsWriterPerField[] fields = new FreqProxTermsWriterPerField[end-start]; for(int i=start;i<end;i++) { fields[i-start] = allFields.get(i); fieldInfo.storePayloads |= fields[i-start].hasPayloads; } // ②如果这个域被建立好的索引,那么把这个域的倒排索引加入到文件中 appendPostings(fields, consumer); // 释放空间 for(int i=0;i<fields.length;i++) { TermsHashPerField perField = fields[i].termsHashPerField; int numPostings = perField.numPostings; perField.reset(); perField.shrinkHash(numPostings); fields[i].reset(); } start = end; } for (Map.Entry<TermsHashConsumerPerThread,Collection<TermsHashConsumerPerField>> entry : threadsAndFields.entrySet()) { FreqProxTermsWriterPerThread perThread = (FreqProxTermsWriterPerThread) entry.getKey(); perThread.termsHashPerThread.reset(true); } consumer.finish(); }
上述代码有几个重要的地方:
①生成倒排表的写对象: 其基本作用就是创建tii,tis,freq, prox文件用于存放倒排索引表。此时还没有写任何数据信息。
public FormatPostingsFieldsWriter(SegmentWriteState state, FieldInfos fieldInfos) throws IOException { dir = state.directory; segment = state.segmentName; totalNumDocs = state.numDocs; this.fieldInfos = fieldInfos; //TermInfosWriter用于写tii,tis termsOut = new TermInfosWriter(dir, segment, fieldInfos, state.termIndexInterval); //DefaultSkipListWriter用于写freq, prox的跳表 skipListWriter = new DefaultSkipListWriter(termsOut.skipInterval, termsOut.maxSkipLevels, totalNumDocs, null, null); //记录写入的文件名, state.flushedFiles.add(state.segmentFileName(IndexFileNames.TERMS_EXTENSION)); state.flushedFiles.add(state.segmentFileName(IndexFileNames.TERMS_INDEX_EXTENSION)); //用以上两个写对象,按照一定的格式写入段 termsWriter = new FormatPostingsTermsWriter(state, this); }
②将同名的域的倒排索引信息添加进文件中
void appendPostings(FreqProxTermsWriterPerField[] fields, FormatPostingsFieldsConsumer consumer) throws CorruptIndexException, IOException { int numFields = fields.length; final FreqProxFieldMergeState[] mergeStates = new FreqProxFieldMergeState[numFields]; for(int i=0;i<numFields;i++) { FreqProxFieldMergeState fms = mergeStates[i] = new FreqProxFieldMergeState(fields[i]); assert fms.field.fieldInfo == fields[0].fieldInfo; boolean result = fms.nextTerm();//对所有的域,取第一个词(Term) assert result; } //添加此域,虽然有多个域,但是由于是同名域,只取第一个域的信息即可。返回的是用于添加此域中的词的对象。 final FormatPostingsTermsConsumer termsConsumer = consumer.addField(fields[0].fieldInfo); //FreqProxFieldMergeState 被DocumentWriter掉用合并多个线程的posting进入同一个段中 FreqProxFieldMergeState[] termStates = new FreqProxFieldMergeState[numFields]; final boolean currentFieldOmitTermFreqAndPositions = fields[0].fieldInfo.omitTermFreqAndPositions; //此while循环是遍历每一个尚有未处理的词的域,依次按照词典顺序处理这些域所包含的词。当一个域中的所有的词都被处理过后,则 numFields减一,并从mergeStates数组中移除此域。直到所有的域的所有的词都处理完毕,方才退出此循环。 while(numFields > 0) { //找出所有域中按字典顺序的下一个词。可能多个同名域中,都包含同一个term,因而要遍历所有的numFields,得到所有的域里的下一个词,numToMerge即为有多少个域包含此词。 termStates[0] = mergeStates[0]; int numToMerge = 1; for(int i=1;i<numFields;i++) { final char[] text = mergeStates[i].text; final int textOffset = mergeStates[i].textOffset; final int cmp = compareText(text, textOffset, termStates[0].text, termStates[0].textOffset); if (cmp < 0) { termStates[0] = mergeStates[i]; numToMerge = 1; } else if (cmp == 0) termStates[numToMerge++] = mergeStates[i]; } //添加此词,返回FormatPostingsDocsConsumer用于添加文档号(doc ID)及词频信息(freq) final FormatPostingsDocsConsumer docConsumer = termsConsumer.addTerm(termStates[0].text, termStates[0].textOffset); //由于共numToMerge个域都包含此词,每个词都有一个链表的文档号表示包含这些词的文档。此循环遍历所有的包含此词的域,依次按照从小到大的循序添加包含此词的文档号及词频信息。当一个域中对此词的所有文档号都处理过了,则numToMerge减一,并从termStates数组中移除此域。当所有包含此词的域的所有文档号都处理过了,则结束此循环。 while(numToMerge > 0) { //找出最小的文档号 FreqProxFieldMergeState minState = termStates[0]; for(int i=1;i<numToMerge;i++) if (termStates[i].docID < minState.docID) minState = termStates[i]; final int termDocFreq = minState.termFreq; //添加文档号及词频信息,并形成跳表,返回FormatPostingsPositionsConsumer用于添加位置(prox)信息 final FormatPostingsPositionsConsumer posConsumer = docConsumer.addDoc(minState.docID, termDocFreq); //ByteSliceReader是用于读取bytepool中的prox信息的。 final ByteSliceReader prox = minState.prox; if (!currentFieldOmitTermFreqAndPositions) { int position = 0; // 此循环对包含此词的文档,添加位置信息 for(int j=0;j<termDocFreq;j++) { final int code = prox.readVInt(); position += code >> 1; final int payloadLength; // 如果此位置有payload信息,则从bytepool中读出,否则设为零。 if ((code & 1) != 0) { payloadLength = prox.readVInt(); if (payloadBuffer == null || payloadBuffer.length < payloadLength) payloadBuffer = new byte[payloadLength]; prox.readBytes(payloadBuffer, 0, payloadLength); } else payloadLength = 0; //添加位置(prox)信息 posConsumer.addPosition(position, payloadBuffer, 0, payloadLength); } //End for posConsumer.finish(); } //判断退出条件,上次选中的域取得下一个文档号,如果没有,则说明此域包含此词的文档已经处理完毕,则从termStates中删除此域,并将 numToMerge减一。然后此域取得下一个词,当循环到(2)的时候,表明此域已经开始处理下一个词。如果没有下一个词,说明此域中的所有的词都处理完毕,则从mergeStates中删除此域,并将numFields减一,当numFields为0的时候,循环(2)也就结束了。 if (!minState.nextDoc()) { int upto = 0; for(int i=0;i<numToMerge;i++) if (termStates[i] != minState) termStates[upto++] = termStates[i]; numToMerge--; assert upto == numToMerge; if (!minState.nextTerm()) { upto = 0; for(int i=0;i<numFields;i++) if (mergeStates[i] != minState) mergeStates[upto++] = mergeStates[i]; numFields--; assert upto == numFields; } } } //经过上面的过程,docid和freq信息虽已经写入段文件,而跳表信息并没有写到文件中,而是写入skip buffer里面了,此处真正写入文件。并且词典(tii, tis)也应该写入文件。 docConsumer.finish(); } termsConsumer.finish(); }
-->(2) TermVectorsTermsWriter.flush() 写入tvx, tvd, tvf三个文件.
TermVectorsTermsWriter.flush (Map<TermsHashConsumerPerThread,Collection<TermsHashConsumerPerField>> threadsAndFields, final SegmentWriteState state) { if (tvx != null) { if (state.numDocsInStore > 0) fill(state.numDocsInStore - docWriter.getDocStoreOffset()); tvx.flush(); tvd.flush(); tvf.flush(); } for (Map.Entry<TermsHashConsumerPerThread,Collection<TermsHashConsumerPerField>> entry : threadsAndFields.entrySet()) { for (final TermsHashConsumerPerField field : entry.getValue() ) { TermVectorsTermsWriterPerField perField = (TermVectorsTermsWriterPerField) field; perField.termsHashPerField.reset(); perField.shrinkHash(); } TermVectorsTermsWriterPerThread perThread = (TermVectorsTermsWriterPerThread) entry.getKey(); perThread.termsHashPerThread.reset(true); } }
第三步:写入标准化因子
发表评论
-
【Lucene3.0 初窥】索引文件格式(2):文件结构总体框架
2010-05-02 16:44 4093Lucene使用文件扩展名标识不同的索引文件。如.fnm文件存 ... -
【Lucene3.0 初窥】索引文件格式(1):预备知识
2010-05-02 16:26 4041注意,本专题内容参见《http://lucene.apache ... -
【Lucene3.0 初窥】索引文件格式(5):posting数据[.frq/.prx]
2010-05-02 12:34 3883★ .frq 词语频 ... -
【Lucene3.0 初窥】索引文件格式(4):dictionary数据[.tii/.tis]
2010-04-30 10:57 3609Terms数据 磁盘文件存储细节 从这篇开始 ... -
【Lucene3.0 初窥】索引文件格式(3):Field数据[.fdx/.fdt/.fnm]
2010-04-23 15:12 5193注意:以下文章是参见h ... -
【Lucene3.0 初窥】索引创建(4):DocumentWriter 处理流程三
2010-04-15 15:36 3613上接《索引创建(3):DocumentWriter 处理流程二 ... -
【Lucene3.0 初窥】索引创建(5):索引数据池及内存数据细节
2010-04-13 13:50 3803上接《索引创建 (2):DocumentWriter处理流程 ... -
【Lucene3.0 初窥】索引创建(3):DocumentWriter 处理流程二
2010-04-10 10:27 4139上接《索引创建(2):DocumentWriter处理流 ... -
【Lucene3.0 初窥】索引创建(2):DocumentWriter 处理流程一
2010-04-08 21:55 3712上接《索引创建(1): IndexWriter索引器》 ... -
【Lucene3.0 初窥】索引创建(1):IndexWriter索引器
2010-04-07 19:11 4882《Lucene索引创建》系列文章将从源代码出发,详细揭示Luc ... -
【Lucene3.0 初窥】数据源内存组织结构—Document/Field
2010-04-07 16:45 3838在检索数据的时候,我们很希望可以检索出数据源的各种信息。就比如 ... -
【Lucene3.0 初窥】文本分析器Analyzer
2010-04-06 14:58 6518一个优秀的IR system要做好的第一件事就是利用自然语言处 ... -
《Introduce to IR》索引创建
2010-04-03 10:41 3429该系列文章是《An Introduce to Inform ... -
《Introduce to IR》布尔检索模型
2010-03-18 09:33 5573该系列文章是《An Introduce to Informat ... -
【Lucene3.0 初窥】Lucene体系结构概述
2010-03-05 11:37 4083Lucene 的基本原理与《 ... -
【Lucene3.0 初窥】全文检索的基本原理
2010-03-04 16:01 5731全文转载:http://blog.csdn.net/forfu ...
相关推荐
### Lucene3.0创建索引 在Lucene3.0中创建索引是一个关键功能,可以帮助用户快速地检索和管理大量的文本数据。本篇文章将详细介绍如何使用Lucene3.0来创建索引,并通过一个具体的例子来演示整个过程。 #### 一、...
《深入剖析Lucene3.0:庖丁解牛与索引搜索实践》 在IT行业中,搜索引擎技术扮演着至关重要的角色,而Lucene作为一个开源全文检索库,为开发者提供了强大的文本搜索功能。本文将深入探讨Lucene3.0版本,结合“庖丁解...
2. 创建索引writer:`IndexWriter` 类负责创建和更新索引,使用 `IndexWriterConfig` 进行配置。 3. 构建文档:为每个要索引的文件创建 `Document` 对象,然后添加字段,如 `Field("content", fileContent, ...
2. IndexWriter:负责创建和更新索引,支持批量添加、删除和更新文档。 3. IndexReader:读取索引,用于搜索和获取文档信息。 4. Searcher:执行查询并返回结果,包括IndexSearcher和MultiSearcher。 5. Analyzer:...
1. **索引构建**: Lucene 2.0 提供了 `IndexWriter` 类,用于创建和更新索引。开发者可以使用 `Document` 类来封装待索引的数据,然后通过 `addDocument()` 方法添加到索引中。 2. **查询构造**: 通过 `QueryParser...
以下是一个简单的Lucene 3.0索引和搜索的Java示例: ```java import org.apache.lucene.analysis.standard.StandardAnalyzer; import org.apache.lucene.document.Document; import org.apache.lucene.document....
在 Lucene 3.0 中,使用 `IndexWriter` 类来写入文档到索引中。相比于 2.0 版本,3.0 引入了更高级的分析器(Analyzer)和文档(Document)处理,使得对文本的预处理更为精细,支持更多的语言和特性。 ```java ...
1. 创建索引:首先,创建一个Directory实例,然后使用IndexWriter添加文档并建立索引。 2. 更新索引:当文档有变化时,可以使用IndexWriter的updateDocument方法进行更新。 3. 搜索索引:使用IndexSearcher实例和...
5. 关闭索引写入器:完成索引后,记得调用`IndexWriter.close()`关闭写入器,确保索引安全保存。 四、搜索操作 1. 创建索引读取器(IndexReader):`IndexReader`用于读取已建立的索引。 2. 创建索引搜索器...
- **IndexWriter** 类是用于创建和更新 Lucene 索引的主要接口。通过 IndexWriter,我们可以添加、删除或更新 Document。 - **倒排索引**:Lucene 使用倒排索引,将每个唯一的词(Term)映射到包含该词的所有文档...
1. **创建索引(Creating an Index)**: 首先,我们需要创建一个 IndexWriter 对象,配置相应的目录(Directory)和索引设置。然后,通过添加 Document 对象到 IndexWriter 来索引文档。 2. **索引字段(Indexing ...
在这个“Lucene3.0增删改查和关键字高亮实例”项目中,我们将深入理解如何利用Lucene 3.0版本进行索引构建、文档的增删改查操作,并学习关键字高亮显示的实现方法。 首先,我们要了解**创建索引**的基本流程。在...
2. **IndexWriter**:用于创建和更新索引,`addDocument(Document doc)`方法用于添加文档到索引,`commit()`方法用于提交所有未保存的更改。 3. **Directory**:用于存储索引的接口,如`FSDirectory`在文件系统中...
Lucene 3.0支持多种文件格式,如倒排索引,提供高效的搜索功能,并且易于集成到各种应用程序中。 **倒排索引** 倒排索引是全文检索的核心概念。在倒排索引中,每个单词(关键词)对应一个索引列表,列表包含所有...
`IndexWriter`在创建索引时会使用`Directory`来保存索引数据。 3. **查询解析**:用户输入的查询字符串需要通过`QueryParser`或`MultiFieldQueryParser`转换成`Query`对象。`Analyzer`同样在这个过程中起作用,用于...
Lucene 3.0 提供了强大的文本检索功能,通过创建索引、更新和删除文档,开发者可以构建高效搜索引擎。索引创建涉及文档对象、Analyzer、IndexWriter等核心组件,而更新和删除则依赖于`IndexWriter`的相应方法。虽然...
Analyzer负责将文本分解为可搜索的词项,IndexWriter则用于创建和更新索引,IndexReader用于读取已建立的索引,而Searcher则执行实际的查询操作。 在部署Lucene 3.0的Demo项目时,我们需要以下步骤: 1. **环境...
4. **IndexWriter**:创建和更新索引的主要接口。使用IndexWriter,你可以向索引中添加、删除或更新Documents。 5. **QueryParser**:用于解析用户输入的查询字符串,生成对应的Query对象。在这个示例中,可能是将...