Lucene全文检索

全文检索是程序开发中非常重要的一个应用,今天带大家来一起学习Java基于Lucene的全文检索机制。

全文检索的概念

1) 从大量的信息中快速、准确地查找出要的信息。

2) 搜索的内容是文本信息(不是多媒体)。

3) 搜索的方式:不是根据语句的意思进行处理。如果要搜索的文本为"西安",那么含有这些词(西安程序员、西安)就能搜索出来。每一个词都是关键词。

4) 全面、快速、准确是衡量全文检索系统的关键指标。

5) 概括:

a) 只处理文本。

b) 不处理语义。

a) 搜索时英文不区分大小写。

b) 结果列表有相关度排序。

全文检索的应用场景

1.站内搜索

通常用于在大量数据出现的系统中,找出你想要的资料。

bbs的关键字搜索:如百度贴吧。

商品网站的搜索:如淘宝、京东、中关村在线等。

文件管理系统:对文件的搜索功能。Window的文件搜索等。

2. 垂直搜索

a) 是针对 某个行业的搜索引擎。

b) 是搜索引擎的细分和延伸。

c) 是针对网页库中的专门信息的整合。

d) 其特点是专、深、精,并具有行业色彩。

e) 可以应用于购物搜索、房产搜索、人才搜索。

全文检索与数据库搜索的区别

1. 数据库的搜索

类似:select * from 表名 where 字段名 like ‘%关键字%’。

例如:select * from article where content like’%here%’。

结果: where here shere。

缺点:

1) 搜索效果比较差。

2) 在搜索的结果中,有大量的数据被搜索出来,有很多数据是没有用的。

3) 查询速度在大量数据的情况下是很难做到快速的。

2. 全文检索

1) 搜索结果按相关度排序:意味着只有前几个页面对于用户来说是比较有用的,其他的结果与用户想要的答案很可能相差甚远。数据库搜索是做不到相关度排序的。

2) 因为全文检索是采用引索的方式,所以在速度上肯定比数据库方式like要快。

3) 所以数据库不能代替全文检索。

全文检索只是一个概念,而具体实现有很多框架,Lucene是其中的一种。Lucene的主页http://lucene.apache.org/。本文用的是3.0.1版本。

互联网搜索结构框图

1) 当用户打开www.baidu.com网页搜索某些数据的时候,不是直接找的网页,而是找的百度的索引库。索引库里包含的内容有索引号和摘要。当我们打开www.baidu.com时,看到的就是摘要的内容。

2) 百度的索引库的索引和互联网的某一个网站对应。

3) 当用户数据要查询的关键字,返回的页面首先是从索引库中得到的。

4) 点击每一个搜索出来的内容进行相关网页查找,这个时候才找的是互联网中的网页。

Lucene的大致结构框图

说明:

1) 在数据库中,数据库中的数据文件存储在磁盘上。索引库也是同样,索引库中的索引数据也在磁盘上存在,我们用Directory这个类来描述。

2) 我们可以通过API来实现对索引库的增、删、改、查的操作。

3) 在数据库中,各种数据形式都可以概括为一种:表。在索引库中,各种数据形式也可以抽象出一种数据格式为Document。

4) Document的结构为:Document(List<Field>)

5) Field里存放一个键值对。键值对都为字符串的形式。

6) 对索引库中索引的操作实际上也就是对Document的操作。

在搜索引擎中我们经常会看到这样的情景:

红色部分的Java称之为高亮显示,Lucene提供了HighLighter模块来实现这一功能。

接下来我们就使用Lucene来模拟实现这一功能,在新闻信息中查找"西安"关键字,并自动生成高亮显示的html代码。

