上一篇讲到了Lucene的检索原理以及简单介绍如何创建索引。
这一篇从Lucene的添加、删除、更新、文档域加权来对构建索引进行总结。
准备所需jar包和数据
新建了一个maven工程,pom.xml如下:
<dependencies>
<!-- lucene核心包 -->
<dependency>
<groupId>org.apache.lucene</groupId>
<artifactId>lucene-core</artifactId>
<version>5.3.1</version>
</dependency>
<!-- lucene查询解析包 -->
<dependency>
<groupId>org.apache.lucene</groupId>
<artifactId>lucene-queryparser</artifactId>
<version>5.3.1</version>
</dependency>
<!-- lucene解析器包 -->
<dependency>
<groupId>org.apache.lucene</groupId>
<artifactId>lucene-analyzers-common</artifactId>
<version>5.3.1</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
</dependency>
</dependencies>
在工程中新建一个junit测试类IndexTest1.java,在类中准备一下用来测试的数据,如下:
//用来测试的数据
private String ids[] = {"1", "2", "3"};
private String dates[] = {"yesterday", "today", "tomorrow"};
private String descs[] = {
"It was a rainy day yesterday",
"It's cloudy today",
"Tomorrow is sunny."};
可以将上面三个数组不同位置上对应的字符串看成一个文档,如对数组位置为0的,这个文档就是,id域为“1”,date域为”yesterday”,desc域就是”It was a rainy day yesterday”。因此就会有三个文档,下面对文档进行分析。
添加文档
添加文档其实就是创建索引,首先使用上一篇讲到的创建索引的方法,首先创建一个写索引,然后通过这个对象去添加文档,每个文档就是一个Lucene的Document,于是可以在继续在IndexTest1.java中添加:
public class IndexingTest1 {
private Directory dir; //存放索引的位置
//用来测试的数据
private String ids[] = {"1", "2", "3"};
private String dates[] = {"yesterday", "today", "tomorrow"};
private String descs[] = {
"It was a rainy day yesterday",
"It's cloudy today",
"Tomorrow is sunny."};
//生成索引
@Test
public void index() throws Exception{
IndexWriter writer = getWriter();
for(int i = 0; i < ids.length; i++){
Document document = new Document();
document.add(new StringField("id", ids[i], Field.Store.YES));
document.add(new StringField("date", dates[i], Field.Store.YES));
document.add(new TextField("desc", descs[i], Field.Store.YES));
writer.addDocument(document);
}
writer.close(); //close了才真正写到文档中
}
//获取IndexWriter实例
public IndexWriter getWriter() throws Exception{
dir = FSDirectory.open(Paths.get("D:\\resource"));
Analyzer analyzer = new StandardAnalyzer();//标准分词器,会自动去掉空格啊,is a the等单词
IndexWriterConfig config = new IndexWriterConfig(analyzer);//将标准分词器配到写索引的配置中
IndexWriter writer = new IndexWriter(dir,config);//实例化写索引对象
return writer;
}
}
从上面的代码可以看出,每个数组元素都是一个filed域。文档添加好了域之后,就添加到写索引的实例writer中写入。也就是先创建一个文件,再在文件中添加信息,在使用写索引写入索引库中。
如图,就有了这样的索引文件的产生:
读取文档
读取文档的话需要IndexReader对象,初始化的时候要传入读取文档所在的路径,也就是之前的D:\resource。
public void testIndexReader() throws Exception {
dir = FSDirectory.open(Paths.get("D:\\resource"));
IndexReader reader = DirectoryReader.open(dir);
System.out.println("最大文档数:" + reader.maxDoc());
System.out.println("实际文档数:" + reader.numDocs());
reader.close();
}
删除文档
删除文档有两种方式。一种是在合并前删除,另一种是在合并后删除。
- 合并前删除指的是并没有真正删除这个文档,只是在这个文档上做一个标记而已;
- 合并后删除指的是真正删掉了这个文档了。
使用场景:
如果一个项目比较大,并发访问的人数比较多,删除肯定会对性能有影响,这个时候就可以用合并前删除,先不删,只是标记一下这个文档属于已删除的文档,等到访问量比较小的时候,再统一删除。同样的,如果数据量不大,删除操作也不怎么影响性能,那么直接删除,也就是合并后删除。
同样的在之前那个例子上添加如下代码:
//合并前删除
@Test
public void testDeleteBefore() throws Exception{
IndexWriter writer = getWriter();
System.out.println("删除前有" + writer.numDocs() + "个文档");
writer.deleteDocuments(new Term("id", "1")); //删除id=1对应的文档
writer.commit(); //提交删除,并没有真正删除
System.out.println("删除后最大文档数:" + writer.maxDoc());
System.out.println("删除后实际文档数:" + writer.numDocs());
writer.close();
}
//合并后删除
@Test
public void testDeleteAfter() throws Exception {
IndexWriter writer = getWriter();
System.out.println("删除前有" + writer.numDocs() + "个文档");
writer.deleteDocuments(new Term("id", "1")); //删除id=1对应的文档
writer.forceMergeDeletes(); //强制合并(强制删除),没有索引了
writer.commit(); //提交删除,真的删除了
System.out.println("删除后最大文档数:" + writer.maxDoc());
System.out.println("删除后实际文档数:" + writer.numDocs());
writer.close();
}
测试结果如下:
合并前删除测试:
合并后删除测试:
在这里,测试的时候要注意,完成合并前删除测试之后,将路径上的所有索引都删除,重新调用上面的index方法重新生成一下,再去测试合并后删除,因为之前删掉一个了,会影响后面的测试。
更新文档
更新文档,就是新建一个Document对象,然后按照前面设置的字段自己再设置个新的,然后更新原来的文档。
在上面的例子上添加:
//更新文档测试
@Test
public void updateTest() throws Exception{
IndexWriter writer = getWriter();
Document document = new Document();
document.add(new StringField("id", ids[1], Field.Store.YES));
document.add(new StringField("date", "today update", Field.Store.YES));
document.add(new TextField("desc", "Today is a good day", Field.Store.NO));
writer.updateDocument(new Term("id", ids[1]),document);
writer.close();
System.out.println(document.getField("desc"));
}
结果如下:
文档域加权
这个其实有点可以理解,比如你想在一个博客上查一个单词,很多篇文档都会有这个单词,但是你希望标题含有这个单词的优先排在前面,或者希望某位作者写的文档放在前面一点,那么你就可以使用加权。
这里的代码我用了网上的一个例子,因为不想想文档具体的内容。就是有四个人写了四篇有关Java的文章,希望匹配将作者c写的放在最前面。因此新建了一个IndexTest2类。
//准备数据,五个人各写了一篇文章,希望先出来C的文章,因此给C加权
private String ids[] = {"1", "2", "3", "4"};
private String authors[] = {"A", "B", "C", "D"};
private String positions[] = {"a", "b", "c", "d"};
private String titles[] = {"Java is a good language.", "Java is a cross platform language", "Java powerful", "You should learn java"};
private String contents[] = {
"If possible, use the same JRE major version at both index and search time.",
"When upgrading to a different JRE major version, consider re-indexing. ",
"Different JRE major versions may implement different versions of Unicode.",
"For example: with Java 1.4, `LetterTokenizer` will split around the character U+02C6."
};
同样的生成索引,区别在于进行了加权操作。
public class IndexTest2 {
private Directory directory;
//准备数据,五个人各写了一篇文章,希望先出来C的文章,因此给C加权
private String ids[] = {"1", "2", "3", "4"};
private String authors[] = {"A", "B", "C", "D"};
private String positions[] = {"a", "b", "c", "d"};
private String titles[] = {"Java is a good language.", "Java is a cross platform language", "Java powerful", "You should learn java"};
private String contents[] = {
"If possible, use the same JRE major version at both index and search time.",
"When upgrading to a different JRE major version, consider re-indexing. ",
"Different JRE major versions may implement different versions of Unicode.",
"For example: with Java 1.4, `LetterTokenizer` will split around the character U+02C6."
};
@Test
public void index() throws Exception {
IndexWriter indexWriter = getWriter();
for (int i = 0; i < ids.length; i++) {
Document document = new Document();
document.add(new StringField("id", ids[i], Field.Store.YES));
document.add(new StringField("author", authors[i], Field.Store.YES));
document.add(new StringField("position", positions[i], Field.Store.YES));
//这部分就是加权操作了,对title这个Field进行加权,因为等会要查这个Field
TextField field = new TextField("title", titles[i], Field.Store.YES);
//先判断这个人是不是c,如果是就加权
if ("c".equals(positions[i])) {
field.setBoost(1.5f); //加权操作,默认为1,1.5表示加权了,小于1就降权了
}
document.add(field);
document.add(new TextField("content", contents[i], Field.Store.NO));
indexWriter.addDocument(document); //添加文档
}
indexWriter.close();
}
//获取IndexWriter实例
public IndexWriter getWriter() throws Exception{
directory = FSDirectory.open(Paths.get("D:\\masterSpring\\code\\chapter18\\src\\resource"));
Analyzer analyzer = new StandardAnalyzer();
IndexWriterConfig config = new IndexWriterConfig(analyzer);
IndexWriter writer = new IndexWriter(directory,config);
return writer;
}
//测试加权的等下写。
}
上面就是创建索引的过程。
`if ("c".equals(positions[i])) {
field.setBoost(1.5f);
}
这句话就是给作者是C的文档加权了,默认为1,大于1表示加权了,小于1就降权了。
结果如下。
可以看见C排在了第一位,如果没有上面的那段加权代码,那么匹配出来的顺序是Lucene中自己的一个算法,可以认为是Lucene默认的顺序,具体是怎样的就不探讨了,我们可以自己根据需求自己设定一下权重。
希望对你有帮助,如有疑问或见解,欢迎提出,共同进步。