Lucene详解

Lucene介绍

Lucene是apache下的一个开源的全文检索引擎工具包。它为软件开发人员提供一个简单易用的工具包(类库),以方便的在目标系统中实现全文检索的功能。
Lucene的官方地址为:http://lucene.apache.org/
Lucene的下载地址为:http://archive.apache.org/dist/lucene/java/

Lucene和搜索引擎的区别

Lucene和搜索引擎是不同的,Lucene是一套用java或其它语言写的全文检索的工具包。它为应用程序提供了很多个api接口去调用,可以简单理解为是一套实现全文检索的类库。搜索引擎是一个全文检索系统,它是一个单独运行的软件系统。

全文检索定义

全文检索是指计算机索引程序通过扫描文章中的每一个词,对每一个词建立一个索引,指明该词在文章中出现的次数和位置,当用户查询时,检索程序就根据事先建立的索引进行查找,并将查找的结果反馈给用户的检索方式。这个过程类似于通过字典中的检索字表查字的过程。全文搜索搜索引擎数据库中的数据。(这段话引用百度百科)

Lucene实现全文检索的流程

全文检索的流程分为两大部分:索引流程、搜索流程。

  • 索引流程:即采集数据->构建文档对象->分析文档(分词)->创建索引。
  • 搜索流程:即用户通过搜索界面->创建查询->执行搜索,搜索器从索引库搜索->渲染搜索结果。

Lucene的入门程序

需求分析

使用Lucene实现对商品表中的数据进行索引和搜索等功能的实现。

环境准备

  • jdk为1.7.0_72
  • Eclipse
  • 数据库MySQL
  • Lucene使用4.10.3

导入的jar

  • mysql5.1驱动包:mysql-connector-java-5.1.7-bin.jar
  • 核心包:lucene-core-4.10.3.jar
  • 分析器通用包:lucene-analyzers-common-4.10.3.jar
  • 查询解析器包:lucene-queryparser-4.10.3.jar
  • junit包:junit-4.9.jar

索引流程

索引流程就是上文所说的即采集数据->构建文档对象->分析文档(分词)->创建索引
第一步:采集数据
这里从数据库中采集商品信息数据,并把采集出来的数据封装到List集合中。(这里通过jdbc原生态的方式访问数据库并查询数据)
创建数据封装pojo类:

public class Book {
    // 图书ID
    private Integer id;
    // 图书名称
    private String name;
    // 图书价格
    private Float price;
    // 图书图片
    private String pic;
    // 图书描述
    private String description;
}

dao层接口:

public interface BookDao {
    // 图书查询
    public List<Book> queryBookList() throws Exception;
}

dao层实现类(这里由于简单的测试就使用硬编码了):

public class BookDaoImpl implements BookDao {

    @Override
    public List<Book> queryBookList() throws Exception {
        // 数据库链接
        Connection connection = null;

        // 预编译statement
        PreparedStatement preparedStatement = null;

        // 结果集
        ResultSet resultSet = null;

        // 图书列表
        List<Book> list = new ArrayList<Book>();

        try {
            // 加载数据库驱动
            Class.forName("com.mysql.jdbc.Driver");
            // 连接数据库
            connection = DriverManager.getConnection(
                    "jdbc:mysql://localhost:3306/solr", "root", "root");

            // SQL语句
            String sql = "SELECT * FROM book";
            // 创建preparedStatement
            preparedStatement = connection.prepareStatement(sql);

            // 获取结果集
            resultSet = preparedStatement.executeQuery();

            // 结果集解析
            while (resultSet.next()) {
                Book book = new Book();
                book.setId(resultSet.getInt("id"));
                book.setName(resultSet.getString("name"));
                book.setPrice(resultSet.getFloat("price"));
                book.setPic(resultSet.getString("pic"));
                book.setDescription(resultSet.getString("description"));
                list.add(book);
            }

        } catch (Exception e) {
            e.printStackTrace();
        }

        return list;
    }

}

第二步:创建索引


