zoukankan      html  css  js  c++  java
  • Lucene入门

    Lucene

    倒排索引原理
    Lucene的作用
    Lucene创建索引基本API
    Lucene查询基本API
    了解搜索引擎
    搜索引擎

    什么是搜索引擎?

    所谓搜索引擎,就是根据用户需求与一定算法,运用特定策略从互联网检索出制定信息反馈给用户的一门检索技术。搜索引擎依托于多种技术,如网络爬虫技术、检索排序技术、网页处理技术、大数据处理技术、自然语言处理技术等,为信息检索用户提供快速、高相关性的信息服务。搜索引擎技术的核心模块一般包括爬虫、索引、检索和排序等,同时可添加其他一系列辅助模块,以为用户创造更好的网络使用环境。

    搜索引擎的原理

    蜘蛛从索引区出发抓取网页,将抓取到的网页存放到临时库中进行处理(循环进行),把不符合规则的去除,符合规则的放到索引区,在索引区进行分类归档排序,最后将结果反馈给用户.

    可以看到搜索引擎的功能主要是三部分:

    爬行和抓取数据(爬虫多用Python来编写)
    对数据做预处理(提取文字,中文分词、建立倒排索引)
    提供搜索功能(用户输入关键词后,去索引库搜索数据)
    数据库索引的问题

    如果实现复杂类搜索,类似于百度,京东,使用传统数据库会存在一系列问题:

    单表存储能力有限不能存储海量数据
    可以进行分表,但会增加业务复杂度
    搜索只能模糊匹配,效率极低
    模糊搜索可能导致全表扫描,效率差
    倒排索引

    提高模糊搜索的效率

    倒排索引是一种存储数据的方式,与传统查找有很大的区别:

    传统查找:先找到文档,然后看是否匹配.采用数据按行存储,查找时逐行扫描,或者根据索引查找,然后匹配搜索条件,效率较差。
    倒排索引:先找到词条,然后看看哪些文档包含这些词条。首先对文档数据按照id进行索引存储,然后对文档中的数据分词,记录对词条进行索引,并记录词条在文档中出现的位置。这样查找时只要找到了词条,就找到了对应的文档。
    文档(Document):索引库中的每一条原始数据,例如一个网页信息,一件商品信息
    词条:原始数据按照算法进行分词,得到的每一个词
    创建倒排索引流程

    创建倒排索引分两步:

    创建文档列表(Document)

    首先给每一条原始文档数据创建文档编号(docID),创建索引,形成文档列表

    创建倒排索引列表

    然后对文档中的数据进行分词,得到词条。对词条进行编号,并以词条创建索引。然后记录下包含该词条的所有文档编号(及其它信息)。

    搜索流程

    流程:

    当用户输入任意的内容时,首先对用户输入的内容进行分词,得到用户要搜索的所有词条
    然后拿着这些词条去倒排索引列表中进行匹配。找到这些词条就能找到包含这些词条的所有文档的编号。
    然后根据这些编号去文档列表中找到文档
    什么是Lucene
    官网:http://lucene.apache.org/
    Lucene是一套用于全文检索和搜寻的开源程序库,由Apache软件基金会支持和提供
    全文检索:利用倒排索引技术对需要搜索的数据进行处理,然后提供快速的全文匹配的技术。
    Lucene提供了一个简单却强大的应用程序接口(API),能够做全文索引和搜寻,在Java开发环境里Lucene是一个成熟的免费开放源代码工具
    Lucene并不是现成的搜索引擎产品,但可以用来制作搜索引擎产品
    Lucene的基本使用
    Lucene对于索引的增(创建索引)、删(删除索引)、改(修改索引)、查(搜索数据)。

     创建索引

    基本流程

    准备要添加的文档数据:Document
    初始化索引写出工具:IndexWriter
    设定索引存储目录Directory
    设定其他配置:IndexWriterConfig
    设定分词器:Analyzer
    设定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>
    
    </dependencies>
    <build>
    <plugins>
    <plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-compiler-plugin</artifactId>
    <configuration>
    <source>1.8</source>
    <target>1.8</target>
    <encoding>UTF-8</encoding>
    </configuration>
    </plugin>
    </plugins>
    </build>

    代码实现

    public class CreateIndex {
    
    @Test
    //创建索引
    public void test1() throws IOException {
    //创建文档
    Document document=new Document();
    //name 字段名
    //value 值
    //store 是否存储
    //StringField可以指定是否存储,但不能分词
    document.add(new StringField("id","1", Field.Store.YES));
    document.add(new TextField("title","大幂幂下乡演出Dance", Field.Store.YES));
    //创建索引写出器
    //创建目录对象
    Directory dir= FSDirectory.open(new File("d:/lesson/indexDir"));
    //创建配置对象
    IndexWriterConfig config=new IndexWriterConfig(Version.LATEST,new StandardAnalyzer());
    IndexWriter indexWriter=new IndexWriter(dir,config);
    //添加文档到写出器(保存到索引库)
    indexWriter.addDocument(document);
    //提交,关流
    indexWriter.commit();
    indexWriter.close();
    }
    }

    可以利用索引查看工具查看目录

    创建索引时的细节

    创建索引时有一些细节API

    覆盖或追加

    我们在写索引时,可以在IndexConfigWriter中配置写入模式:覆盖或者追加:

    可以有3种模式:

    CREATE:每次写入都覆盖以前的数据

    APPEND:不覆盖数据,而是使用以前的索引数据后追加

    CREATE_OR_APPEND:如果不存在则创建新的,如果存在则追加数据

     Field字段类型

    刚才创建Document的时候,我们添加了两个字段,StringField和TextField,其实Field还有很多其它实现类:

    DoubleField、FloatField、IntField、LongField、StringField、TextField这些子类创建的字段一定会被创建索引。但是不一定会被存储到文档列表,要通过构造函数中的参数Store来指定:

    Store.YES代表存储,在搜索结果中也会展示出来

    Store.NO代表不存储,在搜索结果中无法展示

    这些字段虽然会创建索引,但是不一定会分词。不分词的字段,会作为一个整体词条存入索引,其中:

    TextField即创建索引,又会被分词。其它Field会创建索引,但是不会被分词。

    如果不分词,会造成整个字段作为一个词条,除非用户完全匹配,否则搜索不到。

    我们一般,需要搜索的字段,都会做分词:

    -上述所有字段都会创建索引,有一个例外:StoreField一定会被存储,但是一定不创建索引

    StoredField可以创建各种数据类型的字段,一些不需要进行搜索的字段我们无需创建索引,就可以使用StoreField类型

    这里最关键的是弄清楚一个字段:是否需要存储、是否需要索引、是否需要分词。弄清楚这个,就能知道怎么选择API了。

     分词器

    我们刚刚使用了StandardAnalyzer分词器,不过此分词器对中文的解析能力很差,我们需要中文分词器:

    paoding,imdict,mmseg4j,ik

    这里我们采用ik分词器

     IK分词器

    导入依赖

    <dependency>
      <groupId>com.janeluo</groupId>
      <artifactId>ikanalyzer</artifactId>
      <version>2012_u6</version>
    </dependency>

    修改代码

    查看结果

    停用词典和扩展词典

    新增加的词条可以通过配置文件添加到IK的词库中,也可以把一些不用的词条去除。

    首先,我们需要在配置文件目录新建一个文件,编写扩展词条和停用词条:

     

    添加配置文件  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">ext.dic;</entry>
        <!--用户可以在这里配置自己的扩展停止词字典 -->
        <entry key="ext_stopwords">stopword.dic;</entry>
    </properties>

    查看结果

    批量创建索引

     我们刚才使用IndexWriter中的addDocument方法来添加文档,然后写出到索引库,是一次添加一个文档。事实上这里也支持批量添加:

    可以看到这个API接收的是一个Interable类型,即迭代器类型,完全可以接收一个集合

        /**
         * 批量创建索引
         */
        @Test
        public void test2() throws IOException {
            // 创建文档的集合
            Collection<Document> docs = new ArrayList<>();
            // 创建文档对象
            Document document1 = new Document();
            document1.add(new StringField("id", "1", Field.Store.YES));
            document1.add(new TextField("title", "大幂幂下乡演出Dance", Field.Store.YES));
            docs.add(document1);
            // 创建文档对象
            Document document2 = new Document();
            document2.add(new StringField("id", "2", Field.Store.YES));
            document2.add(new TextField("title", "大幂幂下乡表演了Dance", Field.Store.YES));
            docs.add(document2);
            // 创建文档对象
            Document document3 = new Document();
            document3.add(new StringField("id", "3", Field.Store.YES));
            document3.add(new TextField("title", "大幂幂下乡演唱Dance舞蹈", Store.YES));
            docs.add(document3);
            // 创建文档对象
            Document document4 = new Document();
            document4.add(new StringField("id", "4", Field.Store.YES));
            document4.add(new TextField("title", "大幂幂下乡演出Dance舞蹈看不下去了", Store.YES));
            docs.add(document4);
            // 创建文档对象
            Document document5 = new Document();
            document5.add(new StringField("id", "5", Field.Store.YES));
            document5.add(new TextField("title", "大幂幂下乡演出Dance人们都看不下去了", Store.YES));
            docs.add(document5);
    
            // 索引目录类,指定索引在硬盘中的位置
            Directory directory = FSDirectory.open(new File("d:\lesson\indexDir"));
            // 引入IK分词器
            Analyzer analyzer = new IKAnalyzer();
            // 索引写出工具的配置对象
            IndexWriterConfig conf = new IndexWriterConfig(Version.LATEST, analyzer);
            // 设置打开方式:OpenMode.APPEND 会在索引库的基础上追加新索引。
            // OpenMode.CREATE会先清空原来数据,再提交新的索引
            conf.setOpenMode(IndexWriterConfig.OpenMode.CREATE);
    
            // 创建索引的工具类。参数:索引的目录和配置信息
            IndexWriter indexWriter = new IndexWriter(directory, conf);
            // 把文档集合交给IndexWriter
            indexWriter.addDocuments(docs);
            // 提交
            indexWriter.commit();
            // 关闭
            indexWriter.close();
        }
    }

    索引的基本查询 

    基本流程

    创建索引搜索工具

      指定索引目录

      创建读取流工具

      创建搜索工具

    创建查询条件

      创建查询解析器

      解析用户搜索语句,得到查询条件对象

    搜索并解析结果

    代码如下

    public class QueryIndex {

    @Test
    //基本查询
    public void test1() throws Exception {
    //1.创建索引搜索工具
    //- 指定索引目录
    Directory directory= FSDirectory.open(new File("d:\lesson\indexDir"));
    //- 创建读取流工具
    IndexReader reader= DirectoryReader.open(directory);
    //- 创建搜索工具
    IndexSearcher indexSearcher=new IndexSearcher(reader);
    //2.创建查询条件
    //- 创建查询解析器
    QueryParser parser=new QueryParser("title", new IKAnalyzer());
    //- 解析用户搜索语句,得到查询条件对象
    Query query = parser.parse("大幂幂");
    //3.搜索并解析结果
    TopDocs topDocs = indexSearcher.search(query, 10);//每次查询几条
    System.out.println("共查询到"+topDocs.totalHits+"条文档");
    System.out.println("最高分数"+topDocs.getMaxScore());

    ScoreDoc[] scoreDocs = topDocs.scoreDocs;
    for (ScoreDoc scoreDoc : scoreDocs) {
    System.out.println("得分"+scoreDoc.score);

    int doc = scoreDoc.doc;
    Document document = reader.document(doc);
    System.out.println(document.get("id"));
    System.out.println(document.get("title"));
    }
    }
    }

    结果:

    索引的高级查询

    在刚才的基本查询中,我们使用QueryParser来解析并获取查询条件对象Query。事实上,Query有很多的子类,代表各种不同的特殊查询方式:

    抽取通用查询方法

    当我们使用各种不同查询时,其它代码几乎不动,就是查询条件在发生变化,因此我们可以把查询代码进行抽取

    public class QueryIndex {
    
        @Test
        //基本查询
        public void test1() throws Exception {
            //2.创建查询条件
            //- 创建查询解析器
            QueryParser parser=new QueryParser("title", new IKAnalyzer());
            //- 解析用户搜索语句,得到查询条件对象
            Query query = parser.parse("大幂幂");
            search(query);
        }
        private void search(Query query) throws IOException {
            //1.创建索引搜索工具
            //- 指定索引目录
            Directory directory= FSDirectory.open(new File("d:\lesson\indexDir"));
            //- 创建读取流工具
            IndexReader reader= DirectoryReader.open(directory);
            //- 创建搜索工具
            IndexSearcher indexSearcher=new IndexSearcher(reader);
    
            //3.搜索并解析结果
            TopDocs topDocs = indexSearcher.search(query, 10);//每次查询几条
            System.out.println("共查询到"+topDocs.totalHits+"条文档");
            System.out.println("最高分数"+topDocs.getMaxScore());
    
            ScoreDoc[] scoreDocs = topDocs.scoreDocs;
            for (ScoreDoc scoreDoc : scoreDocs) {
                System.out.println("得分"+scoreDoc.score);
    
                int doc = scoreDoc.doc;
                Document document = reader.document(doc);
                System.out.println(document.get("id"));
                System.out.println(document.get("title"));
            }
        }
    }

    词条查询

    词条,英文是Term,代表对原始数据进行分词后得到的每一个词语。是搜索匹配时的最小单位,不可再分词。

    因此词条查询必须是精确匹配查询。用户输入的查询条件必须是完整的词条。

    增加方法如下:
    @Test
        public void termQuery() throws IOException {
            //词条查询
            Query query=new TermQuery(new Term("title","表演"));
            search(query);
        }

    FuzzyQuery(模糊查询)

     //模糊查询
        @Test
        public void testFuzzyQuery() throws IOException {
            //创建模糊查询对象:允许用户输错。但是要求错误的最大编辑距离不能超过2
            // 编辑距离:一个单词到另一个单词最少要修改的次数 danca --> dance 需要编辑1次,编辑距离就是1
            //FuzzyQuery query = new FuzzyQuery(new Term("title", "Denca"));
            //可以手动指定编辑距离,但是参数必须在0~2之间
            FuzzyQuery query = new FuzzyQuery(new Term("title", "Danca"),2);
            search(query);
        }

    数值范围查询

    把批量创建索引的StringField改成LongField,然后测试

    /*
         * 测试:数值范围查询
         * 注意:数值范围查询,可以用来对非String类型的ID进行精确的查找
         */
        @Test
        public void testNumericRangeQuery() throws Exception{
            // 数值范围查询对象,参数:字段名称,最小值、最大值、是否包含最小值、是否包含最大值
            Query query = NumericRangeQuery.newLongRange("id", 2L, 2L, true, true);
            search(query);
        }

    修改索引

    基本流程

    创建索引写出对象

      指定目录

      配置

    创建文档

    更新数据

    /**
    * 注意,这里的更新接收的条件时Term,即词条。需要注意两点:
    * 1)搜索条件最好唯一,例如ID,否则后果很严重
    * 2)之前说过,词条要求必须是字符串类型,那如果我们的id是Long类型怎么办?
    * @throws Exception
    */
    @Test
    public void testUpdate() throws Exception{
    // 创建目录对象
    Directory directory = FSDirectory.open(new File("d:\lesson\indexDir"));
    // 创建配置对象
    IndexWriterConfig conf = new IndexWriterConfig(Version.LATEST, new IKAnalyzer());
    // 创建索引写出工具
    IndexWriter writer = new IndexWriter(directory, conf);

    // 创建新的文档数据
    Document doc = new Document();
    doc.add(new StringField("id","1", Field.Store.YES));
    doc.add(new TextField("title","小幂幂下乡表演sing", Field.Store.YES));
    /* 修改索引。参数:
    * 词条:根据这个词条匹配到的所有文档都会被修改
    * 文档信息:要修改的新的文档数据
    */
    writer.updateDocument(new Term("id","1"), doc);
    // 提交
    writer.commit();
    // 关闭
    writer.close();
    }

    删除索引

    创建索引写出工具

    创建删除条件

    删除

       /**
         * 删除的方式有多样:
         * 1)根据Term删除,需要注意:
         *    a. 词条的数据类型必须是字符串
         *    b. 最好根据id进行唯一匹配删除,如果id不是字符串类型怎么办?
         * 2)根据Query删除
         * @throws Exception
         */
        @Test
        public void testDelete() throws Exception {
            // 创建目录对象
            Directory directory = FSDirectory.open(new File("d:\lesson\indexDir"));
            // 创建配置对象
            IndexWriterConfig conf = new IndexWriterConfig(Version.LATEST, new IKAnalyzer());
            // 创建索引写出工具
            IndexWriter writer = new IndexWriter(directory, conf);
    
            // 根据词条进行删除
            //        writer.deleteDocuments(new Term("id", "1"));
    
            // 根据query对象删除,如果ID是数值类型,那么我们可以用数值范围查询锁定一个具体的ID
            //        Query query = NumericRangeQuery.newLongRange("id", 2L, 2L, true, true);
            //        writer.deleteDocuments(query);
    
            // 删除所有
            writer.deleteAll();
            // 提交
            writer.commit();
            // 关闭
            writer.close();
        }
    }
  • 相关阅读:
    完美对接海康、大华、华为等等设备的Onvif/RTSP流媒体服务全终端无插件直播-本地安装启动
    LiveQing高性能RTMP流媒体服务器软件-支持直播、云端录像存储
    LiveQing全新升级的RTMP流媒体服务器支持HLS(m3u8)、RTMP、HTTP-FLV高性能分发
    LiveQing高性能RTMP、FLV、HLS流媒体服务器软件-支持与企业MySQL数据库对接
    视频平台、NVR、摄像头通过GB28181接入实现WEB分屏播放以及大屏展示
    视频流媒体服务WEB播放器集成使用
    GB28181平台实现,支持摄像头公网WEB端直播
    GB28181平台安装部署过程
    element-UI之form表单数字值的校验
    element-UI之表单校验ref标签
  • 原文地址:https://www.cnblogs.com/WonderfulU/p/11191671.html
Copyright © 2011-2022 走看看