1.搭建Lucene环境,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/maven-v4_0_0.xsd">
  <modelVersion>4.0.0</modelVersion>
  <groupId>com.southwind</groupId>
  <artifactId>lucene</artifactId>
  <packaging>war</packaging>
  <version>0.0.1-SNAPSHOT</version>
  <name>lucene Maven Webapp</name>
  <url>http://maven.apache.org</url>
  <dependencies>
    <dependency>
        <groupId>org.apache.lucene</groupId>
        <artifactId>lucene-core</artifactId>
        <version>4.7.2</version>
    </dependency>

    <dependency>
    <groupId>org.apache.lucene</groupId>
        <artifactId>lucene-queryparser</artifactId>
        <version>4.7.2</version>
    </dependency>

    <dependency>
        <groupId>org.apache.lucene</groupId>
        <artifactId>lucene-analyzers-common</artifactId>
        <version>4.7.2</version>
    </dependency>

    <dependency>
        <groupId>org.apache.lucene</groupId>
        <artifactId>lucene-analyzers-smartcn</artifactId>
        <version>4.7.2</version>
    </dependency>

    <dependency>
        <groupId>org.apache.lucene</groupId>
        <artifactId>lucene-highlighter</artifactId>
        <version>4.7.2</version>
    </dependency>

    <dependency>
    <groupId>com.janeluo</groupId>
    <artifactId>ikanalyzer</artifactId>
    <version>2012_u6</version>
    </dependency>

    <dependency>
        <groupId>junit</groupId>
        <artifactId>junit</artifactId>
        <version>4.12</version>
    </dependency>
  </dependencies>
  <build>
    <finalName>lucene</finalName>
  </build>
</project>

2.创建News对象,保存新闻信息。

package com.southwind.entity;

import java.util.ArrayList;
import java.util.List;

/**
 * 新闻
 * @author southwind
 *
 */
public class News {
    private Integer id;
    private String title;
    private String content;
    private String author;

    public Integer getId() {
        return id;
    }
    public void setId(Integer id) {
        this.id = id;
    }
    public String getTitle() {
        return title;
    }
    public void setTitle(String title) {
        this.title = title;
    }
    public String getContent() {
        return content;
    }
    public void setContent(String content) {
        this.content = content;
    }
    public String getAuthor() {
        return author;
    }
    public void setAuthor(String author) {
        this.author = author;
    }
}

3.创建三个新闻对象,保存到List中,建立索引。

步骤:

1) 创建IndexWriter对象。

2) 把File转化为Document。

3) 利用IndexWriter.addDocument方法增加索引。

4) 关闭资源。

package com.southwind.test;

import java.io.File;
import java.util.ArrayList;
import java.util.List;

import org.apache.lucene.analysis.cn.smart.SmartChineseAnalyzer;
import org.apache.lucene.document.Document;
import org.apache.lucene.document.Field.Store;
import org.apache.lucene.document.IntField;
import org.apache.lucene.document.StringField;
import org.apache.lucene.document.TextField;
import org.apache.lucene.index.IndexWriter;
import org.apache.lucene.index.IndexWriterConfig;
import org.apache.lucene.index.Term;
import org.apache.lucene.search.Query;
import org.apache.lucene.search.TermQuery;
import org.apache.lucene.store.Directory;
import org.apache.lucene.store.FSDirectory;
import org.apache.lucene.util.Version;

import com.southwind.entity.News;

/**
 * 创建索引
 * 
 * @author southwind
 *
 */
public class Index {

    private IndexWriter indexWriter;

    private Directory dir;

    public Index() throws Exception {
        File file = new File("d:/lucene");
        if (!file.exists())
            file.mkdirs();
        dir = FSDirectory.open(file);
        SmartChineseAnalyzer analyzer = new SmartChineseAnalyzer(Version.LUCENE_47);
        IndexWriterConfig config = new IndexWriterConfig(Version.LUCENE_47, analyzer);
        indexWriter = new IndexWriter(dir, config);
        indexWriter.deleteAll();
        System.out.println("已经建立的索引的个数:" + indexWriter.maxDoc());
        System.out.println("已经建立的索引的个数:" + indexWriter.numDocs());
    }

    /**
     * 建立索引
     * 
     * @throws Exception
     */
    public void createIndex(List<News> list) throws Exception {
        for (News n : list) {
            Document doc = new Document();
            doc.add(new IntField("id", n.getId(), Store.YES));
            doc.add(new StringField("title", n.getTitle(), Store.YES));
            doc.add(new TextField("content", n.getContent(), Store.YES));
            indexWriter.addDocument(doc);
        }
        System.out.println("已经建立的索引的个数:" + indexWriter.maxDoc());
        System.out.println("已经建立的索引的个数:" + indexWriter.numDocs());
    }

    /**
     * 关闭索引
     * 
     * @throws Excpetion
     */
    public void close() throws Exception {
        indexWriter.close();
    }