补充
在创建索引前先补充几个概念:
文档域:
对非结构化的数据统一格式为Document文档格式,一个文档有多个Field域,不同的文档其field的个数可以不同,建议相同类型的文档包括相同的Field。本例子一个Document对应一条book表的记录。
索引域:
用于搜索,搜索程序将从索引域中搜索一个一个词,根据词找到对应的文档。将Document中的Field的内容进行分词,将分好的词创建索引,索引=Field域名:词
倒排索引表:
传统方法是先找到文件,如何在文件中找内容,在文件内容中匹配搜索关键字,这种方法是顺序扫描方法,数据量大就搜索慢。
倒排索引结构是根据内容(词语)找文档,倒排索引结构也叫反向索引结构,包括索引和文档两部分,索引即词汇表,它是在索引中匹配搜索关键字,由于索引内容量有限并且采用固定优化算法搜索速度很快,找到了索引中的词汇,词汇与文档关联,从而最终找到了文档。


创建索引的流程:文档(Document)->分词器(Analyzer)->索引写对象(IndexWriter)->索引目录流对象(Directory)->索引库。
IndexWriter是索引过程的核心组件,通过IndexWriter可以创建新索引、更新索引、删除索引操作。IndexWriter需要通过Directory对索引进行存储操作。
Directory描述了索引的存储位置,底层封装了I/O操作,负责对索引进行存储。它是一个抽象类,它的子类常用的包括FSDirectory(在文件系统存储索引)、RAMDirectory(在内存存储索引)。

创建Document对象
采集数据的目的是为了索引,在索引前需要将原始数据创建成文档(Document),文档(Document)中包括一个一个的域(Field)。

// 采集数据
BookDao dao = new BookDaoImpl();
List<Book> list = dao.queryBookList();
// Document对象集合
List<Document> docList = new ArrayList<Document>();
// Document对象
Document doc = null;
for (Book book : list) {
    // 创建Document对象,同时要创建field对象
    doc = new Document();
    // 根据需求创建不同的Field
    Field id = new TextField("id", book.getId().toString(), Store.YES);
    Field name = new TextField("name", book.getName(), Store.YES);
    Field price = new TextField("price", book.getPrice().toString(),Store.YES);
    Field pic = new TextField("pic", book.getPic(), Store.YES);
    Field desc = new TextField("description",   book.getDescription(), Store.YES);

    // 把域(Field)添加到文档(Document)中
    doc.add(id);
    doc.add(name);
    doc.add(price);
    doc.add(pic);
    doc.add(desc);

    docList.add(doc);
}

分词
在对Docuemnt中的内容索引之前需要使用分词器进行分词 ,主要过程就是分词、过虑两步。

  • 分词就是将采集到的文档内容切分成一个一个的词,具体应该说是将Document中Field的value值切分成一个一个的词。
  • 过滤包括去除标点符号、去除停用词(的、是、a、an、the等)、大写转小写、词的形还原(复数形式转成单数形参、过去式转成现在式。。。)等。

其实Lucene作为一个工具包提供了许多不同国家的分词器。这些分词器都在lucene-analyzers-common-4.10.3.jar包中。本例子使用StandardAnalyzer,它可以对用英文进行分词。
以下是org.apache.lucene.analysis.standard.standardAnalyzer的部分源码:

@Override
  protected TokenStreamComponents createComponents(final String fieldName, final Reader reader) {
    final StandardTokenizer src = new StandardTokenizer(getVersion(), reader);
    src.setMaxTokenLength(maxTokenLength);
    TokenStream tok = new StandardFilter(getVersion(), src);
    tok = new LowerCaseFilter(getVersion(), tok);
    tok = new StopFilter(getVersion(), tok, stopwords);
    return new TokenStreamComponents(src, tok) {
      @Override
      protected void setReader(final Reader reader) throws IOException {
        src.setMaxTokenLength(StandardAnalyzer.this.maxTokenLength);
        super.setReader(reader);
      }
    };
  }

