Lucene实现索引和查询

0引言

  随着万维网的发展和大数据时代的到来,每天都有大量的数字化信息在生产、存储、传递和转化,如何从大量的信息中以一定的方式找到满足自己需求的信息,使之有序化并加以利用成为一大难题。全文检索技术是现如今最普遍的信息查询应用,生活中利用搜索引擎,在博客论坛中查找信息,这些搜索的核心原理就是本文要实现的全文检索技术。随着文档信息数字化的实现,将信息有效存储并及时准确的提取是每一个公司、企业和单位要做好的基础。针对英文的全文检索已经有很多成熟的理论和方法,开放源代码的全文检索引擎Lucene 是Apache 软件基金会Jakarta 项目组的一个子项目,它的目的是为软件开发人员提供一个简单易用的工具包,方便在目标系统中实现全文检索的功能。Lucene不支持中文,但是目前已有很多开源的中文分词器可以对中文内容进行索引,本文在研究Lucene核心原理的基础上,分别实现了对中英文网页的爬取和检索。

1 Lucene介绍

1.1 lucene简介

Lucene是一个用Java写的全文检索引擎工具包,实现构造了索引和搜索两大核心功能,并且两者相互独立,这使得开发人员可以方便扩展,Lucene提供了丰富的API , 可以与存储在索引中的信息方便的交互。需要说明的是它并不是一个完整的全文检索应用, 而是为应用程序提供索引和搜索功能。即若想让Lucene 真正起作用, 还需在其基础上做一些必要的二次开发。

Lucene的结构设计与数据库的设计较为相似,但Lucene的索引与数据库有着极大的不同。数据库和Lucene建立索引都是为了查找方便,但是数据库仅仅针对部分字段进行建立,且需要把数据转化为格式化信息,并予以保存。而全文检索是将全部信息按照一定方式进行索引。两种检索的不同和相似如表1-1所示。

表1-1:数据库检索与Lucene检索对比


比较项


Lucene检索


数据库检索


数据检索


从Lucene的索引文件中检出


由数据库索引检索记录


索引结构


Document(文档)


Record(记录)


查询结果


Hit:满足关系的文档组成


查询结果集:包含关键字的记录组成


全文检索


支持


不支持


模糊查询


支持


不支持


结果排序


设置权重,进行相关性排序


不能排序

1.2 lucene总体结构

Lucene软件包的发布形式是一个JAR文件,版本更新较快且版本差距较大,本文使用的是5.3.1的版本,主要使用的子包如表1-2所示。

表1-2:子包和功能


包名


功能


Org .apache.lucene .analysis


分词


Org .apache.lucene .document


对索引管理的文档


Org .apache.lucene .index


索引操作,包括增加、删除等


Org .apache.lucene .queryParser


查询器,构造检索表达式


Org .apache.lucene .search


检索管理


Org .apache.lucene .store


数据存储管理


Org .apache.lucene .util


公共类

1.3 lucene架构设计

Lucene功能非常强大,但从根本上来说,主要包括两块:一是从文本内容切分词后索引入库;二是根据查询条件返回结果,即建立索引和进行查询两部分。

如图1-1所示,本文抛出外部接口以及信息来源,重点对网页爬取的文本内容进行索引和查询 。 

                              图1-1:Lucene的架构设计

 

2 JDK的安装和环境变量的配置

  1.jdk的下载:

      在oracle官网下载符合系统版本的压缩包,网址如下。点击安装,根据提示进行安装,在安装过程中会提示是否安装jre,点击是。                                                         http://www.oracle.com/technetwork/java/javase/downloads/index.html

  2.设置环境变量:

      (1)右键计算机=》属性=》高级系统设置=》环境变量=》系统变量=》新建=》JAVA_HOME:安装路径

      (2)Path中新增=》%JAVA_HOME%\bin

  3.测试是否成功:

     开始=》运行=》CMD  回车 在弹出的 DOS 窗口内

      输入:java -version 会出现版本信息,

      输入: javac出现 javac 的用法信息

      出现如图2-1所示为成功。

图2-1:cmd命令框测试java配置

编写Java代码实现对网页内容的获取

    因为Lucene针对不同语言要使用不同的分词器,英文使用标准分词器,中文选择使用smartcn分词器。在获取网页的时候,先获取网页存为html文件,在html中由于标签           的干扰,会对检索效果产生影响,因此需要对html标签进行剔除,并将文本内容转为txt文件进行保存。中英文除了分词器不同,其他基本一致,因此之后的代码和实验结果演            示会选择任一。本文选取五十篇中文故事和英文故事的网页为例。

