搜索简介及Lucene入门

搜索简介及Lucene入门

搜索介绍

搜索实现方案

传统实现方案

根据用户输入的关键词(java), 应用服务器使用SQL语句查询数据库, 将查询到的结果返回给用户.

特点: 如果数据量很大, 用户量大, 数据库服务器压力随之增大, 导致查询速度变慢.

Lucene实现方案

根据用户输入的关键词(java), 应用服务器通过Lucene提供的API查询索引库, 索引库返回搜索结果给应用服务器, 服务器再将查询到的结果返回给用户

特点: 解决数据量大、用户量大、业务系统对查询速度要求高的业务需求(如实时查询).

数据查询方法

顺序扫描法

举例: 有多个文件A、B、C...要求找出文件内容包含有关键字[java]的所有文件.

顺序扫描法的思路: 从A文件开始扫描查找, 再扫描B文件...一直扫描完最后一个文件, 才能得到所有包含了java内容的文件.

特点: 文件数量越多, 查找起来就很慢.

倒排索引法(反向索引)

举例: 使用新华字典查找汉字, 先找到汉字的偏旁部首, 再根据偏旁部首对应的页码找到目标汉字.

以Lucene为例建立倒排索引:

文件一(编号是1): we like java java java

文件二(编号是2): we like Lucene Lucene Lucene

term (doc, freq) (pos)
we (1, 1) (2, 1) (0) (0)
like (1, 1) (2, 1) (1) (1)
java (1, 3) (2, 3, 4)
Lucene (2, 3) (2, 3, 4)

说明:

  • 倒排索引就是建立词语与文件的对应关系(词语在什么文件出现, 出现了多少次, 在什么位置出现);
  • 搜索时, 根据用户输入的关键词, 直接在索引中进行查询, 速度更快.

搜索技术应用场景

  1. 单机软件搜索(Office, Eclipse...);
  2. 站内搜索(京东, 淘宝);
  3. 垂直搜索(限定行业搜索, 如: 医疗, 教育);
  4. 平台搜索(Google, 百度, 360, 搜狗).

Lucene介绍

Lucene是什么

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

Lucene是Apache软件基金会下的一个子项目, 是一个成熟、免费、开放源代码的全文检索引擎工具包. 它提供了一套简单易用的API, 方便在目标系统中实现全文检索功能. 目前已有很多应用系统的搜索功能是基于Lucene来实现, 如Eclipse帮助系统的搜索功能.

Lucene能够为文本类型的数据建立索引, 只需要把数据转换成文本格式, Lucene就可以对文档进行索引和搜索. 比如常见的word文档、html文档、pdf文档, 首先将文档内容转换成文本格式, 交给Lucene进行索引, 把建立好的索引保存在硬盘或者内存中. 然后根据用户输入的查询条件, 在索引文件中查找, 将查询结果返回给用户.

全文检索是什么

计算机通过索引程序扫描文件中的每一个词, 对词建立索引, 指明该词在文件中出现的次数和位置. 用户查询时, 检索程序根据建立好的索引进行查找, 并将查询结果返回给用户.

Lucene与搜索引擎的区别

Lucene是一个用于实现全文检索的工具类库, 相当于汽车的发动机;

搜索引擎是基于全文检索, 独立运行的软件系统, 相当于汽车.

全文搜索流程

索引和检索流程图

索引流程

原始数据

保存在关系数据库中的数据, 存放在硬盘上的文件, 网络上的网页文件等都可作为原始数据.

获取文档

通过JDBC操作数据库获取关系数据库中的数据, 通过IO操作获取硬盘上的文件, 通过爬虫(蜘蛛)程序获取网络上的网页文件.

