欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

Lucene-----信息检索技术

程序员文章站 2022-05-03 22:49:55
1 信息检索概述 1.1 传统检索方式的缺点 • 文件检索 操作系统常见的是硬盘文件检索 文档检索:整个文档打开时已经加载到内存了; 缺点:全盘遍历,慢,内存的海量数据 • 数据库检索 like "%三星%" 全表遍历; like "三星%" 最左特性 不会全表遍历; 无法满足海量数据下准确迅速的定 ......

1 信息检索概述

1.1 传统检索方式的缺点

    • 文件检索
        操作系统常见的是硬盘文件检索
        文档检索:整个文档打开时已经加载到内存了;
        缺点:全盘遍历,慢,内存的海量数据
    • 数据库检索
        like "%三星%" 全表遍历;
        like "三星%" 最左特性 不会全表遍历;
        
        无法满足海量数据下准确迅速的定位
        mysql 单表数据量---千万级
        oracle 单表数据量---亿级
  Lucene-----信息检索技术

  总结:传统的方式无法满足检索的需求(迅速,准确,海量)

 

 

2 全文检索技术(大型互联网公司的搜索功能都是全文检索)

2.1 定义:

 

  • 在海量的信息中,通过固定的数据结构引入索引文件,利用对索引文件的处理实现对应数据的快速定位等功能的技术;
  • 信息检索系统(全文检索技术的应用,搜索引擎百度,google) 
  • 信息采集:通过爬虫技术,将公网的海量非结构化数据爬去到本地的分布式存储系统进行存储  
  • 信息整理:非结构化数据无法直接提供使用,需要整理,整理成索引文件
  • 信息查询:通过建立一个搜索的应用,提供用户的入口进行查询操作,利用查询条件搜索索引文件中的有效数据;

 

 2.2结构

Lucene-----信息检索技术

问题:非结构化数据,海量数据如何整理成有结构的索引文件(索引文件到底什么结构)?

2.3 倒排索引

       索引文件,是全文检索技术的核心内容,创建索引,搜索索引也是核心,搜索在创建之后的;
      如何将海量数据计算输出成有结构的索引文件,需要严格规范的计算逻辑-----倒排索引的计算
    
      以网页为例:
      假设爬虫系统爬去公网海量网页(2条);利用倒排索引的计算逻辑,将这2个非结构化的网页信息数据整理成索引文件;
    
      源数据: 标题,时间,作者,留言,内容
      网页1(id=1): 王思聪的ig战队获得lol世界冠军,结束长达8年的遗憾
      网页2(id=2): 王思聪又换女朋友了吗?嗯,天天换.
    
      倒排索引的第一步:计算分词(数据内容)
          分词:将数据字符串进行切分,形成最小意义的词语  (不同语言底层实现是不一样的)
          并且每个分词计算的词语都会携带计算过程中的一些参数
          词语(来源的网页id,当前网页中该词语出现的频率,出现的位置)
    
      网页1: 王思聪(1,1,1),ig(1,1,2),战队(1,1,3), lol(1,1,4) 世界(1,1,5)
      网页2: 王思聪(2,1,1),女朋友(2,1,1),天天(2,1,1);
    
      倒排索引第二步:合并分词结果
        合并结果:王思聪([1,2],[1,1],[1,1]),ig(1,1,2),战队(1,1,3), lol(1,1,4) 世界(1,1,5),女朋友(2,1,1),天天(2,1,1);
        合并逻辑:所有的网页的分词计算结果一定有重复的分词词汇,合并后所有参数也一起合并,结果形成了一批索引结构的数据;
      倒排索引第三步:源数据整理document对象

      document是索引文件中的文档对象,最小的数据单位(数据库中的一行数据)每个document对应一个网页

     倒排索引第四步:形成索引文件
        将网页的数据对象(document)和分词合并结果(index)一起存储到存储位置,形成整体的索引文件

  索引文件结构:

    数据对象

    合并分词结果

  Lucene-----信息检索技术

  对索引文件中的分词合并后的数据进行复杂的计算处理,获取我们想要的数据集合(document的集合)

3 lucene

3.1介绍

  是一个全文检索引擎工具包,hadoop的创始人doug cutting开发,2000年开始,每周花费2天,完成了lucene的第一个版本;引起搜索界的巨大轰动; java开发的工具包;

3.2 特点

  •   稳定,索引性能高 (创建和搜索的性能)
  •   现代磁盘每小时能索引150g数据(读写中)
  •   对内存要求1mb栈内存
  •   增量索引和批量索引速度一样快
  •   索引的数据占整体索引文件20%
  •   支持多种主流搜索功能.