具体代码设计如下图:Url2Html.java将输入网址的网页转存为html文件,Html2Txt.java文件实现html文档标签的去除,转存为txt文档。具体代码如图3-1和3-2。

public void way(String filePath,String url) throws Exception{
        File dest = new File(filePath);//建立文件
        InputStream is;//接收字节输入流
        FileOutputStream fos = new FileOutputStream(dest);//字节输出流
        URL wangzhi = new URL(url);//设定网址URL
        is = wangzhi.openStream();
        BufferedInputStream bis = new BufferedInputStream(is);//为字节输入流加缓冲
        BufferedOutputStream bos = new BufferedOutputStream(fos);//为字节输出流加缓冲
        /*
         * 对字节进行读取
         */
        int length;
        byte[] bytes = new byte[1024*20];
        while((length = bis.read(bytes, 0, bytes.length)) != -1){
            fos.write(bytes, 0, length);
        }
        /*
         * 关闭缓冲流和输入输出流
         */
        bos.close();
        fos.close();
        bis.close();
        is.close();
    }

public String getBody(String val){
  String zyf = val.replaceAll("</?[^>]+>", ""); //剔出<html>的标签
  return zyf;
}

public void writeTxt(String Str,String writePath) {
  File writename = new File(writePath);
  try {
    writename.createNewFile();
    BufferedWriter out = new BufferedWriter(new FileWriter(writename));
    out.write(Str);
    out.flush();
    out.close();
  } catch (IOException e) {
    e.printStackTrace();
  }
}

以童话故事《笨狼上学》的网页为例,文档路径设为”E:\work \lucene \test \data \html”和”E:\work\lucene\test\data\txt”,在每一次读取网页的时候需要设定的两个参数为文件命名filename和获取目标网址url。新建一个main函数,实现对两个方法的调用。具体实现如图3-3所示:

  public static void main(String[] args) {
    String filename = "jingdizhi";//文件名字
    String url = "http://www.51test.net/show/8072125.html";//需要爬取的网页url
    String filePath = "E:\\work\\lucene\\test\\data\\html\\"+filename+".html";//写出html的文件路径+文件名
    String writePath = "E:\\work\\lucene\\test\\data\\txt\\"+filename+".txt";//写出txt的文件路径+文件名

    Url2Html url2html = new Url2Html();
    try {
      url2html.way(filePath,url);
    } catch (Exception e) {
      e.printStackTrace();
    }

    Html2Txt html2txt = new Html2Txt();
    String read=html2txt.readfile(filePath);//读取html文件
    String txt = html2txt.getBody(read);//去除html标签
    System.out.println(txt);
    try {
      html2txt.writeTxt(txt,writePath);
    } catch (Exception e) {
      e.printStackTrace();
    }
  }

执行程序后,分别在两个文件夹中建立”笨狼上学.html”和”笨狼上学.txt”。

建立索引

索引和查询的基本原理如下:

建立索引:搜索引擎的索引其实就是实现“单词-文档矩阵”的具体数据结构。也是进行全文检索的第一步,lucene提供IndexWriter类进行索引的管理,主要包括add()、delete()、update()。还有对权值的设定,通过不同索引权值的设定,可以在搜索的时候根据相关性大小进行返回。

进行搜索:原本的直接搜索是针对文档进行顺序检索,在建立索引之后,可以通过对索引的查找以找到索引词在文档中出现的位置,然后返回索引项所对的文档中的位置和词。Lucene提供IndexSearcher类进行对文档的检索,检索形式主要分为两类,第一类是Term,针对单个词项的检索;第二类是Parser,可以自定义构造检索表达式,有较多的检索形式,具体的方法会在之后进行实现的演示。

4.1 实验环境

本PC机采用windows 10x64系统,8G内存,256G固态硬盘。开发环境为Myeclipse 10,jdk版本为1.8。在实验过程中,因为部分语法的转变,若干Class采用1.6版本实现。

4.2 建立索引

建立索引库就是往索引库添加一条条索引记录,Lucene为添加一条索引记录提供了接口,添加索引。

主要用到了“写索引器”、“文档”、“域”这3 个类。要建立索引,首先要构造一个Document 文档对象,确定Document的各个域,这类似于关系型数据库中表结构的建立,Document相当于表中的一个记录行,域相当于一行中的列,在Lucene 中针对不同域的属性和数据输出的需求,对域还可以选择不同的索引/存储字段规则,在本实验中,文件名fileName、文件路径fullPath和文本内容content作为Document 的域。

