Lucene全文检索之倒排索引实现原理、API解析【2018.11】

版权声明:本站所提供的文章资讯、软件资源、素材源码等内容均为本作者提供、网友推荐、互联网整理而来(部分报媒/平媒内容转载自网络合作媒体),仅供学习参考,如有侵犯您的版权,请联系我,本作者将在三个工作日内改正。 https://blog.csdn.net/weixin_42323802/article/details/84402990

官网 http://lucene.apache.org/
下载地址:https://mirrors.tuna.tsinghua.edu.cn/apache/lucene/java/7.5.0/

Lucene的全文检索是指什么:

程序扫描文档,对文档document建立索引,并对文档进行分词 ik,对每一个词建立索引并关联文档document的编号(索引);
当用户进行搜索时候,按照搜索条件的分词的索引,匹配出对应的文档索引,把具体的文档展现出来,整个过程就是全文索引(倒排索引基本原理);
->
->全文检索的特点
1、只处理文本不处理语义
2、搜索时英文不区分大小写
3、结果列表有关度 排行

1、全文检索和数据库模糊查询(或者模糊匹配)的区别:

1、数据库模糊匹配会查询出许多和期望无关的数据;
=>假设搜索it相关的:
=>select * from db where name like ‘%’ it ‘%’ 会搜索出git ,显然结果并不是我们所期望的;
2、相关度排序问题数据库无法解决;
3、数据库模糊匹配效率较低;

2、Lucene与Solr的关系

/
->Lucene: 是全文检索的底层API;
->Solr: 基于Lucene开发的搜索服务器;


3、使用Lucene需要的依赖:
    <dependencies>
        <!-- Junit单元测试 -->
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.12</version>
        </dependency>
        <!-- lucene核心库 -->
        <dependency>
            <groupId>org.apache.lucene</groupId>
            <artifactId>lucene-core</artifactId>
            <version>4.10.2</version>
        </dependency>
        <!-- Lucene的查询解析器 -->
        <dependency>
            <groupId>org.apache.lucene</groupId>
            <artifactId>lucene-queryparser</artifactId>
            <version>4.10.2</version>
        </dependency>
        <!-- lucene的默认分词器库 -->
        <dependency>
            <groupId>org.apache.lucene</groupId>
            <artifactId>lucene-analyzers-common</artifactId>
            <version>4.10.2</version>
        </dependency>
        <!-- lucene的高亮显示 -->
        <dependency>
            <groupId>org.apache.lucene</groupId>
            <artifactId>lucene-highlighter</artifactId>
            <version>4.10.2</version>
        </dependency>
        <!--ik分词器 -->
        <dependency>
            <groupId>com.janeluo</groupId>
            <artifactId>ikanalyzer</artifactId>
            <version>2012_u6</version>
        </dependency>
    </dependencies>

》>1、创建测试类完成一次分词:

1、创建文档,添加 StringField、TextField
2、对文档分词:使用 IKAnalyzer 分词器

  • FSDirectory创建目录
  • DirectoryReader 读取Directory
  • 搜索工具IndexSearcher

3、创建写的工具:

  • 配置IndexWriterConfig写的配置,设置规则
  • 生成写的对象IndexWriter

4、提交关闭

  • commit
  • close

以下测试源码分享地址:
https://github.com/medoo-Ai/lucene

/**
 * @auther SyntacticSugar
 * @data 2018/11/23 0023上午 11:37
 */
