JavaEE进阶——Lucene学习笔记

Lucene

1.全文检索

先创建索引然后查询索引的过程叫做全文检索

索引一次创建可以多次是哟红,表现为每次每次查询速度很快

2.应用场景

站内搜索

论坛搜索、微博、文章搜索

电商搜索

淘宝搜索、京东搜索

3.Lucene实现全文检索的流程

什么是Lucene

Lucene是一个基于Java开发全文检索工具包

索引和搜索流程图
在这里插入图片描述

3.1.创建索引

3.1.1 获取文档

​ 原始文档:要给予哪些数据来进行检索,那么这些数据就是原始文档

​ 搜索引擎:使用爬虫获得原始文档

​ 站内搜索:数据库中的数据

​ 案例:直接使用io流读取磁盘上的文件

3.2.2 构建文档对象

  • ​ 对应每个原始文档创建一个Document对象

  • ​ 每个document对象中包含多个域(field)

  • ​ 域中保存就是原始文档数据

    ​ 域的名称

    ​ 域的值
    在这里插入图片描述

  • 注意:每个Document可以有多个Field,不同的Document可以有不同的Field,同一个Document可以有相同的Field(域名和域值都相同)

  • 每个文档都有一个唯一的编号,就是文档id。

3.2.3.分析文档

就是分词的过程

将原始内容创建为包含域(Field)的文档(document),需要再对域中的内容进行分析,分析的过程是经过对原始文档提取单词将字母转为小写去除标点符号去除停用词等过程生成最终的语汇单元,可以将语汇单元理解为一个一个的单词。

比如下边的文档经过分析如下:

原文档内容:

Lucene is a Java full-text search engine. Lucene is not a complete

application, but rather a code library and API that can easily be used

to add search capabilities to applications.

分析后得到的语汇单元:

lucene、java、full、search、engine。。。。

每个单词叫做一个Term,不同的域中拆分出来的相同的单词是不同的term。term中包含两部分一部分是文档的域名(Term中包含),另一部分是单词的内容。

例如:文件名中包含apache和文件内容中包含的apache是不同的term

3.2.4.创建索引

基于关键词列表创建一个索引,保存到索引库中。索引的目的是为了搜索,最终要实现只搜索被索引的语汇单元从而找到Document(文档)。

索引库中包括:

  • 索引

  • document对象

  • 关键词和文档的对应关系
    在这里插入图片描述

注意:创建索引是对语汇单元索引,通过词语找文档,这种索引的结构叫倒排索引结构

传统方法是根据文件找到该文件的内容,在文件内容中匹配搜索关键字,这种方法是顺序扫描方法,数据量大、搜索慢。

倒排索引结构是根据内容(词语)找文档,如下图:

在这里插入图片描述

倒排索引结构也叫反向索引结构,包括索引和文档两部分,索引即词汇表,它的规模较小,而文档集合较大。

3.2.查询索引

查询索引也是搜索的过程。搜索就是用户输入关键字,从索引(index)中进行搜索的过程。根据关键字搜索索引,根据索引找到对应的文档,从而找到要搜索的内容(这里指磁盘上的文件)。

3.3.1.用户查询接口

全文检索系统提供用户搜索的界面供用户提交搜索的关键字,搜索完成展示搜索结果。Lucene不提供制作用户搜索界面的功能,需要根据自己的需求开发搜索界面。

3.3.2.创建查询(构建查询对象)

用户输入查询关键字执行搜索之前需要先构建一个查询对象,查询对象中可以指定查询要搜索的Field文档域查询关键字等,查询对象会生成具体的查询语法,

例如:语法 “fileName:lucene”表示要搜索Field域的内容为“lucene”的文档

3.3.3.执行查询

搜索索引过程:

根据查询语法在倒排索引词典表中分别找出对应搜索词的索引,从而找到索引所链接的文档链表。

比如搜索语法为“fileName:lucene”表示搜索出fileName域中包含Lucene的文档。

搜索过程就是在索引上查找域为fileName,并且关键字为Lucene的term,并根据term找到文档id列表。

3.3.4.渲染结果

以一个友好的界面将查询结果展示给用户,用户根据搜索结果(文档id)找自己想要的信息,为了帮助用户很快找到自己的结果,提供了很多展示的效果,比如搜索结果中将关键字高亮显示,百度提供的快照等。

4.入门程序

4.1.环境搭建

Lucene是开发全文检索功能的工具包,从官方网站下载lucene-7.4.0,并解压。
在这里插入图片描述

官方网站:http://lucene.apache.org/

版本:lucene-7.4.0

Jdk要求:1.8以上

使用的jar包

lucene-core-7.4.0.jar

