Lucene & 全文检索

img_1d5b0cb357ccd8f66c357a9141420653.png

目录结构:
1.全文检索
2.Lucene入门
3.Lucene进阶

全文检索

一, 生活中的搜索:
1.Windows系统中的有搜索功能:打开“我的电脑”,按“F3”就可以使用查找的功能,查找指定的文件或文件夹。搜索的范围是整个电脑中的文件资源。

2.Eclipse中的帮助子系统:点击HelpHelp Contents,可以查找出相关的帮助信息。搜索的范围是Eclipse的所有帮助文件。
搜索引擎,如Baidu或Google等,可以查询到互联网中的网页、PDF、DOC、PPT、图片、音乐、视频等。
3.Mac中的Spotlight搜索
4.数据库中检索检查某一个关键字的例子。
select * from topic where content like '%java%'
文本检索,会使索引失效

存在问题:
1.搜索速度慢
2.搜索效果不好.
3.没有相关度排序

二, 什么是全文检索?

全文检索是指计算机索引程序通过扫描文章中的每一个词,对每一个词建立一个索引,指明该词在文章中出现的次数和位置,当用户查询时,检索程序就根据事先建立的索引进行查找,并将查找的结果反馈给用户的检索方式。这个过程类似于通过字典中的检索字表查字的过程。

在说全文检索之前我们先来了解一下数据分类

结构化数据:指具有固定格式或有限长度的数据,如数据库,元数据等;
半结构化数据:半结构化数据
非结构化数据:指不定长或无固定格式的数据,如邮件,word文档等;
非结构化数据又一种叫法叫全文数据。从全文数据中进行检索就叫全文检索。
特点:只关注文本不考虑语义

三, 为什么使用 ?
搜索速度:将数据源中的数据都通过全文索引

匹配效果:过词元(term)进行匹配,通过语言分析接口的实现,可以实现对中文等非英语的支持。

相关度:有匹配度算法,将匹配程度(相似度)比较高的结果排在前面。

适用场景:关系数据库中进行模糊查询时,数据库自带的索引将不起作用,此时需要通过全文检索来提高速度;比如:
网站系统中针对内容的模糊查询;
select * from article where content like '%上海平安%'
ERP系统中产品等数据的模糊查询,BBS、BLOG中的文章搜索等;
各种搜索引擎运行依赖于全文检索;
只对指定领域的网站进行索引与搜索(即垂直搜索,如“818工作搜索”、“有道购物搜索”)
要在word、pdf等各种各样的数据格式中检索内容;
其它场合:比如搜狐拼音输入法、Google输入法等。

四, 工作原理

1.如何查询全文数据?

顺序扫描法(Serial Scanning):所谓顺序扫描,比如要找内容包含某一个字符串的文件,就是一个文档一个文档的看,对于每一个文档,从头看到尾,如果此文档包含此字符串,则此文档为我们要找的文件,接着看下一个文件,直到扫描完所有的文件。比如Window自带的搜索。
如何提升全文检索的速度?

对非结构化数据顺序扫描很慢,对结构化数据的搜索却相对较快(由于结构化数据有一定的结构可以采取一定的搜索算法加快速度),那么把我们的非结构化数据想办法弄得有一定结构不就行了吗?关系数据库中存储的都是结构化数据,因此很检索都比较快。
从非结构化数据中提取出的然后重新组织的信息,我们称之索引。
字典及图书目录的原理。

2.全文检索的过程

索引创建:将现实世界中所有的结构化和非结构化数据提取信息,创建索引的过程。
搜索索引:就是得到用户的查询请求,搜索创建的索引,然后返回结果的过程。

3.案例分析

索引文件中应该存放什么?
索引文件中只需要存放单词及文档编号即可
要查出即包含is,又包括 shanghai及pingan的文档,先获得包含is的文档列表,再获得包含shanghai及pingan的文档列表,最合做一个集合并运算,就得出文档1及文档3。

文档0
What is your name?
文档1
My name is shanghai pingan!
文档2
What is that?
文档3
It is shanghai pingan, ShangHai Pingan

首先将我们非结构化数据存储到文档区

文档编号内容
0What is your name?
1My name is shanghai pingan!
2What is that?
3It is shanghai pingan, ShangHai Pingan

如何建立索引?
第一步:分词组件(Tokenizer)对文档进行处理,此过程称为Tokenize。

  1. 将文档分成一个一个单独的单词。(用空格分开)
  2. 去除标点符号。
  3. 去除停词(Stop word)。大量出现的助词,比如is,it等。中文:的,了,呢
    经过分词(Tokenizer)后得到的结果称为词元(Token)。词元(Token)如下:
    shanghai,ShangHai,pingan,My,name,What,your,pingan

