Lucene 全文检索

Lucene 全文检索

Field域

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

是否分词

  • 分词就是对文件的内容或者其他的属性进行分割形成一个一个的语汇单元,分词的过程就是将一些动词,定冠词,不定冠词等内容去掉,保留名词。比如文件的内容,商品的介绍,这些内容都是需要用户输入关键词来查询的,因此这个必须分词
  • 但是对于商品的id,订单号,身份证号这些是不用分词的,这个是必须全局匹配才会找到相关的内容

是否索引

  • 索引的目的就是为了将来作为查询条件来搜索,比如商品的名称,商品的介绍,文章的内容,这些内容需要输入关键词搜索的,我们必须进行索引,如果不索引将会不能爱按照这些内容搜索。
  • 不索引: 商品的id,图片的路径等这个是不需要作为查询条件的,因此不需要索引

是否存储

  • 将Field值存储在文档中,存储在文档中的Field才可以从Document中获取。
  • 比如:商品名称、订单号,凡是将来要从Document中获取的Field都要存储。
  • 否:不存储Field值,不存储的Field无法通过Document获取
  • 比如:商品简介,内容较大不用存储。如果要向用户展示商品简介可以从系统的关系数据库中获取商品简介。
  • 如果需要商品描述,则根据搜索出的商品ID去数据库中查询,然后显示出商品描述信息即可。

Field的常用类型

Field改进

  • 图书id
    • 是否分词:不用分词,因为不会根据商品id来搜索商品
    • 是否索引:不索引,因为不需要根据图书ID进行搜索
    • 是否存储:要存储,因为查询结果页面需要使用id这个值。
  • 图书名称:
    • 是否分词:要分词,因为要将图书的名称内容分词索引,根据关键搜索图书名称抽取的词。
    • 是否索引:要索引。
    • 是否存储:要存储。
  • 图书价格
    • 是否分词:要分词,lucene对数字型的值只要有搜索需求的都要分词和索 引,因为lucene对数字型的内容要特殊分词处理,本例子可能要根据价格范 围搜索,需要分词和索引。
    • 是否索引:要索引 是否存储:要存储
  • 图书图片地址:
    • 是否分词:不分词
    • 是否索引:不索引
    • 是否存储:要存储,因为只有根据图片地址才能找到对应的图片
  • 图书描述:
    • 是否分词:要分词
    • 是否索引:要索引
    • 是否存储:因为图书描述内容量大,不在查询结果页面直接显示,不存储。 不存储是来不在lucene的索引文件中记录,节省lucene的索引文件空间, 如果要在详情页面显示描述,思路: 从lucene中取出图书的id,根据图书的id查询关系数据库中book表 得到描述信息。

添加依赖

  • 这里使用的IKAnalyzer这个中文分词器
<!-- 添加lucene支持 -->
        <dependency>
            <groupId>org.apache.lucene</groupId>
            <artifactId>lucene-core</artifactId>
            <version>4.10.3</version>
        </dependency>
​
        <dependency>
            <groupId>org.apache.lucene</groupId>
            <artifactId>lucene-queryparser</artifactId>
            <version>4.10.3</version>
        </dependency>
​
        <dependency>
            <groupId>org.apache.lucene</groupId>
            <artifactId>lucene-analyzers-common</artifactId>
            <version>4.10.3</version>
        </dependency>
​
        <dependency>
            <groupId>commons-io</groupId>
            <artifactId>commons-io</artifactId>
            <version>2.4</version>
        </dependency>
​
​
        <dependency>
            <groupId>org.apache.lucene</groupId>
            <artifactId>lucene-highlighter</artifactId>
            <version>4.10.3</version>
        </dependency>
​
        <!-- IK分词器 -->
        <dependency>
            <groupId>com.janeluo</groupId>
            <artifactId>ikanalyzer</artifactId>
            <version>2012_u6</version>
        </dependency>

添加IK中文分词器的扩展

  • 只需要将这些文件下载下载出来,然后添加到src/main/resource路径下即可

Lucene的工具类

  • 其中自己封装了一些方法
/**
 * Lucene的工具类
 * @author chenjiabing
 */
public class LuceneUtils {
    /**
     * 获取IndexWriter  用于创建索引库
     * @return IndexWriter对象
     * @throws Exception
     */
    public static IndexWriter getIndexWriter() throws Exception{
        //创建索引库存放的位置
        Directory directory=FSDirectory.open(new File("/home/chenjiabing/Documents/Lucene"));
        //使用IK中文分词器
        IKAnalyzer ikAnalyzer=new IKAnalyzer(true);
        
        //创建IndexWriteConfig对象,其中传入的是分析器对象
        IndexWriterConfig indexWriterConfig=new IndexWriterConfig(Version.LATEST, ikAnalyzer);
        
        //创建索引,其中的变量是索引库的位置,索引配置对象
        IndexWriter indexWriter=new IndexWriter(directory, indexWriterConfig);
        return indexWriter;
    }
    
