Lucene 实例教程(一)初识Lucene

一、Lucene的介绍

     Lucene是一个全文检索的框架,apache组织提供了一个用java实现的全文搜索引擎的开源项目,其功能非常的强大,api非常简单,并且有了全文检索的功能支持可以非常方便的实现根据关键字来搜索整个应用系统的内容,大大提高了用户的体验效果。   使用Lucene来建立、搜索功能和操作数据库有一点像,这样就可想而知,Lucene使用起来还是蛮方便的。

    那么为什么要使用Lucene 呢? 因为如果没有使用Lucene ,那么就要根据某个关键字来搜索数据库表记录, 就要使用like 一个字符一个字符去匹配,这样子查询的方式的要累坏程序员不说,并且查询数据库的性能开销可想而知。

二、Lucene的执行流程

    前面说了Lucene的操作方式和操作数据库有点相似,所以如果要使用Lucene 就要先创建“数据库”,然后往这个“数据表”中一行一行的插入数据,数据插入成功之后,就可以操作这张“数据表”,实现增删改查操作了。

   总的来说,可以这样理解:

  1、创建一个索引文件目录,然后把需要检索的信息 用Field 对应匹配的 封装成一个Document文档对象,将这个对象放入索引文件目录中,这里既可以将索引存放到磁盘中,也可以放入内存中,如果放入内存,那么程序关闭索引就没有了,所以一般都是将索引放入磁盘中;

  2、如果发现信息有问题需要删除,那么索引文件也要删除,否则检索的时候还是会查询得到,这个时候需要根据索引id去删除对应的索引;

  3、如果发现信息被更新了,那么索引文件也要更新,这个时候需要先将旧的索引删除然后添加新的索引;

  4、最后重头戏是全文搜索了,这和查询数据库一样,先需要创建索引读取对象,然后封装Query查询对象,调用search()方法 得到检索结果。

三、使用Lucene的准备条件

   lucene-core-3.6.0.jar

   lucene-highlighter-3.6.0.jar

   lucene-memory-3.6.0.jar

下载地址:http://download.csdn.net/detail/ch656409110/5971413

四、使用Lucene实战

1、使用Lucene将索引 写入内存

实现的思路如下:

   <1>创建了内存目录对象RAMDirectory 和 索引写入器IndexWriter  ;

   <2>利用索引写入器将指定的数据存入内存目录对象中;

   <3>创建IndexSearch 索引查询对象,然后根据关键字封装Query查询对象;

   <4>调用search()方法,将查询的结果返回给TopDocs ,迭代里面所有的Document对象,显示查询结果;

   <5>关闭IndexWriter 写入器,关闭RAMDirectory目录对象。