3.3分词代码测试

  准备依赖的jar包(lucene6.0)

  

    <!-- lucene查询扩展转化器 -->
        <dependency>
            <groupid>org.apache.lucene</groupid>
            <artifactid>lucene-queryparser</artifactid>
               <version>6.0.0</version>
        </dependency>
        <!-- lucene自带的智能中文分词器 -->
        <dependency>
            <groupid>org.apache.lucene</groupid>
            <artifactid>lucene-analyzers-smartcn</artifactid>
            <version>6.0.0</version>
        </dependency>
        <!-- lucene核心功能包 -->
        <dependency>
            <groupid>org.apache.lucene</groupid>
            <artifactid>lucene-core</artifactid>
            <version>6.0.0</version>
        </dependency>

 

 

 

  lucene分词测试

  索引的查询都是基于分词的计算结果完成的,这种计算分词的过程叫做词条化,得到的每一个词汇称之为词项,lucene提供抽象类analyzer表示分词器对象,不同的实现类来自不同的开发团队,实现这个analyzer完成各自分词的计算;lucene也提供了多种分词器计算

  •       standardanalyzer 标准分词器,分词英文
  •    whitespaceanalyzer 空格分词器 
  •      simpleanalyzer 简单分词器
  •    smartchineseanalyzer 智能中文分词器

  

 1 package com.jt.test.lucene;
 2 
 3 import java.io.stringreader;
 4 
 5 import org.apache.lucene.analysis.analyzer;
 6 import org.apache.lucene.analysis.tokenstream;
 7 import org.apache.lucene.analysis.cn.smart.smartchineseanalyzer;
 8 import org.apache.lucene.analysis.core.simpleanalyzer;
 9 import org.apache.lucene.analysis.core.whitespaceanalyzer;
10 import org.apache.lucene.analysis.standard.standardanalyzer;
11 import org.apache.lucene.analysis.tokenattributes.chartermattribute;
12 import org.junit.test;
13 
14 /**
15  *测试不同分词器对同一个字符串的分词结果
16  */
17 public class analyzertest {
18     
19     //编写一个静态方法, string str,analayzer a
20     //实现传入的字符串进行不同分词器的计算词项结果
21     public static void printa(analyzer analyzer,string str) throws exception{
22         //org.apache.lucene
23         //获取str的刘对象
24         stringreader reader=new stringreader(str);
25         //通过字符串流获取分词词项流,每个不同的analyzer实现对象
26         //词项流的底层计算时不一样的;
27         //fieldname是当前字符串 代表的document的域名/属性名
28         tokenstream tokenstream = analyzer.tokenstream("name", reader);
29         //对流进行参数的重置reset,才能获取词项信息
30         tokenstream.reset();
31         //获取词项的打印结果
32         chartermattribute attribute 
33         = tokenstream.getattribute(chartermattribute.class);
34         while(tokenstream.incrementtoken()){
35             system.out.println(attribute.tostring());
36         }
37     }
38     @test
39     public void test() throws exception{
40         string str="近日,有网友偶遇诸葛亮王思聪和网红焦可然一起共进晚餐,"
41                 + "照片中,焦可然任由王思聪点菜,自己则专注玩手机,";
42         //创建不同的分词计算器
43         analyzer a1=new standardanalyzer();
44         analyzer a2=new smartchineseanalyzer();
45         analyzer a3=new simpleanalyzer();
46         analyzer a4=new whitespaceanalyzer();
47         //调用方法测试不同分词器的分词效果
48         system.out.println("*******标准分词器*******");
49         analyzertest.printa(a1, str);
50         system.out.println("*******智能中文分词器*******");
51         analyzertest.printa(a2, str);
52         system.out.println("*******简单分词器*******");
53         analyzertest.printa(a3, str);
54         system.out.println("*******空格分词器*******");
55         analyzertest.printa(a4, str);
56     }
57 }

 

