Lucene简介

Lucene全文检索技术简介

  1. Lucene简介

作者:doug cutting

Lucene是apache下的一个全文检索引擎工具包,底层封装的是一个全文检索算法。因为在大并发和大数据量的访问下,我们在传统数据库中进行like模糊查找会很慢,因为like分装的是顺序扫描法,所以在这个时候,面对这个问题就产生了全文检索算法,而Lucene就是这个算法的一个工具包。

  1. 算法介绍;

        Sql语句中的like使用的是顺序扫描法:

  1. 顺序扫描算法

        将需要查询的文本或者是数据,从头开始检索,直到找到需要查询的关键字为止

        优点:准确度高

        缺点:效率会随着数据量的提高越来越慢

  1. 全文检索算法

        是在查询之前。需要先将需要查询的内容中的词提取出来(切分词—就是将一句话中的一个一个的词提取出来,去掉停用词,去掉标点符号,去掉空格,大写字母转换成小写字母)组成索引查询的时候,需要先查询索引,根据索引找文档,这样的算法叫做全文检索方法

        优点:查询速度快,而且不会随着数据量的增大而使查询速度变慢

        缺点:此算法是空间换时间,也就是索引会额外占用更多的磁盘空间,但是速度会变快。

  1. Lucene的实现过程

在这里我们可以吧一本书当成一个文档,文档中包括一个一个的域,域中存的是这本书的信息,域是以Key——value的 存在的,我们会把域中的对应的描述分为若干个词语,在分词的过程中建立索引,并生成权量(表示匹配的程度),

Lucene简介

Lucene简介

 

 

  1. Lucene基于java的索引创建

import java.io.File;

import java.util.ArrayList;

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.Store;

import org.apache.lucene.document.TextField;

import org.apache.lucene.index.IndexWriter;

import org.apache.lucene.index.IndexWriterConfig;

import org.apache.lucene.store.Directory;

import org.apache.lucene.store.FSDirectory;

import org.apache.lucene.util.Version;

import org.junit.Test;

//创建索引

public class CreatIndex {

    @Test

    public void testCreateIndex() throws Exception{

        //1:采集数据

        Dao dao=new DaoImpl();

        List<Book> list =dao.queryBookList();

        

        //2.创建Document文当对象

        List<Document> documents=new ArrayList<>();

        for (Book book : list) {

            Document document=new Document();

            // Document文档中添加Field

            // Store.YES:表示存储到文档域中

            document.add(new TextField("id", book.getId().toString(), Store.YES));

            // 图书名称

            document.add(new TextField("name", book.getName().toString(), Store.YES));

            // 图书价格

            document.add(new TextField("price", book.getPrice().toString(), Store.YES));

            // 图书图片地址

            document.add(new TextField("pic", book.getPic().toString(), Store.YES));

            // 图书描述

            document.add(new TextField("desc", book.getDesc().toString(), Store.YES));

            

            // Document放到list

            documents.add(document);

        }

        //3.创建Analyzer分词器,分析文档,对文档进行分词

        Analyzer analyzer = new StandardAnalyzer();

        //4.创建Directory对象,声明索引库的位置

        Directory directory = FSDirectory.open(new File("D:/luceneindex"));

        

        //5.创建IndexWriteConfig对象,写入索引需要的配置 需要版本 分词器

        IndexWriterConfig config = new IndexWriterConfig(Version.LUCENE_4_10_3, analyzer);

        

        // 6.创建IndexWriter写入对象

        IndexWriter indexWriter = new IndexWriter(directory, config);

        // 7.写入到索引库,通过IndexWriter添加文档对象document

        for (Document doc : documents) {

            //

            indexWriter.addDocument(doc);

        }

        // 8.释放资源

        indexWriter.close();

    }

}

 

  1. Lucene基于java的索引查询

import java.io.File;

import org.apache.lucene.analysis.Analyzer;

import org.apache.lucene.analysis.standard.StandardAnalyzer;

import org.apache.lucene.document.Document;

import org.apache.lucene.index.DirectoryReader;

import org.apache.lucene.index.IndexReader;

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.junit.Test;

 

