Lucene

Lucene简介

Luceneapache下的一个开源的全文检索引擎工具包。

 

 全文检索(Full-text Search 

定义

全文检索就是先分词创建索引,再执行搜索的过程。

分词:就是将一段文字分成一个个单词

 

全文检索就将一段文字分成一个个单词去查询数据!!!

 

应用场景

搜索引擎(了解)

搜索引擎是一个基于全文检索、能独立运行、提供搜索服务的软件系统。

 

电商站内搜索(重点)

思考:电商网站内,我们都是通过输入关键词来搜索商品的。如果我们根据关键词,直接查询数据库,会有什么后果?

答:我们只能使用模糊搜索,来进行匹配,会导致很多数据匹配不到。所以,我们必须使用全文检索。

 Lucene实现全文检索的流程

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

索引流程:采集数据--->构建文档对象--->创建索引(将文档写入索引库)

搜索流程:创建查询--->执行搜索--->渲染搜索结果。

入门示例

 需求

使用Lucene实现电商项目中图书类商品的索引和搜索功能。

 配置步骤说明

1)搭建环境(先下载Lucene

2)创建索引库

3)搜索索引库

 

 配置步骤

 第一部分:搭建环境(创建项目,导入包)

前提:已经创建好了数据库(直接导入book.sql

 

 第一步下载Lucene

Lucene是开发全文检索功能的工具包,使用时从官方网站下载,并解压。

官方网站:http://lucene.apache.org/ 

下载地址http://archive.apache.org/dist/lucene/java/

下载版本:4.10.3(要求:jdk1.7及以上)

 

核心包lucene-core-4.10.3.jar(附常用API

 

第二步创建项目导入包

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

 

项目结构如下:

 

 

 

 第二部分:创建索引

步骤说明:

1)采集数据

2)将数据转换成Lucene文档

3)将文档写入索引库,创建索引

 

 第一步:采集数据

Lucene全文检索,不是直接查询数据库,所以需要先将数据采集出来。

 

1)创建Book

 

public class Book {
    private Integer bookId;  // 图书ID
    private String name;   // 图书名称
    private Float price;    // 图书价格
    private String pic;    // 图书图片
    private String description; // 图书描述
    // 补全get\set方法
}

 

 

 

(2)创建一个BookDao类

 

package cn.gzsxt.lucene.dao;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;

import cn.gzsxt.lucene.pojo.Book;

