Lucene介绍

信息检索

从信息集合中找出与用户需求相关的信息。被检索的信息除了文本外,还有图像、音频、视频等多媒体信息,我们只讨论文本的检索。

 

信息检索分为:全文检索、数据检索、知识检索

 

全文检索

把用户的查询请求和全文中的每一个词进行比较,不考虑查询请求与文本语义上的匹配。在信息检索工具中,全文检索是最具通用性和实用性的
知识检索:强调的是基于知识的、语义上的匹配

 

信息检索与数据库比较

  1. 匹配效果:ant搜索plant不应出现

2、查询结果没有相关度排序

3、数据库搜索速度慢

 

全文检索流程

首先从信息源中采集信息,加工(对信息集合中的信息建立一个索引,实质是分词器首先将文本进行分词,然后对词建立索引),放入信息集合(也叫索引库)。

查询时首先分词,然后在索引中查询,再从索引库中的文档集合中取出信息。

 

索引库由索引表也叫词汇表(存放索引(即每个词在哪些文档中出现,词和文档内部编号的对应关系))和数据(文档集合,每个文档都有一个"文档内部编号")

 

Lucene中:索引库是一组文件的集合,索引库的位置用Directory表示,可以是硬盘位置,也可以是内存位置。里面的每一条数据,是一个Document(即文档集合的每个文档)

 

 

Lucene

Lucene主页

http://lucene.apache.org

 

Lucene配置和使用

1、添加jar包

lucene-core-2.9.4.jar

lucene-analyzers-2.9.4.jar

lucene-highlighter-2.9.4.jar

 

2、确定信息源和索引库的位置

如:

3、创建索引和搜索文档示例

HelloWorld.java内容

package com.citti.lucene.helloworld;

 

import java.io.IOException;

 

import junit.framework.TestCase;

 

import org.apache.lucene.analysis.Analyzer;

import org.apache.lucene.analysis.standard.StandardAnalyzer;

import org.apache.lucene.document.Document;

import org.apache.lucene.index.CorruptIndexException;

import org.apache.lucene.index.IndexWriter;

import org.apache.lucene.index.IndexWriter.MaxFieldLength;

import org.apache.lucene.queryParser.MultiFieldQueryParser;

import org.apache.lucene.queryParser.QueryParser;

import org.apache.lucene.search.Filter;

import org.apache.lucene.search.IndexSearcher;

import org.apache.lucene.search.Query;

import org.apache.lucene.search.ScoreDoc;

import org.apache.lucene.search.TopDocs;

 

import com.citti.lucene.utils.File2Document;

 

public class HelloWorld extends TestCase{

 

    // 信息源的文件路径

    private String filePath = "D:\\workspace\\wlw\\LuceneDemo1\\LuceneDatasource\\IndexWriter addDocument's javadoc.txt";

    // 索引库位置

    private String indexPath = "D:\\workspace\\wlw\\LuceneDemo1\\luceneIndex";

    // 分词器

    private Analyzer analyzer = new StandardAnalyzer();

    

    /**

     * 创建索引

     * 操作索引用IndexWriter(增、删、改)

     * @throws IOException

     * @throws CorruptIndexException

     */

    public void testCreateIndex() throws CorruptIndexException, IOException {

        Document doc = File2Document.file2Docoument(filePath);

        // file --> doc

 

        IndexWriter indexWriter = new IndexWriter(indexPath, analyzer, true, MaxFieldLength.UNLIMITED);

        // true表示每次都删除以前创建的索引,重新创建;没有该参数表示如果有就不创建,没有就创建;false表示不管有没有都不创建索引

        // MaxFieldLength.LIMITED表示最多分出前10000个词,UNLIMITED表示没有限制分词个数,也可以是new MaxFieldLength(10000)

        

        indexWriter.addDocument(doc);

        indexWriter.close();

    }

    

    /**

     * 进行搜索

     * 搜索用IndexSearcher

     * @throws Exception

     */

    public void testSearch() throws CorruptIndexException, Exception {

        String queryString = "document";

        

        // 1、把要搜索的文本解析为 Query

        QueryParser queryParser = new MultiFieldQueryParser(new String[] {"name", "content"}, analyzer);

        // 只在标题和内容中查询

        Query query = queryParser.parse(queryString);

        

        // 进行查询

        IndexSearcher indexSearcher = new IndexSearcher(indexPath);

        Filter filter = null;//过滤器,查询结果再进行过滤,比如某些机密的不让显示

        TopDocs topDocs = indexSearcher.search(query, filter, 1000);// 10000表示一次查询1000个文档(默认50),用于提高效率

        

        //打印结果,TopDocs包含了总条数和查询结果

        System.out.println("总共包含【" + topDocs.totalHits + "】条记录");

        for (ScoreDoc scoreDoc : topDocs.scoreDocs) {

            int docSn = scoreDoc.doc; // 文档内部编号

            Document doc = indexSearcher.doc(docSn); // 根据文档内部编号取出文档,类似于hibernateget方法

            File2Document.printDocumentInfo(doc);

        }

    }

}

 

