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配置

3 编写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”。

4 建立索引

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

建立索引:搜索引擎的索引其实就是实现“单词-文档矩阵”的具体数据结构。也是进行全文检索的第一步,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命名的文件路径加权后,返回的时候顺序发生变化,实现对权重的测试。

5 进行查询

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:高亮显示结果

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

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

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

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

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

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

7 总结

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

(0)

相关推荐

  • Java实现lucene搜索功能的方法(推荐)

    直接上代码: package com.sand.mpa.sousuo; import java.io.BufferedReader; import java.io.File; import java.io.FileReader; import java.io.FileWriter; import java.io.IOException; import java.io.PrintWriter; import java.sql.Connection; import java.sql.DriverMa

  • Lucene实现索引和查询的实例讲解

    0引言 随着万维网的发展和大数据时代的到来,每天都有大量的数字化信息在生产.存储.传递和转化,如何从大量的信息中以一定的方式找到满足自己需求的信息,使之有序化并加以利用成为一大难题.全文检索技术是现如今最普遍的信息查询应用,生活中利用搜索引擎,在博客论坛中查找信息,这些搜索的核心原理就是本文要实现的全文检索技术.随着文档信息数字化的实现,将信息有效存储并及时准确的提取是每一个公司.企业和单位要做好的基础.针对英文的全文检索已经有很多成熟的理论和方法,开放源代码的全文检索引擎Lucene 是Apa

  • python执行数据库的查询操作实例讲解

    1.fetchone该方法获取下一个查询结果集.结果集是一个对象. 2.fetchall接收全部的返回结果行. 3.rowcount这是一个只读属性,并返回执行execute方法后影响的行数. 实例 from pymysql import * def main(): # 创建Connection连接 conn = connect(host='localhost',port=3306,user='root',password='mysql',database='jing_dong',charset

  • jQuery easyui datagrid动态查询数据实例讲解

    该插件组小巧使用方便,以下是一个从前台提交查询条件,从MSSQL返回json数据的一个事例 HTML前端代码 复制代码 代码如下: <?php include_once("auth.php"); ?> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /> <link rel=&qu

  • 使用bitset实现毫秒级查询(实例讲解)

    前言 因为业务要求api的一次请求响应时间在10ms以内,所以传统的数据库查询操作直接被排除(网络io和磁盘io).通过调研,最终使用了bieset,目前已经正常运行了很久 bitset介绍 看JDK中的解释简直一头雾水,用我自己的理解概括一下 1.bitset的内部实现是long数组 2.set中每一个位的默认值为false(0) 3.bitset长度按需增长 4.bitset非线程安全 bitset关键方法分析 /** * Sets the bit at the specified inde

  • Java分页查询--分页显示(实例讲解)

    当数据库中数据条数过多时,一个页面就不能显示,这是要设置分页查询,首先要使用的是数据库sql语句的limit条件实现分组查询 sql语句大概形式为: select * from table limit 开始索引,显示条数 用该语句就会实现分块查询,并且每页显示固定条数. 首先要实现后台分页,我们需要知道它有多少页,每页有多少行,这就需要知道一共多少行,调用sql语句时还需要知道每一页的开始索引,开始索引是根据当前页数算出来的,所以还需要知道当前页数,查询后会返回一个列表存储当前页数据.将这些属性

  • ADO调用分页查询存储过程的实例讲解

    一.分页存储过程 ----------使用存储过程编写一个分页查询----------------------- set nocount off --关闭SqlServer消息 --set nocount on --开启SqlServer消息 go create proc usp_getMyStudentsDataByPage --输入参数 @pagesize int=7,--每页记录条数 @pageindex int=1,--当前要查看第几页的记录 --输出参数 @recordcount in

  • 对Python中list的倒序索引和切片实例讲解

    Python中list的倒序索引和切片是非常常见和方便的操作,但由于是倒序,有时候也不太好理解或者容易搞混. >>> nums = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] >>> print(nums[-1]) 9 >>> print(nums[-2:]) [8, 9] >>> print(nums[:-3]) [0, 1, 2, 3, 4, 5, 6] 例如,给定一个数组nums. 索引操作 nums[-1]

  • 对pandas里的loc并列条件索引的实例讲解

    如下所示: def Family_feature(df): df['Fam_Size'] =df['SibSp']+df['Parch'] df['Fam_Size'].loc[df['Fam_Size'] == 0] = 1 df['Fam_Size'].loc[(df['Fam_Size'] > 1) & (df['Fam_Size'] <= 3)] = 2 # df['Fam_Size'].loc[df['Fam_Size'] == 2] = 2 # df['Fam_Size']

  • 实例讲解MySQL 慢查询

    简介 开启慢查询日志,可以让MySQL记录下查询超过指定时间的语句,通过定位分析性能的瓶颈,才能更好的优化数据库系统的性能. 一.配置慢查询 1.参数说明 slow_query_log : 慢查询开启状态(默认关闭) slow_query_log_file : 慢查询日志存放的位置(这个目录需要MySQL的运行帐号的可写权限, 一般设置为MySQL的数据存放目录) long_query_time : 查询超过多少秒才记录(默认10秒) 2.查看慢查询相关参数 show variables lik

  • MYSQL慢查询和日志实例讲解

    一.简介 开启慢查询日志,可以让MySQL记录下查询超过指定时间的语句,通过定位分析性能的瓶颈,才能更好的优化数据库系统的性能. 二.参数说明 slow_query_log 慢查询开启状态 slow_query_log_file 慢查询日志存放的位置(这个目录需要MySQL的运行帐号的可写权限,一般设置为MySQL的数据存放目录) long_query_time 查询超过多少秒才记录 三.设置步骤 1.查看慢查询相关参数 mysql> show variables like 'slow_quer

随机推荐