java_lucene

为什么要用全文检索技术?

数据库搜索:

  1. 效率低 索引失效
  2. “苹果7是个好手机”这种就搜不出来东西

1. 全文检索

1. 概念

1. 数据的分类
  • 结构化数据

格式固定、长度固定、数据类型固定。例如:数据库中的数据

  • 非结构化数据

word文档、PDF文档、邮件、html、txt

格式不固定、长度不固定、数据类型不固定。

2. 结构化数据的查询

通常使用sql语句进行查询,简单、速度快。

3. 非结构化数据的查询
1. 顺序扫描法 Serial Scanning

比如要找内容包含某一个字符串(如spring)的文件,就是一个文档一个文档的看,从头到尾,如果包含,接着看下一个文件,直到扫描完。windows的搜索、杀毒软件的全盘扫描就是这种。相当慢。

2. 全文检索Full-text Search

将非结构化数据中的一部分信息提取出来,重新组织,使其变得有一定结构,然后对此有一定结构的数据进行搜索,达到搜索相对较快的目的。这部分从非结构化数据中提取出来然后重新组织的信息,称为索引

索引:一个为了提高查询速度,创建某种数据结构的集合,如B+tree

这种先建立索引,再对索引进行搜索的过程就叫全文检索(Full-text Search)。

虽然创建索引的过程非常耗时,但是一旦建立可以多次使用。全文检索主要处理的是查询。

流程:非结构化–>分词、结构化–>创建索引–>查询数据

2. 应用场景

  1. 搜索引擎

    百度、360搜索、谷歌、搜狗

  2. 站内搜索

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

  3. 电商搜索

    淘宝、京东搜索

只要有搜索的地方就可以使用全文检索技术。

3. 如何实现全文检索

使用Lucene。是Apache下的开源项目,提供完整的查询引擎和索引引擎,部分文本分析引擎。

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

1. 索引和搜索流程图

就是帮助数据库进行高效获取数据的数据结构,如B+tree

在这里插入图片描述

在这里插入图片描述

2. 创建索引

1. 获得原始文档

指的是要索引和搜索的内容。包括互联网上的网页、数据库的数据、磁盘的文件等。

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

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

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

2. 构建文档对象

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

每个document对象包含多个域field

域中保存的就是原始文档数据。

在这里插入图片描述

域的名称

域的值

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

eg. 商品,goods.java name field_name=name field_value=“苹果7”

3. 分析文档

分词的过程。

  1. 根据空格进行字符串拆分得到单词列表
  2. 把单词统一转换为小写(或者大写),使得大小写不敏感。
  3. 去除标点符号
  4. 去除停用词(无意义的词,如the,a,and)

每个关键词都封装为一个Term对象中。term中包含两部分内容:关键词所在的域;关键词本身。

注意:不同的域中拆分出来的相同的关键词是不同的term。

4. 创建索引

基于关键词列表来创建一个索引。保存到索引库中。

索引库中:索引、document对象和两者对应关系。

在这里插入图片描述

倒排索引,通过词语找文档,这种结构叫倒排索引,采用链表

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

在这里插入图片描述

3. 查询索引

1. 用户查询接口

用户输入查询条件的地方,如:百度搜索框

2. 把关键词封装为一个查询对象

两部分:要查询的域,要搜索的关键词

3. 执行查询

根据要查询的关键词到对应的域上进行搜索。

找到关键词,根据关键词找到对应的文档。

4. 渲染结果

用户友好界面展示,如高亮显示、分页处理等。

4. 入门程序

1. 创建索引
环境

需要下载lucene

最低要求JDK1.8

工程搭建

创建java工程、

添加jar包

  • lucene-analyzers-common-7.4.0.jar
  • lucene-core-7.4.0.jar
  • commons-io.jar
步骤
  1. 创建一个Directory对象,指定索引库保存的位置。
  2. 基于Directory对象来创建一个IndexWriter对象
  3. 读取磁盘的文件,对应每个文件创建一个文档对象
  4. 向文档对象中添加field
  5. 把文档对象写入索引库
  6. 关闭IndexWriter对象