File2Document.java内容

package com.citti.lucene.utils;

 

import java.io.BufferedReader;

import java.io.File;

import java.io.FileInputStream;

import java.io.IOException;

import java.io.InputStreamReader;

 

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;

 

public class File2Document {

 

    // 文件:name, content, size, path

    public static Document file2Docoument(String path) throws IOException {

        File file = new File(path);

        Document doc = new Document();

        doc.add(new Field("name", file.getName(), Store.YES, Index.ANALYZED));

        doc.add(new Field("content", readFileContent(file), Store.YES, Index.ANALYZED));

        doc.add(new Field("size", String.valueOf(file.length()), Store.YES, Index.NOT_ANALYZED));

        doc.add(new Field("path", file.getPath(), Store.YES, Index.NO));

        // Stroe表示该字段是否存储(COMPRESS表示压缩之后再存;NO表示不存,例如某些pdf很大,不存可以节省空间)

        

        // Index表示该字段是否建立索引

        // NO:不建立索引,如网页的网址就只需要存,不需要建立索引

        // 因为如果知道网址,就不需要搜索了,不会通过网址来搜索

        

        // 进行索引分为"分词后索引(ANALYZED)""不分词直接索引(NOT_ANALYZED,把整个值当成一个关键词)"

        // 例如yyyy-mm-dd的时间就不需要分词

        

        return doc;

    }

    

    /**

     * 读取文件内容

     * @param file

     * @return

     * @throws IOException

     */

    public static String readFileContent(File file) throws IOException {

        BufferedReader reader = new BufferedReader(new InputStreamReader(new FileInputStream(file)));

        StringBuffer returnString = new StringBuffer();

        for (String line = null; (line = reader.readLine()) != null; ) {

            returnString.append(line);

        }

        return returnString.toString();

    }

    

    /**

     * 打印文档内容

     * @param doc

     */

    public static void printDocumentInfo(Document doc) {

//        Field f = doc.getField("name");

//        System.out.println(f.stringValue());

        System.out.println(doc.get("name"));

        System.out.println(doc.get("content"));

        System.out.println(doc.get("size"));

        System.out.println(doc.get("path"));

    }

}

 

 

Directory

Directroy表示索引库的位置,在

 

IndexWriter indexWriter = new IndexWriter(indexPath, analyzer, MaxFieldLength.UNLIMITED);

 

中,默认用indexPath创建了一个Directory,相当于

 

Directory directory = FSDirectory.getDirectory(indexPath);

IndexWriter indexWriter = new IndexWriter(directory, analyzer, MaxFieldLength.UNLIMITED);

 

FSDirectory和RAMDirectory

FSDirectory将索引放到文件,可以保存到硬盘

RAMDirectory将索引放到内存中,访问速度快

 

因此,一般是在程序启动时用FSDirectory读取磁盘索引,再放到RAMDirectory中,搜索时用RAMDirectory,速度快,程序退出时用FSDirectory保存到磁盘。

 

        Directory fsDirectory = FSDirectory.getDirectory(indexPath);

        //1、启动时读取

        Directory ramDirectory = new RAMDirectory(fsDirectory);

        //此时已经将硬盘索引读取到了ramDirectory

        

        //2、程序运行时操作ramDirectory

        IndexWriter ramIndexWriter = new IndexWriter(ramDirectory, analyzer, MaxFieldLength.LIMITED);

        

        //2.1、添加document

        Document document = File2Document.file2Docoument(filePath);

        ramIndexWriter.addDocument(document);

        ramIndexWriter.close();//此处必须关闭,清空缓存

        

        //3、退出时保存

        IndexWriter fsIndexWriter = new IndexWriter(fsDirectory, analyzer, true , MaxFieldLength.LIMITED);

        fsIndexWriter.addIndexesNoOptimize(new Directory[] {ramDirectory});

        fsIndexWriter.close();

优化

fsIndexWriter.optimize();

//优化,合并文件,减少文件个数

 

分词器

英文分词

分词过程,,如

IndexWriter addDocument's a javadoc.txt

  1. 切分词

IndexWriter

addDocument's

a

javadoc.txt

 

2、排除停用词

IndexWriter

addDocument's

javadoc.txt

 

3、形态还原