第二步:将得到的词元(Token)传给语言处理组件(Linguistic Processor),对于英语,处理大致如下:

  1. 变为小写(Lowercase)。
  2. 将单词缩减为词根形式,如“cars”到“car”等。这种操作称为:stemming。
  3. 将单词转变为词根形式,如“drove”到“drive”等。这种操作称为:lemmatization。
    语言处理组件(linguistic processor)的结果称为词(Term)。结果如下:
    shanghai,pingan,my,name,what,your

第三步:把得到的词Term传给索引组件(Indexer)处理,处理过程如下:
1、把得到的词创建一个字典表

词term文档Document
what0
name0
My1
name1
shanghai1
pingan1
what2
that2
shanghai3
pingan3
shanghai3
pingan3

2、对字典按字母顺序进行排序

词term文档Document
shanghai1
shanghai3
shanghai3
pingan1
pingan3
pingan3
my1
name0
name1
what0
what2
your0

3、合并相同的词(Term)成为文档倒排(Posting List)链表。

词term出现次数文档Frequency文档Frequency
shanghai31132
pingan31132
my111~~
name20111
what20121
your101~~

最终会存储两部分一个文档区和一个索引区

词元文档编号
what0,2
your0
name0,1
my1
shanghai1,3,3
pingan1,3,3
that2

搜索处理的大致流程:
1、接收用户输入的搜索词及关键字并作简单处理;
2、对查询语句进行词法分析,语法分析,及语言处理;
3、查询到包含输出词的文档列表,并进行相关逻辑运算;
4、根据文档的相关性进行排序,把相关性最高的文档返回出来。

4.文档相关性

计算词的权重:
1、找出词(Term)对文档的重要性的过程称为计算词的权重(Term weight)的过程。主要有两个因素:
A、Term Frequency (tf):即此Term在此文档中出现了多少次。tf 越大说明越重要。
B、 Document Frequency (df):即有多少文档包含该Term。df 越大说明越不重要。

2、判断Term之间的关系从而得到文档相关性的过程,也即向量空间模型的算法(VSM)。
实现方式:把文档看作一系列词(Term),每一个词(Term)都有一个权重(Term weight),不同的词(Term)根据自己在文档中的权重来影响文档相关性的打分计算

5.全文检索应用架构
img_10cba9eadb3b511daf3bea0388313d69.jpe
6.全文检索的流程对应的Lucene 实现的包结构
img_e32401fa448f0b33a81284a1e94797af.png

Lucene 的analysis 模块主要负责词法分析及语言处理而形成Term。
Lucene的index模块主要负责索引的创建,里面有IndexWriter。
Lucene的store模块主要负责索引的读写。
Lucene 的QueryParser主要负责语法分析。
Lucene的search模块主要负责对索引的搜索。

Lucene入门
Lucene是什么?

Lucene是一个用Java写的高性能、可伸缩的全文检索引擎工具包,它可以方便的嵌入到各种应用中实现针对应用的全文索引/检索功能。Lucene的目标是为各种中小型应用程序加入全文检索功能。

开发步骤

建立索引文件

1,创建一个测试类LuceneTest
2,导入jar包
lucene-core-4.10.4.jar 核心包
lucene-analyzers-common-4.10.4.jar 分词器包
3,创建索引写入器IndexWriter 传入对应的参数:索引需要存放的位置,索引写入器配置对象(配置版本,分词器)
4.内容写入之后,写入到二进制文件中不方便查看,使用工具(lukeall-4.10.0.jar)查看索引库

public class LuceneTest {
    String content1 = "hello world";
    String content2 = "hello java world";
    String content3 = "hello lucene world";
    String indexPath = "hello";
    Analyzer analyzer = new StandardAnalyzer();//分词器

    @Test
    public void testCreateIndex() throws Exception {
        //1.创建索引写入器
        Directory d = FSDirectory.open(new File(indexPath));//索引需要存放的位置
        //创建索引写入器配置对象
        IndexWriterConfig conf = new IndexWriterConfig(Version.LUCENE_4_10_4, analyzer);
        IndexWriter writer = new IndexWriter(d, conf);
        //2.写入文档信息
        //添加文档 定义字段的存储规则
        FieldType type = new FieldType();
        type.setIndexed(true);//是否要索引
        type.setStored(true);//是否需要存储
        Document document1 = new Document();//数据库中的一条数据
        //new Field("字段名","字段内容","字段的配置属性")
        document1.add(new Field("title", "doc1", type));//该条记录中的字段 title:doc1
        document1.add(new Field("content", content1, type));//content: hello world
        writer.addDocument(document1);

        Document document2 = new Document();
        document2.add(new Field("title", "doc2", type));
        document2.add(new Field("content", content2, type));
        writer.addDocument(document2);

        Document document3 = new Document();
        document3.add(new Field("title", "doc3", type));
        document3.add(new Field("content", content3, type));
        writer.addDocument(document3);

        //需要把添加的记录保存
        writer.commit();
        writer.close();
    }
}

