+ -
当前位置:首页 → 问答吧 → lucene 1.简单的入门

lucene 1.简单的入门

时间:2010-06-30

来源:互联网

Lucene不是一个完整的全文索引应用,而是是一个用Java写的全文索引引擎工具包,它可以方便的嵌入到各种应用中实现针对应用的全文索引/检索功能。

Lucene的作者:Lucene的贡献者Doug Cutting是一位资深全文索引/检索专家,曾经是V-Twin搜索引擎(Apple的Copland操作系统的成就之一)的主要开发者,后在 Excite担任高级系统架构设计师,目前从事于一些INTERNET底层架构的研究。他贡献出的Lucene的目标是为各种中小型应用程序加入全文检索功能。

Lucene的发展历程:早先发布在作者自己的www.lucene.com,后来发布在SourceForge,2001年年底成为APACHE基金会jakarta的一个子项目:http://jakarta.apache.org/lucene/

已经有很多Java项目都使用了Lucene作为其后台的全文索引引擎.

Eclipse:基于Java的开放开发平台,帮助部分的全文索引使用了Lucene

对于中文用户来说,最关心的问题是其是否支持中文的全文检索。但通过后面对于Lucene的结构的介绍,你会了解到由于Lucene良好架构设计,对中文的支持只需对其语言词法分析接口进行扩展就能实现对中文检索的支持。
下面是一个入门demo ,基于 Lucene 3.0.2测试.
package org.surpass.test;

import java.io.IOException;

import org.apache.lucene.analysis.cn.ChineseAnalyzer;
import org.apache.lucene.document.Document;
import org.apache.lucene.document.Field;
import org.apache.lucene.document.Field.Index;
import org.apache.lucene.document.Field.Store;
import org.apache.lucene.index.CorruptIndexException;
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.ScoreDoc;
import org.apache.lucene.search.Searcher;
import org.apache.lucene.search.TopDocs;
import org.apache.lucene.search.WildcardQuery;
import org.apache.lucene.store.Directory;
import org.apache.lucene.store.LockObtainFailedException;
import org.apache.lucene.store.RAMDirectory;

public class OneDemo {
        private static String queryKey = "索*";
        private static Directory path = new RAMDirectory();

        public static void main(String[] args) {
                OneDemo fd = new OneDemo ();
                // 创建
                fd.createLuceneIndex();
                System.out.println("-------------------");
                // Hits.通过遍历Hits可获取返回的结果的Document,通过Document则可获取Field中的相关信息了。
                // 测试
                TopDocs topDocs = fd.searchLuceneIndex(queryKey);
                if (topDocs != null) {
                        System.out.println("命中:" + topDocs.totalHits);
                        // 输出结果
                        ScoreDoc[] scoreDocs = topDocs.scoreDocs;
                        for (int i = 0; i < scoreDocs.length; i++) {
                                try {
                                        Searcher search = new IndexSearcher(path);                                       
                                        Document targetDoc = search.doc(scoreDocs.doc);
                                        System.out.println("内容:" + targetDoc.toString());
                                        System.out.println(scoreDocs.score);
                                } catch (Exception e) {
                                        e.printStackTrace();
                                }
                                 System.out.println("===========================");  
                        }
                }
        }

        /**
         * 创建索引
         */
        public void createLuceneIndex() {
                try {
                        // IndexWriter,通过它建立相应的索引表,相当于数据库中的table
                        // IndexWriter(索引路径, 分词器, 是否覆盖已存在)
                        IndexWriter iwriter = new IndexWriter(path, new ChineseAnalyzer(),
                                        true, IndexWriter.MaxFieldLength.LIMITED);
                        // Document,有点类似数据库中table的一行记录
                        Document doc1 = new Document();
                        // Field,这个和数据库中的字段类似
                        // Store {COMPRESS: 压缩保存。用于长文本或二进制数据,YES :保存,NO :不保存}
                        // Index {NO :不 建索引,TOKENIZED :分词, 建索引,UN_TOKENIZED :不分词, 建索引,
                        // NO_NORMS :不分词, 建索引。但是Field的值不像通常那样被保存,而是只取一个byte,这样节约存储空间}
                        Field field1 = new Field("content", "搜索引擎", Store.YES,
                                        Index.ANALYZED);
                        doc1.add(field1);
                        Document doc2 = new Document();
                        Field field2 = new Field("content", "创建索引", Store.YES,
                                        Index.ANALYZED);
                        doc2.add(field2);
                        iwriter.addDocument(doc1);
                        iwriter.addDocument(doc2);
                        iwriter.close();
                } catch (CorruptIndexException e) {
                        e.printStackTrace();
                } catch (LockObtainFailedException e) {
                        e.printStackTrace();
                } catch (IOException e) {
                        e.printStackTrace();
                }
        }