public class BookDao {
    public List<Book> getAll() {
        // 数据库链接
        Connection connection = null;
        // 预编译statement
        PreparedStatement preparedStatement = null;
        // 结果集
        ResultSet resultSet = null;
        // 图书列表
        List<Book> list = new ArrayList<Book>();
        try {
            // 加载数据库驱动
    Class.forName("com.mysql.cj.jdbc.Driver");
            // 连接数据库
            connection = DriverManager.getConnection(
    "jdbc:mysql://localhost:3306/mybatis?characterEncoding=utf-8&serverTimezone=GMT%2B8", "root", "gzsxt");

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

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

            // 结果集解析
            while (resultSet.next()) {
                Book book = new Book();
                book.setBookId(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();
            
        }finally {
            
            if(null!=resultSet){
                try {
                    resultSet.close();
                } catch (SQLException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
            }
            if(null!=preparedStatement){
                try {
                    preparedStatement.close();
                } catch (SQLException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
            }
            if(null!=connection){
                try {
                    connection.close();
                } catch (SQLException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
            }
        }
        return list;
    }
}

 

3)创建一个测试类BookDaoTest

 

import java.util.List;

import org.junit.Test;
import cn.gzsxt.lucene.dao.BookDao;
import cn.gzsxt.lucene.pojo.Book;

public class BookDaoTest {

    @Test
    public void getAll(){
        BookDao dao = new BookDao();
        List<Book> books = dao.getAll();
        
        for (Book book : books) {
            System.out.println("图书id:"+book.getBookId()+",图书名称:"+book.getName());
        }
    }
}

 

4)测试结果,采集数据成功

 

 

 第二步:将数据转换成Lucene文档

 

Lucene是使用文档类型来封装数据的,所有需要先将采集的数据转换成文档类型。其格式为:

修改BookDao,新增一个方法,转换数据

public List<Document> getDocuments(List<Book> books){
        // Document对象集合
        List<Document> docList = new ArrayList<Document>();
        // Document对象
        Document doc = null;
        for (Book book : books) {
            // 创建Document对象,同时要创建field对象
            doc = new Document();
            // 根据需求创建不同的Field
            Field id = new TextField("id", book.getBookId().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);
    }
    return docList;
}

 

 

 第三步创建索引库

说明Lucene是在将文档写入索引库的过程中,自动完成分词、创建索引的。因此创建索引库,从形式上看,就是将文档写入索引库!

 

修改测试类,新增createIndex方法

 

    @Test
    public void createIndex(){
       try {

           //1、创建一个分词器Analyzer
          Analyzer sr = new StandardAnalyzer();
           //2、指定索引库的目录。目录存在磁盘上
          Directory dy = FSDirectory.open(new File("D:\\idea\\001"));
            //3、创建indexwriter写对象,将文档写入索引库
           //创建一个配置类Version.LATEST最新版本
           IndexWriterConfig ig = new IndexWriterConfig(Version.LATEST, sr);
           //创建writer对象
           IndexWriter indexWriter = new IndexWriter(dy, ig);
           //4、将文档写入索引库,完成分词、创建索引的过程
           BookDao bookDao = new BookDao();
           indexWriter.addDocuments(bookDao.getDocuments(bookDao.getAll()));
           //5、提交事物。(可省)
           indexWriter.commit();

           indexWriter.close();

           System.out.println("创建索引库成功!!!");




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


    }

测试结果,创建成功!!!

 

三部分搜索索引

说明

搜索的时候需要指定搜索哪一个域(也就是字段),并且,还要对搜索的关键词做分词处理。

 

 执行搜索

修改测试类,新增searchDocumentByIndex方法

 

 

 @Test
    public void searchDocumentByIndex(){
        try {

            //1、创建分词器
            Analyzer analyzer = new StandardAnalyzer();

            //2、指定索引库
            Directory directory = FSDirectory.open(new File("F:\\idea\\001"));

            //3、创建查询对象Query,用来封装查询的关键词
            //第一个参数:默认的搜索域
            //第二个参数:分词器
            QueryParser parser = new QueryParser("name", analyzer);

            //指定搜索的域   格式     域名:关键词
            //如果搜索的域和默认搜索域相同,则可以省略域的名称
            Query query = parser.parse("name:java教程");

            //4、创建indexSearcher,用来执行搜索
            IndexReader reader = DirectoryReader.open(directory);

            IndexSearcher searcher = new IndexSearcher(reader);

            //5、执行搜索
            //第一个参数:查询对象
            //第二个参数:默认搜索的条数,用来做分页,相当于数据库中的每页的容量pageSize
            TopDocs docs = searcher.search(query, 10);

            int totalHits = docs.totalHits;
            System.out.println("共搜索到"+totalHits+"条满足条件的数据!");

            ScoreDoc[] scoreDocs = docs.scoreDocs;
            for (ScoreDoc scoreDoc : scoreDocs) {
                //docID是lucene在写入文档的时候,自动给每一个文档加上的id编号
                int docID = scoreDoc.doc;

                Document doc = searcher.doc(docID);
                System.out.println("图书的id:"+doc.get("id"));
                System.out.println("图书的名称:"+doc.get("name"));
                System.out.println("图书的图片:"+doc.get("pic"));
                System.out.println("图书的价格:"+doc.get("price"));
                System.out.println("图书的描述信息:"+doc.get("desc"));

            }

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


    }

测试结果,非常成功!!!

 

小结

Lucene全文检索,确实可以实现对关键词做分词、再执行搜索功能。并且结果更精确。

 

 分词

重要性

分词是全文检索的核心。

 

所谓的分词,就是将一段文本,根据一定的规则,拆分成一个一个词。

 

Lucene是根据分析器实现分词的。针对不同的语言提供了不同的分析器。并且提供了一个通用的标准分析器StandardAnalyzer

 

 分词过程

--说明我们通过分析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);

      }

    };

  }

 

对应Lucene分词的过程,我们可以做如下总结:

1)分词的时候,是以域为单位的。不同的域,相互独立。

       同一个域中,拆分出来相同的词,视为同一个词Term

       不同的域中,拆分出来相同的词,不是同一个词。

 

       其中,TermLucene最小的语汇单元,不可再细分。

 