public class findIndex {

    @Test

    public void findIndexTest() throws Exception {

        //1.创建query搜索对象

        

        //创建分词器

        Analyzer analyzer=new StandardAnalyzer();

        //创建搜索解析器。第一个参数:默认field域,第二个参数:分词器

        QueryParser    queryParser=new QueryParser("name",analyzer );

        //创建搜索对象

        Query query =queryParser.parse("name:java AND 思想");

        

        //2.创建Directory流对象,声明索引库位置

        Directory directory=FSDirectory.open(new File("D:/luceneindex"));

        

        //3.创建索引读取对象indexReader

        IndexReader reader=DirectoryReader.open(directory);

        

        // 4. 创建索引搜索对象

        IndexSearcher searcher = new IndexSearcher(reader);

 

        // 5. 使用索引搜索对象,执行搜索,返回结果集TopDocs

        // 第一个参数:搜索对象,第二个参数:返回的数据条数,指定查询结果最顶部的n条数据返回

        TopDocs topDocs = searcher.search(query, 10);

        System.out.println("查询到的数据总条数是:" + topDocs.totalHits);

        // 获取查询结果集

        ScoreDoc[] docs = topDocs.scoreDocs;

        // 6. 解析结果集

        for (ScoreDoc scoreDoc : docs) {

            // 获取文档

            int docID = scoreDoc.doc;

            Document doc = searcher.doc(docID);

            System.out.println("=============================");

            System.out.println("docID:" + docID);

            System.out.println("bookId:" + doc.get("id"));

            System.out.println("name:" + doc.get("name"));

            System.out.println("price:" + doc.get("price"));

            System.out.println("pic:" + doc.get("pic"));

            System.out.println("desc:" + doc.get("desc"));

        }

        // 7. 释放资源

        reader.close();

    }

}

 

  1. Field域的属性

    Field是文档中的域,包括Field名和Field值两部分,一个文档可以包括多个Field,Document只是Field的一个承载体,Field值即为要索引的内容,也是要搜索的内容。

     

    1. 是否分词(tokenized)

    是:作分词处理,即将Field值进行分词,分词的目的是为了索引

     

    比如:商品名称、商品描述等,这些内容用户要输入关键字搜索,由于搜索的内容格式大、内容多需要分词后将语汇单元建立索引

     

    否:不作分词处理

    比如:商品id、订单号、身份证号等

    1. 是否索引(indexed)

    是:进行索引。将Field分词后的词或整个Field值进行索引,存储到索引域,索引的目的是为了搜索

     

    比如:商品名称、商品描述分析后进行索引,订单号、身份证号不用分词但也要索引,这些将来都要作为查询条件。

     

    否:不索引。

    比如:图片路径、文件路径等,不用作为查询条件的不用索引。

    1. 是否存储(stored)

    是:将Field值存储在文档域中,存储在文档域中的Field才可以从Document中获取。

     

    比如:商品名称、订单号,凡是将来要从Document中获取的Field都要存储。

     

    否:不存储Field值

    比如:商品描述,内容较大不用存储。如果要向用户展示商品描述可以从系统的关系数据库中获取。

 

  1. Field常用类型

下边列出了开发中常用 Filed类型,注意Field的属性,根据需求选择:

Field

数据类型

Analyzed

是否分词

Indexed

是否索引

Stored

是否存储

说明

StringField(FieldName, FieldValue,Store.YES))

字符串

N

Y

YN

这个Field用来构建一个字符串Field,但是不会进行分词,会将整个串存储在索引中,比如(订单号,身份证号等)

是否存储在文档中用Store.YESStore.NO决定

LongField(FieldName, FieldValue,Store.YES)

Long

Y

Y

YN

这个Field用来构建一个Long数字型Field,进行分词和索引,比如(价格)

是否存储在文档中用Store.YESStore.NO决定

StoredField(FieldName, FieldValue) 

重载方法,支持多种类型

N

N

Y

这个Field用来构建不同类型Field

不分析,不索引,但要Field存储在文档中

TextField(FieldName, FieldValue, Store.NO)