        /**
         * 检索索引
         *
         * @param word
         *            关键字
         * @return
         */
        public TopDocs searchLuceneIndex(String word) {
                // Query,Lucene提供了几种经常可以用到的
                // Query:TermQuery、
                // MultiTermQuery、BooleanQuery、WildcardQuery、PhraseQuery、PrefixQuery、
                // PhrasePrefixQuery、FuzzyQuery、RangeQuery、SpanQuery,
                // Query其实也就是指对于需要查询的字段采用什么样的方式进行查询,
                // 如模糊查询、语义查询、短语查询、范围查询、组合查询等,还有就是QueryParser,
                // QueryParser可用于创建不同的Query,还有一个MultiFieldQueryParser支持对于多个字段进行同一关键字的查询,
                // IndexSearcher概念指的为需要对何目录下的索引文件进行何种方式的分析的查询,有点象对数据库的哪种索引表进行查询并按一定方式进行记录中字段的分解查询的概念,
                // 通过IndexSearcher以及Query即可查询出需要的结果
                Query query = new WildcardQuery(new Term("content", word));
                Searcher search = null;
                try {
                        search = new IndexSearcher(path);
                        return search.search(query, 5);
                } catch (CorruptIndexException e) {
                        e.printStackTrace();
                } catch (IOException e) {
                        e.printStackTrace();
                }
                return null;
        }
}

作者: surpass_li   发布时间: 2010-06-30

搜索测试
package org.surpass.test;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.InputStreamReader;
import java.util.Date;
import java.util.HashSet;
import java.util.Set;

import org.apache.lucene.analysis.cn.ChineseAnalyzer;
import org.apache.lucene.document.Document;
import org.apache.lucene.document.Field;
import org.apache.lucene.document.Field.Index;
import org.apache.lucene.document.Field.Store;
import org.apache.lucene.index.IndexWriter;
import org.apache.lucene.index.Term;
import org.apache.lucene.queryParser.QueryParser;
import org.apache.lucene.search.BooleanClause;
import org.apache.lucene.search.BooleanQuery;
import org.apache.lucene.search.IndexSearcher;
import org.apache.lucene.search.PhraseQuery;
import org.apache.lucene.search.Query;
import org.apache.lucene.search.ScoreDoc;
import org.apache.lucene.search.TermQuery;
import org.apache.lucene.search.TopDocs;
import org.apache.lucene.search.WildcardQuery;
import org.apache.lucene.search.spans.SpanQuery;
import org.apache.lucene.search.spans.SpanTermQuery;
import org.apache.lucene.store.Directory;
import org.apache.lucene.store.RAMDirectory;
import org.apache.lucene.util.Version;

public class Search {
        Date startTime, endTime;

        /**
         * 索引文件的存放位置,本例是放入内存中.
         */
        private Directory path = new RAMDirectory();

        /**
         * 创建索引
         */
        public void createLuceneIndex() {
                IndexWriter writer;
                try {
                        writer = new IndexWriter(path, new ChineseAnalyzer(), true,
                                        IndexWriter.MaxFieldLength.LIMITED);
                        Document docA = new Document();
                        // 相当于数据库中列的概念,因此第一个参数是列名,第二个参数是列的值,最后两个参数是enum类型的(JDK1.5),对创建的索引的设置
                        // Field.Store 是否覆盖原来的索引文件,而不是重新建一个
                        Field fieldA = new Field("content", "搜索引擎19:58:25", Store.YES,
                                        Index.ANALYZED);
                        // 我们把列(fieldA)加到某一行(docA)中
                        docA.add(fieldA);
                        // 英文 测试
                        docA.add(new Field("content", "hello lucene ,I love you",
                                        Store.YES, Index.ANALYZED));
                        docA.add(new Field("lastModifyTime", "2010个人 19:58:25", Store.YES,
                                        Index.ANALYZED));

                        Document docB = new Document();
                        // 相当于数据库中列的概念,因此第一个参数是列名,第二个参数是列的值,最后两个参数是enum类型的(JDK1.5),对创建的索引的设置
                        Field fieldB = new Field("content", "创建索引", Store.YES,
                                        Index.ANALYZED);
                        // 我们把列(fieldB)加到某一行(docB)中
                        docB.add(fieldB);
                        docB.add(new Field("content", "i live in shanghai.i come from cn",
                                        Store.YES, Index.ANALYZED));
                        docB.add(new Field("lastModifyTime", "2020个人", Store.YES,
                                        Index.ANALYZED));
                        Document docC = new Document();
                        Field fieldC = new Field("content", "19:58:25", Store.YES,
                                        Index.ANALYZED);
                        // 我们把列(fieldC)加到某一行(docC)中
                        docC.add(fieldC);
                        docC.add(new Field("content", "this is a test demo", Store.YES,
                                        Index.ANALYZED));
                        docC.add(new Field("lastModifyTime", "2010", Store.YES,
                                        Index.ANALYZED));

                        writer.addDocument(docA);
                        writer.addDocument(docB);

                        writer.addDocument(docC);

                        // 如果对海量数据进行创建索引的时候,需要对索引进行优化,以便提高速度
                        writer.optimize();

                        // 跟数据库类似,打开一个连接,使用完后,要关闭它
                        writer.close();

                } catch (Exception e) {
                        e.printStackTrace();
                }
        }

