Lucene

目录

Lucene

Lucene介绍

全文检索是什么?

Lucene是什么?

Lucene官方网站

全文检索流程介绍

索引和检索流程图

索引流程详细介绍

倒排索引

数据库&索引库结构对比

分词器

IK分词器使用

特性与种类

Lucene基本使用

环境搭建

索引流程实现

luke查看索引数据

检索流程实现

高级搜索

词条搜索

范围查询

组合查询

条件表达式

分页和排序


Lucene

Lucene介绍

全文检索引擎工具包,实现从海量数据中快速查询数据。

方案对比

  • 传统方案

    • 优点:使用简单,简单的SQL语句即可。

    • 缺点:1、数据量大时查询性能差(不会通过索引查询);2、查询的结果不够全面。

  • 全文检索方案

    • 缺点:使用相对复杂,通过提供的API进行操作。

    • 优点:1、数据量大时查询性能好;2、查询的结果全面

全文检索是什么?

  • 索引流程:通过索引程序切分数据的内容成一个个词语,建立词语与每一条数据的对应关系

  • 检索流程:通过检索程序,根据搜索关键词,在索引库查找目标内容

Lucene是什么?

Lucene是apache软件基金会下的一个子项目。 是一个免费、开放源代码的全文检索引擎工具包

Lucene官方网站

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

全文检索流程介绍

索引和检索流程图

索引流程详细介绍

  • 采集数据
    • 通过JDBC操作获取到关系数据库中的业务数据

    • 通过IO流获取文件上的数据

    • 通过爬虫(蜘蛛)程序获取网络上的网页数据

  • 创建文档对象
    • 说明:文档对象(Document),一个文档对象包含有多个域(Field)。一个文档对象就相当于关系数据库表中的一条记录,一个域就相当于一个字段。

  • 分析文档对象

    • 把原始数据,转换成文档对象后,使用分词器把文档中域的内容切分成一个个词语,为后续建立索引做准备。

  • 建立倒排索引

    • 建立词语和文档的对应关系,词语在什么文档出现,出现了几次,在什么位置出现(倒排索引),并且保存到索引库。

倒排索引

样例:

  • 文档0(编号0): we like java java java

  • 文档1(编号1): we like lucene lucene lucene

(Term 词条)(Doc 文档,Freq 频率)(Pos 位置)
we(0,1) (1,1)(0)(0)
like(0,1) (1,1)(1)(1)
java(0,3)(2,3,4)
lucene(1,3)(2,3,4)

存储格式:词条/词语在哪个文档出现(文档编号)、出现的频率(次数)、出现的位置(下标)

数据库&索引库结构对比

数据库
Databases实例TablesRowscolumn
索引库的存储目录Document文档Field域

分词器

分词器介绍

  • 在对文档(Document)中的内容进行存储之前,需要使用分词器对域(Field)中的内容进行分词,目的是为了建立倒排索引,过程是先分词,再过滤。

    • 分词:将Document中Field域的值根据分词器内部的词典切分成一个一个的单词,具体的切分方式根据使用的分词器而不同。

    • 过滤:分词完毕后,通过过滤器排除一些标点符号以及一些停用词,也会将词条的大写转换小写。

    • 停用词说明:停用词是指为了节省存储空间和提高搜索效率,在对field内容或搜索关键字分词时会自动排除的字或词,这些字或词被称为“stop words”。如语气助词、副词、介词、连接词等等(的、在、是、啊)

注:索引流程和检索流程使用分词器要一致

IK分词器使用

IK分词器本身就是对Lucene提供的分词器Analyzer扩展实现,使用方式与Lucene的分词器一致

扩展中文词库

  • 在企业项目中,有一些词语根据业务需要不需要分词,需要作为一个整体,比如:蓝瘦香菇

特性与种类

Field的特性

Document(文档)是Field(域)的承载体,一个Document是由多个Field组成。Field由名称和值两部分组成,Field的值是要索引的内容,也是要搜索的内容。

Field的种类