运行测试类会在该项目目录下生成一个hello文件夹

img_da4ed4166418c41ae9679cb78213558e.png

打开_0.xfs文件,这时我们看不出一个所以然

img_7c1d0fd3c438aaaaedf5adcf9b23edd3.png

使用工具(lukeall-4.10.0.jar)查看索引库
只需在终端通过命令行 java -jar lukeall-4.10.0.jar 即可

需要在Path路径上找到hello索引库的绝对路径


img_153490414a85685ab2914825f256ca47.png

点击OK即可看到索引库


img_eb383f766336865e370a07c0b80edbc1.png

查询索引库

0.导入jar包lucene-queryparser-4.10.4.jar(将字符串变成Query对象)
1.创建测试方法searchIndex()
2.创建索引查询对象IndexSearcher
3.根据查询的文本内容解析成Query查询对象(导入jar包lucene-queryparser-4.10.4.jar)设置查询字段,分词器
4.根据查询器查询到文档编号
5.通过文档编号查询对应的文档内容

//索引查询过程
@Test
 public void searchIndex() throws Exception {
    //1.创建索引写入器
    Directory d = FSDirectory.open(new File(indexPath));
    //创建分词器
    Analyzer analyzer = new StandardAnalyzer();
    //打开索引目录
    IndexReader r = DirectoryReader.open(d);
    //创建索引查询对象
    IndexSearcher searcher = new IndexSearcher(r);
    QueryParser parser = new QueryParser("content", analyzer);

    Query query = parser.parse("hello");//查询hello
    //search(查询对象,符合条件的前n条记录)
    TopDocs search = searcher.search(query, 10000);//n:前几个结果
    System.out.println("符合条件的记录有多少个:" + search.totalHits);

    ScoreDoc[] scoreDocs = search.scoreDocs;
    for (int i = 0; i < scoreDocs.length; i++) {
        System.out.println("*******************************");
        System.out.println("分数:" + scoreDocs[i].score);//相关度的排序
        int docId = scoreDocs[i].doc;//文档编号
        Document document = searcher.doc(docId);
        System.out.println("文档编号 docId--->" + docId);
        System.out.println("标题内容 title:--->" + document.get("content"));
    }
}
    

打印结果:

img_00b4fc1ef3f60e04ee939940fbd929e9.png
常用API

Directory:索引目录用于存放lucene索引文件
Directory是一个对索引目录的一个抽象,索引目录可以存放在普通的文件中,也可以位于数据库,或其它的远程服务中;一般情况下均使用文件来索引目录,这时一个Directory就相当于一个文件夹。
SimpleFSDirectory:直接使用java.io.RandomAccessFile类来操作索引文件,在普通的Lucene应用中,可以直接使用SimpleFSDirectory。

SimpleFSDirectory类:直接使用java.io.RandomAccessFile类来操作索引文件,在普通的Lucene应用中,这是最简单的用法。
构造函数:
SimpleFSDirectory(File path) :直接根据一个文件夹地址来创建索引目录;
MMapDirectory(File path) :让OS把整个索引文件映射到虚拟地址空间,这样Lucene就会觉得索引在内存中。

Document:当往索引中加入内容的时候,每一条信息用一个子Document来表示,Document的意思表示文档,也可以理解成记录,与关系数据表中的一行数据记录类似;
在Document创建完以后,直接调用其提供的字段操作方法来操作其中的字段对象。
Document提供的方法主要包括:
字段添加:add(Field field)
字段删除:removeField、removeFields
获取字段或值:get、getBinaryValue、getField、getFields等

Field:Field代表Document中的一行数据,相当于一条Lucene记录中的一列。
Lucene提供了一个接口Fieldable,其它的API大多针对这个接口编程,因此Lucene中的列对象实际上是由Fieldable来定义,实现该接口的除了Field类,还包括NumericField等。在实际开发中,主要使用的是Field类。
Field类提供的常用构造方法:
1、Field(String name, String value, Field.Store store, Field.Index index) -通过字段名称,字段值,存储类型及索引方式来创建一个字段;
2、Field(String name, byte[] value, Field.Store store) -通过字段名称、字段值(字节码)及字段存储方式创建字段对象;
3、Field(String name, Reader reader) -根据字段名称及Reader对象创建字段对象;
4、其它构造方法,详情查看API。
new Field("title", "中国太平", Store.NO, Index.ANALYZED);
new Field("content", "比较好的保险公司", Store.YES, Index.ANALYZED);