IndexWriter

addDocument

javadoc.txt

 

4、转为小写

indexwriter

adddocument

javadoc.txt

 

中文分词

过程:切分词-排除停用词

单字分词

如:StandardAnalyzer

 

二分法分词

如:CJKAnalyzer

 

词典(词库)分词

如:MMAnalyzer(极易分词,需要加入je-analysis-1.5.3.jar)、庖丁分词

 

测试分词器

public class AnalyzerTest extends TestCase {

 

    private String enText = "IndexWriter addDocument's javadoc.txt";

    private String zhText = "中文分词测试";

    

    private Analyzer en1 = new StandardAnalyzer();

    private Analyzer en2 = new SimpleAnalyzer(); //这个分词器可以按照"."拆分,但没排除停用词

    

    private Analyzer zh1 = new CJKAnalyzer(); // 二分法分词

    private Analyzer zh2 = new MMAnalyzer(); // 词库分词(极易分词,需要加入je-analysis-1.5.3.jar)

 

    public void test() throws Exception {

        analyze(zh2, zhText);

    }

    public void analyze(Analyzer analyzer, String text) throws Exception {

        System.out.println("分词器:" + analyzer.getClass());

        TokenStream tokenStream = analyzer.tokenStream("content", new StringReader(text));

        for (Token token = null; (token = tokenStream.next()) != null;) {

            System.out.println(token);

        }

    }

}

高亮器

高亮的作用:1、生成摘要信息。2、搜索文字高亮

用法:

    public void testSearch() throws Exception{

        String queryString = "绿色软件";

        // 1、把要搜索的文本解析为 Query

        QueryParser queryParser = new MultiFieldQueryParser(new String[] {"name", "content"}, analyzer);

        // 只在标题和内容中查询

        Query query = queryParser.parse(queryString);

        

        // 进行查询

        IndexSearcher indexSearcher = new IndexSearcher(indexPath);

        Filter filter = null;//过滤器,查询结果再进行过滤,比如某些机密的不让显示

        TopDocs topDocs = indexSearcher.search(query, filter, 1000);// 10000表示一次查询1000个文档(默认50),用于提高效率

        

        //打印结果,TopDocs包含了总条数和查询结果

        System.out.println("总共包含【" + topDocs.totalHits + "】条记录");

        

        // 准备高亮器

        Formatter formatter = new SimpleHTMLFormatter("<font color='red'>", "</font>");//设置高亮的前缀和后缀

        Scorer scorer = new QueryScorer(query);

        Fragmenter fragmenter = new SimpleFragmenter(50);//设置显示多少个字符

        

        Highlighter highlighter = new Highlighter(formatter, scorer);

        highlighter.setTextFragmenter(fragmenter);

        

        for (ScoreDoc scoreDoc : topDocs.scoreDocs) {

            int docSn = scoreDoc.doc; // 文档内部编号

            Document doc = indexSearcher.doc(docSn); // 根据文档内部编号取出文档,类似于hibernateget方法

            

            // 高亮

            String hc = highlighter.getBestFragment(new MMAnalyzer(), "content", doc.get("content"));

            if (hc == null) {

                // 如果内容中没有找到需要高亮文字,就截取前50个字符

                hc = doc.get("content").substring(0, 50);

            }

            doc.getField("content").setValue(hc);

            

            File2Document.printDocumentInfo(doc);

        }

    }

查询

Query的构建:

1、直接用字符串构建

String queryString = "绿色软件";

QueryParser queryParser = new MultiFieldQueryParser(new String[] {"name", "content"}, analyzer);

Query query = queryParser.parse(queryString);

查询对象构建的query.toSting()可以得到对应的查询字符串。

 

2、用TermQuery对象构建(关键词查询):

对应查询字符串:name:abc

Term term = new Term("name", "abc");

Query query = new TermQuery(term);

在name字段中搜索abc字符串,搜索的字符串全为小写,大写不可能搜索到。

 

3、用RangeQuery对象构建(范围查询):

对应查询字符串:size:[0000000000002s TO 000000000000dw]

Term lowerTerm = new Term("size", "100");

Term upperTerm = new Term("size", "500");

Query query = new RangeQuery(lowerTerm, upperTerm, true);

//true表示包含边界

默认都是字符串比较,例如200到1000之间不会包含500。

如果需要比较数字,需要用到NumberTools,搜索时:

 

Term lowerTerm = new Term("size", NumberTools.longToString(100));

Term upperTerm = new Term("size", NumberTools.longToString(500));

创建索引时:

 

doc.add(new Field("size", NumberTools.longToString(file.length()), Store.YES, Index.NOT_ANALYZED));