    public static void main(String[] args) {
        List<News> list = new ArrayList();
        News n1 = new News();
        n1.setId(1);
        n1.setTitle("西安IT");
        n1.setContent("西北总部落户西安 阿里巴巴与西安政府达成战略合作。");
        n1.setAuthor("南风");
        list.add(n1);

        News n2 = new News();
        n2.setId(2);
        n2.setTitle("Lucene高亮显示");
        n2.setContent("红色部分我们称之为高亮显示,lucene提供了HighLighter模块来实现这一功能。 ");
        n2.setAuthor("南风");
        list.add(n2);

        News n3 = new News();
        n3.setId(3);
        n3.setTitle("全文检索");
        n3.setContent("全文数据库是全文检索系统的主要构成部分。所谓全文数据库是将一个完整的信息源的全部内容转化为计算机可以识别、处理的信息单元而形成的数据集合。");
        n3.setAuthor("南风");
        list.add(n3);
        Index i = null;
        try {
            i = new Index();
            i.createIndex(list);
            i.indexWriter.close();

        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if(i!=null)
                try {
                    i.close();
                } catch (Exception e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
        }
    }

}

4.执行main方法,在D:/lucene中创建索引文件。

5.在D:/lucene的文件中,以"西安"为关键字对文本信息进行检索,并将"西安"进行标注,生成高亮显示的html代码。

步骤:

1) 创建IndexSearch。

2) 创建Query对象。

3) 进行搜索。

4) 获得总结果数和前N行记录ID列表。

5) 根据目录ID列表把Document,,并输入document存放的File信息。

package com.southwind.test;

import java.io.File;
import java.io.FileOutputStream;
import java.io.OutputStream;
import java.io.StringReader;

import org.apache.lucene.analysis.TokenStream;
import org.apache.lucene.analysis.cn.smart.SmartChineseAnalyzer;
import org.apache.lucene.analysis.tokenattributes.CharTermAttribute;
import org.apache.lucene.analysis.tokenattributes.OffsetAttribute;
import org.apache.lucene.analysis.tokenattributes.PositionIncrementAttribute;
import org.apache.lucene.analysis.tokenattributes.TypeAttribute;
import org.apache.lucene.document.Document;
import org.apache.lucene.index.DirectoryReader;
import org.apache.lucene.index.IndexReader;
import org.apache.lucene.index.Term;
import org.apache.lucene.queryparser.classic.QueryParser;
import org.apache.lucene.search.BooleanClause;
import org.apache.lucene.search.BooleanQuery;
import org.apache.lucene.search.IndexSearcher;
import org.apache.lucene.search.Query;
import org.apache.lucene.search.ScoreDoc;
import org.apache.lucene.search.TermQuery;
import org.apache.lucene.search.TopDocs;
import org.apache.lucene.search.highlight.Fragmenter;
import org.apache.lucene.search.highlight.Highlighter;
import org.apache.lucene.search.highlight.QueryScorer;
import org.apache.lucene.search.highlight.SimpleHTMLFormatter;
import org.apache.lucene.search.highlight.SimpleSpanFragmenter;
import org.apache.lucene.store.Directory;
import org.apache.lucene.store.FSDirectory;
import org.apache.lucene.util.Version;

public class Search {

    /**
     * 按内容检索
     * @param dirPath 索引文件所在的目录
     * @param qstr 要查询的内容
     * @throws Exception
     */
    public static void search(String dirPath, String qstr) throws Exception {
        File file =new File(dirPath);
        Directory dir = FSDirectory.open(file);
        //读取索引文件
        IndexReader indexReader = DirectoryReader.open(dir);
        //建立索引对象
        IndexSearcher is = new IndexSearcher(indexReader);
        //创建中文分词器
        SmartChineseAnalyzer analyzer = new SmartChineseAnalyzer(Version.LUCENE_47);
        //创建两个词表
        Term t1 = new Term("title", qstr);
        Term t2 = new Term("content", qstr);
        //创建两个TermQuery
        TermQuery q1 =new TermQuery(t1);
        TermQuery q2 =new TermQuery(t2);
        //创建BooleanQuery对象
        BooleanQuery query = new BooleanQuery();
        //将两个TermQuery加入到BooleanQuery的子句中去,且其关系均为必须满足
        query.add(q1,BooleanClause.Occur.MUST_NOT);
        query.add(q2,BooleanClause.Occur.MUST);
        long l = System.currentTimeMillis();
        //获取查询结果
        TopDocs hits = is.search(query, 3);
        long l2 = System.currentTimeMillis();
        System.out.println("共用时:" + (l2 - l) / 1000);
        //创建QueryScorer对象
        QueryScorer scorer = new QueryScorer(query);
        //设置高亮显示
        Fragmenter fragmenter = new SimpleSpanFragmenter(scorer);
        SimpleHTMLFormatter simpleHTMLFormatter = new SimpleHTMLFormatter("<b><font color='red'>", "</font></b>");
        Highlighter highlighter = new Highlighter(simpleHTMLFormatter, scorer);
        highlighter.setTextFragmenter(fragmenter);
        for (ScoreDoc scoreDoc : hits.scoreDocs) {
            Document doc = is.doc(scoreDoc.doc);
            String content = doc.get("content");
            if (content != null) {
                TokenStream tokenStream = analyzer.tokenStream("content", new StringReader(content));
                String str = "<html><head><title>demo</title></head><body>";
                str += highlighter.getBestFragment(tokenStream, content);
                str += "</body></html>";
                System.out.println(str);
                //将设置完成的高亮代码写入html
                OutputStream os = new FileOutputStream("demo.html");
                byte[] bs = str.getBytes();
                os.write(bs);
                os.close();
            }
        }
        indexReader.close();
    }