FieldType:Lucene中,在创建Field的时候,可以指定Field的store及index属性;
store属性:表示字段值是否存储,True表示要存储,而False则表示不存储;
type.setStored(true);//是否需要存储在文档区中
indexed属性:表示字段的是否需要建立索引,即是否支持搜索。tokenized属性:表示字段是否需要根据Analyzer规则进行分词

创建FieldTest测试类(复制上面的类修改类名)
定义字段的存储规则

  FieldType type2 = new FieldType();
  type2.setIndexed(true);//该字段是否要索引
  type2.setStored(true);//是否需要存储在文档区中
  type2.setTokenized(false);//字段是否分词
  type2.setTokenized(false);//字段是否分词

设置所有的字段的配置属性为type2

document1.add(new Field("content", content1, type2));
document2.add(new Field("content", content2, type2));
document3.add(new Field("content", content3, type2));
public class FieldTest {
    String content1 = "hello world";
    String content2 = "hello java world";
    String content3 = "hello lucene world";
    String indexPath = "fieldType";
    Analyzer analyzer = new StandardAnalyzer();//分词器

    //创建索引
    @Test
    public void testCreateIndex() throws Exception {
        //1.创建索引写入器
        Directory d = FSDirectory.open(new File(indexPath));//索引需要存放的位置
        //创建索引写入器配置对象
        IndexWriterConfig confg = new IndexWriterConfig(Version.LUCENE_4_10_4, analyzer);
        confg.setOpenMode(IndexWriterConfig.OpenMode.CREATE);//索引每次重新创建
        IndexWriter writer = new IndexWriter(d, confg);
        //2.写入文档信息
        //添加文档 定义字段的存储规则
        FieldType type = new FieldType();
        type.setIndexed(true);//该字段是否要索引
        type.setStored(true);//是否需要存储
        type.setTokenized(true);

        FieldType type2 = new FieldType();
        type2.setIndexed(true);//该字段是否要索引
        type2.setStored(true);//是否需要存储
        type2.setTokenized(false);//字段是否分词

        Document document1 = new Document();//数据库中的一条数据
        //new Field("字段名","字段内容","字段的配置属性")
        document1.add(new Field("title", "doc1", type));//该条记录中的字段 title:doc1
        document1.add(new Field("content", content1, type2));//content: hello world
        writer.addDocument(document1);

        Document document2 = new Document();
        document2.add(new Field("title", "doc2", type));
        document2.add(new Field("content", content2, type2));
        writer.addDocument(document2);

        Document document3 = new Document();
        document3.add(new Field("title", "doc3", type));
        document3.add(new Field("content", content3, type2));
        writer.addDocument(document3);

        //需要把添加的记录保存
        writer.commit();
        writer.close();
    }
}

运行测试类


img_91c0889964dc358c7be7b1beba6f9142.png

查看索引库


img_656aeb208c9d161f9a05f2fc983a16b3.png

当我们搜索用户名或者地名希望是完整的词元,不希望被分割,此时就可以设置该字段的tokenize属性为false,设置不进行分词
在索引库中:
1.标题和内容都通过分词器进行索引了.
2.标题是完整储存在文档区中,内容值截取前30个字符存储在存储区
3.文章ID只是存储在文档区但是没有进行分词
4.时间,作者,阅读量,评论数,来源是没索引也没存储的

Analyzer(词法分析器)

创建一个测试类AnalyzerTest
封装一个测试各个分词器的方法analyzerMethod(Analyzer analyzer, String content);

public class AnalyzerTest {
    String en = "good morning boy";
    String ch = "你好 恭喜发财 东方明珠三生三世十里桃花";

    @Test
    public void analyzerMethod(Analyzer analyzer, String content) throws Exception {

        TokenStream tokenStream = analyzer.tokenStream("content", content);
        tokenStream.reset();
        while (tokenStream.incrementToken()) {
            System.out.println(tokenStream);
        }
    }

    //英文分词器SimpleAnalyzer测试
    @Test
    public void testSimpleAnalyzer() throws Exception {
        analyzerMethod(new SimpleAnalyzer(), en);
    }
}

英文分词:
SimpleAnalyzer:最简单的词法分析器,按英文单词建立索引,以空格为分隔符;

  //英文分词器SimpleAnalyzer测试
    @Test
    public void testSimpleAnalyzer() throws Exception {
        analyzerMethod(new SimpleAnalyzer(), en);
    }
img_7c6557a55004b71081a61b335c4cc954.png

StandardAnalyzer:按英文单词及中文字符来进行分析。

 //英文分词器StandardAnalyzer测试
    @Test
    public void testStandardAnalyzer() throws Exception {
        analyzerMethod(new StandardAnalyzer(), en);
    }

对于英文StandardAnalyzer也是采取空格进行分词
下面对中文进行分词测试(对于中文他是单字分词)

 //英文分词器StandardAnalyzer测试
    @Test
    public void testStandardAnalyzer() throws Exception {
        analyzerMethod(new StandardAnalyzer(), ch);
    }