lucene-analyzers-common-7.4.0.jar

commonns-io.jar

4.2.创建索引

4.2.1.实现步骤

  1. 创建一个Director对象,指定索引库保存的地址
  2. 基于Directory对象创建一个IndexWriter对象
  3. 读取磁盘上文件,对应每个文件创建一个文档对象
  4. 向文档对象中添加域
  5. 把文档对象写入索引库
  6. 关闭indexwriter对象

代码实现

@Test
public void createIndex() throws Exception {
    // 1. 创建一个Director对象,指定索引库保存的地址
    //把索引库保存在内存中
    //Directory directory = new RAMDirectory();
    //把索引库保存在硬盘中
    Directory directory = FSDirectory.open(new File("H:\\STUDENT\\IDEA_Project_Menu\\Lucene_project\\index").toPath());
    // 2. 基于Directory对象创建一个IndexWriter对象
    //使用IKAnalyzer
    IndexWriterConfig config = new IndexWriterConfig(new IKAnalyzer());
    IndexWriter indexWriter = new IndexWriter(directory,config);
    // 3. 读取磁盘上文件,对应每个文件创建一个文档对象
    File dir = new File("H:\\STUDENT\\IDEA_Project_Menu\\Lucene_project\\searchsource");
    File[] files = dir.listFiles();  //得到一个文件列表
    for (File f : files){
        //取文件名
        String fileName = f.getName();
        //文件的路径
        String filePath =  f.getPath();
        //文件的内容
        String s = FileUtils.readFileToString(f,"UTF-8");
        //文件的大小
        Long fileSize = FileUtils.sizeOf(f);
        //创建Field
        //参数1 域的名称,2.域的内容 3,是否存储   //域类型改造
        Field fieldName = new TextField("name",fileName,Field.Store.YES);
        //Field fieldPath = new TextField("filePath",filePath, Field.Store.YES);
        Field fieldPath = new StoredField("filePath",filePath); //只存储
        Field fieldContent = new TextField("content",s, Field.Store.YES);
        //Field fieldSize = new TextField("size",fileSize+"",Field.Store.YES);
        Field fieldSizeValue = new LongPoint("size",fileSize);   //只存值
        Field fieldSizeStore = new StoredField("size",fileSize); //只存储
        //创建文档对象
        Document document = new Document();
        //4.向文档对象中添加域
        document.add(fieldName);
        document.add(fieldPath);
        document.add(fieldSizeValue);
        document.add(fieldSizeStore);
        document.add(fieldContent);
        // 5. 把文档对象写入索引库
        indexWriter.addDocument(document);
    }
    // 6. 关闭indexwriter对象
    indexWriter.close();
}

4.2.2.使用Luke工具查看索引文件

在这里插入图片描述

我们使用的luke的版本是luke-7.4.0,跟lucene的版本对应的。可以打开7.4.0版本的lucene创建的索引库。需要注意的是此版本的Luke是jdk9编译的,所以要想运行此工具还需要jdk9才可以。

4.3.查询索引

4.3.1.实现步骤

  1. 创建一个Directory对象,指定索引库的位置
  2. 创建一个indexReader对象,需要指定Directory对象
  3. 创建一个indexsearcher对象,需要指定IndexReader对象
  4. 创建一个Query对象,TermQuery,指定查询的域和查询的关键词。
  5. 执行查询,得到一个查询结果TopDocs对象
  6. 取查询结果的总记录数
  7. 取文档列表
  8. 打印文档中内容
  9. 关闭IndexReader对象

查找接口的实现类:ctrl + alt +B

查看类或接口的继承关系:ctrl + h

代码实现

@Test
public void searchIndex() throws Exception{
    //1. 创建一个Directory对象,指定索引库的位置
    Directory directory = FSDirectory.open(new File("H:\\STUDENT\\IDEA_Project_Menu\\Lucene_project\\index").toPath());
    //2. 创建一个indexReader对象,需要指定Directory对象
    IndexReader indexReader = DirectoryReader.open(directory);
    //3. 创建一个indexsearcher对象,需要指定IndexReader对象
    IndexSearcher indexSearcher = new IndexSearcher(indexReader);
    //4. 创建一个Query对象,TermQuery,指定查询的域和查询的关键词。
    Query query = new TermQuery(new Term("name","apache"));
    //5. 执行查询,得到一个查询结果TopDocs对象
    //参数1、查询对象 2、查询结果返回的最大记录数
    TopDocs topDocs =  indexSearcher.search(query,10);
    //6. 取查询结果的总记录数
    System.out.println("查询的总记录数"+topDocs.totalHits);
    //7. 取文档列表
    ScoreDoc[] scoreDocs = topDocs.scoreDocs;
    //8. 打印文档中内容
    for (ScoreDoc doc: scoreDocs){
        //取文档id
        int docId = doc.doc;
        //根据id取文档对象
        Document document = indexSearcher.doc(docId);
        System.out.println(document.get("name"));
        System.out.println(document.get("filePath"));
        System.out.println(document.get("size"));
        System.out.println(document.get("content"));
        System.out.println("--------------------------");
    }
    //9. 关闭IndexReader对象
    indexReader.close();
}

