lucene学习笔记之索引的创建

1             索引库维护

在第4节,我们需要重新创建索引的时候,是去索引库目录下,手动删除的。

 

而在实际的开发中,我们可能压根就不知道索引库在哪,就算知道,我们也不可能每次都去手动删除,非常之麻烦!!!

所以,我们必须学习如何维护索引库,使用程序来操作索引库。

需要注意的是,索引是与文档紧密相连的,因此对索引的维护,实际上就是对文档的增删改

 

1.1          添加索引(文档)

1.1.1        需求

数据库中新上架了图书,必须把这些图书也添加到索引库中,不然就搜不到该新上架的图书了。

 

1.1.2        代码实现

调用 indexWriter.addDocument(doc)添加索引。

参考入门示例中的创建索引。

 

1.2          删除索引(文档)

1.2.1        需求

某些图书不再出版销售了,我们需要从索引库中移除该图书。

1.2.2        代码实现

    @Test

    public void deleteIndex() throws Exception {

       // 1、指定索引库目录

       Directory directory = FSDirectory.open(new File("F:\\lucene\\0719"));

       // 2、创建IndexWriterConfig

       IndexWriterConfig cfg = new IndexWriterConfig(Version.LATEST,

              new StandardAnalyzer());

       // 3、 创建IndexWriter

       IndexWriter writer = new IndexWriter(directory, cfg);

       // 4、通过IndexWriter来删除索引

       // 删除指定索引

       writer.deleteDocuments(new Term("name", "apache"));

       // 5、关闭IndexWriter

       writer.close();

      

       System.out.println("删除成功");

      

    }

1.2.3        清空索引库

@Test

    public void deleteIndex() throws Exception {

       // 1、指定索引库目录

       Directory directory = FSDirectory.open(new File("F:\\lucene\\0719"));

       // 2、创建IndexWriterConfig

       IndexWriterConfig cfg = new IndexWriterConfig(Version.LATEST,

              new StandardAnalyzer());

       // 3、 创建IndexWriter

       IndexWriter writer = new IndexWriter(directory, cfg);

       // 4、通过IndexWriter来删除索引

       // 删除指定索引

       writer.deleteAll();

       // 5、关闭IndexWriter

       writer.close();

      

       System.out.println("清空索引库成功");

      

    }

 

1.3          更新索引(文档)

1.3.1        说明

Lucene更新索引比较特殊,是先删除满足条件的索引,再添加新的索引。

1.3.2        代码实现

// 修改索引

    @Test

    public void updateIndex() throws Exception {

       // 1、指定索引库目录

       Directory directory = FSDirectory.open(new File("F:\\lucene\\0719"));

       // 2、创建IndexWriterConfig

       IndexWriterConfig cfg = new IndexWriterConfig(Version.LATEST,

              new StandardAnalyzer());

       // 3、 创建IndexWriter

       IndexWriter writer = new IndexWriter(directory, cfg);

       // 4、通过IndexWriter来修改索引

       // a)、创建修改后的文档对象

       Document document = new Document();

 

       // 文件名称

       Field filenameField = new StringField("name", "updateIndex", Store.YES);

       document.add(filenameField);

 

       // 修改指定索引为新的索引

       writer.updateDocument(new Term("name", "apache"), document);

 

       // 5、关闭IndexWriter

       writer.close();

      

       System.out.println("更新成功");

    }

 

2             搜索

问题:我们在入门示例中,已经知道Lucene是通过IndexSearcher对象,来执行搜索的。那我们为什么还要继续学习Lucene呢?

答:因为在实际的开发中,我们的查询的业务是相对复杂的,比如我们在通过关键词查找的时候,往往进行价格、商品类别的过滤。

而Lucene提供了一套查询方案,供我们实现复杂的查询。

 

2.1          创建查询的两种方法

执行查询之前,必须创建一个查询Query查询对象。

 

Query自身是一个抽象类,不能实例化,必须通过其它的方式来实现初始化。

 

在这里,Lucene提供了两种初始化Query查询对象的方式。

2.1.1        使用Lucene提供Query子类

Query是一个抽象类,lucene提供了很多查询对象,比如TermQuery项精确查询,NumericRangeQuery数字范围查询等。

 

使用TermQuery实例化

Query query = new TermQuery(new Term("name", "lucene"));

 

2.1.2        使用QueryParse解析查询表达式

       QueryParser会将用户输入的查询表达式解析成Query对象实例。如下代码:

QueryParser queryParser = new QueryParser("name", new IKAnalyzer());