img_843db102f053e95af76237f2bfd174aa.png

PerFieldAnalyzerWrapper:

public void testPerFieldAnalyzerWrapper() throws Exception {
  Map<String, Analyzer> analyzerMap = new HashMap<>();
  analyzerMap.put("en", new SimpleAnalyzer());//使用SimpleAnalyzer分词器
  analyzerMap.put("ch", new StandardAnalyzer());//使用StandardAnalyzer
  //设置默认分词器
  PerFieldAnalyzerWrapper wrapper = new PerFieldAnalyzerWrapper(new SimpleAnalyzer(), analyzerMap);
   //会根据传入的字段名在PerFieldAnalyzerWrapper找到这个字段对应的分词器
   //如果PerFieldAnalyzerWrapper没有该字段对应的分词器就会应用默认的的分词器
   //tokenStream("content", xxxxxxxxx);根据xxxxxx来判断选择的分词器
   TokenStream tokenStream = wrapper.tokenStream("content", ch);
   tokenStream.reset();
   while (tokenStream.incrementToken()) {
     System.out.println(tokenStream);
   }
 }

中文分词:
StandardAnalyzer:单字分词,把每一个字当成一个词

//中文分词器StandardAnalyzer测试
@Test
public void testStandardAnalyzer() throws Exception {
   analyzerMethod(new StandardAnalyzer(), ch);
 }
img_4e93063055e371963d7c25ccda79d14d.png

CJKAnalyzer:二分法分词,把相临的两个字当成一个词,比如我们是中国人;我们,们是,是中,中国,国人等

//中文分词器CJKAnalyzer测试
@Test
public void testCJKAnalyzer() throws Exception {
analyzerMethod(new CJKAnalyzer(), ch);
}
img_36d290a203436911d374dac83c6eec54.png

SmartChineseAnalyzer:字典分词,也叫词库分词;把中文的词全部放置到一个词库中,按某种算法来维护词库内容;如果匹配到就切分出来成为词语。通常词库分词被认为是最理想的中文分词算法。如:“我们是中国人”,效果为:“我们”、“中国人”。(可以使用SmartChineseAnalyzer,“极易分词” MMAnalyzer ,或者是“庖丁分词”分词器、IKAnalyzer。推荐使用IKAnalyzer )

//中文分词器SmartChineseAnalyzer测试
//需要导入jar包lucene-analyzers-smartcn-4.10.4.jar
@Test
public void testSmartChineseAnalyzer() throws Exception {
   analyzerMethod(new SmartChineseAnalyzer(), ch);
  }
}
img_f2452757fc28a3d4936ba67de6610783.png

IKAnalyzer:第三方的
1.导入jar包 IKAnalyzer2012FF_u1.jar(这个包在中央仓库是没有的)支持停词和自定义拓展词
2.添加停词词典stopword.dic
3.添加拓展词典ext.dic

//中文分词器IKAnalyzer测试
//需要导入jar包IKAnalyzer2012FF_u1.jar
 @Test
 public void testIKAnalyzer() throws Exception {
   analyzerMethod(new IKAnalyzer(), ch);
 }

如果想去掉"的","了","吗".....的语气词我们可以加入配置文件
IKAnalyzer.cfg.xml和stopword.dic

img_1765beaa9d9105aa4c6fbc79e2fd2077.png

在stopword.dic文件里添加我们不需要的分词即可,这样拆分词元就不会把这些停词作为分词了

img_b1be6b4c58ef5876e7579a4f79177cf0.png

我们如果想加入一些我们自己需要的词元则需要在配置文件IKAnalyzer.cfg.xml中配置一个额外分词文件 拓展词典ext.dic
在拓展词典ext.dic中设置我们自定义的词元


img_29575289c4e4a366cc794ede77f8740f.png
img_940052e39c72b3df248838a37fd9af53.png
索引库的更新
public class CRUDTest {

    String content1 = "hello world";
    String content2 = "hello java world";
    String content3 = "hello lucene world";
    String indexPath = "luncecrud";
    Analyzer analyzer = new StandardAnalyzer();//分词器

    //创建索引
    @Test
    public void testCreateIndex() throws Exception {
        //1.创建索引写入器
        Directory d = FSDirectory.open(new File(indexPath));//索引需要存放的位置
        //创建索引写入器配置对象
        IndexWriterConfig conf = new IndexWriterConfig(Version.LUCENE_4_10_4, analyzer);
        IndexWriter writer = new IndexWriter(d, conf);
        //2.写入文档信息
        //添加文档 定义字段的存储规则
        FieldType type = new FieldType();
        type.setIndexed(true);//是否要索引
        type.setStored(true);//是否需要存储
        Document document1 = new Document();//数据库中的一条数据
        //new Field("字段名","字段内容","字段的配置属性")
        document1.add(new Field("title", "doc1", type));//该条记录中的字段 title:doc1
        document1.add(new Field("content", content1, type));//content: hello world
        writer.addDocument(document1);

        Document document2 = new Document();
        document2.add(new Field("title", "doc2", type));
        document2.add(new Field("content", content2, type));
        writer.addDocument(document2);

        Document document3 = new Document();
        document3.add(new Field("title", "doc3", type));
        document3.add(new Field("content", content3, type));
        writer.addDocument(document3);

        //需要把添加的记录保存
        writer.commit();
        writer.close();
        testSearch();
    }