5.分析器

默认使用的是标准分析器strandardAnalyzer

5.1.查看默认标准分析器的分析效果

使用Analyzer对象的tokenstram方法返回一个TokenStream对象,词对象中包含了最终的分词结果

5.1.1.实现步骤

  • 创建一个Analyzer对象,strandardAnalyzer对象

  • 使用分析器对象的tokenStream方法获取一个TokenStream对象

  • 向TokenStream对象中设置一个引用。相当于是一个指针

  • 调用TokenStream对象的rest方法,如果不调用则抛异常

  • 使用while循环遍历TokenStream对象

  • 关闭TokenStream对象

    /**
     * 查看分析器的效果
     * @throws Exception
     */
    @Test
    public void testTokenStream() throws Exception{
        //- 创建一个Analyzer对象,strandardAnalyzer对象
        //Analyzer analyzer = new StandardAnalyzer();
        Analyzer analyzer = new IKAnalyzer();
        //- 使用分析器对象的tokenStream方法获取一个TokenStream对象
        TokenStream tokenStream =  analyzer.tokenStream("test","Lucene是apache软件基金会4 jakarta项目组的一个子项目,是一个开放源代码的全文检索引擎工具包,但它不是一个完整的全文检索引擎,而是一个全文检索引擎的架构");
        //- 向TokenStream对象中设置一个引用。相当于是一个指针
        CharTermAttribute charTermAttribute = tokenStream.addAttribute(CharTermAttribute.class);
        //- 调用TokenStream对象的rest方法,如果不调用则抛异常
        tokenStream.reset();
        //- 使用while循环遍历TokenStream对象
        while (tokenStream.incrementToken()){
            System.out.println(charTermAttribute.toString());
        }
        //- 关闭TokenStream对象
        tokenStream.close();
    }
    

5.1.2.标准分析器

StandardAnalyzer:

单字分词:就是按照中文一个字一个字地进行分词。如:“我爱中国”,
效果:“我”、“爱”、“中”、“国”。

SmartChineseAnalyzer

对中文支持较好,但扩展性差,扩展词库,禁用词库和同义词库等不好处理

5.2.中文分析器

5.2.1.使用步骤

  • 把IKAnalyzer的jar包添加到工程中
  • 把配置文件和扩展文件添加到工程的classpath下

注意:扩展词典严禁使用Windows记事本编辑,保证扩展词典的编码格式是utf-8

扩展词典:添加一些新词

停用词词典:无意义的词或者敏感词

5.2.2.使用自定义分析器

@Test
public void addDocument() throws Exception {
    //索引库存放路径
    Directory directory = FSDirectory.open(new File("D:\\temp\\index").toPath());
    IndexWriterConfig config = new IndexWriterConfig(new IKAnalyzer());
    //创建一个indexwriter对象
    IndexWriter indexWriter = new IndexWriter(directory, config);
	//...
}

6.索引库维护

6.1.索引库的添加

6.1.1.Field域的属性

是否分析:是否对域的内容进行分词处理。前提是我们要对域的内容进行查询。

是否索引:将Field分析后的词或整个Field值进行索引,只有索引方可搜索到。

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

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

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

是否存储的标准:是否要将内容展示给用户

Field类数据类型Analyzed 是否分析Indexed 是否索引Stored 是否存储说明
StringField(FieldName, FieldValue,Store.YES))字符串NYY或N这个Field用来构建一个字符串Field,但是不会进行分析,会将整个串存储在索引中,比如(订单号,姓名等) 是否存储在文档中用Store.YES或Store.NO决定
LongPoint(String name, long… point)Long型YYN可以使用LongPoint、IntPoint等类型存储数值类型的数据。让数值类型可以进行索引。但是不能存储数据,如果想存储数据还需要使用StoredField。
StoredField(FieldName, FieldValue)重载方法,支持多种类型NNY这个Field用来构建不同类型Field 不分析,不索引,但要Field存储在文档中
TextField(FieldName, FieldValue, Store.NO) 或 TextField(FieldName, reader)字符串 或 流YYY或N如果是一个Reader, lucene猜测内容比较多,会采用Unstored的策略.

6.1.2.索引库的添加