Query query = queryParser.parse("name:lucene");

 

2.2          常用的Query子类搜索

2.2.1        TermQuery

特点:查询的关键词不会再做分词处理,作为整体来搜索。代码如下:

/**

     * Query子类查询之 TermQuery

     * 

     * 特点:不会再对查询的关键词做分词处理。

     *

     * 需要:查询书名与java教程相关书。

     */

@Test

    public void queryByTermQuery(){

       //1、获取一个查询对象

       Query query = new TermQuery(new Term("name", "编程思想"));

       doSearch(query);

      

    }

   private void doSearch(Query query) {

       try {

          

          

           //2、创建一个查询的执行对象

           //指定索引库的目录

           Directory d = FSDirectory.open(new File("F:\\lucene\\0719"));

           //创建流对象

           IndexReader reader = DirectoryReader.open(d);

           //创建搜索执行对象

           IndexSearcher searcher = new IndexSearcher(reader);

          

           //3、执行搜索

           TopDocs result = searcher.search(query, 10);

          

           //4、提出结果集,获取图书的信息

           int totalHits = result.totalHits;

           System.out.println("共查询到"+totalHits+"条满足条件的数据!");

           System.out.println("-----------------------------------------");

           //提取图书信息。

           //score即相关度。即搜索的关键词和 图书名称的相关度,用来做排序处理

           ScoreDoc[] scoreDocs = result.scoreDocs;

          

           for (ScoreDoc scoreDoc : scoreDocs) {

              /**

               * scoreDoc.doc的返回值,是文档的id, 即 将文档写入索引库的时候,lucene自动给这份文档做的一个编号。

               *

               * 获取到这个文档id之后,即可以根据这个id,找到这份文档。

               */

              int docId = scoreDoc.doc;

              System.out.println("文档在索引库中的编号:"+docId);

             

              //从文档中提取图书的信息

              Document doc = searcher.doc(docId);

               System.out.println("图书id:"+doc.get("id"));

              System.out.println("图书name:"+doc.get("name"));

              System.out.println("图书price:"+doc.get("price"));

              System.out.println("图书pic:"+doc.get("pic"));

              System.out.println("图书description:"+doc.get("description"));

              System.out.println();

              System.out.println("------------------------------------");

             

           }

          

           //关闭连接,释放资源

           if(null!=reader){

              reader.close();

           }

       } catch (Exception e) {

           e.printStackTrace();

       }

    }

 

2.2.2        NumericRangeQuery

指定数字范围查询.(创建field类型时,注意与之对应)

/**

     * Query子类查询  之  NumricRangeQuery

     * 需求:查询所有价格在[60,80)之间的书

     * @param query

     */

@Test

    public void queryByNumricRangeQuery(){

        /**

         * 第一个参数:要搜索的域

         * 第二个参数:最小值

         * 第三个参数:最大值

         * 第四个参数:是否包含最小值

         * 第五个参数:是否包含最大值

         */

        Query query = NumericRangeQuery.newFloatRange("price", 60.0f, 80.0f, true, false);

       

        doSearch(query);

    }

 

2.2.3        BooleanQuery

BooleanQuery,布尔查询,实现组合条件查询。

/**

     * Query子类查询  之  BooelanQuery查询   组合条件查询

     *

     * 需求:查询书名包含java,并且价格区间在[60,80)之间的书。

     */

    @Test

    public void queryBooleanQuery(){

        //1、要使用BooelanQuery查询,首先要把单个创建出来,然后再通过BooelanQuery组合

        Query price = NumericRangeQuery.newFloatRange("price", 60.0f, 80.0f, true, false);

        Query name = new TermQuery(new Term("name", "java"));

       

        //2、创建BooleanQuery实例对象

        BooleanQuery query = new BooleanQuery();

        query.add(name, Occur.MUST_NOT);

        query.add(price, Occur.MUST);

        /**

         * MSUT  表示必须满足                          对应的是  +

         * MSUT_NOT  必须不满足                   应对的是  -

         * SHOULD  可以满足也可以不满足     没有符号

         *

         * SHOULD 与MUST、MUST_NOT组合的时候,SHOULD就没有意义了。

         */

       

        doSearch(query);

    }

 

2.3          通过QueryParser搜索

2.3.1        特点

对搜索的关键词,做分词处理。

2.3.2        语法

2.3.2.1     基础语法

域名:关键字

实例:name:java

 

2.3.2.2     组合条件语法

条件1 AND 条件2 

条件1 OR 条件2