NumberTools转的数字是14位,它用36禁止存储,为了节省长度空间。第一位是正负号位。实际13位

DateTools.dateToString(new Date(), Resolution.Day));

可以转换日期,Resolution.Day表示精确到天

4、WildcardQuery构建(通配符查询)

对应查询字符串:content:绿*

PrefixQuery前缀查询也类似

    /**

     * 通配符查询

     * ?代表1个,*代表0个或多个

     */

    public void testWildcardQuery() throws Exception {

        Term term = new Term("content", "绿*");

        Query query = new WildcardQuery(term);

        indexDao.search(query);

    }

 

5、PhraseQuery构建(短语查询)

对应查询字符串:content:"? 因此 ? 需要"~5

 

    /**

     * 短语查询

     * @throws Exception

     */

    public void testPhraseQuery() throws Exception {

        PhraseQuery phraseQuery = new PhraseQuery();

        phraseQuery.add(new Term("content", "因此"), 1);

        phraseQuery.add(new Term("content", "需要"), 3);

        indexDao.search(phraseQuery);

    }

其中1和3代表关键词的相对位置,中间间隔的关键词。例如有关键词的顺序是"因此"、"如果"、"需要",则可以查出结果。同10和12等等都一样。

如果去掉这个参数,变成:

        phraseQuery.add(new Term("content", "因此"));

        phraseQuery.add(new Term("content", "需要"));

则忽略位置关系,只要两个都有就会查出来。

如果加上:

        phraseQuery.setSlop(5);

表示两个关键词之间相隔不到5个关键词才会查出来。

 

如果同时设置了相对位置参数,也有setSlop,则以setSlop为准

 

6、BooleanQuery(布尔查询(逻辑查询))

对应查询字符串:+content:绿* +content:"因此 需要"~5

        Query query1 = new WildcardQuery(new Term("content", "绿*"));

 

        PhraseQuery query2 = new PhraseQuery();

        query2.add(new Term("content", "因此"));

        query2.add(new Term("content", "需要"));

        query2.setSlop(5);

        

        BooleanQuery booleanQuery = new BooleanQuery();

        booleanQuery.add(query1, Occur.MUST);

        booleanQuery.add(query2, Occur.MUST);

 

booleanQuery可以组合多个查询的逻辑关系,有

Occur.MUST(必须符合条件)、Occur.MUST_NOT(必须不符合条件)、OCCUR.SHOULD(符不符合都可以)三种

1、MUST和MUST,取交集

2、MUST和MUST_NOT,MUST的必须符合且MUST_NOT的必须不符合

3、SHOULD和SHOULD,取并集

 

特殊情况:

1、MUST和SHOULD,此时SHOULD相当于没有

2、MUST_NOT和MUST_NOT,无意义,检索无结果

3、MUST_NOT和SHOULD,此时SHOULD相当于MUST,变成了MUST和MUST_NOT

4、单独使用SHOULD,相当于MUST

5、单独使用MUST_NOT,无意义,检索无结果

排序

排序分为"相关度排序"和"自定义排序"

相关度排序

查询结果默认按照相关度(关键词出现的频率、位置等因素)计算得分,按相关度得分排序。

相关度排序中,可以指定boost(权重),分为

搜索时指定Field的boost

索引时指定Document的boost

默认boost都是1.0f

 

1、指定Fieldboost

Map<String, Float> boosts = new HashMap<String, Float>();

 

boosts.put("name", 3f);

//boosts.put("content", 1.0f);//默认就是1.0f

 

QueryParser queryParser = new MultiFieldQueryParser(new String[] {"name", "content"}, analyzer, boosts);

 

2、指定Documentboost

Document doc = File2Document.file2Docoument(filePath);

doc.setBoost(3f);

 

自定义排序

按照某个字段进行排序,此时相关度排序就无效了

Sort sort = new Sort(new SortField("name"));//默认升序,false也是升序,true降序

//Sort sort = new Sort(new SortField("name", true));//降序        

TopDocs topDocs = indexSearcher.search(query, filter, 1000, sort);

// 查询结果按照"name"字段值进行排序

过滤器

过滤器可以过滤查询结果(例如需要让一些机密的内容不显示),但它会极大降低效率,不建议使用。

 

Filter filter = new RangeFilter("size", "200", "1000", true, true);

//过滤器,查询结果再进行过滤,比如某些机密的不让显示,此处为查询size2001000的,后面两个逻辑类型分别表示是否包含下边界和上边界    

TopDocs topDocs = indexSearcher.search(query, filter, 1000, sort);

posted @ 2021-01-27 19:22  吴克兢  阅读(143)  评论(0)    收藏  举报