public class CreateIndexTest {
    /**
     * 给 文档添加fieldName /FieldText/store 是yes or no 控制是否保存
     */
    @Test
    public void createIndexTest() {
        Document document = new Document();
        document.add(new StringField("id", "1", Field.Store.YES));
        document.add(new TextField("title", "谷歌地图之父跳槽facebook厉害了我的哥碉堡了" +
                "蓝瘦香菇", Field.Store.YES));
        // 创建储存目录、分词
        try {
            FSDirectory indexDir = FSDirectory.open(new File("indexDir"));
//            StandardAnalyzer analyzer = new StandardAnalyzer();
            IKAnalyzer analyzer = new IKAnalyzer();
            /**
             * 创建文档配置对象,IndexWriterConfig.OpenMode.CREATE  清空创建索引
             * IndexWriterConfig.OpenMode.APPEND 在原来基础上追加
             */
            IndexWriterConfig indexWriterConfig = new IndexWriterConfig(Version.LATEST, analyzer);
            indexWriterConfig.setOpenMode(IndexWriterConfig.OpenMode.CREATE);
            //索引
            IndexWriter indexWriter = new IndexWriter(indexDir, indexWriterConfig);
            //
            //写入索引,提交,关闭
            indexWriter.addDocument(document);
            indexWriter.commit();
            indexWriter.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
       }

》生成如下indexDir目录,目录内是分词;
生成

》2、建新测试,添加多个document

文档document中分词,可以添加集合list
并且 ,创建文档配置对象 indexWriterConfig 时候:

  • IndexWriterConfig.OpenMode.CREATE 清空创建索引
  • IndexWriterConfig.OpenMode.APPEND 在原来基础上追加
    /**
     * 给 文档添加filedName /FiledText/store 是yes or no
     */
    @Test
    public void createIndexTest2() throws IOException {
        ArrayList<Document> documents = new ArrayList<>();
        Document document1 = new Document();
        document1.add(new StringField("id", "1", Field.Store.YES));
        document1.add(new TextField("title", "谷歌地图之父跳槽facebook厉害了我的哥碉堡了" +
                "蓝瘦香菇", Field.Store.YES));
        Document document2 = new Document();
        document2.add(new StringField("id", "2", Field.Store.YES));
        document2.add(new TextField("title", "谷歌地图之父拉斯加盟社交网站Facebook" +
                "蓝瘦香菇", Field.Store.YES));
        //添加到集合中
        documents.add(document1);
        documents.add(document2);
        // 创建储存目录、分词
        FSDirectory indexDir = FSDirectory.open(new File("indexDir"));
//            StandardAnalyzer analyzer = new StandardAnalyzer();
        IKAnalyzer analyzer = new IKAnalyzer();
        /**
         * 创建文档配置对象,IndexWriterConfig.OpenMode.CREATE  清空创建索引
         * IndexWriterConfig.OpenMode.APPEND 在原来基础上追加
         */
        IndexWriterConfig indexWriterConfig = new IndexWriterConfig(Version.LATEST, analyzer);
        indexWriterConfig.setOpenMode(IndexWriterConfig.OpenMode.CREATE);
        //索引
        IndexWriter indexWriter = new IndexWriter(indexDir, indexWriterConfig);
        //
        //写入索引,提交,关闭s
        indexWriter.addDocuments(documents);
        indexWriter.commit();
        indexWriter.close();
    }

》3、新建测试类,使用分词查询 “谷歌地图之父” ,为多条件查询

3、Query查询:

步骤如下:
1、创建目录FSDirectory 、创建索引读取工具DirectoryReader、创建索引搜索工具indexSearcher
2、查询解析器:QueryParser parser

  • 解析要查询的 parse(String query)生成query对象

3、使用搜索工具indexSearcher进行查询,把query对象传参进去,返回topDocs文档对象

  • topDocs包含totalHits查询的总数目、scoreDocs分数
  • 对scoreDocs 进行遍历,获取每一个文档的 编号docId 、得分
  • 通过搜索工具 indexSearcher对文档docId 编号搜索,找到具体的文档对象 document

4、document文档对象中,包含id / title 两部分,get取出

  • String id = doc.get(“id”);
  • String title = doc.get(“title”);

对于查询解析器 parse,源码如下:

解析

 @Test
    public void testSearch() throws IOException, ParseException {
        FSDirectory indexDir = FSDirectory.open(new File("indexDir"));
        /**
         * 索引读取工具
         * 索引搜索工具
         */
        IndexReader open = DirectoryReader.open(indexDir);
        IndexSearcher indexSearcher = new IndexSearcher(open);
        //创建查询解析器[可以传参入一个数组,多条件进行查询],创建要查询的对象
//        QueryParser parser = new QueryParser("title", new IKAnalyzer());
        QueryParser parser = new MultiFieldQueryParser(new String[]{"id", "title"}, new IKAnalyzer());
        Query query = parser.parse("谷歌地图之父");
        //搜索
        //topDocs包含totalHits、scoreDocs
        TopDocs topDocs = indexSearcher.search(query, 5);//参数5,就是排名前五名的
        System.out.println("查找到数据的条数" + topDocs.totalHits);
        /**
         * 获取文档编号中文档的编号以及文档的得分
         */
        ScoreDoc[] scoreDocs = topDocs.scoreDocs;
        for (ScoreDoc scoreDoc : scoreDocs) {
            int docId = scoreDoc.doc;
            float score = scoreDoc.score;
            //根据文档编号查找 doc
            Document doc = indexSearcher.doc(docId);
            String id = doc.get("id");
            String title = doc.get("title");
            System.out.println(id + ":" + title);
        }
    }

多


》抽取search查询的工具类;

经常要用:
抽取了一个query查询的工具类:

    public void search(Query query) throws IOException {
        //建新目录,读取工具,搜索工具
        FSDirectory directory = FSDirectory.open(new File("indexDir"));
        DirectoryReader reader = DirectoryReader.open(directory);
        IndexSearcher searcher = new IndexSearcher(reader);
        /**
         * 搜索符合条件的topDocs
         * 对文档进行解析  获取  totalHits   ,scoreDocs
         */
        TopDocs topDocs = searcher.search(query, 10);
        System.out.println("本次搜索到的条数:" + topDocs.totalHits);
        ScoreDoc[] scoreDocs = topDocs.scoreDocs;
        //遍历
        for (ScoreDoc scoreDoc : scoreDocs) {
            int docId = scoreDoc.doc;
            float score = scoreDoc.score;
            //查找到对应的文档
            Document doc = searcher.doc(docId);
            System.out.println(doc.get("id") + ":" + doc.get("title") + ":" + score);
        }
    }

》词条查询:

    /**
     * testTermQuery  词条查询
     */
    @Test
    public void testTermQuery() throws IOException {
        // 创建词条查询的对象
        TermQuery title = new TermQuery(new Term("title", "谷歌"));
        //调用查询方法进行查询
        search(title);
    }
    /**
     * WildcardQuery 通配符 ? 查询
     */
    @Test
    public void WildcardQuery() throws IOException {
        TermQuery query = new TermQuery(new Term("title", "??"));
        search(query);
    }

词条

》通配符查询:

testFuzzyQuery 通配符查询、 Fuzzy:英文有 :失真、模糊 的意思

  • 词条中并没有 “谷歌啊” 这样的词条,却查出来信息,因为模糊设置了编辑距离(容错);
  • 编辑距离 0-2
    /**
     * testFuzzyQuery 通配符查询  Fuzzy:失真、模糊
     * fscevool 编辑距离0-2
     */
    @Test
    public void testFuzzyQuery() throws IOException {
        FuzzyQuery query = new FuzzyQuery(new Term("title", "谷歌啊"), 1);
        search(query);
    }

斌
》组合查询:

1、NumericRangeQuery 数字查询范围:

  • 可以进行精确的查找
  • 参数:字段名称,最小值、最大值、是否包含最小值、是否包含最大值

2、 BooleanQuery(组合查询)
创建组合查询对象query,把查询对象query1,query2分别添加到query中

    @Test
    public void testNumericRangeQuery() throws IOException {
        //Query接口
        Query query = NumericRangeQuery.newLongRange("id", 2L, 2L, true, true);
        search(query);
    }
    /**
     * BooleanQuery(组合查询)
     * Occur.MUST_NOT  不是必须的
     * Occur.SHOULD    必须的
     */
    @Test
    public void testBooleanQuery() throws IOException {
        Query query1 = NumericRangeQuery.newLongRange("id", 2L, 2L, true, true);
        Query query2 = NumericRangeQuery.newLongRange("id", 2L, 4L, true, true);
        //创建boolean查询,然后添加query1/query2
        BooleanQuery query = new BooleanQuery();
        query.add(query1, BooleanClause.Occur.MUST_NOT);
        query.add(query2, BooleanClause.Occur.SHOULD);
        search(query);
    }

》修改,删除

/**
     * 修改索引
     * 先删除,后创建
     * 1、所以我们是修改   一把针对唯一的进行修改
     * 2、deleteDocuments 若id 是数字,直接删除即可
     */
    @Test
    public void testUpdate() throws IOException {
        //创建目录,创建IndexWriterConfig,创建索引写出对象
        FSDirectory directory = FSDirectory.open(new File("indexDir"));
        IndexWriterConfig config = new IndexWriterConfig(Version.LATEST, new IKAnalyzer());
        IndexWriter indexWriter = new IndexWriter(directory, config);
        //创建文档索引数据,而后进行修改
        Document document = new Document();
        document.add(new StringField("id", "1", Field.Store.YES));
        document.add(new TextField("title", "谷歌地图之父跳槽facebook 为了加入复仇者联盟 屌爆了啊", Field.Store.YES));
        /**
         * 修改
         * Term  词条
         * document   文档
         */
        indexWriter.updateDocument(new Term("id", "1"), document);
        indexWriter.commit();
        indexWriter.close();
    }
    /**
     * testDelete
     * 删除词条 ,通过id 来删除   indexWriter.deleteDocuments(new Term("id", "1"));
     * 1、删除所有  deleteAll
     * 2、条件删除   query来删除
     */
    @Test
    public void testDelete() throws IOException {
        //创建目录,创建IndexWriterConfig,创建索引写出对象
        FSDirectory directory = FSDirectory.open(new File("indexDir"));
        IndexWriterConfig config = new IndexWriterConfig(Version.LATEST, new IKAnalyzer());
        IndexWriter indexWriter = new IndexWriter(directory, config);
        //创建文档索引数据
//        indexWriter.deleteDocuments(new Term("id", "1"));
        Query query = NumericRangeQuery.newLongRange("id", 2L, 2L, true, true);
        indexWriter.deleteDocuments(query);
        indexWriter.commit();
        indexWriter.close();
    }

》代码高亮显示:

完成代码高亮显示;
主要使用了格式化器和高亮工具:

  • 代码高亮工具构造器:public Highlighter(Formatter formatter, Scorer fragmentScorer) , 其中fragmentScorer 是切面评分,也就是queryScorer,对显示的内容进行一个评分(因为要显示的东西太多了,不能全部都高亮的吧,通过queryScorer来确定);
  • 格式化器SimpleHTMLFormatter 作为参数传递,指定了格式,本例中采用斜体
    @Test
    public void testHighlighter() throws Exception {
        // 目录,directoryreader ,searcher
        FSDirectory directory = FSDirectory.open(new File("indexDir"));
        DirectoryReader reader = DirectoryReader.open(directory);
        IndexSearcher searcher = new IndexSearcher(reader);
        //分词
        QueryParser parser = new QueryParser("title", new IKAnalyzer());
        Query query = parser.parse("谷歌地图");
        searcher.search(query, 10);
        /**
         * 格式化器:
         * 准备高亮工具:
         */
        SimpleHTMLFormatter formatter = new SimpleHTMLFormatter("<em>", "</em>");
        QueryScorer queryScorer = new QueryScorer(query);
        Highlighter highlighter = new Highlighter(formatter, queryScorer);
        //搜索文档数据
        TopDocs topDocs = searcher.search(query, 10);
        System.out.println("本次搜索到的条数:" + topDocs.totalHits);
        //bianli

        ScoreDoc[] scoreDocs = topDocs.scoreDocs;
        for (ScoreDoc scoreDoc : scoreDocs) {
            // 获取文档编号,得分编号docId / score
            int docId = scoreDoc.doc;
            float score = scoreDoc.score;
            //查询文档的 title  、得分
            Document doc = searcher.doc(docId);
            String title = doc.get("title");
            //   高亮处理  Analyzer analyzer, String fieldName, String text
            String bestFragment = highlighter.getBestFragment(new IKAnalyzer(), "title", title);
            System.out.println("高亮显示:" + bestFragment);
            //打印得分到控制台
            System.out.println(score);
        }
    }

xuying d
》排序:

排序:
创建排序对象 ,搜索
默认降序

   @Test
    public void testSortQuery() throws Exception {
        //  目录对象,目录读取,搜索工具
        FSDirectory directory = FSDirectory.open(new File("indexDir"));
        DirectoryReader reader = DirectoryReader.open(directory);
        IndexSearcher searcher = new IndexSearcher(reader);
        //分词,创建查询对象 query
        QueryParser parser = new QueryParser("title", new IKAnalyzer());
        Query query = parser.parse("谷歌地图");

        /**
         * 排序对象、查询
         * 排序字段参数,默认降序  String field, SortField.Type type, boolean reverse)
         */
        Sort sort = new Sort(new SortField("", SortField.Type.LONG, true));
        TopDocs topDocs = searcher.search(query, 1, sort);
        //
        //展示  topDocs文档中的数据
        System.out.println("共查询到数据条数:" + topDocs.totalHits);
        ScoreDoc[] scoreDocs = topDocs.scoreDocs;
        //遍历
        for (ScoreDoc scoreDoc : scoreDocs) {
            int docId = scoreDoc.doc;
            Document doc = searcher.doc(docId);
            System.out.println("文档编号:" + doc.get("id") + ":" + doc.get("title") + "得分:" + scoreDoc.score);
        }
    }

运行下下;
sdf

猜你喜欢

转载自blog.csdn.net/weixin_42323802/article/details/84402990