    @Test
    public void testUpdate() throws Exception {
        //创建索引写入器
        Directory d = FSDirectory.open(new File(indexPath));
        IndexWriterConfig config = new IndexWriterConfig(Version.LUCENE_4_10_4, analyzer);
        IndexWriter writer = new IndexWriter(d, config);
        //更新对象
        Term term = new Term("title", "doc2");//更新的条件
        Document updateDoc = new Document();//更新之后的文档对象
        FieldType type = new FieldType();
        type.setIndexed(true);
        type.setStored(true);
        updateDoc.add(new Field("title", "doc2", type));
        updateDoc.add(new Field("content", "hello黄河之水天上来吧我要更新内容啦", type));
        writer.updateDocument(term, updateDoc);
        //提交更新内容 释放资源
        writer.commit();
        writer.close();
        testSearch();
    }

    //索引查询过程
    @Test
    public void testSearch() throws Exception {
        //1.创建索引写入器
        Directory d = FSDirectory.open(new File(indexPath));

        //打开索引目录
        IndexReader r = DirectoryReader.open(d);
        IndexSearcher searcher = new IndexSearcher(r);
        QueryParser parser = new QueryParser("content", analyzer);

        Query query = parser.parse("hello");//查询hello
        //search(查询对象,符合条件的前n条记录)
        TopDocs search = searcher.search(query, 10000);//n:前几个结果
        System.out.println("符合条件的记录有多少个:" + search.totalHits);
        ScoreDoc[] scoreDocs = search.scoreDocs;
        Document doc = null;
        for (int i = 0; i < scoreDocs.length; i++) {
            System.out.println("*******************************");
            System.out.println("分数:" + scoreDocs[i].score);//相关度的排序
            int docId = scoreDocs[i].doc;//文档编号
            Document document = searcher.doc(docId);
            System.out.println("文档编号 docId--->" + docId);
            System.out.println("标题内容 title:--->" + document.get("title"));
            System.out.println("正文内容 content:--->" + document.get("content"));
        }
    }
}

先创建一个创建索引的方法testCreateIndex()和索引查询的方法testSearch()然后创建一个索引更新的方法testUpdate();
先执行testCreateIndex()

img_975f97a64bc85fa2fd1957c66c89a551.png

在执行testUpdate();

img_91e199a8237dad980d26f669b3d94dff.png

把文档标题为doc2 的内容更新为新的内容,同时文档编号发生变化,文档编号为1的被删除,增加类文档编号3.说明更新的操作是先删除后添加

删除索引库
 @Test
    public void testDelete()throws Exception{
        //创建索引写入器
        Directory d = FSDirectory.open(new File(indexPath));
        IndexWriterConfig config = new IndexWriterConfig(Version.LUCENE_4_10_4, analyzer);
        IndexWriter writer = new IndexWriter(d, config);
        //删除记录
        /**
         * 方式一
         Term term=new Term("title","doc2");
         writer.deleteDocuments(term);
         */
        //方式二
        QueryParser parser = new QueryParser("title", analyzer);
        Query query = parser.parse("doc3");
        writer.deleteDocuments(query);

        //将删除操作提交
        writer.commit();
        writer.close();
        testSearch();
    }
img_73f17cef9b9716d581f331dc85eb44fe.png
Lucene进阶
查询所有
//索引查询过程1
public void search1(String content) throws Exception {
  //1.创建索引写入器
  Directory d = FSDirectory.open(new File(indexPath));
  //创建分词器
  Analyzer analyzer = new StandardAnalyzer();
  //打开索引目录
  IndexReader r = DirectoryReader.open(d);
  IndexSearcher searcher = new IndexSearcher(r);
  QueryParser parser = new QueryParser("content", analyzer);

  Query query = parser.parse(content);//查询hello
  //search(查询对象,符合条件的前n条记录)
  TopDocs search = searcher.search(query, 10000);//n:前几个结果
  System.out.println("符合条件的记录有多少个:" + search.totalHits);

  ScoreDoc[] scoreDocs = search.scoreDocs;
    for (int i = 0; i < scoreDocs.length; i++) {
    System.out.println("*******************************");
    System.out.println("分数:" + scoreDocs[i].score);//相关度的排序
    int docId = scoreDocs[i].doc;//文档编号
    Document document = searcher.doc(docId);
    System.out.println("文档编号 docId--->" + docId);
    System.out.println("标题内容 title:--->" + document.get("title"));
    System.out.println("正文内容 content:--->" + document.get("content"));
        }
    }