        /**
         * 创建文件索引
         */
        public void createIndexByFile() {
                IndexWriter writer;
                try {
                        File file = new File("test.txt");
                        String filePath = file.getAbsolutePath();
                        System.out.printf("fielPahth:====" + filePath);
                        System.out
                                        .printf("\n====================================================\n");
                        String content = file2String(filePath, "UTF-8");
                        System.out.printf("content:====" + content);
                        System.out
                                        .printf("\n====================================================\n");
                        writer = new IndexWriter(path, new ChineseAnalyzer(), true,
                                        IndexWriter.MaxFieldLength.LIMITED);

                        Document docA = new Document();

                        Field fieldA = new Field("content", content, Field.Store.YES,
                                        Field.Index.ANALYZED);
                        docA.add(new Field("path", filePath, Field.Store.YES,
                                        Field.Index.NOT_ANALYZED));
                        docA.add(fieldA);

                        writer.addDocument(docA);

                        // 如果对海量数据进行创建索引的时候,需要对索引进行优化,以便提高速度
                        writer.optimize();

                        // 跟数据库类似,打开一个连接,使用完后,要关闭它
                        writer.close();

                } catch (Exception e) {
                        e.printStackTrace();
                }
        }

        private String file2String(String fileName, String charset)
                        throws Exception {
                BufferedReader reader = new BufferedReader(new InputStreamReader(
                                new FileInputStream(fileName), charset));
                // StringBuilder ,StringBuffer
                StringBuilder builder = new StringBuilder();
                String line = null;
                while ((line = reader.readLine()) != null) {
                        builder.append(line);
                }
                return builder.toString();
        }

        /**
         * 相当于sql中where 后面的条件,WildcardQuery不推荐大家使用 通配符搜索
         */
        private Query wildcardQuery() {
                // where username = 'lucene' and password='apache'
                // ?代表至少有一个字符在前面
                // 搜索"*搜*",找到一条数据;搜索"*索*",找到两条数据;搜索"*搜索*",找到0条数据;搜索"*索引*",找到0条数据;
                Term term = new Term("content", "*索*");
                return new WildcardQuery(term);
        }

        // 基于lucene的分词 -- TermQuery只能对单个中文进行搜索。英文只能对当个单词进行搜索
        public Query termQuery() {
                Term term = new Term("content", "come");
                // Term term = new Term("content", "搜");
                return new TermQuery(term);
        }

        /**
         * 智能搜索
         *
         * @return
         */
        public Query queryParser() {
                QueryParser queryParser = new QueryParser(Version.LUCENE_30,
                                "content", new ChineseAnalyzer());
                try {
                        return queryParser.parse("搜索  擎");
                } catch (Exception e) {
                        e.printStackTrace();
                }
                return null;
        }

        /**
         * '与或'--搜索
         *
         * @return
         */
        public Query booleanQuery() {
                Term term1 = new Term("content", "索");
                Term term2 = new Term("content", "搜");

                TermQuery tempQuery1 = new TermQuery(term1);
                TermQuery tempQuery2 = new TermQuery(term2);

                // 本人觉得他更应该叫做JoinQuery
                BooleanQuery booleanQuery = new BooleanQuery();
                booleanQuery.add(tempQuery1, BooleanClause.Occur.MUST);
                booleanQuery.add(tempQuery2, BooleanClause.Occur.SHOULD);
                return booleanQuery;
        }

        /**
         * 多关键词搜索
         *
         * @return
         */
        public Query phraseQuery() {
                PhraseQuery phraseQuery = new PhraseQuery();
                phraseQuery.setSlop(1);
                phraseQuery.add(new Term("content", "搜"));
                phraseQuery.add(new Term("content", "擎"));
                return phraseQuery;
        }