3.4中文分词器常用ikanalyzer

  可以实现中文的只能分词,并且支持扩展,随着语言的各种发展,可以利用ext.dic文档补充词项,也支持停用,stop.dic;

  • 实现类的编写(ikanalyzer需要自定义实现一些类)
     1 package com.jt.lucene.ik;
     2 
     3 import java.io.ioexception;
     4 
     5 import org.apache.lucene.analysis.tokenizer;
     6 import org.apache.lucene.analysis.tokenattributes.chartermattribute;
     7 import org.apache.lucene.analysis.tokenattributes.offsetattribute;
     8 import org.apache.lucene.analysis.tokenattributes.typeattribute;
     9 import org.wltea.analyzer.core.iksegmenter;
    10 import org.wltea.analyzer.core.lexeme;
    11 
    12 public class iktokenizer6x extends tokenizer{
    13     //ik分词器实现
    14     private iksegmenter _ikimplement;
    15     //词元文本属性
    16     private final chartermattribute termatt;
    17     //词元位移属性
    18     private final offsetattribute offsetatt;
    19     //词元分类属性
    20     private final typeattribute typeatt;
    21     //记录最后一个词元的结束位置
    22     private int endposition;
    23     //构造函数,实现最新的tokenizer
    24     public iktokenizer6x(boolean usesmart){
    25         super();
    26         offsetatt=addattribute(offsetattribute.class);
    27         termatt=addattribute(chartermattribute.class);
    28         typeatt=addattribute(typeattribute.class);
    29         _ikimplement=new iksegmenter(input, usesmart);
    30     }
    31 
    32     @override
    33     public final boolean incrementtoken() throws ioexception {
    34         //清除所有的词元属性
    35         clearattributes();
    36         lexeme nextlexeme=_ikimplement.next();
    37         if(nextlexeme!=null){
    38             //将lexeme转成attributes
    39             termatt.append(nextlexeme.getlexemetext());
    40             termatt.setlength(nextlexeme.getlength());
    41             offsetatt.setoffset(nextlexeme.getbeginposition(), 
    42                     nextlexeme.getendposition());
    43             //记录分词的最后位置
    44             endposition=nextlexeme.getendposition();
    45             typeatt.settype(nextlexeme.getlexemetext());
    46             return true;//告知还有下个词元
    47         }
    48         return false;//告知词元输出完毕
    49     }
    50     
    51     @override
    52     public void reset() throws ioexception {
    53         super.reset();
    54         _ikimplement.reset(input);
    55     }
    56     
    57     @override
    58     public final void end(){
    59         int finaloffset = correctoffset(this.endposition);
    60         offsetatt.setoffset(finaloffset, finaloffset);
    61     }
    62 
    63 }

     

     1 package com.jt.lucene.ik;
     2 
     3 import org.apache.lucene.analysis.analyzer;
     4 import org.apache.lucene.analysis.tokenizer;
     5 
     6 public class ikanalyzer6x extends analyzer{
     7     private boolean usesmart;
     8     public boolean usesmart(){
     9         return usesmart;
    10     }
    11     public void setusesmart(boolean usesmart){
    12         this.usesmart=usesmart;
    13     }
    14     public ikanalyzer6x(){
    15         this(false);//ik分词器lucene analyzer接口实现类,默认细粒度切分算法
    16     }
    17     //重写最新版本createcomponents;重载analyzer接口,构造分词组件
    18     @override
    19     protected tokenstreamcomponents createcomponents(string filedname) {
    20         tokenizer _iktokenizer=new iktokenizer6x(this.usesmart);
    21         return new tokenstreamcomponents(_iktokenizer);
    22     }
    23     public ikanalyzer6x(boolean usesmart){
    24         super();
    25         this.usesmart=usesmart;
    26     }
    27     
    28 }

     

  • 手动导包
    build-path添加依赖的jar包到当前工程   ikanalyzer2012_u6.jar
  • 扩展词典和停用词典的使用
        <entry key="ext_dict">ext.dic;</entry> 
        <!--用户可以在这里配置自己的扩展停止词字典-->
        <entry key="ext_stopwords">stopword.dic;</entry> 
        和配置文件同目录下准备2个词典;
            确定分词器使用的代码编码字符集与词典编码是同一个    

     

4 lucene创建索引

4.1概念

查询(query):对于全文检索,最终都是使用词项指向一批document文档对象的集合,利用对词项的逻辑计算可以实现不同的查询功能;查询时构建的对象就是query;

文档(document):是索引文件中的一个最小的数据单位,例如非结构化数据中的网页将会封装成一个document存储在索引文件中,而封装过程中写在对象里的所有数据都会根据逻辑进行分词计算,不同的结构数据源会对应创建具有不同属性的document对象

文档的域(field):每个文档对象根据不同的数据来源封装field的名称,个数和数据,导致document的结构可能各不相同

词条化(tokenization):计算分词过程

词项(term):计算分词的结果每一个词语都是一个项