具体代码如下:

    package com.lucene.test;  
      
    import java.io.IOException;  
      
    import org.apache.lucene.analysis.SimpleAnalyzer;  
    import org.apache.lucene.document.Document;  
    import org.apache.lucene.document.Field;  
    import org.apache.lucene.index.IndexWriter;  
    import org.apache.lucene.index.Term;  
    import org.apache.lucene.search.IndexSearcher;  
    import org.apache.lucene.search.Query;  
    import org.apache.lucene.search.TermQuery;  
    import org.apache.lucene.search.TopDocs;  
    import org.apache.lucene.store.RAMDirectory;  
      
    /** 
     * lucene 检索内存索引 非常简单的例子 
     *  
     * @author Administrator 
     *  
     */  
    public class RAMDirectoryDemo {  
        public static void main(String[] args) throws IOException {  
            long startTime = System.currentTimeMillis();  
            System.out.println("*****************检索开始**********************");  
            // 创建一个内存目录对象,所以这里生成的索引不会放在磁盘中,而是在内存中。  
            RAMDirectory directory = new RAMDirectory();  
            /* 
             * 创建索引写入对象,该对象既可以把索引写入到磁盘中也可以写入到内存中。 参数说明:  
             * public IndexWriter(Directory d, Analyzer a, boolean create, MaxFieldLength mfl) 
             * directory:目录对象,也可以是FSDirectory 磁盘目录对象  
             * analyzer:分词器,分词器就是将检索的关键字分割成一组组词组, 它是lucene检索查询的一大特色之一, new SimpleAnalyzer()这个是lucene自带的最为简单的分词器; create: 是否新建,这里肯定要设为true; 
             * maxFieldLength:这个是分词器拆分最大长度,因为各种不同类型的分词器拆分的字符颗粒细化程度不一样,所以需要设置一个最长的拆分长度。IndexWriter.MaxFieldLength.UNLIMITED表示无限制; 
             */  
            IndexWriter writer = new IndexWriter(directory, new SimpleAnalyzer(),true, IndexWriter.MaxFieldLength.UNLIMITED);  
            // 创建Document 文档对象,在lucene中创建的索引可以看成数据库中的一张表,表中也可以有字段,往里面添加内容之后可以根据字段去匹配查询  
            // 下面创建的doc对象中添加了三个字段,分别为name,sex,dosomething,  
            Document doc = new Document();  
            /* 
            * 参数说明 public Field(String name, String value, Store store, Index index)  
            * name : 字段名称  
            * value : 字段的值 store : 
            *  Field.Store.YES:存储字段值(未分词前的字段值) Field.Store.NO:不存储,存储与索引没有关系 
            *  Field.Store.COMPRESS:压缩存储,用于长文本或二进制,但性能受损  
            * index : 建立索引的方式,是否建立分词等等 
            *  Field.Index.ANALYZED:分词建索引 
            *  Field.Index.ANALYZED_NO_NORMS:分词建索引,但是Field的值不像通常那样被保存,而是只取一个byte,这样节约存储空间  
            *  Field.Index.NOT_ANALYZED:不分词且索引 ,一旦指定为这种类型后将会被lucenn录入索引中,但不会被作为关键搜索,除非输入所有的关键字 
            *  Field.Index.NOT_ANALYZED_NO_NORMS:不分词建索引,Field的值去一个byte保存 
            */  
            doc.add(new Field("name", "Chenghui", Field.Store.YES,Field.Index.ANALYZED));  
            doc.add(new Field("sex", "男的", Field.Store.YES,Field.Index.NOT_ANALYZED));  
            doc.add(new Field("dosometing", "I am learning lucene ",Field.Store.YES, Field.Index.ANALYZED));  
            writer.addDocument(doc);  
            writer.close(); // 这里可以提前关闭,因为dictory 写入内存之后 与IndexWriter 没有任何关系了  
      
            // 因为索引放在内存中,所以存放进去之后要立马测试,否则,关闭应用程序之后就检索不到了  
            // 创建IndexSearcher 检索索引的对象,里面要传递上面写入的内存目录对象directory  
            IndexSearcher searcher = new IndexSearcher(directory);  
            // 根据搜索关键字 封装一个term组合对象,然后封装成Query查询对象  
            // dosometing是上面定义的字段,lucene是检索的关键字  
             Query query = new TermQuery(new Term("dosometing", "lucene"));  
            // Query query = new TermQuery(new Term("sex", "男"));  
            // Query query = new TermQuery(new Term("name", "cheng"));   
              
            // 去索引目录中查询,返回的是TopDocs对象,里面存放的就是上面放的document文档对象  
            TopDocs rs = searcher.search(query, null, 10);  
            long endTime = System.currentTimeMillis();  
            System.out.println("总共花费" + (endTime - startTime) + "毫秒,检索到" + rs.totalHits + "条记录。");  
            for (int i = 0; i < rs.scoreDocs.length; i++) {  
                // rs.scoreDocs[i].doc 是获取索引中的标志位id, 从0开始记录  
                Document firstHit = searcher.doc(rs.scoreDocs[i].doc);  
                System.out.println("name:" + firstHit.getField("name").stringValue());  
                System.out.println("sex:" + firstHit.getField("sex").stringValue());  
                System.out.println("dosomething:" + firstHit.getField("dosometing").stringValue());  
            }  
            writer.close();  
            directory.close();  
            System.out.println("*****************检索结束**********************");  
        }  
    }  