@Test
    public void createIndex() throws Exception {
        //1、创建一个Director对象,指定索引库保存的位置。
        //把索引库保存在内存中
        //Directory directory = new RAMDirectory();
        //把索引库保存在磁盘
        Directory directory = FSDirectory.open(new File("E:\\indexRepo").toPath());
        //2、基于Directory对象创建一个IndexWriter对象
        IndexWriterConfig config = new IndexWriterConfig(new IKAnalyzer());
        IndexWriter indexWriter = new IndexWriter(directory, config);
        //3、读取磁盘上的文件,对应每个文件创建一个文档对象。
        File dir = new File("E:\\searchsource");
        File[] files = dir.listFiles();
        for (File f :
                files) {
            //取文件名
            String fileName = f.getName();
            //文件的路径
            String filePath = f.getPath();
            //文件的内容
            String fileContent = 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("path", filePath, Field.Store.YES);
            Field fieldPath = new StoredField("path", filePath);
            Field fieldContent = new TextField("content", fileContent, 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();
            //向文档对象中添加域
            document.add(fieldName);
            document.add(fieldPath);
            document.add(fieldContent);
            //document.add(fieldSize);
            document.add(fieldSizeValue);
            document.add(fieldSizeStore);
            //5、把文档对象写入索引库
            indexWriter.addDocument(document);
        }
        //6、关闭indexwriter对象
        indexWriter.close();
    }
2. 使用luke查看索引库中的内容
3. 查询索引库

步骤

  1. 创建一个Director对象,指定索引库的位置
  2. 创建一个IndexReader对象
  3. 创建一个IndexSearcher对象,构造方法中的参数indexReader对象
  4. 创建一个Query对象,TermQuery
  5. 执行查询,得到一个TopDocs对象
  6. 取查询结果的总记录数
  7. 取文档列表
  8. 打印文档中的内容
  9. 关闭indexReader对象
@Test
    public void searchIndex() throws Exception{
        //1. 创建一个Director对象,指定索引库的位置
        Directory directory = FSDirectory.open(new File("E:\\indexRepo").toPath());
        //2. 创建一个IndexReader对象
        IndexReader indexReader = DirectoryReader.open(directory);
        //3. 创建一个IndexSearcher对象,构造方法中的参数indexReader对象
        IndexSearcher indexSearcher = new IndexSearcher(indexReader);
        //4. 创建一个	Query对象,TermQuery
        Query query = new TermQuery(new Term("content","spring"));
        //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("path"));
            System.out.println(document.get("size"));
//            System.out.println(document.get("content"));
            System.out.println("----------分割线-------------");

        }
        //9. 关闭indexReader对象
        indexReader.close();
    }

5. 分析器

默认使用标准分析器StandardAnalyzer,中文采用IKAnalyzer

1. 查看分析器的分析效果

使用Analyzer对象的tokenStream方法,返回一个tokenStream对象,词对象包含最终分词结果。

public final TokenStream tokenStream(String fieldName, Reader reader)

实现步骤:

  1. 创建一个Analyzer对象,StandardAnalyzer对象
  2. 使用分析器对象的tokenStream方法获得一个tokenStream对象
  3. 向token对象中设置一个引用,相当于一个指针
  4. 调用tokenStream对象的reset方法,重置指针,如果不调用抛出异常
  5. 使用while循环遍历tokenStream对象
  6. 关闭tokenStream对象
2. IKAnalizer的使用方法
  1. 把IKAnalyzer的jar包添加到工程中
  2. 把配置文件和扩展词典添加到工程的classpath下。

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

扩展词典:添加一些新词

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

@Test// 添加停用词和常用词没有生效
    public void testTokenStream() throws Exception{
        // 1. 创建一个Analyzer对象,StandardAnalyzer对象
//        Analyzer analyzer = new StandardAnalyzer();
        Analyzer analyzer = new IKAnalyzer();
        // 2. 使用分析器对象的tokenStream方法获得一个tokenStream对象
        TokenStream tokenStream = analyzer.tokenStream("","2017年12月14日 - 传智播客Lucene概述公安局Lucene是一款高性能的、可扩展的信息检索(IR)工具库。信息检索是指文档搜索、文档内信息搜索或者文档相关的元数据搜索等操作");
        // 3. 向token对象中设置一个引用,相当于一个指针
        CharTermAttribute charTermAttribute = tokenStream.addAttribute(CharTermAttribute.class);
        // 4. 调用tokenStream对象的reset方法,重置指针,如果不调用抛出异常
        tokenStream.reset();
        // 5. 使用while循环遍历tokenStream对象
        while(tokenStream.incrementToken()){
            System.out.println(charTermAttribute.toString());
        }
        // 6. 关闭tokenStream对象
        tokenStream.close();
    }

6. 索引库的维护

1. Field域的属性

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

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

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

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

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

path不需要分词,不需要索引,StoredField

身份证号不需要分词,StringField

在这里插入图片描述

 Field fieldName = new TextField("name", fileName, Field.Store.YES);
//Field fieldPath = new TextField("path", filePath, Field.Store.YES);
Field fieldPath = new StoredField("path", filePath);// 不需要分词和索引,只存储
Field fieldContent = new TextField("content", fileContent, Field.Store.YES);
//Field fieldSize = new TextField("size", fileSize + "", Field.Store.YES);
// 文件大小查询时需要比较,必须LongPoint,但是也要取出来,StoredField
Field fieldSizeValue = new LongPoint("size", fileSize);// 可以做运算,查询,不能存储
Field fieldSizeStore = new StoredField("size", fileSize);// 存储
2. 添加文档
3. 删除文档

删除全部

根据查询、关键词删除文档

4. 修改文档

修改的原理是先删除再添加

7. 查询

范围查询

  1. 使用query的子类
    1. TermQuery

queryParser进行查询

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值