IndexWriter 负责接收新加入的文档,并写入索引库中。在创建“写索引器”IndexWriter 时需要指定所使用的语言分析器。建立索引分为两个类别,第一:不加权索引;第二:加权索引。

public Indexer(String indexDir)throws Exception{
        Directory dir=FSDirectory.open(Paths.get(indexDir));
        Analyzer analyzer=new StandardAnalyzer(); // 标准分词器
        //SmartChineseAnalyzer analyzer = new SmartChineseAnalyzer();
        IndexWriterConfig iwc=new IndexWriterConfig(analyzer);
        writer=new IndexWriter(dir, iwc);
    }

设置索引字段,Store表示是否对索引内容存储:fileName和fullPath占用内存较少可以进行存储,以方便查询返回。

private Document getDocument(File f)throws Exception {
        Document doc=new Document();
        doc.add(new TextField("contents", new FileReader(f)));
        doc.add(new TextField("fileName", f.getName(),Store.YES));
        doc.add(new TextField("fullPath",f.getCanonicalPath(),Store.YES));//路径索引
        return doc;
    }

执行主代码后结果如图:设计在索引某个文件的时候返回文件“索引文件:+文件路径”,且计算输出索引全部文件花费的时间。

4.3 对索引的删除和修改

一般对数据库的操作包括CRUD(增加、删除、更改、查询),增加就是对索引项的选择和建立,查询作为较为核心的功能会在之后展开论述,这里主要记录一下在删除、更新索引时用到的方法。

删除分为两种类型,包括普通的删除和彻底删除,因为索引的删除影响到整个数据库,而且对于大型的系统而言,删除索引意味着对系统的底层进行更改,耗时耗力而且无法返回,前面索引的时候看到建立索引后生成若干小文件,当进行查找的时候会将各个文件进行合并然后查找。普通删除仅仅是对之前建立的索引做个简单的标记,致使无法进行查找返回。彻底删除则是对索引进行销毁,无法撤销。以删除索引项“id”为1的索引为例:

普通的删除(在合并前删除):

writer.deleteDocuments(new Term("id","1"));

writer.commit();

彻底的删除(在合并后删除):

   writer.deleteDocuments(new Term("id","1"));

   writer.forceMergeDeletes(); // 强制删除

   writer.commit();

对索引的修改原理比较简单,就是在原有索引的基础上实现覆盖,实现代码跟上文的增加索引一样,在此不多做阐述。

4.4 对索引的加权

Lucene默认按照相关度排序,Lucene对Field提供了一个可以设置的Boosting参数,这个参数用来表示记录的重要性,在满足搜索条件是,会优先考虑重要性高的记录,返回结果靠前,如果记录较多,权值低的记录会排到首页之后,因此,对索引的加权操作是影响返回结果满意度的重要因素,在实际设计信息系统的时候,应该有严格的权值计算公式,方便对Field权值的更改,更好的满足用户的需求。

例如搜索引擎将点击率高,链入链出的网页给定较高的权重,在返回的时候排到第一页。实现代码如图4-1所示,不加权和加权结果对比如图4-2所示。

     TextField field = new TextField("fullPath", f.getCanonicalPath(), Store.YES);
        if("A GREAT GRIEF.txt".equals(f.getName())){
            field.setBoost(2.0f);//对文件名为secondry story.txt的fullPath路径加权;
        }                        //默认权重为1.0,改为1.2即增加权重。
        doc.add(field);

图4-1:索引加权

图4-2:加权之前

图4-2:加权之后

由图4-2结果可以看出,不加权时,按照字典顺序排列返回,因此first在secondry之前,在对secondry命名的文件路径加权后,返回的时候顺序发生变化,实现对权重的测试。

进行查询

Lucene 的检索接口主要由QueryParser、IndexSearcher、Hits这3 个类构成,QueryParser 是查询解析器,负责解析用户提交的查询关键字,在新建一个解析器时需要指定要解析的域和使用什么语言分析器,这里使用的语言分析器必须与索引库建立时使用的解析器相同,否则查询结果不正确。IndexSearcher是索引搜索器,在实例化IndexSearcher时需要指定索引库所在的目录,IndexSearcher有一个search 方法执行索引的检索,这个方法接受Query 作为参数,返回Hits,Hists 是一系列排好序的查询结果的集合,集合的元素是Document。通过Document的get 方法可以得到与这个文档对应文件的信息,比如:文件名、文件路径、文件内容等。

5.1 基本查询

