3.2.3 向索引库中添加索引文档
先增加文档,然后提交更新到索引库。和数据库表一样,索引库是结构化的,一个文档往往有很多列,例如标题和内容列等。往索引添加数据时涉及的几个类的关系如图3-5所示。
图3-5 往索引中添加文档
TextField是一个快捷类,它不存储词向量。如果你需要词向量,就只使用Field类。这需要更多的代码,因为首先要创建一个FieldType类的实例,然后设置storeTermVectors和tokenizer为true,并在Field构造器中使用这个FieldType实例。
FieldType t = new FieldType(); t.setStoreTermVectorOffsets(true); t.setTokenized(true); fieldTitle = new Field("title", "标题", t);
使用StringField:
field = new StringField("url", "bar", Store.YES);
使用FieldType实现的等价代码:
FieldType fieldType = new FieldType(); fieldType.setStored(true); fieldType.setTokenized(false); fieldType.setIndexed(true); field = new Field("url", "lietu.com", fieldType);
下面这段程序是向索引库中添加网页地址、标题和内容列:
//如果初次使用Lucene,往索引中写入的每条记录最好都新创建一个Document与之对应, //也就是说不要重复使用Document对象,否则可能会出现意想不到的错误 Document doc = new Document(); //创建网址列 Field f = new Field("url", news.URL , Field.Store.YES, Field.Index.UN_TOKENIZED, Field.TermVector.NO); doc.add(f); //创建标题列 f = new Field("title", news.title , Field.Store.YES, Field.Index.TOKENIZED, Field.TermVector.WITH_POSITIONS_OFFSETS); doc.add(f); //创建内容列 f = new Field("body", news.body.toString() , Field.Store.YES, Field.Index.TOKENIZED, Field.TermVector.WITH_POSITIONS_OFFSETS); doc.add(f); index.addDocument(doc);
下面两种写法是等价的:
new Field("title", "标题", TextField.TYPE_STORED); new TextField("title", "标题", Store.YES );
和一般的数据库不一样,一个文档的一个列可以有多个值。例如一篇文档既可以属于互联网类,又可以属于科技类。
Lucene中的API相对数据库来说比较灵活,没有类似数据库先定义表结构后再使用的过程。如果前后两次写索引时定义的列名称不一样,Lucene会自动创建新的列,所以Field的一致性需要我们自己掌握。
列选项组合见表3-2。
表3-2 Field的类型
这里的POSITIONS表示以词为单位的位置,也就是语义位置,而OFFSETS表示词在文本中的实际物理位置。
FieldType类设置这些组合。例如:
Analyzer ca = new CJKAnalyzer(); IndexWriterConfig indexWriterConfig = new IndexWriterConfig(ca); FieldType nameType = new FieldType(); nameType.setIndexed(true); nameType.setStored(true); IndexWriter indexWriter = new IndexWriter(DIRECTORY, indexWriterConfig); Document document = new Document(); document.add(new Field("name", "我购买了道具和服装", nameType)); indexWriter.addDocument(document);
在增加文档的阶段,给新的词分配TokenID,新的文档分配DocID。
增加索引后,记得提交索引,否则reader不一定能搜索到。
writer.commit(); //提交更新到索引库
索引创建完成后可以用索引查看工具Luke(https://github.com/dmitrykey/luke)来查看索引内容并维护索引库。Luke是一个可以执行的jar包,是用Java实现的Windows程序。在Windows下可以双击lukeall-1.0.1.jar,启动Luke。然后,可以选择菜单File→Open Lucene index,打开data/index文件夹,然后可以在窗口看到索引创建的详细信息。
为了提高索引速度,可以重用Field,而不是每次都创建新的。从Lucene 2.3开始,有新的setValue()方法,可以改变Field的值。这样可以在增加许多Document的时候重用单个的Field实例,从而节省许多垃圾回收消耗的时间。
开始新建一个独立的Document实例,然后增加许多Field实例,并且增加每个文档到索引的时候都重用这些Field。例如,有一个idField、bodyField和nameField等。当加入一个Document后,可以通过idField.setValue()等直接改变Field的值,然后再增加文档实例。下面是一个重用Field的例子。
//创建索引 IndexWriter writer = new IndexWriter(directory, analyzer); //创建文档结构 Document doc = new Document(); //定义id重用列 Field idField = new Field("id", Field.Store.YES, Field.Index.UN_TOKENIZED, Field.TermVector.NO); doc.add(idField); //定义标题重用列 Field titleField = new Field("title", null , Field.Store.YES, Field.Index.ANALYZED, Field.TermVector.WITH_POSITIONS_OFFSETS); doc.add(titleField); //定义内容重用列 Field bodyField = new Field("body", null , Field.Store.YES, Field.Index.ANALYZED, Field.TermVector.WITH_POSITIONS_OFFSETS); doc.add(bodyField); for(all document) { //处理所有的文档,填充Field值,并索引文档 Article a = parse(document); idField.setValue(a.id); titleField.setValue(a.title); bodyField.setValue(a.body); writer.addDocument(doc); //doc仅仅是一个容器 } writer.close();
注意,不能在文档中重用单个Field实例,不应该改变列的值,直到包含这个Field的Document已经加入到索引库。
可以同时增加多个文档到索引:addDocuments()。
List<Document> docs = new ArrayList<Document>(); Document doc1 = new Document(); doc1.add(new Field("content", "猎兔搜索", Field.Store.YES, Field.Index.ANALYZED)); Document doc2 = new Document(); doc2.add(new Field("content", "中文分词", Field.Store.YES, Field.Index.ANALYZED)); Document doc3 = new Document(); doc3.add(new Field("content", "中国", Field.Store.YES, Field.Index.ANALYZED)); Document doc4 = new Document(); doc4.add(new Field("content", "NBA", Field.Store.YES, Field.Index.ANALYZED)); docs.add(doc1); docs.add(doc2); docs.add(doc3); docs.add(doc4); writer.addDocuments(docs);
当在Windows系统下使用的时候,最好取消勾选杀毒软件的“自动删除已感染病毒文件”选项,否则当索引带病毒特征的文档时,杀毒软件可能会破坏Lucene的索引文件。
每一个添加的文档都被传递给DocConsumer类,它处理该文档并且与索引链表(indexing chain)中其他的consumers相互发生作用。确定的consumers,就像StoredFieldWriter和TermVectorsTermsWriter,提取一个文档中的词,并且马上把字节写入文件。
IndexWriter.setRAMBufferSizeMB方法可以设置当更新文档使用的内存达到指定大小之后才写入硬盘。这样可以提高写索引的速度,尤其是在批量创建索引的时候。
在Lucene 2.3版本之前,存入索引的每个Token都是新创建的。重复利用Token可以加快索引速度。新的Tokenizer类可以回收利用已用过的Token。