上一篇博文中已经对全文检索有了一定的了解,这篇文章主要来总结一下全文检索的第一步:构建索引。其实上一篇博文中的示例程序已经对构建索引写了一段程序了,而且那个程序还是挺完善的。不过从知识点的完整性来考虑,我想从Lucene的添加文档删除文档修改文档以及文档域加权四个部分来展开对构建索引的总结,也便于我后期的查看。会重点分析一下删除文档(因为有两中方式)和文档域加权这(实际中会用到比较多)两个部分。

1. 准备阶段

新建一个maven工程,pom.xml如下:

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>demo.lucene</groupId>
<artifactId>Lucene02</artifactId>
<version>0.0.1-SNAPSHOT</version>
<build/> <dependencies>
<!-- lucene核心包 -->
<dependency>
<groupId>org.apache.lucene</groupId>
<artifactId>lucene-core</artifactId>
<version>6.1.0</version>
</dependency>
<!-- lucene查询解析包 -->
<dependency>
<groupId>org.apache.lucene</groupId>
<artifactId>lucene-queryparser</artifactId>
<version>6.1.0</version>
</dependency>
<!-- lucene解析器包 -->
<dependency>
<groupId>org.apache.lucene</groupId>
<artifactId>lucene-analyzers-common</artifactId>
<version>6.1.0</version>
</dependency> <dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
</dependency>
</dependencies>
</project>

lucene6 需要jdk1.8  所以JDK1.7 1.6最好使用5.3.1的lucene

因为要测试的比较多,直接在工程中新建一个junit测试类IndexingTest1.Java,然后在类中准备一下用来测试的数据,如下:

public class IndexingTest1 {

    private Directory dir; //存放索引的位置

    //准备一下用来测试的数据
private String ids[] = {"1", "2", "3"}; //用来标识文档
private String citys[] = {"shanghai", "nanjing", "qingdao"};
private String descs[] = {
"Shanghai is a bustling city.",
"Nanjing is a city of culture.",
"Qingdao is a beautiful city"
}; }

这个数据就好比是数据库中存的三张表,文档标识表,城市表,城市描述表,那么每个文件中的内容实际上可以理解为包含id,城市和城市描述这样子。也就是说相当于有三个文件,每个文件中的内容描述了一个一个城市。下面开始每一部分的测试与分析了。

2. 添加文档

  添加文档其实就是建立索引,那么首先得获取写索引的对象,然后通过这个对象去添加文档,每个文档就是一个Lucene的Document,先来看下程序,继续在IndexingTest.java中添加:

public class IndexingTest1 {

    private Directory dir; //存放索引的位置

    //准备一下用来测试的数据
private String ids[] = {"1", "2", "3"}; //用来标识文档
private String citys[] = {"shanghai", "nanjing", "qingdao"};
private String descs[] = {
"Shanghai is a bustling city.",
"Nanjing is a city of culture.",
"Qingdao is a beautiful city"
}; //生成索引
@Test
public void index() throws Exception {
IndexWriter writer = getWriter(); //获取写索引的实例
for(int i = 0; i < ids.length; i++) {
Document doc = new Document();
doc.add(new StringField("id", ids[i], Field.Store.YES));
doc.add(new StringField("city", citys[i], Field.Store.YES));
doc.add(new TextField("descs", descs[i], Field.Store.NO));
writer.addDocument(doc); //添加文档
}
writer.close(); //close了才真正写到文档中
} //获取IndexWriter实例
private IndexWriter getWriter() throws Exception {
dir = FSDirectory.open(Paths.get("D:\\lucene2"));
Analyzer analyzer = new StandardAnalyzer(); //标准分词器,会自动去掉空格啊,is a the等单词
IndexWriterConfig config = new IndexWriterConfig(analyzer); //将标准分词器配到写索引的配置中
IndexWriter writer = new IndexWriter(dir, config); //实例化写索引对象
return writer;
} }

可以看出,其实相当于id、城市名和城市描述是一个文档中的不同的部分,然后用这三个作为了一个Field,便于后面去查询。每个文档添加好了域之后,就添加到写索引的实例writer中写入。实际中是先获取一个文件,然后根据这个文件的信息去设定一些Field, 然后将这些Field封装到Document对象中传给写索引的实例,类似于上一篇博文中的那些代码。 
  然后运行一下index方法,即可在D:\lucene2\目录下看到生成的索引文件。我们也可以写一个测试方法,测试一下生成了几个文档:

public class IndexingTest1 {

    //省略上面的代码

    /***********  下面来测试了  ****************/
//测试写入了几个文档
@Test
public void testIndexWriter() throws Exception {
IndexWriter writer = getWriter();
System.out.println("总共写入了" + writer.numDocs() + "个文档");
writer.close();
}
}

3. 读取文档

  读取文档的话需要IndexReader对象,初始化的时候要传入读取文档所在的路径,也就是刚刚上面生成文档的路径D:\lucene2\,然后即可读取文档数量,测试一下:

public class IndexingTest1 {

    //省略上面的代码

    //测试读取文档
@Test
public void testIndexReader() throws Exception {
dir = FSDirectory.open(Paths.get("D:\\lucene2"));
IndexReader reader = DirectoryReader.open(dir);
System.out.println("最大文档数:" + reader.maxDoc());
System.out.println("实际文档数:" + reader.numDocs());
reader.close();
}
}

因为从测试数据中看,只有三个文档,测试结果如下:

最大文档数:3 
实际文档数:3

4. 删除文档

  这里我要着重说一下,删除文档有两种方式,这两种方式各有特点。一种是在合并前删除,另一种是在合并后删除,什么意思呢?合并前删除指的是并没有真正删除这个文档,只是在这个文档上做一个标记而已;而合并后删除指的是真正删掉了这个文档了。 
  这两个各有什么用呢?比如一个项目比较大的话,访问量也很多,那么在并发访问的情况下,频繁的删除操作会给系统的性能造成一定的影响,那么这个时候就可以用合并前删除,先不删,只是标记一下该文档属于已删除的文档,等到访问量比较小的时候(比如检测CPU比较闲的时候),我再调用删除程序统一删除标记过的文档,这样可以提升系统的性能。相反,如果数据量不大,删除操作也影响不了多大性能的话,那就直接删除好了,即使用合并后删除。下面针对这两个删除,各写一个测试程序测试一下:

public class IndexingTest1 {

    //省略上面的代码

    //测试删除文档,在合并前
@Test
public void testDeleteBeforeMerge() 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 testDeleteAfterMerge() 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方法重新生成一下,再去测试合并后删除,因为之前删掉一个了,会影响后面的测试。看一下测试结果:

合并前删除: 
 删除前有3个文档 
 删除后最大文档数:3 
 删除后实际文档数:2 
合并后删除: 
 删除前有3个文档 
 删除后最大文档数:2 
 删除后实际文档数:2

5. 修改文档

  修改文档也就是更新文档,思路是先新建一个Document对象,然后按照前面设置的字段自己再设置个新的,然后更新原来的文档,看一下测试程序:

public class IndexingTest1 {

    //省略上面的代码

    //测试更新
@Test
public void testUpdate() throws Exception {
IndexWriter writer = getWriter();
//新建一个Document
Document doc = new Document();
doc.add(new StringField("id", ids[1], Field.Store.YES));
doc.add(new StringField("city", "shanghai22", Field.Store.YES));
doc.add(new TextField("descs", "shanghai update", Field.Store.NO)); //将原来id为1对应的文档,用新建的文档替换
writer.updateDocument(new Term("id", "1"), doc);
writer.close();
System.out.println(doc.getField("descs"));
}
}

  看一下执行结果,会打印出indexed,tokenized<descs:shanghai update>,从decs描述中可以看出,这个描述是我们新建的那个文档的描述,说明我们已经修改成功了。

6. 文档域加权

  这部分要着重说明一下,比如说我们在查询的时候,如果查询的字段在多个文档中都会存在,则会根据Lucene自己的排序规则给我们列出,但是如果我想优先看查询出来的某个文档呢?或者说我如何设定让Lucene按照自己的意愿的顺序给我列出查询出的文档呢? 
  这么说可能有点难以理解,举个通俗易懂的例子,有ABCD四个人都写了一篇关于java的文章,即文章标题都有java,现在我要查询有“java”这个字符串的文章,但是D是老板,我想如果查出来的文章中有老板写的,我要优先看老板的文章,也就是说要把老板的文章放在最前面,这个时候我就可以在程序中设定权重了。 
  要模拟这个场景,新建一个测试类IndexingTest2.java。我再造一下模拟的数据,如下:

public class IndexingTest2 {