如图查询主要有两种方式,但是推荐使用第一种构造QueryParser表达式,它可以有灵活的组合方式,包括布尔逻辑表达、模糊匹配等,但是第二种Term只能针对词汇查询。

1.构造QueryParser查询式:

    QueryParser parser=new QueryParser("fullPath", analyzer);

       Query query=parser.parse(q);

2.对特定项的查询:

    Term t = new Term("fileName", q);

       Query query = new TermQuery(t);

查询结果如图5-1所示:以查询文件名fileName包含“大”为例。

图5-1:“大”查询结果

5.2 模糊查询

在构造QueryParser时,通过对词项q的修改可以实现精确匹配和模糊匹配。模糊匹配通过在“q”之后加“~”进行修改。如图5-2所示:

图5-2:模糊匹配

5.3 限定条件查询

布尔逻辑查询和模糊查询只需要对查询词q进行更改,而限定条件查询需要对query表达式进行设定,主要分为以下几类:

分别为指定项范围搜索、指定数字范围、指定字符串开头和多条件查询,分别列出应用的查询,true参数指的:是否包含上限和下限在内。

指定项范围:

TermRangeQuery query=new TermRangeQuery("desc", new BytesRef("b".getBytes()), new BytesRef("c".getBytes()), true, true);

指定数字范围:

NumericRangeQuery<Integer> query=NumericRangeQuery.newIntRange("id", 1, 2, true, true);

指定字符串开头:

PrefixQuery query=new PrefixQuery(new Term("city","a"));

多条件查询:

NumericRangeQuery<Integer>query1=NumericRangeQuery.newIntRange("id", 1, 2, true, true);

PrefixQuery query2=new PrefixQuery(new Term("city","a"));

BooleanQuery.Builder booleanQuery=new BooleanQuery.Builder();

booleanQuery.add(query1,BooleanClause.Occur.MUST);

booleanQuery.add(query2,BooleanClause.Occur.MUST);

5.4 高亮查询

在百度、谷歌等搜索引擎中,进行查询时,返回的网页包含查询关键字的时候会显示为红色,且进行摘要显示,即对包含关键字的部分内容进行截取并返回。高亮查询即为实现对关键字的样式更改,本实验在myeclipse中进行,返回结果并不会有样式的改变,只会对返回内容的关键字添加html标签,如果显示到网页即产生样式的变化。

高亮的设置代码如图5-3所示,结果如图5-4所示,会对南京匹配词添加<b>和<font>标签,显示到网页上为加粗和变红。

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);

图5-3:高亮设置

图5-4:高亮显示结果

实验过程中遇到的问题和不足

Lucene版本更新较快,在jdk版本、eclipse版本和lucene版本之间需要一个良好的衔接,否则会造成很多的不兼容,在调试版本以及jdk1.6和jdk1.8的选择上出现很多困难,比如网页抓取中的append方法在1.8版本已经删除,不能使用。但是对文档路劲的读取FSDirectory.open()则需要jdk1.8才支持。

本实验的不足之处主要表现在:

代码的灵活性较低,在爬取网页的时候需要手工进行,且需要对中文和英文分别进行,应该完善代码使得对网页的语言有个判定,然后自动选择执行不同的分词器。

代码的复用性较低,没有较为合理的分类和方法的构建,为了简便,基本在几个核心代码中进行注释和标记而实现效果,有待改进。

代码的可移植性较低,对网页的爬取使用的是jdk1.6的版本,Lucene的实现使用的是jdk1.8的版本,在导出到其他机器上,需要对环境稍加修改和配置,无法实现一键式操作。

总结

本文从Lucene的原理出发,了解了全文检索的思路和方法,并对常用的功能进行了实验和测试。在实验的过程中,了解了搜索引擎的原理,基于信息检索课程的内容上,有了一个更好的实操体验。Lucene 是一个优秀的开源全文本搜索技术框架,通过对它的深入研究,对其实现机制更加熟悉,在研究它的过程中学习了很多面向对象的编程方法和思想,它良好的系统框架和扩展性值得学习借鉴。

时间: 2024-11-03 20:52:02

Lucene实现索引和查询的相关文章

lucene 建立索引与查询

Lucene 简介 Lucene 是一个基于 Java 的全文信息检索工具包,它不是一个完整的搜索应用程序,而是为你的应用程序提供索引和搜索功能.Lucene 目前是 Apache Jakarta 家族中的一个开源项目.也是目前最为流行的基于 Java 开源全文检索工具包. 目前已经有很多应用程序的搜索功能是基于 Lucene 的,比如 Eclipse 的帮助系统的搜索功能.Lucene 能够为文本类型的数据建立索引,所以你只要能把你要索引的数据格式转化的文本的,Lucene 就能对你的文档进行