分析源码:
从一个Reader字符流开始,创建一个基于Reader的Tokenizer分词器,经过三个TokenFilter生成语汇单元Token。

  • 同一个域中相同的语汇单元(Token)对应同一个Term(词),它记录了语汇单元的内容及所在域的域名等。
  • 不同的域中拆分出来的相同的单词对应不同的term。
  • 相同的域中拆分出来的相同的单词对应相同的term。

补充:
Tokenizer是分词器,负责将reader转换为语汇单元即进行分词,Lucene提供了很多的分词器,也可以使用第三方的分词,比如IKAnalyzer一个中文分词器。
tokenFilter是分词过滤器,负责对语汇单元进行过滤,tokenFilter可以是一个过滤器链儿,Lucene提供了很多的分词器过滤器,比如大小写转换、去除停用词等。


对Document中的数据进行分词:

        // 创建标准分词器
        Analyzer analyzer = new StandardAnalyzer();
        // 创建Directory
        Directory directory = FSDirectory.open(new File("F:\\index"));

        IndexWriterConfig indexConfig = new IndexWriterConfig(Version.LUCENE_4_10_3, analyzer);

        // 创建IndexWriter
        IndexWriter write = new IndexWriter(directory, indexConfig);

        // 通过IndexWriter把Document写到索引库中去
        for (Document docment : docLists) {
            write.addDocument(docment);
        }
        // 关闭writer
        write.close();

通过上面的操作就把数据库中的数据采集下来进行分词并写入到索引库中去了。下面就就介绍搜索流程:

搜索流程

索引过程大体分为5步:

  • 用户定义查询语句,用户确定查询什么内容(输入什么关键字)指定查询语法,相当于sql语句。(关于Lucene查询语法下面会详解)
  • IndexSearcher索引搜索对象,定义了很多搜索方法,程序员调用此对象搜索。
  • IndexReader索引读取对象,它对应的索引维护对象IndexWriter,IndexSearcher通过IndexReader读取索引目录中的索引文件。
  • Directory索引流对象,IndexReader需要Directory读取索引库,使用FSDirectory文件系统流对象
  • IndexSearcher搜索完成,返回一个TopDocs。

用户输入关键字进行搜索,和索引过程一样,也需要对关键字进行分词,一般情况索引和搜索使用的分词器一致。
分词后得到的词(term),term与document相关联,找到了term就找到了关联的document,从document取出Field中的信息即是要搜索的信息。
搜索代码实现:
由于是演示入门程序,这里就使用创建QueryParser对象来对用户输入的查询条件进行解析。关于有多少种查询方式,同样也是在下文中会有详细的介绍(查询方式)

    @Test
    public void indexSearch() throws Exception {
        // 创建query对象
        // 使用QueryParser搜索时,需要指定分词器,搜索时的分词器要和索引时的分词器一致
        // 第一个参数:默认搜索的域的名称
        // 第二个参数:对查询条件分词使用的分词器。
        QueryParser parser = new QueryParser("description",
                new StandardAnalyzer());

        // 通过queryparser来创建query对象
        // 参数:输入的lucene的查询语句(关键字一定要大写)
        Query query = parser.parse("description:java AND lucene");

        // 创建IndexSearcher
        // 指定索引库的地址
        File indexFile = new File("F:\\index");
        Directory directory = FSDirectory.open(indexFile);
        IndexReader reader = DirectoryReader.open(directory);
        IndexSearcher searcher = new IndexSearcher(reader);

        // 通过searcher来搜索索引库
        // 第二个参数:指定需要显示的顶部记录的N条
        TopDocs topDocs = searcher.search(query, 10);

        // 根据查询条件匹配出的记录总数
        int count = topDocs.totalHits;
        System.out.println("匹配出的记录总数:" + count);
        // 根据查询条件匹配出的记录
        ScoreDoc[] scoreDocs = topDocs.scoreDocs;

        for (ScoreDoc scoreDoc : scoreDocs) {
            // 获取文档的ID
            int docId = scoreDoc.doc;

            // 通过ID获取文档
            Document doc = searcher.doc(docId);
            System.out.println("商品ID:" + doc.get("id"));
            System.out.println("商品名称:" + doc.get("name"));
            System.out.println("商品价格:" + doc.get("price"));
            System.out.println("商品图片地址:" + doc.get("pic"));
            System.out.println("==========================");
            // System.out.println("商品描述:" + doc.get("description"));
        }
        // 关闭资源
        reader.close();
    }