运行结果如下:

由上可知: 上面根据“lucene”关键字查询成功了,返回的是一个个Document封装的对象。

另外 如果在创建索引写入器IndexWriter的时候 create 参数指定为false的话 ,会报错,找不到索引文件,因为每一次读取都是”现存现取“的模式,具体如下:

Exception in thread "main" org.apache.lucene.index.IndexNotFoundException: no segments* file found in org.apache.lucene.store.RAMDirectory@156ee8e lockFactory=org.apache.lucene.store.SingleInstanceLockFactory@47b480: files: []


根据dosomething可以查询成功,同样根据sex字段 和name字段一样可以实现查询功能。

<1>如果把 Query query = new TermQuery(new Term("sex", "男")); 取消注释 ,然后查询一下:

结果发现根本没有记录,这是因为在生成索引的时候指定的sex字段 是Field.Index.NOT_ANALYZED 类型的,所以Lucene没有为该字段建立索引,也就不能根据sex字段去查询。

<2>然后 将sex 字段改为Field.Index.ANALYZED类型的,再查询一下:

结果发现依然没有记录,为什么?

这是因为使用的分词器SimpleAnalyzer 没有那么智能化,它只会对关键字中包含空格的词组进行分词和匹配,简而言之:如果“中国”是搜索关键字,那么它只会匹配索引表中对应字段中包含“ 中国 ”这样的词组,而包含“中”或者“国”的汉字不会被匹配, 记住这里面前后都有空格分开。同样的,如果创建的索引中数据有“我是中国人” ,那么要根据“中国“这个关键字根本匹配不到,因为SimpleAnalyzer 这个分词器只会去匹配 以空格分开的词组,所以要想匹配成功,那么添加索引的数据应该改为“我是 中国 人”,这样才会被检索到。

所以所以,要想使用”男“这个关键字匹配成功,就需要在添加索引的时候 改为”男 的“才可以。


既然中文汉字 是这样,试试看英文字母 是否亦如此。

果不其然:

<1>如果录入name字段的索引为”chenghui“  关键字指定为"cheng"搜不到;

<2>如果录入name字段的索引为”cheng hui“  关键字指定为"cheng"可以搜到;

<3>如果录入name字段的索引为”cheng hui“  关键字指定为"Cheng"搜不到;

<4>如果录入name字段的索引为”Cheng hui“  关键字指定为"Cheng"搜不到;

<5>如果录入name字段的索引为”Cheng hui“  关键字指定为"cheng"可以搜到;


由此可见: 索引录入的时候会统一转换成小写,但是关键字 没有转换成小写去匹配,这才导致大小写匹配不到的情况。

所以英文与数字和中文一样 不是那么人性化,并且我检索的时候连字母的大小写都没有区分,同时 数字 也是一样子的问题,   可见Lucene对其搜索的不完善性。


有没有解决方案呢? 有,上面例子使用的是SimpleAnalyzer 分词器,这个分词器是一段一段话进行分,现在介绍另外一个分词器StandardAnalyzer分词器,标准分词拿来分中文和ChineseAnalyzer一样的效果。

把上述代码中传递的分词器对象换成 new StandardAnalyzer(Version.LUCENE_36) ,试试效果,发现 StandardAnalyzer 在 SimpleAnalyzer 的基础上进行了优化,遗憾的是只是中文方面的,比如:

<1>如果录入sex索引为”男的“  关键字指定为"男" 可以搜到;


但是仅仅这样,远远不能满足全文检索的需求,这就需要使用更加高级的分词器来实现该功能。

个人资料
飘雪无垠
等级:6
文章:24篇
访问:2.1w
排名: 16
推荐
欢迎关注 “BAT笔试面试” 微信公众号
全栈面试题,你想要的都在这^_^
上一篇: Instagram的技术架构
下一篇: Lucene 实例教程(二)之IKAnalyzer中文分词器
标签: lucene、field、索引、分词器、indexwriter、面试题