[lucene系列笔记2]在eclipse里初步使用lucene的索引和查询功能

首先,new一个java project,名字叫做LuceneTools. 然后,在project里new一个class,名字叫做IndexFiles.这个类用来给文件建索引(建好索引以后就可以高效检索了). 在写代码之前,我们要先引入一下lucene包,就类似于C语言里的include.如图: 点击之后看到如下窗口,选择"Add External JARs" 然后找到C:\Lucene-6.2.1目录下(如果是按上一篇文章配置的话应该是在这个目录里)的三个包(这里我们暂时只用到这三个

一步一步跟我学习lucene(8)---lucene搜索之索引的查询原理和查询工具类示例

昨天我们了解了lucene搜索之IndexSearcher构建过程(http://blog.csdn.net/wuyinggui10000/article/details/45698667),对lucene的IndexSearcher有一个大体的了解,知道了怎么创建IndexSearcher,就要开始学会使用IndexSearcher进行索引的搜索,本节我们学习索引的查询原理和根据其相关原理写索引查询的工具类的编写: IndexSearcher提供了几个常用的方法: IndexSearcher.

Solr4.8.0源码分析(10)之Lucene的索引文件(3)

Solr4.8.0源码分析(10)之Lucene的索引文件(3) 1. .si文件 .si文件存储了段的元数据,主要涉及SegmentInfoFormat.java和Segmentinfo.java这两个文件.由于本文介绍的Solr4.8.0,所以对应的是SegmentInfoFormat的子类Lucene46SegmentInfoFormat. 首先来看下.si文件的格式 头部(header) 版本(SegVersion) doc个数(SegSize) 是否符合文档格式(IsCompoundF

lucene内存索引库、分词器

内存索引库 特点 在内存中开辟一块空间,专门为索引库存放.这样有以下几个特征: 1)    因为索引库在内存中,所以访问速度更快. 2)    在程序退出时,索引库中的文件也相应的消失了. 3)    如果索引库比较大,必须得保证足够多的内存空间. 编码 在cn.hqu.directory 下新建:DirectoryTest /** * 1.能不能设置很多个索引库 *    可以设置很多个索引库 * 2.索引库能不能合并起来 *    如果是内存索引库 *      Directory ramD

一步一步跟我学习lucene(14)---lucene搜索之facet查询原理和facet查询实例

Facet说明 我们在浏览网站的时候,经常会遇到按某一类条件查询的情况,这种情况尤以电商网站最多,以天猫商城为例,我们选择某一个品牌,系统会将该品牌对应的商品展示出来,效果图如下: 如上图,我们关注的是品牌,选购热点等方面,对于类似的功能我们用lucene的term查询当然可以,但是在数据量特别大的情况下还用普通查询来实现显然会因为FSDirectory.open等耗时的操作造成查询效率的低下,同时普通查询是全部document都扫描一遍,这样显然造成了查询效率低: lucene提供了facet

4.Lucene3.案例介绍,创建索引,查询等操作验证

 案例: Article.java package cn.toto.lucene.quickstart; publicclassArticle { privateintid; private Stringtitle; private Stringcontent; /** * @return the id */ publicint getId() { returnid; } /** * @param id the id to set */ publicvoid setId(int id) {

solr 的客户端调用solrj 建索引+分页查询

一.利用SolrJ操作solr API 使用SolrJ操作Solr会比利用httpClient来操作Solr要简单.SolrJ是封装了httpClient方法,来操作solr的API的.SolrJ底层还是通过使用httpClient中的方法来完成Solr的操作. 需要的包如下: 1. apache-solr-solrj-3.5.0.jar 2. commons-httpclient-3.1.jar 3.slf4j-api-1.6.0.jar 4.commons-logging-1.1.jar 在

一步一步跟我学习lucene(15)---lucene搜索之正则表达式查询RegExQuery和手机邮箱查询示例

今天快下班的时候收到了一个群友的问题,大意是读取文本文件中的内容,找出文件中的手机号和邮箱,我自己写了一个读取文档的内容的正则查询示例,用于匹配文件中是否含有邮箱或者手机号,这个等于是对之前的文本处理工具的一个梳理,同时结合lucene内部提供的正则匹配查询RegexQuery: 废话不多说了,直接上代码,这里先对文件内容读取分类处理,分为pdf word excel 和普通文本四类,不同的种类读取文本内容不一样 pdf利用pdfbox读取内容,word和excel利用poi进行读取内容,文本文