        /**
         * 范围搜索
         *
         * @return
         */
        public Query rangeQuery() {
                Set set = new HashSet();
                SpanQuery rangeQuery = new SpanTermQuery(new Term("lastModifyTime",
                                "20100603"));
                set.add(new Term("lastModifyTime", "20150808"));
                rangeQuery.extractTerms(set);
                return rangeQuery;
        }

        public void search() {
                try {
                        // 相当于sql中的 select * from talbeName
                        IndexSearcher search = new IndexSearcher(path);

                        startTime = new Date();
                        // 抽象的查询对象
                        Query query = queryParser();
                        // query = wildcardQuery();
                        //query = termQuery();
                        //query = phraseQuery();
                        //query = booleanQuery();
                       
                        // 搜索结果集和JDBC的查询结果集完全类似的概念 -- 为什么是这样的呢?
                        // lucene在设计的时候,就参照了JDBC的很多概念
                        TopDocs topDocs = search.search(query, 5);
                        if (topDocs != null) {
                                System.out.println("命中:" + topDocs.totalHits);
                                // 输出结果
                                ScoreDoc[] scoreDocs = topDocs.scoreDocs;
                                for (int i = 0; i < scoreDocs.length; i++) {
                                        try {
                                                Document targetDoc = search.doc(scoreDocs[i].doc);
                                                System.out.println("内容:" + targetDoc.toString());
                                                System.out.println(scoreDocs[i].score);
                                        } catch (Exception e) {
                                                e.printStackTrace();
                                        }
                                        System.out.println("===========================");
                                }
                        }

                        endTime = new Date();

                        System.out.println("本次搜索用时:"
                                        + (endTime.getTime() - startTime.getTime()) + "毫秒");

                } catch (Exception e) {
                        e.printStackTrace();
                }
        }

        /**
         * @param args
         */
        public static void main(String[] args) {
                Search search = new Search();
                search.createLuceneIndex();
                // search.createIndexByFile();

                search.search();
        }

}

text.txt手动创建,内容为索引内容.

作者: surpass_li   发布时间: 2010-06-30

Field.Store解析
Store
      YES:保存
      NO:不保存
源码如下:

/** Store the original field value in the index. This is useful for short texts
     * like a document's title which should be displayed with the results. The
     * value is stored in its original form, i.e. no analyzer is used before it is
     * stored.
     */
    YES {
      @Override
      public boolean isStored() { return true; }
    },

    /** Do not store the field value in the index. */
    NO {
      @Override
      public boolean isStored() { return false; }
    };

作者: surpass_li   发布时间: 2010-06-30

lucene自带的分词方式对中文分词十分的不友好,基本上可以用惨不忍睹来形容,所以这里推荐使用IKAnalyzer进行中文分词。
IKAnalyzer分词器是一个非常优秀的中文分词器。
下面是官方文档上的介绍
采用了特有的“正向迭代最细粒度切分算法“,具有60万字/秒的高速处理能力。
采用了多子处理器分析模式,支持:英文字母(IP地址、Email、URL)、数字(日期,常用中文数量词,罗马数字,科学计数法),中文词汇(姓名、地名处理)等分词处理。
优化的词典存储,更小的内存占用。支持用户词典扩展定义.
针对Lucene全文检索优化的查询分析器
IKQueryParser;采用歧义分析算法优化查询关键字的搜索排列组合,能极大的提高Lucene检索的命中率。
1.IKAnalyzer的部署:将IKAnalyzer3.X.jar部署于项目的lib目录中;IKAnalyzer.cfg.xml与ext_stopword.dic文件放置在代码根目录下即可。

作者: surpass_li   发布时间: 2010-06-30

对索引进行查询并进行高亮highlighter处理
部分代码如下:
//高亮设置  
       Analyzer analyzer = new IKAnalyzer();//设定分词器  
       SimpleHTMLFormatter simpleHtmlFormatter = new SimpleHTMLFormatter("<B>","</B>");//设定高亮显示的格式,也就是对高亮显示的词组加上前缀后缀  
       Highlighter highlighter = new Highlighter(simpleHtmlFormatter,new QueryScorer(query));  
       highlighter.setTextFragmenter(new SimpleFragmenter(150));//设置每次返回的字符数.想必大家在使用搜索引擎的时候也没有一并把全部数据展示出来吧,当然这里也是设定只展示部分数据  
       for(int i=0;i<hits.length;i++){  
           Document doc = search.doc(hits[i].doc);  
           TokenStream tokenStream = analyzer.tokenStream("",new StringReader(doc.get("content")));  
           String str = highlighter.getBestFragment(tokenStream, doc.get("content"));  
           System.out.println(str);  
       }

作者: surpass_li   发布时间: 2010-06-30

支持

作者: starxing   发布时间: 2010-06-30