    /**
     * 获取IndexSearcher,用于查询
     * @return IndexSearcher对象
     * @throws Exception
     */
    public static IndexSearcher getIndexSearcher()throws Exception{
        //创建Directory对象,指定索引库的位置
        Directory directory=FSDirectory.open(new File("/home/chenjiabing/Documents/Lucene"));
        //创建IndexReader对象
        IndexReader indexReader=DirectoryReader.open(directory);
        //创建IndexSearcher对象
        IndexSearcher indexSearcher=new IndexSearcher(indexReader);
        return indexSearcher;
    }
    
    /**
     * 根据查询语句,打印结果
     * @param indexSearcher  IndexSearch对象
     * @param query  查询对象
     * @param n  显示结果数量
     * @throws IOException 
     */
    public static void doSearch(IndexSearcher indexSearcher,Query query,Integer n) throws IOException{
        //执行查询
        TopDocs topDocs=indexSearcher.search(query, n);
        
        //返回查询结果
        ScoreDoc[] scoreDocs=topDocs.scoreDocs;
        
        //遍历查询结果
        for (ScoreDoc scoreDoc : scoreDocs) {
            int doc=scoreDoc.doc;  //返回文档的编号
            
            //根据编号查询文档
            Document document=indexSearcher.doc(doc);
            
            //输出文档中定义的域
            System.out.println(document.get("fileName"));
            /*System.out.println(document.get("fileSize"));
            System.out.println(document.get("fileContent"));
            System.out.println(document.get("filePath"));*/
        }
    }
}

创建索引库

// 创建索引
    @Test
    public void testIndex() throws Exception {
        IndexWriter indexWriter=LuceneUtils.getIndexWriter();
        //创建File对象,这里是需要索引的文件
        File file=new File("/home/chenjiabing/Documents/Blog");
        
        //获取文件夹下的所有文件
        File[] files=file.listFiles();
        
        //遍历所有的文件,取出相关 的内容
        for (File f : files) {
            Document document=new Document();  //创建文档对象
            
            //获取文件名
            String fileName=f.getName();
            //创建域  文件名  分词,索引 存储
            Field fieldName=new  TextField("fileName", fileName, Store.YES);
            
            //获取文件大小
            Long fileSize=FileUtils.sizeOf(f);
            //创建文件大小的域,分词,索引,存储
            Field fieldSize=new LongField("fileSize", fileSize, Store.YES);
            
            //获取文件路径
            String filePath=f.getPath();
            //创建文件路径的域 不分词,不索引 但是必须存储,用来找到指定的文件
            Field fieldPath=new StoredField("filePath", filePath);
            
            //获取文件内容
            String fileContent=FileUtils.readFileToString(f);
            
            //创建文件内容的域,分词,索引,存储
            Field fieldContent=new TextField("fileContent", fileContent,Store.YES);
            //将分出的这些域添加到文档中
            document.add(fieldContent);
            document.add(fieldName);
            document.add(fieldPath);
            document.add(fieldSize);
            
            //将文档写入索引库
            indexWriter.addDocument(document);
        }
        //关闭IndexWriter对象
        indexWriter.close();
    }

Query 搜索