Field种类数据类型是否 分词是否 索引是否 存储说明
StringField(FieldName, FieldValue,Store.YES)字符串NYY或N字符串类型Field,不分词,作为一个整体进行索引(比如:身份证号,订单编号),是否需要存储根据Store.YES或Store.NO决定

LongField(FieldName, FieldValue,Store.YES)、

DoubleField(FieldName,FieldValue,Store.YES)...

数值型代表YYY或N数值型Field代表,分词并且索引(比如:价格),是否需要存储根据Store.YES或Store.NO决定
StoredField(FieldName, FieldValue)重载方法,支持多种类型NNY不分词,不索引,默认存储(比如:商品图片路径)
TextField(FieldName, FieldValue,Store.NO)文本类型YYY或N文本类型Field,分词并且索引,是否需要存储根据Store.YES或Store.NO决定

Lucene基本使用

环境搭建

pom依赖

<properties>
    <mysql.version>5.1.46</mysql.version>
    <lucene.version>4.10.3</lucene.version>
</properties>

<dependencies>
    <!--mysql连接驱动-->
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <version>${mysql.version}</version>
    </dependency>
    <!--ik分词器-->
    <dependency>
        <groupId>com.janeluo</groupId>
        <artifactId>ikanalyzer</artifactId>
        <version>2012_u6</version>
    </dependency>
    <!--查询解析器-->
    <dependency>
        <groupId>org.apache.lucene</groupId>
        <artifactId>lucene-queryparser</artifactId>
        <version>${lucene.version}</version>
    </dependency>
    <dependency>
        <groupId>junit</groupId>
        <artifactId>junit</artifactId>
        <version>4.12</version>
    </dependency>
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <version>1.18.16</version>
    </dependency>
</dependencies>

实体类

@Data
public class Book {
    // 图书id
    private int id;
    // 图书名称
    private String bookName;
    // 图书价格
    private float bookPrice;
    // 图书图片
    private String bookPic;
    // 图书描述
    private String bookDesc;
}

DAO实现类