    private Directory dir; //存放索引的位置

    //准备一下数据,四个人写了四篇文章,Json是boss
private String ids[]={"1","2","3","4"};
private String authors[]={"Jack","Marry","John","Json"};
private String positions[]={"accounting","technician","salesperson","boss"};
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 IndexingTest2 {

    //省略上面代码

    @Test
public void index() throws Exception { //生成索引
dir = FSDirectory.open(Paths.get("D:\\lucene2"));
IndexWriter writer = getWriter();
for(int i = 0; i < ids.length; i++) {
Document doc = new Document();
doc.add(new StringField("id", ids[i], Field.Store.YES));
doc.add(new StringField("author", authors[i], Field.Store.YES));
doc.add(new StringField("position", positions[i], Field.Store.YES)); //这部分就是加权操作了,对title这个Field进行加权,因为等会我要查这个Field
TextField field = new TextField("title", titles[i], Field.Store.YES);
//先判断之个人对应的职位是不是boss,如果是就加权
if("boss".equals(positions[i])) {
field.setBoost(1.5f); //加权操作,默认为1,1.5表示加权了,小于1就降权了
} doc.add(field);
doc.add(new TextField("content", contents[i], Field.Store.NO));
writer.addDocument(doc); //添加文档
}
writer.close(); //close了才真正写到文档中
} //获取IndexWriter实例
private IndexWriter getWriter() throws Exception {
Analyzer analyzer = new StandardAnalyzer(); //标准分词器,会自动去掉空格啊,is a the等单词
IndexWriterConfig config = new IndexWriterConfig(analyzer); //将标准分词器配到写索引的配置中
IndexWriter writer = new IndexWriter(dir, config); //实例化写索引对象
return writer;
}
}

从代码中看出,如果想对那个field进行加权,就直接用该field去调用setBoost()方法即可,在调用之前,根据自己设定的条件进行判断就行了。先运行一下上面的index方法生成索引,然后我们写一个测试类来测试一下:

public class IndexingTest2 {

    //省略上面代码

    //文档域加权测试
@Test
public void search() throws Exception {
dir = FSDirectory.open(Paths.get("D:\\lucene2"));
IndexReader reader = DirectoryReader.open(dir);
IndexSearcher search = new IndexSearcher(reader);
String searchField = "title"; //要查询的Field
String q = "java"; //要查询的字符串
Term term = new Term(searchField, q);
Query query = new TermQuery(term); TopDocs hits = search.search(query, 10);
System.out.println("匹配" + q + "总共查询到" + hits.totalHits + "个文档");
for(ScoreDoc score : hits.scoreDocs) {
Document doc = search.doc(score.doc);
System.out.println(doc.get("author")); //打印一下查出来记录对应的作者
}
reader.close();
}
}

看一下执行结果:

匹配java总共查询到4个文档 
Json 
John 
Jack 
Marry

所以Json排在了第一位,因为他是Boss!~如果没有上面的那段加权代码,那么匹配出来的顺序是Lucene中自己的一个算法,可以认为是Lucene默认的顺序,这个底层的算法我就不去研究了,但是我们可以自己根据需求自己设定一下权重,这是实际中用的比较多的。

org.apache.lucene.index.IndexNotFoundException: no segments* file found in MMapDirectory@D:\lucene2  本篇遇到了这个问题,百度了说writer没关闭 但是检查了有关闭的

换了个D:lucene3就可以了 碰到search()方法查询到0个也是 这个问题

【Lucene】Apache Lucene全文检索引擎架构之构建索引2的更多相关文章

  1. Lucene作为一个全文检索引擎

    Lucene作为一个全文检索引擎,其具有如下突出的优点: (1)索引文件格式独立于应用平台.Lucene定义了一套以8位字节为基础的索引文件格式,使得兼容系统或者不同平台的应用能够共享建立的索引文件. ...

  2. 【Lucene】Apache Lucene全文检索引擎架构之入门实战1

    Lucene是一套用于全文检索和搜寻的开源程式库,由Apache软件基金会支持和提供.Lucene提供了一个简单却强大的应用程式接口,能够做全文索引和搜寻.在Java开发环境里Lucene是一个成熟的 ...