//索引查询过程2
 public void search2(Query query) throws Exception {
//1.创建索引写入器
 Directory d = FSDirectory.open(new File(indexPath));
//创建分词器
 Analyzer analyzer = new StandardAnalyzer();
  //打开索引目录
 IndexReader r = DirectoryReader.open(d);
 IndexSearcher searcher = new IndexSearcher(r);
 QueryParser parser = new QueryParser("content", analyzer);
 //search(查询对象,符合条件的前n条记录)
 TopDocs search = searcher.search(query, 10000);//n:前几个结果
 System.out.println("符合条件的记录有多少个:" + search.totalHits);

 ScoreDoc[] scoreDocs = search.scoreDocs;
 for (int i = 0; i < scoreDocs.length; i++) {
    System.out.println("*******************************");
    System.out.println("分数:" + scoreDocs[i].score);//相关度的排序
    int docId = scoreDocs[i].doc;//文档编号
    Document document = searcher.doc(docId);
    System.out.println("文档编号 docId--->" + docId);
    System.out.println("标题内容 title:--->" + document.get("title"));
    System.out.println("正文内容 content:--->" + document.get("content"));
    }
    }

@Test
public void test1() throws Exception {
 search1("*:*");//查询所有,匹配所有字段
 search2(new MatchAllDocsQuery());
 }
单词搜索
 /**
     * 单词搜索
     *
     * @throws Exception
     */
    @Test
    public void test2() throws Exception {
        //search("title:doc1"); --->public void search(String content)
        search(new TermQuery(new Term("title", "doc1")));//--->search(Query query)
    }
img_1d8530c18dad734922a6b2cf194e0652.png
段落查询
 /**
     * 段落查询
     * @throws Exception
     */
    @Test
    public void test3() throws Exception {
     // search("content:\"hello world\"");
      PhraseQuery query =new PhraseQuery();
      query.add(new Term("content","hello"));
      query.add(new Term("content","world"));
      search(query);
    }
img_8d392ab0c639271471f263a5c5ba57c2.png
通配符检索
/**
 * 通配符检索
 * @throws Exception
 */
@Test
public void test4() throws Exception {
//查询所有
//方式1
 search("l*ne");
//方式2
 search("luenc?");
//方式3
 WildcardQuery query = new WildcardQuery(new Term("content","l*ne"));
   search(query);
}
img_d5dc92733d07aa072e7701494bab389c.png

search("l**ne");中的 *表示多个字符
search("luenc?");中的?表示一个字符

单词模糊查询

Lucene支持单词容错content:lucenx ~1 表示支持单词容错一个字母,content:lucenx~N N最大值为2

@Test
public void test5() throws Exception{
search("content:lxcenX~2");
FuzzyQuery query = new FuzzyQuery(new Term("content","lucenx"),1);
search(query);
}

相似查询在关键字后面使用 ~ ( 波浪线)符号,后面可以跟一个表示相似度的数字,比如~0.85 , ~ 0.3 , ~1,值在0-1之间,1表示非常相似度最高,默认为0.5。

@Test
public void test6() throws Exception{
search("lqcenX~1");
FuzzyQuery query = new FuzzyQuery(new Term("content","lqcenX"));
search(query);
}
段落查询 (临近查询)

content:"hello world"~1 表示这个段落中间可以插入1个单词
content:"hello world"~N 表示这个段落中间可以插入N个单词

 /**
  * 段落查询 (临近查询)
  * @throws Exception
  */
 @Test
 public void test7() throws Exception{
  //~1 表示这个段落中间可以插入一个单词
  //content:\"hello world\"~N 表示这个段落中间可以插入N个单词
  //search("content:\"hello world\"~1");
   PhraseQuery query = new PhraseQuery();
   query.add(new Term("content","hello"));
   query.add(new Term("content","world"));
   query.setSlop(1);//设置中间有一个停词
   search(query);
 }
范围检索
 /**
  * 范围检索
  */
@Test
public void test8() throws Exception {
//  {:左开区间
//  }:右开区间
//  [:左闭区间
//  ]:右闭区间
//search("inputtime:{20101010 TO 20101012}");
//TermRangeQuery(查询字段,左边的值,右边的值,是否左闭区间,是否右闭区间);
  TermRangeQuery query = new TermRangeQuery("inputtime", new BytesRef("20101010"), new BytesRef("20101012"), false, false);
   search(query);
}
组合查询