TermQuery

  • 这个是精确搜索
  • TermQuery项查询,TermQuery不使用分析器,搜索关键词作为整体来匹配Field域中的词进行查询,比如订单号、分类ID号等。
    • 这个查询的方式不会通过分词器进行分词查询,而是整个内容匹配。比如其中的查询条件为文件的名称:springmvc拦截器,那么只有当名称为...springmvc拦截器.....这样整个词语连接在一起的时候才会查询到
  • `TermQuery(new Term("域名","搜索的词语")) : 这里的域名是创建Field的时候指定的:new Field("域名",值)
     @Test
    public void testTermQuerySearch() throws Exception{
        //获取IndexSearcher
        IndexSearcher indexSearcher=LuceneUtils.getIndexSearcher();
        //创建一个TermQuery对象,指定查询的域和需要查询的词
        TermQuery query=new TermQuery(new Term("fileName","springmvc拦截器"));
        System.out.println(query);
        //打印查询结果
        LuceneUtils.doSearch(indexSearcher, query, 10);
        //关闭IndexReader
        indexSearcher.getIndexReader().close();
    }

NumericRangeQuery

  • 指定数字范围查询.
    • 适合查询价格等数字类型的
  • 其中有许多创建查询范围的静态方法,适合多种数据类型的查询
    @Test
    public void testNumericRangeQuery() throws Exception{
        IndexSearcher indexSearcher=LuceneUtils.getIndexSearcher();
        /**
         * 这里是查询文件大小的域:fileSize
         * 第一个参数: 域名
         * 第二个参数:最小值
         * 第三个参数: 最大值
         * 第四个参数: 是否包含最小值
         * 第五个参数: 是否包含最大值
         */
        Query query=NumericRangeQuery.newLongRange("fileSize", 1000L, 2000L, true, true);
        //输出查询条件:fileSize:[1000 TO 2000]
        System.out.println(query);
        LuceneUtils.doSearch(indexSearcher, query, 10);
        //关闭IndexReader
        indexSearcher.getIndexReader().close();
    }

BooleanQuery

  • BooleanQuery,布尔查询,实现组合条件查询。
  • Occur.MUST : 当前的查询条件必须满足
  • Occur.SHOULD : 当前的查询条件可满足可不满足,相当于or
  • MUST_NOT:查询条件不能满足,相当于NOT非+
 @Test
    public void testBooleanQuery() throws Exception{
        IndexSearcher indexSearcher=LuceneUtils.getIndexSearcher();
        
        //第一个查询条件 根据fileName域查询
        Query query1=new TermQuery(new Term("fileName", "springmvc"));
        //第二个查询条件,根据fileSize查询
        Query query2=NumericRangeQuery.newLongRange("fileSize", 1000L, 2000L, true, true);
        
        //创建BooleanQuery
        BooleanQuery query=new BooleanQuery();
        
        //添加查询条件,这个条件是必须满足的 :Occur.MUST
        query.add(query1,Occur.MUST);
        //添加第二个查询条件,这个条件可满足可不满足,相当于or
        query.add(query2,Occur.SHOULD);
        System.out.println(query);
        //执行查询
        LuceneUtils.doSearch(indexSearcher, query, 10);
        
        indexSearcher.getIndexReader().close();
    }

MatchAllDocsQuery

  • 查询所有
  • 返回的是索引库中所有的文件信息
 //查询所有
    @Test
    public void testMatchAllDoc() throws Exception{
        IndexSearcher indexSearcher=LuceneUtils.getIndexSearcher();
        Query query=new MatchAllDocsQuery();
        System.out.println(query);
        LuceneUtils.doSearch(indexSearcher, query, 10);
        indexSearcher.getIndexReader().close();
    }

QueryParser [常用]

  • 通过QueryParser也可以创建Query,QueryParser提供一个Parse方法,此方法可以直接根据查询语法来查询
 @Test
    public void testQueryParser() throws Exception{
        IndexSearcher indexSearcher=LuceneUtils.getIndexSearcher();
        /**
         * 第一个参数:指定了默认的查询的域名
         * 第二个参数: 指定了分词器
         */
        QueryParser parser=new QueryParser("fileName",new IKAnalyzer());
        /**
         * 其中的字符串的形式为: *:*
         * 查询所有: *:*
         * 根据默认域名查询直接写一个查询内容即可: "springmvc"
         * 根据指定域名查询: "fileContent:springmvc"
         */
        Query query=parser.parse("fileContent:拦截器");
        System.out.println(query);
        LuceneUtils.doSearch(indexSearcher, query, 50);
        indexSearcher.getIndexReader().close();
    }

MultiFieldQueryParser

  • 通过MultiFieldQueryParser对多个域查询。
  @Test
    public void testMulitFiledQueryParser() throws Exception{
        IndexSearcher indexSearcher=LuceneUtils.getIndexSearcher();
        //指定默认查询的域名
        String[] fields={"fileName","fileContent"};
        
        /**
         * 创建对象
         * 第一个参数: 指定默认域名的数组
         * 第二参数: 指定分词器,这里使用中文分词器
         */
        MultiFieldQueryParser parser=new MultiFieldQueryParser(fields, new IKAnalyzer());
        //这里没有指定域名,因此使用上面指定的两个默认的域名进行查询,这两个默认域名之间是or关系,只要满足就查询返回
        //Query query=parser.parse("springmvc");
        
        //指定filePath域名中搜索
        Query query=parser.parse("filePath:/home/chenjiabing");
        System.out.println(query);
        LuceneUtils.doSearch(indexSearcher, query, 50);
        indexSearcher.getIndexReader().close();
    }

索引维护

  • 每个document代表一个索引,维护索引其实就是对document的增删改查

添加索引

  • 如果数据库系统做了变更,那么我们需要添加索引,那么此时就需要添加索引
  • indexWriter.addDocument(doc)
  • 这个就像是创建索引库的时候,其实就是在添加索引

删除索引

指定条件删除

  • 根据Term项删除索引,满足条件的将全部删除。
  • Term是索引域中最小的单位。根据条件删除时,建议根据唯一键来进行删除。在solr中就是根据ID来进行删除和修改操作的。
  • writer.deleteDocuments(new Term("域名","值"));
@Test
public void deleteIndex() throws Exception {
    // 创建分词器,标准分词器
    Analyzer analyzer = new IKAnalyzer();
​
    // 创建IndexWriter
    IndexWriterConfig cfg = new IndexWriterConfig(Version.LUCENE_4_10_3,
            analyzer);
    Directory directory = FSDirectory
            .open(new File("E:\\11-index\\hcx\\"));
    // 创建IndexWriter
    IndexWriter writer = new IndexWriter(directory, cfg);
​
    // Terms
    writer.deleteDocuments(new Term("id", "1"));
​
    writer.close();
}

删除全部

  • 将索引目录的索引信息全部删除,直接彻底删除,无法恢复。慎用!
// 删除索引
@Test
public void deleteIndex() throws Exception {
    // 1、指定索引库目录
    Directory directory = FSDirectory.open(new File("E:\\11-index\\0720"));
    // 2、创建IndexWriterConfig
    IndexWriterConfig cfg = new IndexWriterConfig(Version.LATEST,
            new StandardAnalyzer());
    // 3、 创建IndexWriter
    IndexWriter writer = new IndexWriter(directory, cfg);
    // 4、通过IndexWriter来删除索引
    // a)、删除全部索引
    writer.deleteAll();
    // 5、关闭IndexWriter
    writer.close();
}

修改索引

  • 更新索引是先删除再添加,建议对更新需求采用此方法并且要保证对已存在的索引执行更新,可以先查询出来,确定更新记录存在执行更新操作。
@Test
public void updateIndex() throws Exception {
    // 创建分词器,标准分词器
    Analyzer analyzer = new StandardAnalyzer();
​
    // 创建IndexWriter
    IndexWriterConfig cfg = new IndexWriterConfig(Version.LUCENE_4_10_3,
            analyzer);
​
    Directory directory = FSDirectory
            .open(new File("E:\\11-index\\hcx\\"));
    // 创建IndexWriter
    IndexWriter writer = new IndexWriter(directory, cfg);
​
    // 第一个参数:指定查询条件
    // 第二个参数:修改之后的对象
    // 修改时如果根据查询条件,可以查询出结果,则将以前的删掉,然后覆盖新的Document对象,如果没有查询出结果,则新增一个Document
    // 修改流程即:先查询,再删除,在添加
    Document doc = new Document();
    doc.add(new TextField("name", "lisi", Store.YES));
    writer.updateDocument(new Term("name", "zhangsan"), doc);
​
    writer.close();
}

原创声明,本文系作者授权云+社区发表,未经许可,不得转载。

如有侵权,请联系 yunjia_community@tencent.com 删除。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏黑泽君的专栏

day65_Lucene学习笔记

注意由于语言不同分析器的切分规则也不同,本例子使用StandardAnalyzer,它可以对用英文进行分词。 如下是org.apache.lucene.ana...

893
来自专栏MasiMaro 的技术博文

ADO对SQL Server 2008数据库的基础操作

最近在学习ADO与数据库的相关知识,现在我将自己学到的东西整理写出来,也算是对学习的一种复习。

1362
来自专栏Python小屋

Python+SQLite开发无界面版通信录管理系统

本文重点在于演示Python对SQLite数据库的操作,以及命令行式菜单的工作原理和实现。 首先使用SQLite Database Browser创建SQLit...

41213
来自专栏kevindroid

room的使用-以demo为例

3733
来自专栏坚毅的PHP

mysql数据迁移hbase问题

无法直接dump,写了java多线程程序做迁移 问题1:Operation not allowed after ResultSet closed 裸jdbc语句...

3845
来自专栏小狼的世界

Mysql运行模式及1690错误处理

经过查询,发现这个错误的原因是两个时间字段进行减法运算时,如果有一个时间为0000-00-00时造成的,根本原因是因为这样减法的结果会超过Mysql数值字段的范...

1092
来自专栏数据小魔方

MySQL数据库基础——本地文件交互

从这一篇开始,大概会花四五篇的内容篇幅,归纳整理一下之前学过的SQL数据库,一来可以为接下来数据分析工作提前巩固基础,二来把以前学的SQL内容系统化、结构化。 ...

33912
来自专栏Java技术栈

去 BAT 面试,总结了这 55 道 MySQL 面试题!

Mysql数据库软件是一个客户端或服务器系统,其中包括:支持各种客户端程序和库的多线程SQL服务器、不同的后端、广泛的应用程序编程接口和管理工具。

7532
来自专栏文渊之博

SQL Server 2016 JSON原生支持实例说明

背景 Microsoft SQL Server 对于数据平台的开发者来说越来越友好。比如已经原生支持XML很多年了,在这个趋势下,如今也能在SQLServer2...

21610
来自专栏java架构师

【SQL Server】系统学习之二:索引优化

页大小8192个字节,行限制为8060字节(大型对象除外)。 包含varchar nvarchar varbinary sql_variant(8012,obj...

2466

扫码关注云+社区