  3. 【Lucene】Apache Lucene全文检索引擎架构之中文分词和高亮显示4

    前面总结的都是使用Lucene的标准分词器,这是针对英文的,但是中文的话就不顶用了,因为中文的语汇与英文是不同的,所以一般我们开发的时候,有中文的话肯定要使用中文分词了,这一篇博文主要介绍一下如何使用 ...

  4. 【Lucene】Apache Lucene全文检索引擎架构之搜索功能3

    上一节主要总结了一下Lucene是如何构建索引的,这一节简单总结一下Lucene中的搜索功能.主要分为几个部分,对特定项的搜索:查询表达式QueryParser的使用:指定数字范围内搜索:指定字符串开 ...

  5. 全文检索引擎sphinx 与 Elasticsearch 索引速度对比

    sphinx的特色之一是建立索引速度快,最近转投Elasticsearch后,一直想做个对比,网上资料常见说法是10倍的差距. 测试环境 硬件:单核,2G内存的E5-2630 虚拟机 操作系统:Cen ...

  6. Apache Lucene(全文检索引擎)—创建索引

    目录 返回目录:http://www.cnblogs.com/hanyinglong/p/5464604.html 本项目Demo已上传GitHub,欢迎大家fork下载学习:https://gith ...

  7. Apache Lucene(全文检索引擎)—分词器

    目录 返回目录:http://www.cnblogs.com/hanyinglong/p/5464604.html 本项目Demo已上传GitHub,欢迎大家fork下载学习:https://gith ...

  8. Apache Lucene(全文检索引擎)—搜索

    目录 返回目录:http://www.cnblogs.com/hanyinglong/p/5464604.html 本项目Demo已上传GitHub,欢迎大家fork下载学习:https://gith ...

  9. 全文检索引擎 Lucene.net

    全文搜索引擎是目前广泛应用的主流搜索引擎.它的工作原理是计算机索引程序通过扫描文章中的每一个词,对每一个词建立一个索引,指明该词在文章中出现的次数和位置,当用户查询时,检索程序就根据事先建立的索引进行 ...

随机推荐

  1. java基础练习 18

    import java.util.Scanner; public class Eightheen { /*判断一个素数能被几个9整除*/ public static void main(String[ ...

  2. Kubernetes网络配置

    #flannel#所有node都安装#下载https://github.com/coreos/flannel/releases#解压并把flanneld和mk-codker-opts.sh复制到/us ...

  3. 洛谷——2871[USACO07DEC]手链Charm Bracelet——01背包

    题目描述 Bessie has gone to the mall's jewelry store and spies a charm bracelet. Of course, she'd like t ...

  4. ARC 098 D - Xor Sum 2

    Problem Statement There is an integer sequence A of length N. Find the number of the pairs of intege ...

  5. maven配置memcached.jar

    由于目前java memcached client没有官方的maven repository可供使用,因此使用时需要手动将其安装到本地repository. java memcached client ...

  6. Linux查看哪些进程用了Swap分区

    如果系统的物理内存用光了,则会用到swap.系统就会跑得很慢,但仍能运行;如果Swap空间用光了,那么系统就会发生错误.通常会出现“application is out of memory”的错误,严 ...

  7. sql server 性能调优 资源等待之网络I/O

    原文:sql server 性能调优 资源等待之网络I/O 一.概述 与网络I/O相关的等待的主要是ASYNC_NETWORK_IO,是指当sql server返回数据结果集给客户端的时候,会先将结果 ...

  8. ARM32 Linux kernel virtual address space

    http://thinkiii.blogspot.jp/2014/02/arm32-linux-kernel-virtual-address-space.html   The 32-bit ARM C ...

  9. 从vue.js的源码分析,input和textarea上的v-model指令到底做了什么

    v-model是 vue.js 中用于在表单表单元素上创建双向数据绑定,它的本质只是一个语法糖,在单向数据绑定的基础上,增加了监听用户输入事件并更新数据的功能:对,它本质上只是一个语法糖,但到底是一个 ...

  10. redis--AOF

    Redis 分别提供了 RDB 和 AOF 两种持久化机制: RDB 将数据库的快照( snapshot)以二进制的方式保存到磁盘中. 相当于MySQL binlog 的 raw模式 AOF 则以协议 ...