TextField(FieldName, reader)

字符串

Y

Y

YN

如果是一个Reader, lucene猜测内容比较多,会采用Unstored的策略.

  1. 对之前编写的CreatIndex ()方法进行修改。

代码片段

// Document文档中添加域

// 图书Id

// Store.YES:表示存储到文档域中

// 不分词,不索引,储存

document.add(new StoredField("id", book.getId().toString()));

// 图书名称

// 分词,索引,储存

document.add(new TextField("name", book.getName().toString(), Store.YES));

// 图书价格

// 分词,索引,储存

document.add(new FloatField("price", book.getPrice(), Store.YES));

// 图书图片地址

// 不分词,不索引,储存

document.add(new StoredField("pic", book.getPic().toString()));

// 图书描述

// 分词,索引,不储存

document.add(new TextField("desc", book.getDesc().toString(), Store.NO));

 

  1. 索引的增删改

    1. 添加索引

调用 indexWriter.addDocumentdoc)添加索引。

参考入门程序的创建索引。

  1. 删除索引

    1. 删除指定索引

根据Term删除索引,满足条件的将全部删除。

@Test

public void testIndexDelete() throws Exception {

    // 创建Directory流对象

    Directory directory = FSDirectory.open(new File("C:/itcast/lucene/index"));

    IndexWriterConfig config = new IndexWriterConfig(Version.LUCENE_4_10_3, null);

    // 创建写入对象

    IndexWriter indexWriter = new IndexWriter(directory, config);

 

    // 根据Term删除索引库,name:java

    indexWriter.deleteDocuments(new Term("name", "java"));

    

    // 释放资源

    indexWriter.close();

}

 

效果如下图:索引域没有变化

Lucene简介

 

文档域数据被删除掉

Lucene简介

Lucene简介

 

 

  1. 删除全部索引(慎用)

将索引目录的索引信息全部删除,直接彻底删除,无法恢复

 

建议参照关系数据库基于主键删除方式,所以在创建索引时需要创建一个主键Field,删除时根据此主键Field删除。

索引删除后将放在Lucene的回收站中,Lucene3.X版本可以恢复删除的文档,3.X之后无法恢复。

代码:

@Test

public void testIndexDelete() throws Exception {

    // 创建Directory流对象

    Directory directory = FSDirectory.open(new File("D:/itcast/lucene/index"));

    IndexWriterConfig config = new IndexWriterConfig(Version.LUCENE_4_10_3, null);

    // 创建写入对象

    IndexWriter indexWriter = new IndexWriter(directory, config);

 

    // 根据Term删除索引库,name:java

    // indexWriter.deleteDocuments(new Term("name", "java"));

    // 全部删除

    indexWriter.deleteAll();

 

    // 释放资源

    indexWriter.close();

}

 

索引域数据清空

Lucene简介

 

 

文档域数据也清空

Lucene简介

 

 

  1. 修改索引

更新索引是先删除再添加,建议对更新需求采用此方法并且要保证对已存在的索引执行更新,可以先查询出来,确定更新记录存在执行更新操作。

 

如果更新索引的目标文档对象不存在,则执行添加。

代码

@Test

public void testIndexUpdate() throws Exception {

    // 创建分词器

    Analyzer analyzer = new IKAnalyzer();

    // 创建Directory流对象

    Directory directory = FSDirectory.open(new File("C:/itcast/lucene/index"));

    IndexWriterConfig config = new IndexWriterConfig(Version.LUCENE_4_10_3, analyzer);

    // 创建写入对象

    IndexWriter indexWriter = new IndexWriter(directory, config);

 

    // 创建Document

    Document document = new Document();

    document.add(new TextField("id", "1002", Store.YES));

    document.add(new TextField("name", "lucene测试test 002", Store.YES));

 

    // 执行更新,会把所有符合条件的Document删除,再新增。

    indexWriter.updateDocument(new Term("name", "test"), document);

 

    // 释放资源

    indexWriter.close();

}

 

  1. 搜索

    1. 创建查询的两种方法