这里就完成了Lucene的入门程序的大体介绍。通过入门程序,我们接下来就再对入门程序中使用到的Field域,对索引的操作,分词等做详细的介绍。

Field域

Field是文档中的域,包括Field名和Field值两部分,一个文档可以包括多个Field,Document只是Field的一个承载体,Field值即为要索引的内容,也是要搜索的内容。

Field属性

在入门程序中,我们使用到Field域是TextField域,并且也对TextField域设置了Store.YES存储。下面我们就来看看Field域到底有哪些属性:
Field属性大体有3个:
- 是否分词(tokenized)
是:作分词处理,即将Field值进行分词,分词的目的是为了索引。
比如:商品名称、商品简介等,这些内容用户要输入关键字搜索,由于搜索的内容格式大、内容多需要分词后将语汇单元索引。
否:不作分词处理。
比如:商品id、订单号、身份证号等。

  • 是否索引(indexed)
    是:进行索引。将Field分词后的词或整个Field值进行索引,索引的目的是为了搜索。
    比如:商品名称、商品简介分析后进行索引,订单号、身份证号不用分析但也要索引,这些将来都要作为查询条件。
    否:不索引。该域的内容无法搜索到。
    比如:商品id、文件路径、图片路径等,不用作为查询条件的不用索引。

  • 是否存储(stored)
    是:将Field值存储在文档中,存储在文档中的Field才可以从Document中获取。
    比如:商品名称、订单号,凡是将来要从Document中获取的Field都要存储。
    否:不存储Field值,不存储的Field无法通过Document获取。
    比如:商品简介,内容较大不用存储。如果要向用户展示商品简介可以从系统的关系数据库中获取商品简介。
    注意:是否存储对索引是没有影响的。索引只是为了搜索,存储只是为了能否从索引的结果中获取出内容。两者没有太大的必然联系。

Field类型

下边列出了开发中常用 的Filed类型,注意Field的属性,根据需求选择。

Field类 数据类型 Analyzed是否分词 Indexed是否索引 Stored是否存储 说明
StringField(FieldName, FieldValue,Store.YES)) 字符串 N Y Y或N 这个Field用来构建一个字符串Field,但是不会进行分词,会将整个串存储在索引中,比如(订单号,身份证号等)是否存储在文档中用Store.YES或Store.NO决定。
LongField(FieldName, FieldValue,Store.YES) Long型 Y Y Y或N 这个Field用来构建一个Long数字型Field,进行分词和索引,比如(价格)是否存储在文档中用Store.YES或Store.NO决定。
StoredField(FieldName, FieldValue) 重载方法,支持多种类型 N N Y 这个Field用来构建不同类型Field不分析,不索引,但要Field存储在文档中,比如:图片地址
TextField(FieldName, FieldValue, Store.NO)或TextField(FieldName, reader) 字符串或流 Y Y Y或N 如果是一个Reader, lucene猜测内容比较多,会采用Unstored的策略.

通过对Fiel域的了解,我们对入门程序做出修改:

            // 参数:域名、域中存储的内容、是否存储
            // 不分词、索引、要存储
            // Field id = new TextField("id", book.getId().toString(),
            // Store.YES);
            Field id = new StoredField("id", book.getId().toString(), Store.YES);
            // 图书名称
            // 分词、索引、存储
            Field bookname = new TextField("bookname", book.getName(),Store.YES);
            // 图书价格
            // 分词、索引、存储
            Field price = new FloatField("price", book.getPrice(), Store.YES);
            // 图书图片
            // 不分词、不索引、要存储
            Field pic = new StoredField("pic", book.getPic());
            // 图书描述
            // 分词、索引、不存储
            Field description = new TextField("description",
            book.getDescription(), Store.NO);

