阿里云-云小站(无限量代金券发放中)
【腾讯云】云服务器、云数据库、COS、CDN、短信等热卖云产品特惠抢购

初识Lucene 4.5全文搜索

132次阅读
没有评论

共计 13189 个字符,预计需要花费 33 分钟才能阅读完成。

近期想研究下 lucene,但网络上的教程大多都是 lucne 3.x 版本的讲解。可是 lucene 版本的更新速度快的惊人,目前已经到了 4.8 版了,只好去查阅官方文档。虽然英文不大好,但稍微对比了下发现 3.x 版本至 4.x 版本的修改非常之大。接下来我就以 4.5 版来操作,分享下我对 luence 的初步认识。

先给大家看一张图(来至《Lucene  in  action》http://www.linuxidc.com/Linux/2013-10/91052.htm):

初识 Lucene 4.5 全文搜索

此图很形象的描述了 lucene 的基本流程,简而言之就是:1、创建索引;2、检索索引。

————————————– 分割线 ————————————–

基于 Lucene 多索引进行索引和搜索 http://www.linuxidc.com/Linux/2012-05/59757.htm

Lucene 实战(第 2 版) 中文版 配套源代码 http://www.linuxidc.com/Linux/2013-10/91055.htm

Lucene 实战(第 2 版) PDF 高清中文版 http://www.linuxidc.com/Linux/2013-10/91052.htm

使用 Lucene-Spatial 实现集成地理位置的全文检索 http://www.linuxidc.com/Linux/2012-02/53117.htm

Lucene + Hadoop 分布式搜索运行框架 Nut 1.0a9 http://www.linuxidc.com/Linux/2012-02/53113.htm

Lucene + Hadoop 分布式搜索运行框架 Nut 1.0a8 http://www.linuxidc.com/Linux/2012-02/53111.htm

Lucene + Hadoop 分布式搜索运行框架 Nut 1.0a7 http://www.linuxidc.com/Linux/2012-02/53110.htm

Project 2-1: 配置 Lucene, 建立 WEB 查询系统[Ubuntu 10.10] http://www.linuxidc.com/Linux/2010-11/30103.htm

————————————– 分割线 ————————————–

太深的道理与原理我目前也还是一知半解,所以就以小白的思维来阐述。

Lucene 与数据库有许多相通之处,以下我们做个简单对比:

 
数据库
Luecene
基本概念
/ 字段
Field
/ 记录
Document
基本操作
查询(SELECT)
Searcher
添加(INSERT)

IndexWriter. addDocument

删除(DELETE)
IndexReader.delete
修改(UPDATE)
不支持 ( 可删除后重新添加)

上面的表格式某位网友博文里的对比,我觉得挺好理解的。可以这么去认为吧,lucene 把你数据库里的数据做了个索引,以后你要全文查找某数据时就可以从索引中查找,就好比字典的索引目录!

废话说了一大堆,还是用代码来说话,首先要往代码里导入三个包:

lucene-analyzers-common-4.5.0、lucene-core-4.5.0、lucene-queryparser-4.5.0

要有面向对象的思维嘛,先创建一个 javabean:luceneBeans,用来存放你所要的数据

package pojo;

public class LuceneBeans {

private String id;

private String title;

private String introduce;

private String addtime;

private String category;

public LuceneBeans() {

super();

}

public LuceneBeans(String id, String title, String introduce,

String addtime, String category) {

super();

this.id = id;

this.title = title;

this.introduce = introduce;

this.addtime = addtime;

this.category = category;

}

public String getId() {

return id;

}

public void setId(String id) {

this.id = id;

}

public String getTitle() {

return title;

}

public void setTitle(String title) {

this.title = title;

}

public String getIntroduce() {

return introduce;

}

public void setIntroduce(String introduce) {

this.introduce = introduce;

}

public String getAddtime() {

return addtime;

}

public void setAddtime(String addtime) {

this.addtime = addtime;

}

public String getCategory() {

return category;

}

public void setCategory(String category) {

this.category = category;

}

}

更多详情见请继续阅读下一页的精彩内容:http://www.linuxidc.com/Linux/2014-07/104532p2.htm

以下代码把 lucene 基本操作方法封装在了 IndexUtil 类中,其中考虑到创建 indexReader 开销过大,就设计了单实例模式。注释中会穿插些 4.x 版本与 3.x 版本的不同之处。

package lucene;

import java.io.File;

import java.io.IOException;

import java.util.ArrayList;

import java.util.Date;

import java.util.List;

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;

import org.apache.lucene.document.StringField;

import org.apache.lucene.document.TextField;

import org.apache.lucene.document.Field.Store;

import org.apache.lucene.index.DirectoryReader;

import org.apache.lucene.index.IndexReader;

import org.apache.lucene.index.IndexWriter;

import org.apache.lucene.index.IndexWriterConfig;

import org.apache.lucene.index.Term;

import org.apache.lucene.index.IndexWriterConfig.OpenMode;

import org.apache.lucene.queryparser.classic.ParseException;

import org.apache.lucene.queryparser.classic.QueryParser;

import org.apache.lucene.search.IndexSearcher;

import org.apache.lucene.search.Query;

import org.apache.lucene.search.ScoreDoc;

import org.apache.lucene.search.TopDocs;

import org.apache.lucene.store.Directory;

import org.apache.lucene.store.FSDirectory;

import org.apache.lucene.util.Version;

import pojo.LuceneBeans;

public class IndexUtil {

private static Directory directory = null;

private static IndexReader reader = null;

/**

* 设置存储路径

* @return

*/

public static String getIndexDir(){

// 得到.class 文件所在路径

String classpath = LuceneUtil.class.getResource(“/”).getPath();

// 将 class 中的 %20 替换成为空格

classpath = classpath.replaceAll(“%20″, ” “);

// 索引存储位置:WEB-INF/classes/index

String path = classpath+”index/”;

return path;

}

public IndexUtil(){

try {

// 存储方式有 CompoundFileDirectory, FileSwitchDirectory, FSDirectory,

//NRTCachingDirectory, RAMDirectory……等分别对应的不同方式

// 用 FSDirectory 的好处就在于它会自动帮你分配该使用哪种方式

directory = FSDirectory.open(new File(getIndexDir()));

//3.x 是 reader=IndexReader.open(directory);

// 但 4.x 后已经不建议使用,改成了 DirectoryReader.open(directory)

reader = DirectoryReader.open(directory);

} catch (IOException e) {

e.printStackTrace();

}

}

/**

* 创建 IndexWriter

* @return

*/

public static IndexWriter getIndexWriter(){

// 创建分词器,分词器可根据自己需求去自定义创建,此处以 lucene 自带的标准分词器分词

Analyzer analyzer=new StandardAnalyzer(Version.LUCENE_45);

IndexWriter indexWriter = null;

try {

IndexWriterConfig iwConfig = new IndexWriterConfig(Version.LUCENE_45, analyzer);

iwConfig.setOpenMode(OpenMode.CREATE_OR_APPEND);

indexWriter = new IndexWriter(directory, iwConfig);

return indexWriter;

} catch (IOException e) {

e.printStackTrace();

return null;

}

}

//——– 省略余下方法代码

嗯~ 创建索引前我们需要先获得 IndexWriter,其实步骤就像你往数据库中插入一条数据(document), 得再这条数据中加入字段(Field),得给这个字段做个说明(Store、Index)……

/**

* 批量创建索引

* @param list

* @return

*/

public boolean createIndexList(List<LuceneBeans> list){

boolean result = false;

IndexWriter indexWriter = IndexUtil.getIndexWriter();

try {

if(list!=null&&list.size()>0){

Document doc = null;

for(int i=0;i<list.size();i++){

doc = new Document();

LuceneBeans lb = list.get(i);

doc.add(new StringField(“id”,lb.getId(),Store.YES));

doc.add(new TextField(“title”,lb.getTitle(),Store.YES));

doc.add(new TextField(“introduce”,lb.getIntroduce(),Store.YES));

doc.add(new StringField(“addtime”,lb.getAddtime(),Store.YES));

doc.add(new StringField(“category”,lb.getCategory(),Store.YES));

indexWriter.addDocument(doc);

indexWriter.commit();

result = true;

}

}

} catch (IOException e) {

e.printStackTrace();

}finally{

if(indexWriter!=null){

try {

indexWriter.close();

} catch (IOException e) {

e.printStackTrace();

}

}

}

return result;

}

之前 3.x 版本是这样写的:

doc.add(new Field(“id”,lb.getI(),Field.Store.YES,Field.Index.NOT_ANALYZED_NO_NORMS));

那我们就顺便来分析下 store、index

(存储域选项)Store 判断是否把域存入文件(yes 存入,可还原;no 不存入,不可还原但可被索引)

(索引域选项)Index:

//|—-ANALYZED 进行分词和索引,适用于标题、内容等

//|—-NO_ANALYZED 进行索引但是不进行分词,身份号、姓名、ID 等,适用于精确搜索

//|—-ANALYZED_NOT_NORMS 进行分词但不存储 norms 信息,这个 norms 中包括了创建索引的时间和权值等信息

//|—-NOT_ANALYZED_NOT_NORMS 既不进行分词也不存储 norms 信息

但 4.x 之后就改变了,它需要更为精确的 StringField、TextField、IntField……

初识 Lucene 4.5 全文搜索

那为什么 4.x 版本只写了 Store 而没 Index? 还是让我们看看官方文档上的描述吧:

先看 TxetField:自动分词

初识 Lucene 4.5 全文搜索

再看看 StringField:不分词

初识 Lucene 4.5 全文搜索

所以,4.x 有很多写法是与 3.x 不同的,虽然还可以向下兼容,但总是要跟着时代的脚步前进嘛!

近期想研究下 lucene,但网络上的教程大多都是 lucne 3.x 版本的讲解。可是 lucene 版本的更新速度快的惊人,目前已经到了 4.8 版了,只好去查阅官方文档。虽然英文不大好,但稍微对比了下发现 3.x 版本至 4.x 版本的修改非常之大。接下来我就以 4.5 版来操作,分享下我对 luence 的初步认识。

先给大家看一张图(来至《Lucene  in  action》http://www.linuxidc.com/Linux/2013-10/91052.htm):

初识 Lucene 4.5 全文搜索

此图很形象的描述了 lucene 的基本流程,简而言之就是:1、创建索引;2、检索索引。

————————————– 分割线 ————————————–

基于 Lucene 多索引进行索引和搜索 http://www.linuxidc.com/Linux/2012-05/59757.htm

Lucene 实战(第 2 版) 中文版 配套源代码 http://www.linuxidc.com/Linux/2013-10/91055.htm

Lucene 实战(第 2 版) PDF 高清中文版 http://www.linuxidc.com/Linux/2013-10/91052.htm

使用 Lucene-Spatial 实现集成地理位置的全文检索 http://www.linuxidc.com/Linux/2012-02/53117.htm

Lucene + Hadoop 分布式搜索运行框架 Nut 1.0a9 http://www.linuxidc.com/Linux/2012-02/53113.htm

Lucene + Hadoop 分布式搜索运行框架 Nut 1.0a8 http://www.linuxidc.com/Linux/2012-02/53111.htm

Lucene + Hadoop 分布式搜索运行框架 Nut 1.0a7 http://www.linuxidc.com/Linux/2012-02/53110.htm

Project 2-1: 配置 Lucene, 建立 WEB 查询系统[Ubuntu 10.10] http://www.linuxidc.com/Linux/2010-11/30103.htm

————————————– 分割线 ————————————–

太深的道理与原理我目前也还是一知半解,所以就以小白的思维来阐述。

Lucene 与数据库有许多相通之处,以下我们做个简单对比:

 
数据库
Luecene
基本概念
/ 字段
Field
/ 记录
Document
基本操作
查询(SELECT)
Searcher
添加(INSERT)

IndexWriter. addDocument

删除(DELETE)
IndexReader.delete
修改(UPDATE)
不支持 ( 可删除后重新添加)

上面的表格式某位网友博文里的对比,我觉得挺好理解的。可以这么去认为吧,lucene 把你数据库里的数据做了个索引,以后你要全文查找某数据时就可以从索引中查找,就好比字典的索引目录!

废话说了一大堆,还是用代码来说话,首先要往代码里导入三个包:

lucene-analyzers-common-4.5.0、lucene-core-4.5.0、lucene-queryparser-4.5.0

要有面向对象的思维嘛,先创建一个 javabean:luceneBeans,用来存放你所要的数据

package pojo;

public class LuceneBeans {

private String id;

private String title;

private String introduce;

private String addtime;

private String category;

public LuceneBeans() {

super();

}

public LuceneBeans(String id, String title, String introduce,

String addtime, String category) {

super();

this.id = id;

this.title = title;

this.introduce = introduce;

this.addtime = addtime;

this.category = category;

}

public String getId() {

return id;

}

public void setId(String id) {

this.id = id;

}

public String getTitle() {

return title;

}

public void setTitle(String title) {

this.title = title;

}

public String getIntroduce() {

return introduce;

}

public void setIntroduce(String introduce) {

this.introduce = introduce;

}

public String getAddtime() {

return addtime;

}

public void setAddtime(String addtime) {

this.addtime = addtime;

}

public String getCategory() {

return category;

}

public void setCategory(String category) {

this.category = category;

}

}

更多详情见请继续阅读下一页的精彩内容:http://www.linuxidc.com/Linux/2014-07/104532p2.htm

那接下来我们可以写个测试方法看看效果,至于 luceneBeans 的数据我就不贴出来了,大家可以自己导入,我们不仅可以用 javaBean 的数据来创建索引,像 txt、pdf、ini 等文件的数据也是可以导入来创建索引的;

如果你创建成功了则在对应的 index 文件夹下会多出这些文件:

初识 Lucene 4.5 全文搜索

创建好索引后,当然我们要开始检索索引咯,重要的是获得 indexReader,然后创建 indexSearch 去检索。我知识举个最常规的用法,其中还有很多东西还需个人去体会。(接上面代码写在 IndexUtil 类内)

/**

* 创建 IndexReader

* 为保证检索时,索引的同步更新,需对 reader 进行判断

* @return

*/

public static IndexSearcher getIndexSearcher(){

try {

// 判断 reader 是否为空

if(reader==null){

reader = DirectoryReader.open(directory);

}else{

//3.x 版本是用 IndexReader.openIfChange(reader); 来判断 reader 是否有改变。

//reader 有改变则返回改变后的; 若无改变则返回 null

IndexReader ir = DirectoryReader.openIfChanged((DirectoryReader)reader);

if(ir!=null){

//reader 已改变则关闭原先的,再将改变后的赋值给 reader

reader.close();

reader = ir;

}

}

return new IndexSearcher(reader);

} catch (IOException e) {

e.printStackTrace();

return null;

}

   

/**

* 从索引中检索

* @param content

* @return

*/

public List<LuceneBeans> searchIndex(String content){

List<LuceneBeans> list = new ArrayList<LuceneBeans>();

try {

System.out.println(“ 文档数 numDocs:”+reader.numDocs());

System.out.println(“ 文档总数 maxDocs:”+reader.maxDoc());

System.out.println(“ 删除数 deleteDocs:”+reader.numDeletedDocs());

IndexSearcher searcher = IndexUtil.getIndexSearcher();

// 使用查询解析器创建 Query(Version.LUCENE_45 对应版本号,“introduce”默认索引域)

//new StandardAnalyzer(Version.LUCENE_45))标准分词器

//Query 的方法还有很多种,例如 TermQuery 精确查询、PrefixQuery 前缀查询、WildcardQuery 通配符查询……等

QueryParser questParser = new QueryParser(Version.LUCENE_45,”introduce”,

new StandardAnalyzer(Version.LUCENE_45));

Query query = null;

try {

query = questParser.parse(content);

} catch (ParseException e) {

e.printStackTrace();

}

System.out.println(“ 查询语句:”+query.toString());

long begin = new Date().getTime();

TopDocs topDocs = searcher.search(query, 15);//15 为检索出的个数

if(topDocs!=null){

ScoreDoc[] scoreDoc = topDocs.scoreDocs;

System.out.println(“ 共搜索到(”+topDocs.totalHits+”)条匹配结果 ”);

for(ScoreDoc sd : scoreDoc){

Document doc = searcher.doc(sd.doc);

System.out.println(“ 标题:”+doc.get(“title”)+”  评分 score:”+sd.score);

LuceneBeans lb = new LuceneBeans();

lb.setId(doc.get(“id”));

lb.setTitle(doc.get(“title”));

lb.setIntroduce(doc.get(“introduce”));

lb.setAddtime(doc.get(“addtime”));

lb.setCategory(doc.get(“category”));

list.add(lb);

}

}

long end = new Date().getTime();

System.out.println(“ 搜索完毕 … … 共用时:” + (end – begin) +” 毫秒 …”);

} catch (IOException e) {

e.printStackTrace();

}

return list;

}

具体的每个方法和属性,就不一一讲解,毕竟挺别人说不一定全对,还是去查看官方文档更为准确。写个测试方法看看上面那段代码:

@Test

public void search(){

IndexUtil lu = new IndexUtil();

List<LuceneBeans> list = lu.searchIndex(“ 小王子 ”);

if(list!=null&&list.size()>0){

System.out.println(“ 检索到的结果数据:”);

for(LuceneBeans lb:list){

System.out.println(“===================================”);

System.out.println(“id:”+lb.getId()+”===title:”+lb.getTitle());

}

}

}

初识 Lucene 4.5 全文搜索

嗯~ 夜已深,先分享到这边。之后陆续还会再分享关于 lucene 的相关博文,希望对您有所帮助。初出茅如,若文中有错误之处还望包涵指正。喜欢交流、喜欢分享~

已经介绍了如何创建索引与检索索引。接下来就是删除与更新啦~

 一、删除索引。

    原本 3.x 版本时 IndexWriter 与 IndexReader 都是有删除方法的,让我们先来看看 lucene 3.6 api 文档中的 IndexReader 的描述:

初识 Lucene 4.5 全文搜索

    从 4.0 开始已经被删除了,所以现在只能用 IndexWriter 中的方法来进行删除。有哪些方法呢?继续看文档(lucene 4.5 api):

初识 Lucene 4.5 全文搜索

    除了上面的六个外还有一个方法 tryDeleteDocument(IndexReader reader,int docID),但这个方法是有限制的(NOTE: this method can only delete documents visible to the currently open NRT reader. If you need to delete documents indexed after opening the NRT reader you must use the other deleteDocument methods.)我们最常用的就是 deleteDocuments(),不同的参数方法被重载了四次。我以 deleteDocuments(Term term)为例做个 Demo 演示:
/**
  * 删除索引
  * @param id
  */
 public void delete(String id){
  IndexWriter indexWriter = IndexUtil.getIndexWriter();
  //Term 为最小的单元相当于是对应着 field 的字段
  Term term = new Term(“id”,id);
  try {
  // 参数可以使一个选项,也可以是个 query 或者 term(精确查找值)
  indexWriter.deleteDocuments(term);
  indexWriter.commit(); 
  } catch (IOException e) {
  e.printStackTrace();
  }finally{
  if(null!=indexWriter){
    try {
    indexWriter.close();
    } catch (IOException e) {
    e.printStackTrace();
    }       
  }
  }
 }
    嗯~ 这样就能准确的删除掉你所指定要删的索引文件了。这里又出现了不同之处,之前版本删除了的索引并不是真的被删除,而是把索引文件存入 .del 文件中(像我们 windows 一样把文件存入垃圾箱)。然后还可以通过 undeleteAll() 方法来进行恢复,但是 4.0 版本后这个方法也被移除了,并且没有新的方法可替代。

二、更新索引
/**
  * 更新(lucene 提供的并不是真实的更新方法,实际是先删除后再添加)
  * @param lb
  * @return
  */
 public boolean update(LuceneBeans lb){
  boolean result = false;
  IndexWriter indexWriter = IndexUtil.getIndexWriter();
  try {
  Term term = new Term(“id”,lb.getId());
  Document doc = new Document();
  doc.add(new StringField(“id”,lb.getId(),Store.YES));
  doc.add(new TextField(“title”,lb.getTitle(),Store.YES));
  doc.add(new TextField(“introduce”,lb.getIntroduce(),Store.YES));
  doc.add(new StringField(“addtime”,lb.getAddtime(),Store.YES));
  doc.add(new StringField(“category”,lb.getCategory(),Store.YES));
  // 参数一需要删除的索引字段、参数二将要更新的新数据
  indexWriter.updateDocument(term, doc);
  indexWriter.commit(); 
  result=true;
  } catch (IOException e) {
  e.printStackTrace();
  }finally{
  if(null!=indexWriter){
    try {
    indexWriter.close();
    } catch (IOException e) {
    e.printStackTrace();
    }       
  }
  }
  return result;
 }

 

更新操作没什么改变,同样也是传入不同参数重载方法实现更新操作。

结语:lucene 4.5 的基本操作就先介绍到这里,后续还会再更新更多的 lucene 博文。文中若有不准确或不正确之处还望指正,一起交流一起进步!菜鸟的天空,只要努力,越飞越高!

正文完
星哥说事-微信公众号
post-qrcode
 0
星锅
版权声明:本站原创文章,由 星锅 于2022-01-20发表,共计13189字。
转载说明:除特殊说明外本站文章皆由CC-4.0协议发布,转载请注明出处。
【腾讯云】推广者专属福利,新客户无门槛领取总价值高达2860元代金券,每种代金券限量500张,先到先得。
阿里云-最新活动爆款每日限量供应
评论(没有评论)
验证码
【腾讯云】云服务器、云数据库、COS、CDN、短信等云产品特惠热卖中