对要搜索的信息创建Query查询对象,Lucene会根据Query查询对象生成最终的查询语法。类似关系数据库Sql语法一样,Lucene也有自己的查询语法,比如:"name:lucene"表示查询名字为nameField域中的"lucene"的文档信息。

    可通过两种方法创建查询对象:

    1)使用Lucene提供Query子类

    Query是一个抽象类,lucene提供了很多查询对象,比如TermQuery项精确查询,NumericRangeQuery数字范围查询等。

    如下代码:

    Query query = new TermQuery(new Term("name", "lucene"));

 

2)使用QueryParse解析查询表达式

    QueryParser会将用户输入的查询表达式解析成Query对象实例。

    如下代码:

    QueryParser queryParser = new QueryParser("name", new IKAnalyzer());

    Query query = queryParser.parse("name:lucene");

 

  1. 通过Query子类搜索

    1. TermQuery

TermQuery词项查询,TermQuery不使用分析器,搜索关键词进行精确匹配Field域中的词,比如订单号、分类ID号等。

 

搜索对象创建:

@Test

public void testSearchTermQuery() throws Exception {

    // 创建TermQuery搜索对象

    Query query = new TermQuery(new Term("name", "lucene"));

 

    doSearch(query);

}

 

 

抽取搜索逻辑:

private void doSearch(Query query) throws IOException {

    // 2. 执行搜索,返回结果集

    // 创建Directory流对象

    Directory directory = FSDirectory.open(new File("D:/itcast/lucene/index"));

 

    // 创建索引读取对象IndexReader

    IndexReader reader = DirectoryReader.open(directory);

 

    // 创建索引搜索对象

    IndexSearcher searcher = new IndexSearcher(reader);

 

    // 使用索引搜索对象,执行搜索,返回结果集TopDocs

    // 第一个参数:搜索对象,第二个参数:返回的数据条数,指定查询结果最顶部的n条数据返回

    TopDocs topDocs = searcher.search(query, 10);

 

    System.out.println("查询到的数据总条数是:" + topDocs.totalHits);

 

    // 获取查询结果集

    ScoreDoc[] docs = topDocs.scoreDocs;

 

    // 解析结果集

    for (ScoreDoc scoreDoc : docs) {

        // 获取文档id

        int docID = scoreDoc.doc;

        Document doc = searcher.doc(docID);

 

        System.out.println("======================================");

 

        System.out.println("docID:" + docID);

        System.out.println("bookId:" + doc.get("id"));

        System.out.println("name:" + doc.get("name"));

        System.out.println("price:" + doc.get("price"));

        System.out.println("pic:" + doc.get("pic"));

        // System.out.println("desc:" + doc.get("desc"));

    }

    // 3. 释放资源

    reader.close();

}

 

  1. NumericRangeQuery

NumericRangeQuery,指定数字范围查询.

@Test

public void testSearchNumericRangeQuery() throws Exception {

    // 创建NumericRangeQuery搜索对象,数字范围查询.

    // 个参数分别是:域名、最小值、最大值、是否包含最小值,是否包含最大值

    Query query = NumericRangeQuery.newFloatRange("price", 54f, 56f, false, true);

    doSearch(query);

}

 

  1. BooleanQuery

BooleanQuery,布尔查询,实现组合条件查询。

@Test

public void testSearchBooleanQuery() throws Exception {

    // 创建TermQuery搜索对象

    Query query1 = new TermQuery(new Term("name", "lucene"));

    // 创建NumericRangeQuery搜索对象,数字范围查询.

    // 四个参数分别是:域名、最小值、最大值、是否包含最小值,是否包含最大值

    Query query2 = NumericRangeQuery.newFloatRange("price", 54f, 66f, false, true);

 

    // 创建BooleanQuery搜索对象,组合查询条件

    BooleanQuery boolQuery = new BooleanQuery();

 

    // 组合条件,

    // 第一个参数,查询条件,第二个参数,组合方式

    boolQuery.add(query1, Occur.MUST_NOT);

    boolQuery.add(query2, Occur.MUST);

 

    doSearch(boolQuery);

}

 

组合关系代表的意思如下:

1MUSTMUST表示"与"的关系,即"交集"。

2MUSTMUST_NOT前者包含后者不包含。

3MUST_NOTMUST_NOT没意义

4SHOULDMUST表示MUSTSHOULD失去意义;

5SHOULDMUST_NOT相当于MUSTMUST_NOT

6SHOULDSHOULD表示"或"的关系,即"并集"。

  1. 通过QueryParser搜索

通过QueryParser也可以创建QueryQueryParser提供一个Parse方法,此方法可以直接根据查询语法来查询。可以通过打印Query对象的方式,查看生成的查询语句。

    

  1. 查询语法

    1、基础的查询语法,关键词查询:

    域名+":"+搜索的关键字

    例如:name:java

  2. 范围查询

    域名+":"+[最小值 TO 最大值]

    例如:size:[1 TO 1000]

    注意:QueryParser不支持对数字范围的搜索,它支持字符串范围。数字范围搜索建议使用NumericRangeQuery

  3. 组合条件查询

Occur.MUST 查询条件必须满足,相当于AND

+(加号)

Occur.SHOULD 查询条件可选,相当于OR

空(不用符号)

Occur.MUST_NOT 查询条件不能满足,相当于NOT

-(减号)

 

  1. QueryParser

@Test

public void testSearchIndex() throws Exception {

    // 创建分词器

    Analyzer analyzer = new StandardAnalyzer();

    // 1. 创建Query搜索对象

    // 创建搜索解析器,第一个参数:默认Field域,第二个参数:分词器

    QueryParser queryParser = new QueryParser("desc", analyzer);

 

    // 创建搜索对象

    // Query query = queryParser.parse("desc:java学习");

    Query query = queryParser.parse("desc:java AND lucene");

 

    // 打印生成的搜索语句

    System.out.println(query);

    // 执行搜索

    doSearch(query);

}

 

  1. MultiFieldQueryParser

通过MultiFieldQueryParse对多个域查询。

@Test

public void testSearchMultiFieldQueryParser() throws Exception {

    // 创建分词器

    Analyzer analyzer = new IKAnalyzer();

    // 1. 创建MultiFieldQueryParser搜索对象

    String[] fields = { "name", "desc" };

    MultiFieldQueryParser multiFieldQueryParser = new MultiFieldQueryParser(fields, analyzer);

    // 创建搜索对象

    Query query = multiFieldQueryParser.parse("lucene");

 

    // 打印生成的搜索语句

    System.out.println(query);

    // 执行搜索

    doSearch(query);

}

 

生成的查询语句:

name:lucene desc:lucene

 

 

 

  1. TopDocs

Lucene搜索结果可通过TopDocs遍历,TopDocs类提供了少量的属性,如下:

 

方法或属性

说明

totalHits

匹配搜索条件的总记录数

scoreDocs

顶部匹配记录

注意:

Search方法需要指定匹配记录数量nindexSearcher.search(query, n)

TopDocs.totalHits:是匹配索引库中所有记录的数量

TopDocs.scoreDocs:匹配相关度高的前边记录数组,scoreDocs的长度小于等于search方法指定的参数n

 

  1. 替换分词器

    1. 使用IKAnalyzer分词器

    导入IKAnalyzer2012FF_u1.jar

    把分词对象替换为

Lucene简介

  1. 自定义停用词和扩展词

    拷贝文件到资源文件夹

    Lucene简介

注意:不要用window自带的记事本保存扩展词文件和停用词文件,那样的话,格式中是含有bom的。

IKAnalyzer.cfg.xml配置文件

<?xml version="1.0" encoding="UTF-8"?>

<!DOCTYPE properties SYSTEM "http://java.sun.com/dtd/properties.dtd">

<properties>

    <comment>IK Analyzer 扩展配置</comment>

    <!--用户可以在这里配置自己的扩展字典     -->

    <entry key="ext_dict">ext.dic;</entry>

 

    <!--用户可以在这里配置自己的扩展停止词字典-->

    <entry key="ext_stopwords">stopword.dic;</entry>

</properties>

Lucene简介

Lucene简介