索引的维护

如果,管理人员在后期维护商城平台,对一些商品数据进行修改和增加删除等操作后,对数据库中的数据发生变化。而此时我们索引库中的数据还是之前的数据,这样就会出现搜索商品和实际商品有不合现象。因此,我们在开发中需要对后台对商品进行修改的同时,也需要对索引库中的数据进行修改,保证索引库中的索引数据和数据库中的数据同步。注意:在同步索引最好不要每次删除索引库中的全部数据,然后再把数据库中的数据采集下来建立索引库,这种方式一般是不可行的。如果系统中商品信息特别多,这样会让系统变的特别迟钝,甚至系统直接崩溃。下面就对索引的删除、增加、修改进行案例演示。

添加索引

添加索引和入门程序中创建索引库基本上是一样的。都是通过IndexWriter对象把添加的Document对象写入到索引库中去。
案例:

public void createIndex() throws IOException {
        // 采集数据
        BookingInformationDao book = new BookingInformationDaoImpl();
        List<BookingInformation> lists = book.querryBookingInformation();
        // 将采集到的数据封装到Document对象中去
        List<Document> docLists = new ArrayList<>();
        Document doc;
        for (BookingInformation booking : lists) {
            doc = new Document();
            // store如果为yes,则说明存储到文档域中。它跟索引域中是否存在没有关系
            // 订单id
            Field id = new TextField("id", booking.getId(), Store.YES);
            // 开始地址
            Field startLocation = new TextField("startLocation", booking.getStartLocation(), Store.YES);
            // 结束地址
            Field endLocation = new TextField("endLocation", booking.getEndLocation(), Store.YES);
            // 订单状态
            Field status = new TextField("status", booking.getStatus(), Store.YES);
            // 订单时间
            Field time = new TextField("time", booking.getTime(), Store.YES);
            // 订单价格
            Field price = new TextField("price", booking.getPrice(), Store.YES);
            // 用户id
            Field userId = new TextField("userId", booking.getUserId(), Store.YES);

            // 将field域设置到document中
            doc.add(id);
            doc.add(startLocation);
            doc.add(endLocation);
            doc.add(status);
            doc.add(time);
            doc.add(price);
            doc.add(userId);

            docLists.add(doc);
        }

        // 创建标准分词器
        //Analyzer analyzer = new StandardAnalyzer();
        //使用第三方中文分词器
        Analyzer analyzer = new IKAnalyzer();

        // 创建Directory
        Directory directory = FSDirectory.open(new File("F:\\index"));

        IndexWriterConfig indexConfig = new IndexWriterConfig(Version.LUCENE_4_10_3, analyzer);

        // 创建IndexWriter
        IndexWriter write = new IndexWriter(directory, indexConfig);

        // 通过IndexWriter把Document写到索引库中去
        for (Document docment : docLists) {
            write.addDocument(docment);
        }

        // 关闭writer
        write.close();

    }

删除索引

删除索引,肯定是需要指定删除哪个索引,因此在删除索引时,也需要指定查询条件的。这里就使用Term词来进行删除索引(Term是索引域中最小的单位。根据条件删除时,建议根据唯一键来进行删除。如果不根据唯一条件删除索引,就会删除满足条件的所有索引进行删除),当然也可以根据Query进行删除指定的索引。关于使用Term还是Query进行删除也只是寻找需要删除的索引方式不同,底层使用的查询语句不同。关于语句,在下面会介绍Lucene的查询语法。

删除指定的索引:

    // 删除索引
    @Test
    public void deleteIndex() throws Exception {
        // 1、指定索引库目录
        Directory directory = FSDirectory.open(new File("F:\\index"));
        // 2、创建IndexWriterConfig
        IndexWriterConfig cfg = new IndexWriterConfig(Version.LUCENE_4_10_3,new StandardAnalyzer());
        // 3、 创建IndexWriter
        IndexWriter writer = new IndexWriter(directory, cfg);
        // 4、通过IndexWriter来删除索引
        // b)、删除指定索引
        writer.deleteDocuments(new Term("filename", "apache"));
        // 5、关闭IndexWriter
        writer.close();
    }