2)分词的时候经历了一系列的过滤器。如大小写转换、去除停用词等。

 

分词后索引库结构

我们这里借助前面的示例来说明

 

 

从上图中我们发现

1)索引库中有两个区域:索引区、文档区。

2)文档区存放的是文档。Lucene给每一个文档自动加上一个文档编号docID

3)索引区存放的是索引。注意

索引是以域为单位的,不同的域,彼此相互独立

索引是根据分词规则创建出来的,根据索引就能找到对应的文档。

 

Luke客户端连接索引库

Luke作为Lucene工具包中的一个工具(http://www.getopt.org/luke/),可以通过可视化界面,连接操作索引库。

 

启动方法

(1)双击start.bat启动

 

 

 

验证分词效果

 Field

问题:我们已经知道,Lucene是在写入文档时,完成分词、索引的。那Lucene是怎么知道如何分词的呢?

答:Lucene是根据文档中的域的属性来确定是否要分词、是否创建索引的。所以,我们必须搞清楚域有哪些属性。

 

 域的属性

三大属性

是否分词tokenized

只有设置了分词属性为truelucene才会对这个域进行分词处理。

 

在实际的开发中,有一些字段是不需要分词的,比如商品id,商品图片等。

而有一些字段是必须分词的,比如商品名称,描述信息等。

 

是否索引indexed

只有设置了索引属性为truelucene才为这个域的Term词创建索引。

在实际的开发中,有一些字段是不需要创建索引的,比如商品的图片等。我们只需要对参与搜索的字段做索引处理。

 是否存储stored

只有设置了存储属性为true,在查找的时候,才能从文档中获取这个域的值。

 

在实际开发中,有一些字段是不需要存储的。比如:商品的描述信息。

因为商品描述信息,通常都是大文本数据,读的时候会造成巨大的IO开销。而描述信息是不需要经常查询的字段,这样的话就白白浪费了cpu的资源了。

因此,像这种不需要经常查询,又是大文本的字段,通常不会存储到索引库。

 

特点

1)三大属性彼此独立。

2)通常分词是为了创建索引。

 

3)不存储这个域文本内容,也可以对这个域先分词、创建索引。

 

 Field常用类型

域的常用类型有很多,每一个类都有自己默认的三大属性。如下:

Field

数据类型

Analyzed

是否分词

Indexed

是否索引

Stored

是否存储

StringField(FieldName, FieldValue,Store.YES))

字符串

N

Y

Y或N

LongField(FieldName, FieldValue,Store.YES)

Long

Y

Y

Y或N

FloatField(FieldName, FieldValue,Store.YES)

Float

Y

Y

Y或N

StoredField(FieldName, FieldValue) 

重载方法,支持多种类型

N

N

Y

TextField(FieldName, FieldValue, Store.NO)

字符串

Y

Y

Y或N

 

改造入门示例中的域类型

分析

1)图书id

是否分词:不用分词,因为不会根据商品id来搜索商品 

是否索引:不索引,因为不需要根据图书ID进行搜索

是否存储:要存储,因为查询结果页面需要使用id这个值。

 

2)图书名称:

是否分词:要分词,因为要将图书的名称内容分词索引,根据关键搜索图书名称抽取的词。

是否索引:要索引。

是否存储:要存储。

 

3)图书价格:

是否分词:要分词,lucene对数字型的值只要有搜索需求的都要分词和索引,因为lucene对数字型的内容要特殊分词处理,本例子可能要根据价格范围搜索,  需要分词和索引。

是否索引:要索引

是否存储:要存储

4)图书图片地址:

是否分词:不分词

是否索引:不索引

是否存储:要存储

5)图书描述:

是否分词:要分词

是否索引:要索引

是否存储:因为图书描述内容量大,不在查询结果页面直接显示,不存储。

 

不存储是来不在lucene的索引文件中记录,节省lucene的索引文件空间,如果要在详情页面显示描述,思路:

lucene中取出图书的id,根据图书的id查询关系数据库

public List<Document> getDocuments(List<Book> books){
        // Document对象集合
        List<Document> docList = new ArrayList<Document>();
        // Document对象
        Document doc = null;
        for (Book book : books) {
            // 创建Document对象,同时要创建field对象
            doc = new Document();
        
            // 图书ID
            // 参数:域名、域中存储的内容、是否存储
            // 不分词、索引、要存储
            // Field id = new TextField("id", book.getId().toString(),Store.YES);
            Field id = new StoredField("id", book.getBookId().toString());
            // 图书名称
            // 分词、索引、存储
            Field name = new TextField("name", book.getName(),Store.YES);
            // 图书价格
            // 分词、索引、存储
            Field price = new FloatField("price", book.getPrice(), Store.YES);
            // 图书图片
            // 不分词、不索引、要存储
            Field pic = new StoredField("pic", book.getPic());
            // 图书描述
            // 分词、索引、不存储
            Field desc = new TextField("description",book.getDescription(), Store.NO);


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

            docList.add(doc);
        }

        return docList;
    }

 

 

ook表得到描述信息。

 

修改BookDaogetDocument方法

 

public List<Document> getDocuments(List<Book> books){
        
        List<Document> docs = new ArrayList<>();
        
        Document doc = null;
        
        for (Book book : books) {
            doc = new Document();
            
            /*
             * 域的三大属性,要根据需求来决定
             * 
             * 原则:参与搜索的字段,需要做分词|索引的处理
             *     参与结果展示的字段,需要做存储处理。
             */
            
            //Document文档,是由域组成的
            //id域:不需要分词、不需要索引,需要存储
//            TextField id = new TextField("id", book.getId()+"", Store.YES);
            StoredField id = new StoredField("id", book.getId());
            
            //name域:分词、索引、存储
            TextField name = new TextField("name", book.getName(), Store.YES);
            
            //图片域:  不分词、不索引,需要存储
//            TextField pic = new TextField("pic", book.getPic(), Store.YES);
            StoredField pic = new StoredField("pic", book.getPic());
            
            //价格域:需要分词、需要索引,需要存储
//            TextField price = new TextField("price", book.getPrice()+"", Store.YES);
            FloatField price = new FloatField("price", book.getPrice(), Store.YES);
            
            //描述信息域:分词、索引,不存储
            TextField desc = new TextField("desc", book.getDescription(), Store.NO);
            
            
            doc.add(id);
            doc.add(name);
            doc.add(pic);
            doc.add(price);
            doc.add(desc);
            
            docs.add(doc);
        }
        
        return docs;
    }

 

 

 

 测试

1)去索引库目录中,手动清空索引库。

2)重新创建索引库。

3)使用Luke验证分词、索引效果。

 

 

 

 索引库维护

在第4节,我们需要重新创建索引的时候,是去索引库目录下,手动删除的。

 

而在实际的开发中,我们可能压根就不知道索引库在哪,就算知道,我们也不可能每次都去手动删除,非常之麻烦!!!

所以我们必须学习如何维护索引库使用程序来操作索引库

需要注意的是,索引是与文档紧密相连的,因此对索引的维护,实际上就是对文档增删改

 

 

 添加索引(文档)

需求

数据库中新上架了图书,必须把这些图书也添加到索引库中,不然就搜不到该新上架的图书了。

 

 代码实现

调用 indexWriter.addDocument(doc)添加索引。

参考入门示例中的创建索引。

 

删除索引(文档)

 需求