AND和&&:目标-->查询出标题中包括One及内容中包括java的文档;
下面两种情况均可:
title:one && content:java
title:one AND content:java

/**
 * 组合查询AND和&&
 * @throws Exception
 */
 @Test
 public  void test9() throws Exception {
   //search("content:hello AND inputtime:{20101010 TO 20101012}");
    search("content:hello && inputtime:{20101010 TO 20101012}");
   /*
    BooleanQuery query = new BooleanQuery();
    query.add(new TermQuery(new Term("content","hello")), BooleanClause.Occur.MUST);
    query.add(new TermRangeQuery("inputtime",new BytesRef("20101010"),new BytesRef("20101012"),false,false), BooleanClause.Occur.MUST);
    search(query);
   */
 }

OR和||:查询出标题中包括One但内容中不包括java的文档;
默认情况下分词组合即为逻辑或(OR)方式。
下面三种情况均可:
title:one || content:java
title:one OR content:java
title:one content:java

/**
 * 组合查询OR和||
 * @throws Exception
 */
@Test
public  void test10() throws Exception {
//search("content:lucene OR inputtime:{20101010 TO 20101012}");
//search("content:lucene || inputtime:{20101010 TO 20101012}");
  BooleanQuery query = new BooleanQuery();
  query.add(new TermQuery(new Term("content","lucene")), BooleanClause.Occur.SHOULD);
  query.add(new TermRangeQuery("inputtime",new BytesRef("20101010"),new BytesRef("20101012"),false,false), BooleanClause.Occur.SHOULD);
  search(query);
}

Not或!:查询出标题中包括One但内容中不包括java的文档;
下面两种情况均可:
title:one ! content:java
title:one NOT content:java

/**
 * 组合查询OR和||
 * @throws Exception
 */
@Test
public  void test10() throws Exception {
  //search("content:lucene OR inputtime:{20101010 TO 20101012}");
  //search("content:lucene || inputtime:{20101010 TO 20101012}");
    BooleanQuery query = new BooleanQuery();
    query.add(new TermQuery(new Term("content","lucene")), BooleanClause.Occur.SHOULD);
    query.add(new TermRangeQuery("inputtime",new BytesRef("20101010"),new BytesRef("20101012"),false,false), BooleanClause.Occur.SHOULD);
    search(query);
}

必须包括(+)及排除(-):目标--->查询出标题中包括One但内容中不包括java的文档;
+title:one -content:title

增加权重

Luence允许我们在组合查询中,指定某一个词的相关性权重值,从而可以让得到相关性高的结果;
要提升一个词的相关性权重,则可以在关键词的后面添加^n来实现。
比如查询jakarta apache,如果要把jakarta 的相关性提高,则可以改为jakarta^4 apache
相关性权重也可以用于词组查询,比如"jakarta apache"^4 "Apache Lucene" 将把与jakarta apache词组最相关的优先排列出来;
相关性权重值默认为1,一般要提升权重时均设置为大于1的整数;该值也可以为0-1的小数,但不能为负数。

/**
 *  增加权重
 * @throws Exception
 */
@Test
public  void test12() throws Exception {
 //search("content:lucene^10 java");
   BooleanQuery query = new BooleanQuery();
   TermQuery termQuery = new TermQuery(new Term("content", "lucene"));
   termQuery.setBoost(10);//该查询对象添加权重
   query.add(termQuery, BooleanClause.Occur.SHOULD);
   query.add(new TermQuery(new Term("content","java")), BooleanClause.Occur.SHOULD);
  search(query);
}
特殊字符

由于| & ! + - ( ) 等符号在查询表达式中被用做关键字,因此要查询这些字符必须使用\来进行转义处理。
当前Lucene查询中的特殊字符:+ - && || ! ( ) { } [ ] ^ " ~ * ? :
比如,要查询包括(1+1):2 的文档,需要使用到如下表达式:
(1+1):2

分组
使用括号()对查询表示式分组Grouping
Lucene查询语法中支持通过()来对查询表达式进行分组,从而组合出各种复杂的查询。
1、查询出标题中包括one或two,但内容中不包括java的文档;
Query query=parser.parse("title:(one OR two) NOT content:java");

高亮实现

1、高亮的概述:从搜索结果中截取一部分摘要,并把符合条件的记录添加高亮显示;
高亮需要使用jar包lucene-highlighter-4.10.4.jar
2、高亮涉及的功能包括两部分:A、截取摘要,B、高亮显示

Formatter formatter = new SimpleHTMLFormatter("<font color=\"red\">","</font>");
Scorer scorer = new QueryScorer(query);
Highlighter hl = new Highlighter(formatter,scorer);
hl.setMaxDocCharsToAnalyze(20);
String str=hl.getBestFragment(new StandardAnalyzer(), "content",doc.get("content"));
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值