删除全部索引(这方式不推荐使用):

    // 删除索引
    @Test
    public void deleteIndex() throws Exception {
        // 1、指定索引库目录
        Directory directory = FSDirectory.open(new File("F:\\index"));
        // 2、创建IndexWriterConfig
        IndexWriterConfig cfg = new IndexWriterConfig(Version.LUCENE_4_10_3,new StandardAnalyzer());
        // 3、 创建IndexWriter
        IndexWriter writer = new IndexWriter(directory, cfg);
        // 4、通过IndexWriter来删除索引
        // a)、删除全部索引
        writer.deleteAll();
        // 5、关闭IndexWriter
        writer.close();
    }

建议参照关系数据库基于主键删除方式,所以在创建索引时需要创建一个主键Field,删除时根据此主键Field删除。
索引删除后将放在Lucene的回收站中,Lucene3.X版本可以恢复删除的文档,3.X之后无法恢复。

修改索引

更新索引是先删除再添加,建议对更新需求采用此方法并且要保证对已存在的索引执行更新,可以先查询出来,确定更新记录存在执行更新操作。
注意:更新索引是会先删除原来的索引,在用新的索引覆盖;如果没有找到最开始的索引,就直接添加新的索引。修改索引的流程:先查询,再删除,再添加。

    // 修改索引
    @Test
    public void updateIndex() throws Exception {
        // 1、指定索引库目录
        Directory directory = FSDirectory.open(new File("F:\\index"));
        // 2、创建IndexWriterConfig
        IndexWriterConfig cfg = new IndexWriterConfig(Version.LUCENE_4_10_3,new StandardAnalyzer());
        // 3、 创建IndexWriter
        IndexWriter writer = new IndexWriter(directory, cfg);
        // 4、通过IndexWriter来修改索引
        // a)、创建修改后的文档对象
        Document document = new Document();

        // 文件名称
        Field filenameField = new StringField("filename", "updateIndex", Store.YES);
        document.add(filenameField);

        // 修改指定索引为新的索引
        //第一个参数:指定查询条件
        //第二个参数:修改之后的对象。
        writer.updateDocument(new Term("filename", "apache"), document);

        // 5、关闭IndexWriter
        writer.close();
    }

这里就对索引的维护介绍结束了。下面我们就来看看Lucene查询语法:

Lucene查询语法

Lucene查询语法以可读的方式书写,然后使用JavaCC进行词法转换,转换成机器可识别的查询。
基础语法查询:
域名:搜索的关键字
例子: name:Java(查询name域中有Java关键字的域)
范围查询:
域名:[min TO max]
例子: size:[1 TO 8](查询size域中值在1到8范围的值)
注意:QueryParser不支持对数字范围的搜索,它支持字符串范围。数字范围搜索建议使用NumericRangeQuery。
组合条件查询:

组合 符号
Occur.MUST 查询条件必须满足,相当于and +(加号)
Occur.SHOULD 查询条件可选,相当于or 空(不用符号)
Occur.MUST_NOT 查询条件不能满足,相当于not非 -(减号)

- 1)+条件1 +条件2:两个条件之间是并且的关系and
例如:+filename:apache +content:apache
- 2)+条件1 条件2:必须满足第一个条件,忽略第二个条件
例如:+filename:apache content:apache
- 3)条件1 条件2:两个条件满足其一即可。
例如:filename:apache content:apache
- 4)-条件1 条件2:必须不满足条件1,要满足条件2
例如:-filename:apache content:apache

这里也提供一片关于Lucene查询语法的博客:https://www.cnblogs.com/xing901022/p/4974977.html

Lucene查询方式