条件1 NOT 条件2

 

2.3.3        QueryParser

2.3.3.1     代码实现

/**

     * 查询解析器查询  之  QueryParser查询

     */

@Test

    public void queryByQueryParser(){

       try {

          

           //1、加载分词器

           Analyzer analyzer = new StandardAnalyzer();

          

           /**

            * 2、创建查询解析器实例对象

            * 第一个参数:默认搜索的域。

            *          如果在搜索的时候,没有特别指定搜索的域,则按照默认的域进行搜索

            *          如何在搜索的时候指定搜索域呢?

            *          答:格式  域名:关键词        即   name:java教程

            *

            * 第二个参数:分词器   ,对关键词做分词处理

            */

           QueryParser parser = new QueryParser("description", analyzer);

          

           Query query = parser.parse("name:java教程");

          

           doSearch(query);

          

       } catch (Exception e) {

           e.printStackTrace();

       }

    }

 

2.3.4        MultiFieldQueryParser

通过MulitFieldQueryParse对多个域查询。

/**

     * 查询解析器查询  之  MultiFieldQueryParser查询

     * 

     *     特点:同时指定多个搜索域,并且对关键做分词处理

     */

    @Test

    public void queryByMultiFieldQueryParser(){

       try {

          

           //1、定义多个搜索的  name、description

           String[] fields = {"name","description"};

           //2、加载分词器

           Analyzer analyzer = new StandardAnalyzer();

          

           //3、创建 MultiFieldQueryParser实例对象

           MultiFieldQueryParser mParser = new MultiFieldQueryParser(fields, analyzer);

          

           Query query = mParser.parse("lucene教程");

          

           doSearch(query);

       } catch (Exception e) {

           e.printStackTrace();

       }

    }

 

3             中文分词器

3.1          什么是中文分词器

学过英文的都知道,英文是以单词为单位的,单词与单词之间以空格或者逗号句号隔开

而中文的语义比较特殊,很难像英文那样,一个汉字一个汉字来划分。

所以需要一个能自动识别中文语义的分词器。

3.2          Lucene自带的中文分词器

3.2.1        StandardAnalyzer:

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

3.2.2        CJKAnalyzer

二分法分词:按两个字进行切分。如:“我是中国人”,效果:“我是”、“是中”、“中国”“国人”。

 

上边两个分词器无法满足需求。

 

3.3          使用中文分词器IKAnalyzer

IKAnalyzer继承Lucene的Analyzer抽象类,使用IKAnalyzer和Lucene自带的分析器方法一样,将Analyzer测试代码改为IKAnalyzer测试中文分词效果。

 

如果使用中文分词器ik-analyzer,就在索引和搜索程序中使用一致的分词器ik-analyzer。

3.3.1        使用luke测试IK中文分词

(1)打开Luke,不要指定Lucene目录。否则看不到效果

(2)在分词器栏,手动输入IkAnalyzer的全路径

org.wltea.analyzer.lucene.IKAnalyzer

 

 

3.3.2        改造代码,使用IkAnalyzer做分词器

3.3.2.1     添加jar包

 

 

3.3.2.2     修改分词器代码

// 创建中文分词器

Analyzer analyzer = new IKAnalyzer();

 

3.3.2.3     扩展中文词库

拓展词库的作用:在分词的过程中,保留定义的这些词

1在src或其他source目录下建立自己的拓展词库,mydict.dic文件,例如:

 

 

2在src或其他source目录下建立自己的停用词库,ext_stopword.dic文件

停用词的作用:在分词的过程中,分词器会忽略这些词。

3在src或其他source目录下建立IKAnalyzer.cfg.xml,内容如下(注意路径对应):

<?xml version="1.0" encoding="UTF-8"?>

<!DOCTYPE properties SYSTEM "http://java.sun.com/dtd/properties.dtd"> 

<properties> 

    <comment>IK Analyzer 扩展配置</comment>

    <!-- 用户可以在这里配置自己的扩展字典 -->

     <entry key="ext_dict">mydict.dic</entry>

     <!-- 用户可以在这里配置自己的扩展停用词字典    -->

    <entry key="ext_stopwords">ext_stopword.dic</entry>

</properties>

 

 

如果想配置扩展词和停用词,就创建扩展词的文件和停用词的文件,文件的编码要是utf-8。

注意:不要用记事本保存扩展词文件和停用词文件,那样的话,格式中是含有bom

 

posted @ 2019-07-18 20:17  于陆  阅读(270)  评论(0编辑  收藏  举报