    public static void main(String[] args) throws Exception {
        Search.search("d:/lucene", "西安");
    }

}

6.生成的html代码。

<html>
<head>
    <title>demo</title>
</head>
<body>
    西北总部落户<b><font color='red'>西安</font></b> 
    阿里巴巴与<b><font color='red'>西安</font></b>政府达成战略合作。
</body>
</html>

7.在浏览器中运行html代码,看到结果。

源码:

github

https://github.com/southwind9801/Lucene.git

原文发布于微信公众号 - Java大联盟(javaunion)

原文发表时间:2018-05-18

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏web开发

java导出Excel表格

最近自己着手写了一个前后端分离的后台管理系统(主要是写着玩,java还是熟悉一点,所以前后端均是自己写),后端使用的Java SpringMVC。后来想着在用户...

548100
来自专栏九彩拼盘的叨叨叨

HanSON是个什么鬼

做Web开发的应该都或多或少知道JSON这东东。我们经常会用JSON文件来做为配置文件,如package.json,bower.json。但JSON有不少让人不...

5910
来自专栏青蛙要fly的专栏

Android技能树 — Rxjava取消订阅小结(1):自带方式

现在很多项目都在使用Rxjava了,对于RxJava的使用,估计都很熟悉了,但是很多人在使用RxJava的时候容易产生内存泄漏问题,比如我们在用RxJava配合...

14030
来自专栏听雨堂

C#实现微信AES-128-CBC加密数据的解密

小程序登录时,获得用户的信息,只是昵称,无法用作ID。而有用的数据,都加密着,腾讯给出了解密的方法: 加密数据解密算法 接口如果涉及敏感数据(如wx.getUs...

54490
来自专栏xingoo, 一个梦想做发明家的程序员

【手把手教你全文检索】Apache Lucene初探

PS: 苦学一周全文检索,由原来的搜索小白,到初次涉猎,感觉每门技术都博大精深,其中精髓亦是不可一日而语。那小博猪就简单介绍一下这一周的学习历程,仅供各位程...

274100
来自专栏分布式系统进阶

Librdkafka的操作处理队列

32320
来自专栏开源FPGA

基于Verilog HDL的二进制转BCD码实现

       在项目设计中,经常需要显示一些数值,比如温湿度,时间等等。在数字电路中数据都是用二进制的形式存储,要想显示就需要进行转换,对于一个两位的数值,对1...

16810
来自专栏岑玉海

Carbondata源码系列(一)文件生成过程

在滴滴的两年一直在加班,人也变懒了,就很少再写博客了,最近在进行Carbondata和hive集成方面的工作,于是乎需要对Carbondata进行深入的研究。 ...

66660
来自专栏Fundebug

JWT究竟是什么呢?

为了保证可读性,本文采用意译而非直译。另外,本文版权归原作者所有,翻译仅用于学习。

13070
来自专栏软件开发

MyBatis学习总结(四)——MyBatis缓存与代码生成

缓存可以提高系统性能,可以加快访问速度,减轻服务器压力,带来更好的用户体验。缓存用空间换时间,好的缓存是缓存命中率高的且数据量小的。缓存是一种非常重要的技术。

29530

扫码关注云+社区

领取腾讯云代金券