对要搜索的信息创建Query查询对象,Lucene会根据Query查询对象生成最终的查询语法。类似关系数据库Sql语法一样,Lucene也有自己的查询语法,比如:“name:lucene”表示查询Field的name为“lucene”的文档信息。

Lucene查询方式分为两种:
1. 使用Lucene提供Query子类
Query是一个抽象类,lucene提供了很多查询对象,比如TermQuery项精确查询,NumericRangeQuery数字范围查询等。
2. 使用QueryParse解析查询表达式
QueryParser会将用户输入的查询表达式解析成Query对象实例。

通过Query子类搜索

TermQuery

TermQuery项查询,TermQuery不使用分析器,搜索关键词作为整体来匹配Field域中的词进行查询,比如订单号、分类ID号等。

    @Test
    public void doSearch(Query query) throws Exception {
        // 创建IndexSearcher
        // 指定索引库的地址
        File indexFile = new File("F:\\index");
        Directory directory = FSDirectory.open(indexFile);
        IndexReader reader = DirectoryReader.open(directory);
        IndexSearcher searcher = new IndexSearcher(reader);

        // 通过searcher来搜索索引库
        // 第二个参数:指定需要显示的顶部记录的N条
        TopDocs topDocs = searcher.search(query, 10);

        // 根据查询条件匹配出的记录总数
        int count = topDocs.totalHits;
        System.out.println("匹配出的记录总数:" + count);
        // 根据查询条件匹配出的记录
        ScoreDoc[] scoreDocs = topDocs.scoreDocs;
        for (ScoreDoc scoreDoc : scoreDocs) {
            // 获取文档的ID
            int docId = scoreDoc.doc;
            // 通过ID获取文档
            Document doc = searcher.doc(docId);
            System.out.println("商品ID:" + doc.get("id"));
            System.out.println("商品名称:" + doc.get("name"));
            System.out.println("商品价格:" + doc.get("price"));
            System.out.println("商品图片地址:" + doc.get("pic"));
            System.out.println("==========================");
            // System.out.println("商品描述:" + doc.get("description"));
        }
        // 关闭资源
        reader.close();
    }
    @Test
    public void testTermQuery() throws Exception {
        // 1、 创建查询(Query对象)
        Query query = new TermQuery(new Term("filename", "apache"));
        // 2、 执行搜索
        doSearch(query);
    }

NumbericRangeQuery

NumericRangeQuery,指定数字范围查询.

    @Test
    public void testNumbericRangeQuery() throws Exception {
        // 创建查询
        // 第一个参数:域名
        // 第二个参数:最小值
        // 第三个参数:最大值
        // 第四个参数:是否包含最小值
        // 第五个参数:是否包含最大值
        Query query = NumericRangeQuery.newLongRange("size", 1l, 100l, true,true);
        // 2、 执行搜索
        doSearch(query);//doSearch方法
    }

BooleanQuery

BooleanQuery,布尔查询,实现组合条件查询。

    @Test
    public void booleanQuery() throws Exception {
        BooleanQuery query = new BooleanQuery();
        Query query1 = new TermQuery(new Term("id", "3"));
        Query query2 = NumericRangeQuery.newFloatRange("price", 10f, 200f,true, true);
        //MUST:查询条件必须满足,相当于AND
        //SHOULD:查询条件可选,相当于OR
        //MUST_NOT:查询条件不能满足,相当于NOT非
        query.add(query1, Occur.MUST);
        query.add(query2, Occur.SHOULD);
        System.out.println(query);
        search(query);
    }

组合关系代表的意思如下:
1、MUST和MUST表示“与”的关系,即“并集”。
2、MUST和MUST_NOT前者包含后者不包含。
3、MUST_NOT和MUST_NOT没意义
4、SHOULD与MUST表示MUST,SHOULD失去意义;
5、SHOUlD与MUST_NOT相当于MUST与MUST_NOT。
6、SHOULD与SHOULD表示“或”的概念。

