zoukankan      html  css  js  c++  java
  • Lucene7-4学习和简单使用

    简述

    前面从新回顾学习了Solr,正好也借此机会顺便学习一下Lucene。

    一、什么是Lucene?

      全文检索的一个实现方式,也是非结构化数据查询的方法。应用场景:在数据量大,数据结构不固定的时候,采用Lucene,比如百度、Google等搜索引擎,网站的站内搜索,电商平台的商品检索等。

    二、Lucene实现全文检索的流程

    在这里插入图片描述

    1. 原始文档
      原始文档是指要索引和搜索的内容。原始内容包括互联网上的网页、数据库中的数据、磁盘上的文件等。
    2. 创建文档对象
      在构建索引之前,需要将原始内容构建成文档(Document),文档中包含一个一个的域(Field),域中存储内容。每个Document可以有多个Field,不同的Document可以有不同的Field,同一个Document可以有相同的Field(域名和域值都相同)。
    3. 分析文档
      将原始内容创建为包含域(Field)的文档(document),需要再对域中的内容进行分析,分析的过程是经过对原始文档提取单词、将字母转为小写、去除标点符号、去除停用词等过程生成最终的语汇单元,可以将语汇单元理解为一个一个的单词。比如我是中国人,经过分析变成:我 是 中国人三个部分。每个语汇单元叫做一个Term,不同的域中拆分出来相同的单词是不同的Term。Term分为两部分一部分是文档的域名,另一部分是单词的内容。
    4. 创建索引
      对所有的语汇单元进行索引,索引的目的就是为了搜索。通过索引找文档,这种索引的结构叫做倒排索引结构:倒排索引结构是根据索引找到文档,如下图:
      在这里插入图片描述
      倒排索引结构也叫反向索引结构,包括索引和文档两部分,索引即词汇表,它的规模较小,而文档集合较大。
      5.查询索引
      查询之前需要先创建查询对象,查询对象中可以指定查询要搜索的Field文档域、查询关键字等,查询对象会生成具体的查询语法,例如:语法 “fileName:lucene”表示要搜索Field域的内容为“lucene”的文档。然后就是在索引上查找域为fileName,并且关键字为Lucene的term,并根据term找到文档id列表。最后将文档内容渲染给用户。可以提供高亮显示。

    三、入门Demo

    • 使用的包
     <dependency>
                <groupId>org.apache.lucene</groupId>
                <artifactId>lucene-core</artifactId>
                <version>7.4.0</version>
            </dependency>
            <dependency>
                <groupId>org.apache.lucene</groupId>
                <artifactId>lucene-analyzers-common</artifactId>
                <version>7.4.0</version>
            </dependency>
            <dependency>
                <groupId>org.apache.lucene</groupId>
                <artifactId>lucene-queryparser</artifactId>
                <version>7.4.0</version>
            </dependency>
    
    • Demo需求 
      实现一个文件的搜索功能,通过关键字搜索文件,凡是文件名或文件内容包括关键字的文件都需要找出来。还可以根据中文词语进行查询,并且需要支持多个条件查询。本案例中的原始内容就是磁盘上的文件,如下图:
      在这里插入图片描述

    • 创建索引

    @Test
        public void createIndex() throws Exception {
            // 1、创建一个Director对象,指定索引库保存的位置。
            // 把索引库保存在内存中
            // Directory directory = new RAMDirectory();
            // 把索引库保存在磁盘
            Directory directory = FSDirectory.open(new File("D:\Lucene\index").toPath());
            // 2、基于Directory对象创建一个IndexWriter对象
            IndexWriter indexWriter = new IndexWriter(directory, new IndexWriterConfig());
            // 3、读取磁盘上的文件,对应每个文件创建一个文档对象。
            File dir = new File("D:\Lucene\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 StoredField("path", filePath);
                Field fieldContent = new TextField("content", fileContent, 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();
        }
    

    运行之后可以看到index文件里面生成了很多索引
    在这里插入图片描述
    可以使用Luke工具查看索引文件
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述

    • 查询索引库
    @Test
        public void searchIndex() throws Exception {
            // 1、创建一个Director对象,指定索引库的位置
            Directory directory = FSDirectory.open(new File("D:\Lucene\index").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("name", "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();
        }
    

    在这里插入图片描述

    • 自定义分词器(IK分词器)
      之前在创建索引的时候没有使用分词器,其实是使用了默认的标准分词器
    IndexWriter indexWriter = new IndexWriter(directory, new IndexWriterConfig());
    

    在这里插入图片描述

    但是这种分词器对中文处理的很不好,所以这里选择使用IK分词器。网上找到的maven坐标导入报错,所以就找了一个jar手动导入
    在这里插入图片描述

    自定义分词器词汇
     把配置文件和扩展词典和停用词词典添加到classpath下 。注意:hotword.dic和stopword.dic文件的格式为UTF-8,注意是无BOM 的UTF-8 编码。也就是说禁止使用windows记事本编辑扩展词典文件使用EditPlus.exe保存为无BOM 的UTF-8 编码格式,如下图:
    在这里插入图片描述
    测试IK分词器

    @Test
        public void testTokenStream() throws Exception {
            // 1)创建一个Analyzer对象,StandardAnalyzer对象
    //    标准分词器     
            Analyzer analyzer = new StandardAnalyzer();// 2)使用分析器对象的tokenStream方法获得一个TokenStream对象
            TokenStream tokenStream = analyzer.tokenStream("","我就是想测试一下Lucene的中文分词器而已,没有别的意思了");
            // 3)向TokenStream对象中设置一个引用,相当于数一个指针
            CharTermAttribute charTermAttribute = tokenStream.addAttribute(CharTermAttribute.class);
            // 4)调用TokenStream对象的rest方法。如果不调用抛异常
            tokenStream.reset();
            // 5)使用while循环遍历TokenStream对象
            while (tokenStream.incrementToken()) {
                System.out.println(charTermAttribute.toString());
            }
            // 6)关闭TokenStream对象
            tokenStream.close();
        }
    

    在这里插入图片描述
    结论:在这里可以看到标准分词器对中文并不友好

    @Test
        public void testTokenStream() throws Exception {
            // 1)创建一个Analyzer对象,StandardAnalyzer对象//IK分词器
            Analyzer analyzer = new IKAnalyzer();
            // 2)使用分析器对象的tokenStream方法获得一个TokenStream对象
            TokenStream tokenStream = analyzer.tokenStream("","我就是想测试一下Lucene的中文分词器而已,没有别的意思了");
            // 3)向TokenStream对象中设置一个引用,相当于数一个指针
            CharTermAttribute charTermAttribute = tokenStream.addAttribute(CharTermAttribute.class);
            // 4)调用TokenStream对象的rest方法。如果不调用抛异常
            tokenStream.reset();
            // 5)使用while循环遍历TokenStream对象
            while (tokenStream.incrementToken()) {
                System.out.println(charTermAttribute.toString());
            }
            // 6)关闭TokenStream对象
            tokenStream.close();
        }
    

    在这里插入图片描述
    结论:可以看到IK中文分词器效果明显比标准分词器好。

    如何使用IK分词器
    创建索引的第二步里面加上IK分词器就可以使用了
    在这里插入图片描述

    IndexWriter indexWriter = new IndexWriter(directory, new IndexWriterConfig(new IKAnalyzer()));
    
    • 索引库的维护
      Field(域)有很多类型。下面就介绍Field(域的属性)
      在这里插入图片描述

    结论:根据不同的类型使用不同的域

    新增索引

    @Test
        public void addDocument() throws Exception {
            // 创建一个IndexWriter对象,需要使用IKAnalyzer作为分析器
            IndexWriter indexWriter = new IndexWriter(FSDirectory.open(new File("D:\Lucene\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", "D:/temp/helo"));
            // 把文档写入索引库
            indexWriter.addDocument(document);
            // 关闭索引库
            indexWriter.close();
        }
    

    在这里插入图片描述

    可以看到文档域变成了16个

    删除索引

    @Test
        public void deleteAllDocument() throws Exception {
            // 创建一个IndexWriter对象,需要使用IKAnalyzer作为分析器
                    IndexWriter indexWriter = new IndexWriter(FSDirectory.open(new File("D:\Lucene\index").toPath()),
                    new IndexWriterConfig(new IKAnalyzer()));
                //删除全部索引
            indexWriter.deleteAll();//关闭索引库
            indexWriter.close();
        }
    
    

    在这里插入图片描述

    这样就会删除全部,当然也可以根据条件删除。我们先把之前增加的索引全部再增加一次。然后查询文档中有包含spring的有几个。
    在这里插入图片描述
    在这里插入图片描述

    @Test
        public void deleteAllDocument() throws Exception {
            // 创建一个IndexWriter对象,需要使用IKAnalyzer作为分析器
                    IndexWriter indexWriter = new IndexWriter(FSDirectory.open(new File("D:\Lucene\index").toPath()),
                    new IndexWriterConfig(new IKAnalyzer()));
                    //根据条件删除
            indexWriter.deleteDocuments(new Term("name", "spring"));
            //关闭索引库
            indexWriter.close();
        }
    
    

    在这里插入图片描述
    原始文档有15个,删除2个包含spring的
    修改索引
    在Lucene中修改的原理是先删除在新增。步骤:先删除全部索引,再增加全部索引。然后替换包换spring的

    @Test
        public void updateDocument() throws Exception {
            IndexWriter indexWriter = new IndexWriter(FSDirectory.open(new File("D:\Lucene\index").toPath()),
                    new IndexWriterConfig(new IKAnalyzer()));
            //创建一个新的文档对象
            Document document = new Document();
            //向文档对象中添加域
            document.add(new TextField("name", "测试索引修改", Field.Store.YES));//更新操作
            indexWriter.updateDocument(new Term("name", "spring"), document);
            //关闭索引库
            indexWriter.close();
        }
    
    

    这时候在查询包含spring的文档
    在这里插入图片描述

    然后查询包含测试的文档
    从这个结论就可以看出,更新就是先删除,然后在增加。
    在这里插入图片描述
    本文中所需要的jar包和资料https://pan.baidu.com/s/1UU0e5_fnh8bZ1y85U4Ibqw 提取码:bxdp

  • 相关阅读:
    Django ORM中常用的字段类型以及参数配置
    python enumerate用法总结
    Django 模板渲染
    Django null=True和blank=True的区别
    Django下关于session的使用
    方程组法求函数的解析式
    求数列通项公式的小众方法
    不等式性质
    多项选择题
    对勾型函数
  • 原文地址:https://www.cnblogs.com/yangk1996/p/12657678.html
Copyright © 2011-2022 走看看