某些图书不再出版销售了,我们需要从索引库中移除该图书。

 代码实现

 

 //删除
    @Test
    public void deleteIndex(){
        try {

            // 1、指定索引库目录
            Directory dy = FSDirectory.open(new File("D:\\idea\\001"));
            // 2、创建IndexWriterConfig
            IndexWriterConfig ig = new IndexWriterConfig(Version.LATEST, new StandardAnalyzer());
            // 3、 创建IndexWriter
            IndexWriter indexWriter = new IndexWriter(dy, ig);
            // 4、通过IndexWriter来删除索引
          //writer.deleteAll();  清空索引库
// 删除指定索引

indexWriter.deleteDocuments(new Term("name","java"));
//5、关闭
            indexWriter.close();

            System.out.println("s删除索引库成功!!!");




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


    }

 

 

 更新索引(文档)

 说明

Lucene更新索引比较特殊,是先删除满足条件的索引,再添加新的索引。

 

//Lucene更新操作,是用新的文档替换旧文档。实际上就是先删除旧文档,再添新文档的过程。
    //需求:更新"mybatis"这本书,将价格修改为60
    @Test
    public void update(){
        
        try {
            
            //1、创建一个分词器Analyzer
            Analyzer analyzer = new StandardAnalyzer();
            
            //2、指定索引库的目录。目录存在磁盘上
            Directory directory = FSDirectory.open(new File("F:\\lucene\\1115"));
        
            //3、创建indexwriter写对象,将文档写入索引库
            //创建一个配置类
            IndexWriterConfig config = new IndexWriterConfig(Version.LUCENE_4_10_3, analyzer);
            
            //创建writer对象
            IndexWriter writer = new IndexWriter(directory, config);
            
            //搜索的条件,相当于mysql中 where后面的内容
            StoredField id = new StoredField("id", 5);
            TextField name = new TextField("name", "mybatis", Store.YES);
            StoredField pic = new StoredField("pic", "6.jpg");
            FloatField price = new FloatField("price", 60.0f, Store.YES);
            TextField desc = new TextField("desc", "持久层框架", Store.NO);
            
            Document doc = new Document();
            
            doc.add(id);
            doc.add(name);
            doc.add(pic);
            doc.add(price);
            doc.add(desc);
            
            writer.updateDocument(new Term("name", "mybatis"), doc);
            
            writer.commit();
            
            writer.close();
            System.out.println("更新成功!!!");
        } catch (Exception e) {
            e.printStackTrace();
        }
        
    }

 

 

 搜索

问题:我们在入门示例中,已经知道Lucene是通过IndexSearcher对象,来执行搜索的。那我们为什么还要继续学习Lucene的查询呢?

答:因为在实际的开发中,我们的查询的业务是相对复杂的,比如我们在通过关键词查找的时候,往往进行价格、商品类别的过滤。

Lucene提供了一套查询方案,供我们实现复杂的查询。

 

 

 创建查询的两种方法

执行查询之前,必须创建一个查询Query查询对象。

 

Query自身是一个抽象类,不能实例化,必须通过其它的方式来实现初始化。

 

在这里,Lucene提供了两种初始化Query查询对象的方式。

 

使用Lucene提供Query子类

 

Query是一个抽象类,lucene提供了很多查询对象,比如TermQuery项精确查询,NumericRangeQuery数字范围查询等。

 

 

 

使用TermQuery实例化

 

Query query = new TermQuery(new Term("name", "lucene"));

 

 

 

 

使用QueryParse解析查询表达式

QueryParser会将用户输入的查询表达式解析成Query对象实例。如下代码:

 

QueryParser queryParser = new QueryParser("name", new IKAnalyzer());

 

Query query = queryParser.parse("name:lucene");

 常用Query子类搜索

TermQuery

特点:查询的关键词不会再做分词处理,作为整体来搜索。代码如下:

/**
     * Query子类查询之 TermQuery
     *  
     * 特点:不会再对查询的关键词做分词处理。
     * 
     * 需要:查询书名与java教程相关书。
     */
@Test
    public void queryByTermQuery(){
        //1、获取一个查询对象
        Query query = new TermQuery(new Term("name", "编程思想"));
        doSearch(query);
        
    }
   private void doSearch(Query query) {
        try {
            
            
            //2、创建一个查询的执行对象
            //指定索引库的目录
            Directory d = FSDirectory.open(new File("F:\\lucene\\0719"));
            //创建流对象
            IndexReader reader = DirectoryReader.open(d);
            //创建搜索执行对象
            IndexSearcher searcher = new IndexSearcher(reader);
            
            //3、执行搜索
            TopDocs result = searcher.search(query, 10);
            
            //4、提出结果集,获取图书的信息
            int totalHits = result.totalHits;
            System.out.println("共查询到"+totalHits+"条满足条件的数据!");
            System.out.println("-----------------------------------------");
            //提取图书信息。
            //score即相关度。即搜索的关键词和 图书名称的相关度,用来做排序处理
            ScoreDoc[] scoreDocs = result.scoreDocs;
            
            for (ScoreDoc scoreDoc : scoreDocs) {
                /**
                 * scoreDoc.doc的返回值,是文档的id, 即 将文档写入索引库的时候,lucene自动给这份文档做的一个编号。
                 * 
                 * 获取到这个文档id之后,即可以根据这个id,找到这份文档。
                 */
                int docId = scoreDoc.doc;
                System.out.println("文档在索引库中的编号:"+docId);
                
                //从文档中提取图书的信息
                Document doc = searcher.doc(docId);
                System.out.println("图书id:"+doc.get("id"));
                System.out.println("图书name:"+doc.get("name"));
                System.out.println("图书price:"+doc.get("price"));
                System.out.println("图书pic:"+doc.get("pic"));
                System.out.println("图书description:"+doc.get("description"));
                System.out.println();
                System.out.println("------------------------------------");
                
            }
            
            //关闭连接,释放资源
            if(null!=reader){
                reader.close();
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

 NumericRangeQuery

指定数字范围查询.(创建field类型时,注意与之对应)

/**
     * Query子类查询  之  NumricRangeQuery
     * 需求:查询所有价格在[60,80)之间的书
     * @param query
     */
@Test
    public void queryByNumricRangeQuery(){
        /**
         * 第一个参数:要搜索的域
         * 第二个参数:最小值
         * 第三个参数:最大值
         * 第四个参数:是否包含最小值
         * 第五个参数:是否包含最大值
         */
        Query query = NumericRangeQuery.newFloatRange("price", 60.0f, 80.0f, true, false);
        
        doSearch(query);
    }

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

 

/**
     * Query子类查询  之  BooelanQuery查询   组合条件查询
     * 
     * 需求:查询书名包含java,并且价格区间在[60,80)之间的书。
     */
    @Test
    public void queryBooleanQuery(){
        //1、要使用BooelanQuery查询,首先要把单个创建出来,然后再通过BooelanQuery组合
        Query price = NumericRangeQuery.newFloatRange("price", 60.0f, 80.0f, true, false);
        Query name = new TermQuery(new Term("name", "java"));
        
        //2、创建BooleanQuery实例对象
        BooleanQuery query = new BooleanQuery();
        query.add(name, Occur.MUST_NOT);
        query.add(price, Occur.MUST);
        /**
         * MSUT  表示必须满足                          对应的是  +
         * MSUT_NOT  必须不满足 (先满足才能有不满足)                  应对的是  -
         * SHOULD  可以满足也可以不满足     没有符号
         * 
         * SHOULD 与MUST、MUST_NOT组合的时候,SHOULD就没有意义了。
         */
        
        doSearch(query);
    }

 

 通过QueryParser搜索

 特点

对搜索的关键词,做分词处理。

 语法

 基础语法

域名:关键字

实例:name:java

 

 组合条件语法

条件1 AND 条件2  

条件1 OR 条件2

条件1 NOT 条件2

 QueryParser

 代码实现

/**
     * 查询解析器查询  之  QueryParser查询
     */
@Test
    public void queryByQueryParser(){
        try {
            
            //1、加载分词器
            Analyzer analyzer = new StandardAnalyzer();
            
            /**
             * 2、创建查询解析器实例对象
             * 第一个参数:默认搜索的域。
             *          如果在搜索的时候,没有特别指定搜索的域,则按照默认的域进行搜索
             *          如何在搜索的时候指定搜索域呢?
             *          答:格式  域名:关键词        即   name:java教程
             * 
             * 第二个参数:分词器   ,对关键词做分词处理
             */
            QueryParser parser = new QueryParser("description", analyzer);
            
            Query query = parser.parse("name:java教程");
            
            doSearch(query);
            
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

 MultiFieldQueryParser

通过MulitFieldQueryParse对多个域查询。

/**
     * 查询解析器查询  之  MultiFieldQueryParser查询
     *  
     *     特点:同时指定多个搜索域,并且对关键做分词处理
     */
    @Test
    public void queryByMultiFieldQueryParser(){
        try {
            
            //1、定义多个搜索的  name、description
            String[] fields = {"name","description"};
            //2、加载分词器
            Analyzer analyzer = new StandardAnalyzer();
            
            //3、创建 MultiFieldQueryParser实例对象
            MultiFieldQueryParser mParser = new MultiFieldQueryParser(fields, analyzer);
            
            Query query = mParser.parse("lucene教程");
            
            doSearch(query);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

OR AND NOT

/**
     * QueryParser  特点:对查询的关键词,做分词处理
     * 
     * 需求1:查询书名中包含java或者lucene的书
     * 需求2:查询描述新中即包含java,也包含lucene的图书
     */
    @Test
    public void testQueryParser(){
        QueryParser parser = new QueryParser("name", new StandardAnalyzer());
        
        try {
//            Query query = parser.parse("name:java OR name:lucene");
            Query query = parser.parse("desc:java AND desc:lucene");
            
            
            doSearch(query);
            
        } catch (ParseException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }

 

 

 中文分词器

 什么是中文分词器

学过英文的都知道,英文是以单词为单位的,单词与单词之间以空格或者逗号句号隔开

标准分词器,无法像英文那样按单词分词,只能一个汉字一个汉字来划分。

所以需要一个能自动识别中文语义的分词器。

 Lucene自带的中文分词器

 StandardAnalyzer

单字分词:就是按照中文一个字一个字地进行分词。如:“我爱中国”
效果:爱”

CJKAnalyzer

二分法分词:按两个字进行切分。如:中国人,效果:是”中国“国人”

 

上边两个分词器无法满足需求。

 

使用中文分词器IKAnalyzer

IKAnalyzer继承LuceneAnalyzer抽象类,使用IKAnalyzerLucene自带的分析器方法一样,将Analyzer测试代码改为IKAnalyzer测试中文分词效果。

 

如果使用中文分词器ik-analyzer,就在索引和搜索程序中使用一致的分词器ik-analyzer。

使用luke测试IK中文分词

1)打开Luke,不要指定Lucene目录。否则看不到效果

2)在分词器栏,手动输入IkAnalyzer的全路径

org.wltea.analyzer.lucene.IKAnalyzer

 改造代码,使用IkAnalyzer做分词器

 添加jar

 

 

 修改分词器代码

 

// 创建中文分词器

Analyzer analyzer = new IKAnalyzer();

 

1.1.1.1 扩展中文词库

拓展词库的作用:在分词的过程中,保留定义的这些词

1src或其他source目录下建立自己的拓展词库,mydict.dic文件,例如:

 

2src或其他source目录下建立自己的停用词库ext_stopword.dic文件

停用词的作用:在分词的过程中,分词器会忽略这些词。

3src或其他source目录下建立IKAnalyzer.cfg.xml,内容如下(注意路径对应):

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE properties SYSTEM "http://java.sun.com/dtd/properties.dtd">  
<properties>  
    <comment>IK Analyzer 扩展配置</comment>
    <!-- 用户可以在这里配置自己的扩展字典 -->
     <entry key="ext_dict">mydict.dic</entry> 
     <!-- 用户可以在这里配置自己的扩展停用词字典    -->
    <entry key="ext_stopwords">ext_stopword.dic</entry>
</properties>

如果想配置扩展词和停用词,就创建扩展词的文件和停用词的文件,文件的编码要是utf-8

注意不要用记事本保存扩展词文件和停用词文件那样的话格式中是含有bom

posted @ 2019-08-12 22:21  茫茫林海  阅读(676)  评论(0编辑  收藏  举报