通过QueryParser搜索

    @Test
    public void testQueryParser() throws Exception {
        // 创建QueryParser
        // 第一个参数:默认域名
        // 第二个参数:分词器
        QueryParser queryParser = new QueryParser("name", new IKAnalyzer());
        // 指定查询语法 ,如果不指定域,就搜索默认的域
        Query query = queryParser.parse("lucene");
        System.out.println(query);
        // 2、 执行搜索
        doSearch(query);
    }

这里QueryParser的parse方法中就需要写入查询语法。

MultiFieldQueryParser

通过MuliFieldQueryParse对多个域查询。

    @Test
    public void testMultiFieldQueryParser() throws Exception {
        // 可以指定默认搜索的域是多个
        String[] fields = { "name", "description" };
        // 创建一个MulitFiledQueryParser对象
        QueryParser parser = new MultiFieldQueryParser(fields, new IKAnalyzer());
        // 指定查询语法 ,如果不指定域,就搜索默认的域
        Query query = parser.parse("lucene");
        // 2、 执行搜索
        doSearch(query);
    }

这里就对Lucene常用的查询方式做了大体的介绍。

相关度排序

相关度排序是查询结果按照与查询关键字的相关性进行排序,越相关的越靠前。比如搜索“Lucene”关键字,与该关键字最相关的文章应该排在前边。

相关度打分
Lucene对查询关键字和索引文档的相关度进行打分,得分高的就排在前边。
Lucene是在用户进行检索时实时根据搜索的关键字计算出来的,分两步:

  1. 计算出词(Term)的权重
  2. 根据词的权重值,计算文档相关度得分。

设置boost值影响相关度排序
boost是一个加权值(默认加权值为1.0f),它可以影响权重的计算。

  • 在索引时对某个文档中的field设置加权值高,在搜索时匹配到这个文档就可能排在前边。
  • 在搜索时对某个域进行加权,在进行组合域查询时,匹配到加权值高的域最后计算的相关度得分就高。

在创建索引时设置
如果希望某些文档更重要,当此文档中包含所要查询的词则应该得分较高,这样相关度排序可以排在前边,可以在创建索引时设定文档中某些域(Field)的boost值来实现,如果不进行设定,则Field Boost默认为1.0f。一旦设定,除非删除此文档,否则无法改变
案例:通过添加Field域的时候就设置boost值来影响打分。下面就是通过添加测试description域来设置boost值

    @Test
    public void setBoost4createIndex() throws Exception {
        // 创建分词器
        Analyzer analyzer = new StandardAnalyzer();
        IndexWriterConfig cfg = new IndexWriterConfig(Version.LUCENE_4_10_3,analyzer);
        Directory directory = FSDirectory.open(new File("F:\\index"));
        // 创建IndexWriter对象,通过它把分好的词写到索引库中
        IndexWriter writer = new IndexWriter(directory, cfg);
        Document doc = new Document();
        Field id = new StringField("id", "11", Store.YES);
        Field description = new TextField("description", "测试设置BOOST值 lucene",Store.YES);
        // 设置boost
        description.setBoost(10.0f);
        // 把域添加到文档中
        doc.add(id);
        doc.add(description);
        writer.addDocument(doc);
        // 关闭IndexWriter
        writer.close();
    }

在查询索引时设置

    @Test
    public void multiFieldQueryParserTest() throws Exception{
        //指定默认搜索的域是哪些
        String[] fields = {"startLocation","endLocation"}; 
        //创建MultiFieldQueryParser对象
        Map<String,Float> boots = new HashMap<>();
        boots.put("startLocation", 10f);
        QueryParser queryParser = new MultiFieldQueryParser(fields, new StandardAnalyzer(),boots);
        //通过QueryParser对象创建Query对象
        Query query = queryParser.parse("成都~");
        System.out.println(query);
        //查询索引
        doSearch(query);
    }

关于Lucene如何添加中文分词器,这里就不再接介绍了,在solr中会介绍如何使用中文分词器。

猜你喜欢

转载自blog.csdn.net/ITITII/article/details/80555358