public class BookDaoImpl implements BookDao {
    //查询所有数据
    @Override
    public List<Book> findAll() {

        Connection connection = null;
        PreparedStatement preparedStatement = null;
        ResultSet resultSet = null;
        try {
            Class.forName("com.mysql.jdbc.Driver");
            connection = DriverManager.getConnection("jdbc:mysql:///test", "root", "root");
            preparedStatement = connection.prepareStatement("select * from book");
            resultSet = preparedStatement.executeQuery();

            List<Book> bookList = new ArrayList<>();
            while (resultSet.next()) {
                Book book = new Book();
                book.setId(resultSet.getInt("id"));
                book.setBookName(resultSet.getString("bookname"));
                book.setBookPrice(resultSet.getFloat("price"));
                book.setBookPic(resultSet.getString("pic"));
                book.setBookDesc(resultSet.getString("bookdesc"));

                bookList.add(book);
            }

            return bookList;
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            try {
                if (connection != null) {
                    connection.close();
                }
                if (preparedStatement != null) {
                    preparedStatement.close();
                }
                if (resultSet != null) {
                    resultSet.close();
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        return null;
    }
}


索引流程实现


public class IndexTest {
    /**
     * 索引流程
     */
    @Test
    public void createIndex() throws Exception {
        BookDao bookDao = new BookDaoImpl();
        List<Book> list = bookDao.findAll();

        // 1 创建分词器(Analyzer),用于分词
        Analyzer analyzer = new IKAnalyzer();

        // 2.创建索引配置对象(IndexWriterConfig),配置索引库
        IndexWriterConfig indexWriterConfig = new IndexWriterConfig(Version.LUCENE_47, analyzer);
        /**
         * 2.1 设置一个打开模式
         *         CREATE-每次都覆盖之前的索引库,创建新的索引库
         *         APPEND-追加,如果存在索引库,直接追加数据,否则报错
         *         CREATE_OR_APPEND-创建或追加,如果存在就追加,没有就创建
         */
        indexWriterConfig.setOpenMode(IndexWriterConfig.OpenMode.CREATE);

        // 3.创建索引目录对象(Directory),指定索引库的位置
        Directory directory = FSDirectory.open(new File("e:/luceneIndex"));

        // 4.创建索引写入对象(IndexWriter)
        IndexWriter indexWriter = new IndexWriter(directory, indexWriterConfig);

        for (Book book : list) {
            //创建文档对象
            Document document = new Document();

            /**
             * TextField参数:
             *     参数一:域的名称
             *     参数二:域的值
             *     参数三:指定是否把域值存储到文档对象中,最终保存在磁盘上
             */
            document.add(new StringField("id", book.getId()+"", Field.Store.YES));
            document.add(new TextField("bookName", book.getBookName(), Field.Store.YES));
            document.add(new DoubleField("bookPrice", book.getBookPrice(), Field.Store.YES));
            document.add(new StoredField("bookPic", book.getBookPic()));
            document.add(new TextField("bookDesc", book.getBookDesc(), Field.Store.NO));
            //把文档对象写入索引库
            indexWriter.addDocument(document);
        }
        // 5.提交事务
        indexWriter.commit();
        // 6.关闭资源
        indexWriter.close();
    }
}

运行结果:

luke查看索引数据

检索流程实现

/**
 * 检索流程
 */
@Test
public void searchIndex() throws Exception {
    // 1.创建分词器对象(Analyzer),用于对关键字分词
    Analyzer analyzer = new IKAnalyzer();

    // 2.创建查询对象(Query)
    // 2.1 创建查询解析器
    QueryParser queryParser = new QueryParser("", analyzer);
    Query query = queryParser.parse("bookName:java");

    // 3.创建索引目录对象(Directory),指定索引库的位置
    FSDirectory directory = FSDirectory.open(new File("e:/luceneIndex"));

    // 4.创建索引读取对象(IndexReader),把索引数据读取到内存中
    IndexReader indexReader = DirectoryReader.open(directory);

    // 5.创建索引查询对象(IndexSearcher),执行搜索
    IndexSearcher indexSearcher = new IndexSearcher(indexReader);

    // 6.执行搜索,返回结果集(TopDocs)
    TopDocs topDocs = indexSearcher.search(query, 10);
    System.out.println("总命中数:" + topDocs.totalHits);
    // 7.处理结果集
    ScoreDoc[] scoreDocs = topDocs.scoreDocs;
    for (ScoreDoc scoreDoc : scoreDocs) {
        //scoreDoc.doc:索引
        Document doc = indexSearcher.doc(scoreDoc.doc);
        System.out.println("图书id = " + doc.get("id"));
        System.out.println("图书名称 = " + doc.get("bookName"));
        System.out.println("图书价格 = " + doc.get("bookPrice"));
        System.out.println("图书图片 = " + doc.get("bookPic"));
        System.out.println("图书描述 = " + doc.get("bookDesc"));
        System.out.println("===========================================");
    }
    // 8.释放资源
    indexReader.close();
}

运行结果:

高级搜索

公共搜索方法:

//公共搜索方法
public void search(Query query) throws Exception{
    //1.创建索引目录对象
    Directory directory = FSDirectory.open(new File("e:/luceneIndex"));
    //2.创建索引读取对象
    DirectoryReader indexReader = DirectoryReader.open(directory);
    //3.创建索引查询对象
    IndexSearcher indexSearcher = new IndexSearcher(indexReader);
    //4.执行搜索
    TopDocs topDocs = indexSearcher.search(query, 5);
    // 6.处理返回结果
    ScoreDoc[] scoreDocs = topDocs.scoreDocs;
    for (ScoreDoc scoreDoc : scoreDocs) {
        System.out.println("文档分数 = " + scoreDoc.score + ",文档id = " + scoreDoc.doc);
        Document doc = indexSearcher.doc(scoreDoc.doc);
        System.out.println("图书id = " + doc.get("id"));
        System.out.println("图书名称 = " + doc.get("bookName"));
        System.out.println("图书价格 = " + doc.get("bookPrice"));
        System.out.println("图书图片 = " + doc.get("bookPic"));
        System.out.println("图书描述 = " + doc.get("bookDesc"));
        System.out.println("=========================================");
    }
    // 7.关闭资源
    indexReader.close();
}

词条搜索

匹配的结果更加精确,不会对关键字进行分词

//查询图书名称域中包含有java的图书
@Test
public void queryByTerm() throws Exception {
    Query query = new TermQuery(new Term("bookName","java"));
    search(query);
}

范围查询

//搜索图书价格大于80,小于100的图书
@Test
public void queryByRange() throws Exception {
    Query query = NumericRangeQuery.newDoubleRange("bookPrice", 80d, 100d,
            false, false);
    search(query);
}

组合查询

//组合查询:查询图书名称中包含有lucene,并且80<=图书价格<=100之间的图书
@Test
public void queryByBool() throws Exception {
    BooleanQuery boolQuery = new BooleanQuery();

    Query termQuery = new TermQuery(new Term("bookName","lucene"));
    Query rangeQuery = NumericRangeQuery.newDoubleRange("bookPrice", 80d, 100d,
            true, true);
    /**
     * 布尔查询
     *      MUST:必须满足,相当于and
     *      MUST_NOT:不能满足,相当于not
     *      SHOULD:表示或,相当于or
     */
    boolQuery.add(termQuery, BooleanClause.Occur.MUST);
    boolQuery.add(rangeQuery, BooleanClause.Occur.MUST);
    search(boolQuery);
}

条件表达式

使用逻辑运算符表达式(必须大写)

  • AND:与
  • OR:或
  • NOT:非
//查询图书名称中包含有java,并且包含lucene的图书
@Test
public void queryByExpress() throws Exception {
    QueryParser queryParser = new QueryParser("", new IKAnalyzer());
    Query query = queryParser.parse("bookName:java AND bookName:lucene");
    search(query);
}

分页和排序

//查询图书名称包含java或lucene的图书,根据价格降序排序,每页显示2条,查询第2页的数据
@Test
public void queryByPage() throws Exception{
    Analyzer analyzer =  new IKAnalyzer();
    QueryParser queryParser = new QueryParser("", analyzer);
    Query query = queryParser.parse("bookName:java OR bookName:lucene");
    /**
     * SoftField字段排序对象
     *      参数一:排序字段
     *      参数二:字段类型
     *      参数三:是否降序排序
     */
    SortField sortField = new SortField("bookPrice", SortField.Type.DOUBLE, false);
    Sort sort = new Sort(sortField);

    //1.创建索引目录对象
    Directory directory = FSDirectory.open(new File("e:/luceneIndex"));
    //2.创建索引读取对象
    DirectoryReader indexReader = DirectoryReader.open(directory);
    //3.创建索引查询对象
    IndexSearcher indexSearcher = new IndexSearcher(indexReader);

    //4.执行搜索
    int pageNum = 2;
    int pageSize = 2;
    TopDocs topDocs = indexSearcher.search(query, pageNum * pageSize, sort);

    //5.处理返回结果
    ScoreDoc[] scoreDocs = topDocs.scoreDocs;
    for (int i=(pageNum - 1)*pageSize; i<pageNum*pageSize; i++) {
        ScoreDoc scoreDoc = scoreDocs[i];
        System.out.println("文档分数 = " + scoreDoc.score + ",文档id = " + scoreDoc.doc);
        Document doc = indexSearcher.doc(scoreDoc.doc);
        System.out.println("图书id = " + doc.get("id"));
        System.out.println("图书名称 = " + doc.get("bookName"));
        System.out.println("图书价格 = " + doc.get("bookPrice"));
        System.out.println("图书图片 = " + doc.get("bookPic"));
        System.out.println("图书描述 = " + doc.get("bookDesc"));
        System.out.println("=========================================");
    }
    //6.关闭资源
    indexReader.close();
}

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值