/**
 * 新添加的文档
 * @throws Exception
 */
@Test
public void addDocument() throws Exception{
    //创建一个IndexWriter对象,需要使用IKAnalyzer作为分析器
    /*IndexWriter indexWriter = new IndexWriter(
            FSDirectory.open(new File("H:\\STUDENT\\IDEA_Project_Menu\\Lucene_project\\index").toPath()),
            new IndexWriterConfig(new IKAnalyzer())
            );*/
    //创建一个Document对象
    Document document = new Document();
    //向document对象中添加域
    document.add(new TextField("name","新添加的文件", Field.Store.YES));
    document.add(new TextField("content","新添加文件的内容",Field.Store.NO));
    document.add(new StoredField("path","H:\\STUDENT\\IDEA_Project_Menu\\Lucene_project\\hello"));
    //把文档写入索引库
    indexWriter.addDocument(document);
    //关闭索引库
    indexWriter.close();
}

6.2索引库的删除

6.2.1.删除全部

@Test
public void deleteAllDocument() throws Exception{
    //删除全部文档
    indexWriter.deleteAll();
    //关闭索引库
    indexWriter.close();
}

6.2.2.指定查询条件删除

@Test
public void deleteDocumentByQuery() throws Exception{
    indexWriter.deleteDocuments(new Term("name","apache"));
    indexWriter.close();
}

6.3.索引库的修改

@Test
public void updateDocument() throws  Exception{
    //创建一个新的文档对象
    Document document = new Document();
    //向文档对象中添加域
    document.add(new TextField("name","这是更新够的文档",Field.Store.YES));
    document.add(new TextField("name1","这是更新够的文档1",Field.Store.YES));
    document.add(new TextField("name2","这是更新够的文档2",Field.Store.YES));
    //更新操作
    indexWriter.updateDocument(new Term("name","spring"),document);
    //关闭索引
    indexWriter.close();
}

更新的原理就是先删除然后再添加

7.Lucene索引库查询

7.1.使用Query子类查询

7.1.1.TermQuery

根据关键词进行查询

需要指定查询的域以及要查询的关键词

7.1.2.数据范围查询

/**
 * 范围区间查询
 * @throws Exception
 */
@Test
public void testRangeQuery() throws Exception{
    //创建一个Query对象
    Query query = LongPoint.newRangeQuery("size",0l,100l);
    //执行查询
    printResult(query);

}
private void printResult(Query query) throws  Exception{
    TopDocs topDocs = indexSearcher.search(query,10);
    System.out.println("命中数据:"+topDocs.totalHits);
    ScoreDoc[] scoreDocs = topDocs.scoreDocs;
    for (ScoreDoc doc : scoreDocs){
        //取文档id
        int docId = doc.doc;
        //根据id取文档对象
        Document document = indexSearcher.doc(docId);
        System.out.println(document.get("name"));
        System.out.println(document.get("filePath"));
        System.out.println(document.get("size"));
        System.out.println(document.get("content"));
        System.out.println("--------------------------");
    }
    indexReader.close();
}

7.2.使用QueryPaser进行查询

可以对要查询的语句内容先分词,根据分词结果进行查询

通过QueryParser也可以创建Query,QueryParser提供一个Parse方法,此方法可以直接根据查询语法来查询。Query对象执行的查询语法可通过System.out.println(query);查询。

需要使用到分析器。建议创建索引时使用的分析器和查询索引时使用的分析器要一致。

需要加入queryParser依赖的jar包:lucene-queryparser-7.4.0.jar

/**
 * 执行QueryPaser对象查询
 */
@Test
public void testQueryParser() throws Exception{
    //创建一个QueryPaser对象 两个参数
     //参数1:默认搜索域,2.分析器对象
    QueryParser queryParser = new QueryParser("name",new IKAnalyzer());
    //使用QueryPaser对象创建一个Query对象
    Query query = queryParser.parse("Lucene是apache软件基金会4 jakarta项目组的一个子项目,是一个开放源代码的全文检索引擎工具包");
    //执行查询
    printResult(query);
}
private void printResult(Query query) throws  Exception{
        TopDocs topDocs = indexSearcher.search(query,10);
        System.out.println("命中数据:"+topDocs.totalHits);
        ScoreDoc[] scoreDocs = topDocs.scoreDocs;
        for (ScoreDoc doc : scoreDocs){
            //取文档id
            int docId = doc.doc;
            //根据id取文档对象
            Document document = indexSearcher.doc(docId);
            System.out.println(document.get("name"));
            System.out.println(document.get("filePath"));
            System.out.println(document.get("size"));
            System.out.println(document.get("content"));
            System.out.println("--------------------------");
        }
        indexReader.close();
 }
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值