4.2 创建一个空的索引文件

  • 指向一个索引文件位置
  • 生成输出对象,进行输出
     1 @test
     2     public void emptyindex() throws exception{
     3         //指向一个文件夹位置
     4         path path = paths.get("./index01");
     5         directory dir=fsdirectory.open(path);
     6         //生成一个输出对象 writer 需要分词计算器,配置对象
     7         analyzer analyzer=new ikanalyzer6x();
     8         indexwriterconfig config=new indexwriterconfig(analyzer);
     9         indexwriter writer=new indexwriter(dir,config);
    10         //写出到磁盘,如果没有携带document,生成一个空的index文件
    11         writer.commit();
    12                     
    13 } 

    在索引中创建数据

  • 将源数据读取封装成document对象,根据源数据的结构定义document的各种field;
     1     @test
     2         public void createdata() throws exception{
     3             /*
     4              * 1 指向一个索引文件
     5              * 2 生成输出对象
     6              * 3 封装document对象(手动填写数据)
     7              * 4 将document添加到输出对象索引文件的输出
     8              */
     9             //指向一个文件夹位置
    10             path path = paths.get("./index02");
    11             directory dir=fsdirectory.open(path);
    12             //生成一个输出对象 writer 需要分词计算器,配置对象
    13             analyzer analyzer=new ikanalyzer6x();
    14             indexwriterconfig config=new indexwriterconfig(analyzer);
    15             indexwriter writer=new indexwriter(dir,config);
    16             //构造document对象
    17             document doc1=new document();//新闻 作者,内容,网站链接地址
    18             document doc2=new document();//商品页面,title,price,详情,图片等
    19             doc1.add(new textfield("author", "韩寒", store.yes));
    20             doc1.add(new textfield("content","我是上海大金子",store.no));
    21             doc1.add(new stringfield("address", "http://www.news.com", store.yes));
    22             doc2.add(new textfield("title", "三星(samsung) 1tb type-c usb3.1 移动固态硬盘",store.yes));
    23             doc2.add(new textfield("price","1699",store.yes));
    24             doc2.add(new textfield("desc","不怕爆炸你就买",store.yes));
    25             doc2.add(new stringfield("image", "image.jt.com/1/1.jpg",
    26                     store.yes));
    27             //将2个document对象添加到writer中写出到索引文件;
    28             writer.adddocument(doc1);
    29             writer.adddocument(doc2);
    30             //写出到磁盘,如果没有携带document,生成一个空的index文件
    31             writer.commit();
    32         }

     

  • 问题一:store.yes和no的区别是什么?????
    • store,yes和no的区别在于,创建索引数据,非领导数据是否在输出到索引时存储到索引文件,按照类的类型进行计算分词,一些过大的数据,查询不需要的数据可以不存储在索引文件中(例如网页内容;计算不计算分词,和存储索引没有关系)
  • 问题二:stringfield和textfield的区别是什么
    • 域的数据需要进行分词计算如果是字符串有两种对应的域类型
    • 其中stringfield表示不对数据进行分词计算,以整体形势计算索引
    • textfield表示对数据进行分词计算,以词形势计算索引
  • 问题三:    问题3:显然document中的不同域field应该保存不同的数据类型
    • 数据中的类型不同,存储的数据计算逻辑也不同;
    •   int long double的数字数据如果使用字符串类型保存域      
    •  只能做到一件事--存储在索引文件上    
    • 以上几个point类型的域会对数据进行二进制数字的计算;     
    •  范围查找,只要利用intpoint,longpoint对应域存储到document对象后    
    •  这种类型的数据在分词计算中就具有了数字的特性 > <
    •  intpoint只能存储数值,不存储数据
    • 如果既想记性数字特性的使用,又要存储数据;需要使用stringfield类型

    

5 lucene索引的搜索

5.1词项查询

    单域查询,查询条件封装指定的域,给定term(词项),lucene调用搜索api可以根据指定的条件,将所有当前查询的这个域中的分词结果进行比对,如果比对成功指向document对象返回数据;

 1 @test
 2     public void search() throws exception{
 3         /*
 4          * 1 指向索引文件
 5          * 2 构造查询条件
 6          * 3 执行搜索获取返回数据
 7          * 4 从返回数据中获取document对象
 8          */
 9         path path = paths.get("./index02");
10         directory dir=fsdirectory.open(path);
11         //获取与输入流reader,从这里生产查询的对象
12         indexreader reader=directoryreader.open(dir);
13         indexsearcher search=new indexsearcher(reader);
14         //由于使用的是term查询,无需包装analyzer;
15         //构造查询条件;
16         term term=new term("title","三星");
17         query termquery=new termquery(term);
18         //查询,获取数据
19         topdocs docs = search.search(termquery, 10);
20         //将docs转化成document的获取逻辑
21         scoredoc[] scoredoc=docs.scoredocs;
22         for (scoredoc sd : scoredoc) {
23             //没循环一次,都可以获取document对象一个
24             document doc=search.doc(sd.doc);
25             system.out.println("author:"+doc.get("author"));
26             system.out.println("content:"+doc.get("content"));
27             system.out.println("address:"+doc.get("address"));
28             system.out.println("title:"+doc.get("title"));
29             system.out.println("image:"+doc.get("image"));
30             system.out.println("price:"+doc.get("price"));
31             system.out.println("rate:"+doc.get("rate"));
32             system.out.println("desc:"+doc.get("desc"));}}