信息采集开源软件

  • Solr(http://lucene.apache.org/solr), Solr是Apache的一个子项目, 是一个独立的企业级搜索应用服务器, 对外提供类似于Web-service的API, 用户可通过HTTP请求, 向搜索引擎服务器提交一定格式的XML文件, 也可通过HTTP的Get操作提出查询请求, 并得到XML格式的返回结果 -- 支持从关系数据库、xml文档中提取原始数据.
  • Nutch(http://lucene.apache.org/nutch), Nutch是Apache的一个子项目, 包括大规模爬虫工具, 能够抓取和分辨Web网站数据.
  • jsoup(http://jsoup.org/), jsoup 是一款Java编写的HTML解析器, 可直接解析某个URL地址、HTML文本内容. 它提供了一套非常省力的API, 可通过DOM, CSS以及类似于jQuery的操作方法来获取和操作数据.

建立文档对象

文档(Document)对象, 相当于关系型数据库中的一条记录;

一个文档对象可以包含多个域(Field), 域相当于数据库表中的一个字段.

分析文档

将原始数据转换成文档对象, 使用分析器(分词器)对文档对象的域中的内容切分成一个个词语, 方便后续建立索引.

建立索引

建立词语与文档的对应关系(词语在什么文档出现, 出现了多少次, 在什么位置出现), 将其保存到索引库中.

检索流程

用户

用户可以是自然人, 也可以是程序.

用户查询

说明: 需要为用户提供一个输入关键词的界面, 如:

建立查询对象

说明: 根据用户输入的关键词, 建立查询对象(Query), Query对象会生成查询的语法.

如: bookName:Java, 表示查询图书名称域中含有Java相关的内容.

执行查询

说明: 根据建立的查询对象, 以及生成的查询语法, 在索引库中查找目标内容, 将查询结果返回给用户.

返回查询结果

说明: 提供一个友好的搜索结果显示页面(如对搜索结果进行排序显示,关键词高亮显示等).

Lucene入门程序

准备环境

JDK: 1.8.0_162

IDE: Eclipse Neon.3

数据库: MySQL 5.7.20

Lucene: 4.10.4(已经很稳定了,高版本对部分分词器支持不好)

准备数据

SET FOREIGN_KEY_CHECKS=0;

-------------------------------- 
Table structure for `book`
--------------------------------
DROP TABLE IF EXISTS `book`;
CREATE TABLE `book` (  
   `id` int(11) DEFAULT NULL,  
   `bookname` varchar(500) DEFAULT NULL,  
   `price` float DEFAULT NULL,  
   `pic` varchar(200) DEFAULT NULL,  
   `bookdesc` varchar(2000) DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

-------------------------------- 
Records of book
--------------------------------
INSERT INTO `book` VALUES ('1', 'java从入门到精通', '56', '1.jpg', '《Java从入门到精通》是人民邮电出版社于 2010年出版的图书, 由国家863中部软件孵化器主编. 以零基础讲解为宗旨, 深入浅出地讲解Java的各项技术及实战技能. 本书从初学者角度出发, 通过通俗易懂的语言、丰富多彩的实例, 详细介绍了使用Java语言进行程序开发应该掌握的各方面技术. 全书共分28章, 包括: 初识Java, 熟悉Eclipse开发工具, Java 语言基础, 流程控制, 字符串, 数组, 类和对象, 包装类, 数字处理类, 接口、继承与多态, 类的高级特性, 异常处理, Swing程序设计, 集合类, I/O输入输出, 反射, 枚举类型与泛型, 多线程, 网络通信, 数据库操作, Swing表格组件, Swing树组件, Swing其他高级组件, 高级布局管理器, 高级事件处理, AWT绘图与音频播放, 打印技术和企业进销存管理系统等. 所有知识都结合具体实例进行介绍, 涉及的程序代码给出了详细的注释, 可以使读者轻松领会Java程序开发的精髓, 快速提高开发技能. ');
INSERT INTO `book` VALUES ('2', 'java web开发', '80', '2.jpg', 'Java Web, 是用Java技术来解决相关web互联网领域的技术总和. web包括: web服务器和web客户端两部分. Java在客户端的应用有java applet, 不过使用得很少, Java在服务器端的应用非常的丰富, 比如Servlet, JSP和第三方框架等等. Java技术对Web领域的发展注入了强大的动力. ');
INSERT INTO `book` VALUES ('3', 'lucene从入门到精通', '100', '3.jpg', '本书总结搜索引擎相关理论与实际解决方案, 并给出了 Java 实现, 其中利用了流行的开源项目Lucene和Solr, 而且还包括原创的实现. 本书主要包括总体介绍部分、爬虫部分、自然语言处理部分、全文检索部分以及相关案例分析. 爬虫部分介绍了网页遍历方法和如何实现增量抓取, 并介绍了从网页等各种格式的文档中提取主要内容的方法. 自然语言处理部分从统计机器学习的原理出发, 包括了中文分词与词性标注的理论与实现以及在搜索引擎中的实用等细节, 同时对文档排重、文本分类、自动聚类、句法分析树、拼写检查等自然语言处理领域的经典问题进行了深入浅出的介绍并总结了实现方法. 在全文检索部分, 结合Lucene 3.0介绍了搜索引擎的原理与进展. 用简单的例子介绍了Lucene的最新应用方法. 本书包括完整的搜索实现过程: 从完成索引到搜索用户界面的实现. 本书还进一步介绍了实现准实时搜索的方法, 展示了Solr 1.4版本的用法以及实现分布式搜索服务集群的方法. 最后介绍了在地理信息系统领域和户外活动搜索领域的应用. ');
INSERT INTO `book` VALUES ('4', 'lucene in action', '90', '4.jpg', '本书深入浅出地介绍了lucene——一个开源的使用java语言编写的全文搜索引擎开发包. 它通过浅显的语言、大量的图注、丰富的代码示例, 以及清晰的结构为读者呈现出作为优秀开源项目的lucene所体现的强大功能. 全书共10章, 分为两大部分. 第1部分lucene的核心, 着重于lucene的核心 api介绍, 并按照把lucene集成到程序中的顺序宋组织;第2部分lucene的应用, 通过对lucene内置工具的介绍, 展示了lucene技术的高级应用和在各种程序语言上的移植. ');
INSERT INTO `book` VALUES ('5', 'Lucene Java精华版', '80', '5.jpg', '本书总结搜索引擎相关理论与实际解决方案, 并给出了 Java 实现, 其中利用了流行的开源项目Lucene和Solr, 而且还包括原创的实现. 本书主要包括总体介绍部分、爬虫部分、自然语言处理部分、全文检索部分以及相关案例分析. 爬虫部分介绍了网页遍历方法和如何实现增量抓取, 并介绍了从网页等各种格式的文档中提取主要内容的方法. 自然语言处理部分从统计机器学习的原理出发, 包括了中文分词与词性标注的理论与实现以及在搜索引擎中的实用等细节, 同时对文档排重、文本分类、自动聚类、句法分析树、拼写检查等自然语言处理领域的经典问题进行了深入浅出的介绍并总结了实现方法. 在全文检索部分, 结合Lucene 3.0介绍了搜索引擎的原理与进展. 用简单的例子介绍了Lucene的最新应用方法. 本书包括完整的搜索实现过程: 从完成索引到搜索用户界面的实现. 本书还进一步介绍了实现准实时搜索的方法, 展示了Solr 1.4版本的用法以及实现分布式搜索服务集群的方法. 最后介绍了在地理信息系统领域和户外活动搜索领域的应用. ');

创建工程

创建Maven Project(打包方式选jar即可)

配置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>com.healchow</groupId>
  <artifactId>lucene-first</artifactId>
  <version>0.0.1-SNAPSHOT</version>
  <packaging>jar</packaging>

  <name>lucene-first</name>
  <url>http://maven.apache.org</url>

  <properties>
       <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
       <!-- mysql版本 -->
       <mysql.version>5.1.44</mysql.version>
       <!-- lucene版本 -->
       <lucene.version>4.10.4</lucene.version>
  </properties>
 
  <dependencies>
       <!-- mysql数据库依赖 -->
       <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>${mysql.version}</version>
       </dependency>
       <!-- lucene依赖包 -->
       <dependency>
            <groupId>org.apache.lucene</groupId>
            <artifactId>lucene-core</artifactId>
            <version>${lucene.version}</version>
       </dependency>
       <dependency>
            <groupId>org.apache.lucene</groupId>
            <artifactId>lucene-analyzers-common</artifactId>
            <version>${lucene.version}</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>
            <scope>test</scope>
       </dependency>
  </dependencies>
</project> 

准备原始数据采集

准备图书POJO

public class Book {
    private Integer id;       // int(11) DEFAULT NULL,
    private String bookname;  // varchar(500) DEFAULT NULL,
    private Float price;      // float DEFAULT NULL,
    private String pic;       // varchar(200) DEFAULT NULL,
    private String bookdesc;  // varchar(2000) DEFAULT NULL
    
    // Getters/Setters
    
    @Override
    public String toString() {
        return "Book [id=" + id + ", bookname=" + bookname + 
            ", price=" + price + ", pic=" + pic + 
            ", bookdesc=" + bookdesc + "]";
    }
}

准备图书DAO接口

public interface BookDao {
    /**
     * 查询全部图书 
     */
    List<Book> queryBookList();
}

实现图书DAO接口

public class BookDaoImpl implements BookDao {
    /**
     * 查询全部图书
     */
    public List<Book> listAll() {
        // 创建图书结果集合List
        List<Book> books = new ArrayList<Book>();
        
        Connection conn = null;
        PreparedStatement preStatement = null;
        ResultSet resultSet = null;
        
        try {
            // 加载驱动
            Class.forName("com.mysql.jdbc.Driver");
            // 创建数据库连接对象
            conn = DriverManager.getConnection(
                       "jdbc:mysql://127.0.0.1:3306/lucene?useSSL=true", 
                       "root", 
                       "password");
            
            // 定义查询SQL
            String sql = "select * from book";
            // 创建Statement语句对象
            preStatement = conn.prepareStatement(sql);
            // 执行语句, 得到结果集
            resultSet = preStatement.executeQuery();
            
            // 处理结果集
            while (resultSet.next()) {
                 // 创建图书对象
                 Book book = new Book();
                 book.setId(resultSet.getInt("id"));
                 book.setBookname(resultSet.getString("bookname"));
                 book.setPrice(resultSet.getFloat("price"));
                 book.setPic(resultSet.getString("pic"));
                 book.setBookdesc(resultSet.getString("bookdesc"));
                 // 将查询到的结果添加到list中
                 books.add(book);
            }
       } catch (Exception e) {
            e.printStackTrace();
       } finally {
            // 释放资源
            try {
                 if (null != conn) conn.close();
                 if (null != preStatement) preStatement.close();
                 if (null != resultSet) resultSet.close();
            } catch (Exception e) {
                 e.printStackTrace();
            }
        }
        return books;
    }
    
    /**
     * 测试功能的主方法
     */
    public static void main(String[] args) {
        // 创建图书Dao的实现对象
        BookDao bookDao = new BookDaoImpl();
        List<Book> books = bookDao.listAll();
        
        // 如果结果不为空, 则便利输出
        for (Book book : books) {
            System.out.println(book);
        }
    }
}

测试结果如下:

索引流程的实现

  1. 采集原始数据;
  2. 创建文档对象(Document);
  3. 创建分析器对象(Analyzer), 用于分词;
  4. 创建索引配置对象(IndexWriterConfig), 用于配置Lucene;
  5. 创建索引库目录位置对象(Directory), 指定索引库的存储位置;
  6. 创建索引写入对象(IndexWriter), 将文档对象写入索引库;
  7. 使用IndexWriter对象, 创建索引;
  8. 释放资源.

示例代码

import org.apache.lucene.analysis.Analyzer;
import org.apache.lucene.analysis.standard.StandardAnalyzer;
import org.apache.lucene.document.Document;
import org.apache.lucene.document.Field.Store;
import org.apache.lucene.document.TextField;
import org.apache.lucene.index.IndexWriter;
import org.apache.lucene.index.IndexWriterConfig;
import org.apache.lucene.store.Directory;
import org.apache.lucene.store.FSDirectory;
import org.apache.lucene.util.Version;

public class IndexManager {
    /**
     * 创建索引功能的测试
     * @throws Exception
     */
    @Test
    public void createIndex() throws IOException{
        // 1. 采集数据
        BookDao bookDao = new BookDaoImpl();
        List<Book> books = bookDao.listAll();
        
        // 2. 创建文档对象
        List<Document> documents = new ArrayList<Document>();
        for (Book book : books) {
            Document document = new Document();
            // 给文档对象添加域
            // add方法: 把域添加到文档对象中, field参数: 要添加的域
            // TextField: 文本域, 属性name:域的名称, value:域的值, store:指定是否将域值保存到文档中
            document.add(new TextField("bookId", book.getId() + "", Store.YES));
            document.add(new TextField("bookName", book.getBookname(), Store.YES));
            document.add(new TextField("bookPrice", book.getPrice() + "", Store.YES));
            document.add(new TextField("bookPic", book.getPic(), Store.YES));
            document.add(new TextField("bookDesc", book.getBookdesc(), Store.YES));

            // 将文档对象添加到文档对象集合中
            documents.add(document);
        }
        // 3. 创建分析器对象(Analyzer), 用于分词
        Analyzer analyzer = new StandardAnalyzer();
        // 4. 创建索引配置对象(IndexWriterConfig), 用于配置Lucene
        // 参数一:当前使用的Lucene版本, 参数二:分析器
        IndexWriterConfig indexConfig = new IndexWriterConfig(Version.LUCENE_4_10_2, analyzer);
        // 5. 创建索引库目录位置对象(Directory), 指定索引库的存储位置
        File path = new File("/your_path/index");
        Directory directory = FSDirectory.open(path);
        // 6. 创建索引写入对象(IndexWriter), 将文档对象写入索引
        IndexWriter indexWriter = new IndexWriter(directory, indexConfig);
        // 7. 使用IndexWriter对象创建索引
        for (Document doc : documents) {
            // addDocement(doc): 将文档对象写入索引库
            indexWriter.addDocument(doc);
        }
        // 8. 释放资源
        indexWriter.close();
    }
}

测试结果

说明: 只要看到以下文件, 说明索引已经创建成功了:

使用Luke工具查看索引

使用说明

Windows OS下,双击运行start.bat文件(前提是需要配置jdk的环境变量);

Mac OS下, 在终端中进入当前目录, 然后键入 ./start.sh 即可运行.

运行界面一

运行界面二

运行界面三

检索流程的实现

  1. 创建分析器对象(Analyzer), 用于分词;
  2. 创建查询对象(Query);
  3. 创建索引库目录位置对象(Directory), 指定索引库的位置;
  4. 创建索引读取对象(IndexReader), 用于读取索引;
  5. 创建索引搜索对象(IndexSearcher), 用于执行搜索;
  6. 使用IndexSearcher对象, 执行搜索, 返回搜索结果集TopDocs;
  7. 处理结果集;
  8. 释放资源.

使用Luke工具搜索

bookName:lucene -- 表示搜索bookName域中包含有lucene.

示例代码

/**
 * 检索索引功能的测试
 * @throws Exception 
 */
@Test
public void searchIndexTest() throws Exception {
    // 1. 创建分析器对象(Analyzer), 用于分词
    Analyzer analyzer = new StandardAnalyzer();
    
    // 2. 创建查询对象(Query)
    // 2.1 创建查询解析器对象
    // 参数一:默认的搜索域, 参数二:使用的分析器
    QueryParser queryParser = new QueryParser("bookName", analyzer);
    // 2.2 使用查询解析器对象, 实例化Query对象
    Query query = queryParser.parse("bookName:lucene");
    
    // 3. 创建索引库目录位置对象(Directory), 指定索引库位置
    Directory directory = FSDirectory.open(new File("/your_path/index"));
    
    // 4. 创建索引读取对象(IndexReader), 用于读取索引
    IndexReader indexReader = DirectoryReader.open(directory);
    
    // 5. 创建索引搜索对象(IndexSearcher), 用于执行索引
    IndexSearcher searcher = new IndexSearcher(indexReader);
    
    // 6. 使用IndexSearcher对象执行搜索, 返回搜索结果集TopDocs
    // 参数一:使用的查询对象, 参数二:指定要返回的搜索结果排序后的前n个
    TopDocs topDocs = searcher.search(query, 10);
    
    // 7. 处理结果集
    // 7.1 打印实际查询到的结果数量
    System.out.println("实际查询到的结果数量: " + topDocs.totalHits);
    // 7.2 获取搜索的结果数组
    // ScoreDoc中有文档的id及其评分
    ScoreDoc[] scoreDocs = topDocs.scoreDocs;
    for (ScoreDoc scoreDoc : scoreDocs) {
        System.out.println("= = = = = = = = = = = = = = = = = = =");
        // 获取文档的id和评分
        int docId = scoreDoc.doc;
        float score = scoreDoc.score;
        System.out.println("文档id= " + docId + " , 评分= " + score);
        // 根据文档Id, 查询文档数据 -- 相当于关系数据库中根据主键Id查询数据
        Document doc = searcher.doc(docId);
        System.out.println("图书Id: " + doc.get("bookId"));
        System.out.println("图书名称: " + doc.get("bookName"));
        System.out.println("图书价格: " + doc.get("bookPrice"));
        System.out.println("图书图片: " + doc.get("bookPic"));
        System.out.println("图书描述: " + doc.get("bookDesc"));
    }
    
    // 8. 关闭资源
    indexReader.close();
}

测试结果

结果说明

  1. 索引库中包含索引域和文档域;
  2. 索引域保存索引数据(倒排索引), 用于索引;
  3. 文档域中保存文档数据, 用于搜索获取数据.

IndexSearcher方法

方法 说明
indexSearcher.search(query, n) 根据Query搜索, 返回评分最高的n条记录
indexSearcher.search(query,filter,n) 根据Query搜索, 添加过滤策略, 返回评分最高的n条记录
indexSearcher.search(query, n, sort) 根据Query搜索, 添加排序策略, 返回评分最高的n条记录
indexSearcher.search(booleanQuery, filter, n, sort) 根据Query搜索, 添加过滤策略, 添加排序策略, 返回评分最高的n条记录

版权声明

作者: ma_shoufeng(马瘦风)

出处: 博客园 马瘦风的博客

您的支持是对博主的极大鼓励, 感谢您的阅读.

本文版权归博主所有, 欢迎转载, 但未经博主同意必须保留此段声明, 且在文章页面明显位置给出原文链接, 否则博主保留追究法律责任的权利.

猜你喜欢

转载自www.cnblogs.com/shoufeng/p/9349440.html