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 条评论
登录 后参与评论

相关文章

来自专栏个人分享

前一天数据偏移至当日数据

客户画像项目中可能会遇到此类问题,因为客户画像要求的是对单个用户信息的查询。 但倘若以日周期为准,今日梳理的数据宽表中某个字段为null,但此类字段不能以nul...

581
来自专栏noteless

9.java web的发展 javaweb是什么 J2EE发展历史 规范 J2EE是什么 发展背景 组件标准 J2EE好处作用 Servlet 含义 本质 发展 java在web中的发展 servl

javaweb 本质上来说就是使用java 语言来解决企业web应用中一系列技术体系与规范;

671
来自专栏企鹅号快讯

httpclient接口测试完整用例

本人是在使用httpclient做接口测试的过程中,总结了一些方法,写了一个基本的测试框架。用例的管理和测试结果的保存都放在数据库中,今天算是基本完成和健全了用...

2405
来自专栏HT

HT图形组件设计之道(一)

HT for Web简称HT提供了涵盖通用组件、2D拓扑图形组件以及3D引擎的一站式解决方案,正如Hightopo官网所表达的我们希望提供:Everything...

1775
来自专栏葬爱家族

Android高级动画(4)完结篇目录回顾封装库总结

Android高级动画(1)http://www.jianshu.com/p/48554844a2db Android高级动画(2)http://www.ji...

742
来自专栏闻道于事

使用ichartjs生成图表

官网:http://www.ichartjs.com/   ichartjs 是一款基于HTML5的图形库。使用纯javascript语言, 利用HTML5的c...

4527
来自专栏玩转全栈

flutter实现一个sideBar

用flutter中实现这么一个sidebar,期初我想了5中方式,但是最后,发现只有一种可以实现,那么是哪5种方式呢?

29010
来自专栏FreeBuf

用零宽度字符水印揭露泄密者身份

零宽度字符是隐藏不显示的,也是不可打印的,也就是说这种字符用大多数程序或编辑器是看不到的。最常见的是零宽度空格,它是Unicode字符空格,就像如果在两个字母间...

752
来自专栏BaronTalk

Android 模块化探索与实践

首发于《程序员》杂志五月刊 前言 万维网发明人 Tim Berners-Lee 谈到设计原理时说过:“简单性和模块化是软件工程的基石;分布式和容错性是互联网的...

3979
来自专栏CSDN技术头条

解析大型.NET ERP系统 20条数据库设计规范

数据库设计规范是个技术含量相对低的话题,只需要对标准和规范的坚持即可做到。当系统越来越庞大,严格控制数据库的设计人员,并且有一份规范书供执行参考。在程序框架中,...

2207

扫码关注云+社区