Luence——代码实现索引及搜索

Posted 雪山上的蒲公英

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Luence——代码实现索引及搜索相关的知识,希望对你有一定的参考价值。

完成需求:使用Lucene完成对数据库中图书信息的索引和搜索功能。

1. 环境准备及工程搭建

1.1 环境准备

mysql5.5+java8+lucene4.10.3(目前最新7.0.1,这里够用就好)

需要注意:lucene从4.8版本以后,必须使用jdk1.7及以上。

1.2 工程搭建

  • Mysql驱动包
  • Analysis的包
  • Core
  • QueryParser
  • Junit(非必须)

2. 索引

2.1 采集数据

Book.java(省略get&set方法)

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

BookDaoImpl.java(实现数据库连接和查询)

public class BookDaoImpl implements BookDao {
    @Override
    public List<Book> queryBooks() {
        // 数据库链接
        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", "123");
            // 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;
    }
}

2.2 创建索引

创建索引流程

                       

IndexWriter是索引过程的核心组件,通过IndexWriter可以创建新索引、更新索引、删除索引操作。IndexWriter需要通过Directory对索引进行存储操作。

Directory描述了索引的存储位置,底层封装了I/O操作,负责对索引进行存储。它是一个抽象类,它的子类常用的包括FSDirectory(在文件系统存储索引)、RAMDirectory(在内存存储索引)。

@Test
    public void createIndex() throws Exception{
        //采集数据
        BookDao dao = new BookDaoImpl();
        List<Book> list = dao.queryBooks();
        
        //将采集到的数据封装到Document对象中
        List<Document> docList = new ArrayList<>();
        Document document;
        for (Book book : list) {
            document = new Document();
            // store:如果是yes,则说明存储到文档域中
            // 图书ID
            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 description = new TextField("description", book.getDescription(), Store.YES);

            // 将field域设置到Document对象中
            document.add(id);
            document.add(name);
            document.add(price);
            document.add(pic);
            document.add(description);

            docList.add(document);
        }
        
                // a)创建分词器,标准分词器(分析文档,对文档中的Field域进行分词)
                Analyzer analyzer = new StandardAnalyzer();

                // b)创建IndexWriterConfig对象
                IndexWriterConfig cfg = new IndexWriterConfig(Version.LUCENE_4_10_3, analyzer);
                // c)创建索引库目录,指定索引库的地址
                File indexFile = new File("D:\\\\DBIndex\\\\");
                Directory directory = FSDirectory.open(indexFile);
                // d)创建IndexWriter对象
                IndexWriter writer = new IndexWriter(directory, cfg);

                // e)通过IndexWriter对象将Document写入到索引库中
                for (Document doc : docList) {
                    writer.addDocument(doc);
                }
                // f)关闭writer
                writer.close();
    }

2.3 分词

2.3.1 Lucene中分词主要分为两个步骤分词过滤

分词field域中的内容一个个的分词

过滤将分好的词进行过滤比如去掉标点符号大写转小写词的型还原(复数转单数、过去式转成现在式)、停用词过滤

停用词单独应用没有特殊意义的词比如的英文中的this is a the等等

例:要分词的内容

Lucene is a Java full-text search engine.     

  经过分词后:

lucene     java  full  text  search  engine

2.3.2 参考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);
      }
    };
  }

2.3.3 语汇单元的生成过程

从一个Reader字符流开始,创建一个基于ReaderTokenizer分词器,经过三个TokenFilter生成语汇单元Token

同一个域中相同的语汇单元(Token)对应同一个Term(词),它记录了语汇单元的内容及所在域的域名等,还包括来该token出现的频率及位置。

  • 不同的域中拆分出来的相同的单词对应不同的term
  • 相同的域中拆分出来的相同的单词对应相同的term

例如:图书信息里面,图书名称中的java和图书描述中的java对应不同的term

 2.4 使用luke工具查看索引

Luke作为Lucene工具包中的一个工具(http://www.getopt.org/luke/),可以通过界面来进行索引文件的查询、修改。

打开Luke方法:

  • 命令运行cmd运行:java  -jar lukeall-4.10.3.jar
  • 手动执行双击lukeall-4.10.3.jar

 创建索引后,打开Luke,Path选为索引库的地址,确定即能查看到索引

luke应用

 2.5 搜索流程

同数据库的sql一样,lucene全文检索也有固定的语法。  最基本的有比如:AND, OR, NOT 等(需要大写)

举个例子,用户想找一个description中包括java关键字和lucene关键字的文档。

它对应的查询语句:description:java AND lucene 

2.5.1 使用luke搜索的例子

2.5.2 代码实现

                             

@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("D:\\\\DBIndex\\\\");
        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();
    }

 

以上是关于Luence——代码实现索引及搜索的主要内容,如果未能解决你的问题,请参考以下文章

[ES] 基础概念

luence优化速度

创建索引和搜索

全文搜索基本原理(倒排索引搜索结果排序)

solr 学习片段

#徐员外#Solr搜索 代码实现