Elasticsearch

在这里插入图片描述

1 初识elasticsearch

1.1 了解ES

  • elasticsearch的作用:开源的分布式搜索引擎,可以用来实现搜索、日志统计、分析、系统监控等功能
  • elastic stack(ELK):以elasticsearch为核心的技术栈,包括beats、Logstash、kibana、elasticsearch
    在这里插入图片描述

1.2 倒排索引

在这里插入图片描述

  • 正向索引:根据id索引的方式,但根据词条查询时,必须先逐条获取每个文档,然后判断文档中是否包含所需要的词条,是根据文档找词条的过程
  • 倒排索引:先找到用户要搜索的词条,根据词条得到保护词条的文档的id,然后根据id获取文档。是根据词条找文档的过程
  • 正向索引和倒排索引的优缺点:
    • 正向索引:
      • 优点:
        • 可以给多个字段创建索引
        • 根据索引字段搜索、排序速度非常快
      • 缺点:
        • 根据非索引字段,或者索引字段中的部分词条查找时,只能全表扫描。
    • 倒排索引:
      • 优点:
        • 根据词条搜索、模糊搜索时,速度非常快
      • 缺点:
        • 只能给词条创建索引,而不是字段
        • 无法根据字段做排序

1.3 es的一些概念

  • Mysql 与 Elasticsearch 中的概念对比:
MySQLElasticsearch说明
TableIndex索引(index),就是文档的集合,类似数据库的表(table)
RowDocument文档(Document),就是一条条的数据,类似数据库中的行(Row),文档都是JSON格式
ColumnField字段(Field),就是JSON文档中的字段,类似数据库中的列(Column)
SchemaMappingMapping(映射)是索引中文档的约束,例如字段类型约束。类似数据库的表结构(Schema)
SQLDSLDSL是elasticsearch提供的JSON风格的请求语句,用来操作elasticsearch,实现CRUD
  • Mysql 与 Elasticsearch 擅长方向:
    • Mysql:擅长事务类型操作,可以确保数据的安全和一致性
    • Elasticsearch:擅长海量数据的搜索、分析、计算
  • Mysql 与 Elasticsearch 的结合使用:
    • 对安全性要求较高的写操作,使用mysql实现
    • 对查询性能要求较高的搜索需求,使用elasticsearch实现
    • 两者再基于某种方式,实现数据的同步,保证一致性
      在这里插入图片描述

1.4 安装 es、kibana

  • es;
  • kibana:提供 elasticsearch 的可视化界面
  • IK 分词器插件:
    • 分词器作用:
      • 创建倒排索引时对文档分词
      • 用户搜索时,对输入的内容分词
    • IK 分词器模式:
      • ik_smart:智能切分,粗粒度
      • ik_max_word:最细切分,细粒度
    • IK 分词器拓展词条、停用词条:
      • 利用config目录的IkAnalyzer.cfg.xml文件添加拓展词典和停用词典
      • 在词典中添加拓展词条或者停用词条

2 索引库操作

  • 索引库 – 数据库表;
  • mapping 映射 – 数据库表的结构约束;
  • DSL – 数据库进行 CRUD 进行请求的 sql 语句;

2.1 mapping 映射属性

  • mapping 是对索引库中文档的约束;
  • 常见 mapping 属性:
    • type:字段数据类型,常见的简单类型有:
      • 字符串:text(可分词的文本)、keyword(精确值,例如:品牌、国家、ip地址)
      • 数值:long、integer、short、byte、double、float、
      • 布尔:boolean
      • 日期:date
      • 对象:object
    • index:是否创建索引,默认为true
    • analyzer:使用哪种分词器
    • properties:该字段的子字段

2.2 索引库的 CRUD

  • 索引库的 CRUD – 在 Kibana 编写 DSL

2.2.1 创建索引库和映射

  • 基本语法:
    • 请求方式:PUT
    • 请求路径:/索引库名,可以自定义
    • 请求参数:mapping映射
  • 格式:
PUT /索引库名称
{
  "mappings": {
    "properties": {
      "字段名":{
        "type": "text",
        "analyzer": "ik_smart"
      },
      "字段名2":{
        "type": "keyword",
        "index": "false"
      },
      "字段名3":{
        "properties": {
          "子字段": {
            "type": "keyword"
          }
        }
      },
      // ...略
    }
  }
}

2.2.2 查询索引库

  • 基本语法:
    • 请求方式:GET
    • 请求路径:/索引库名
    • 请求参数:无
  • 格式:GET /索引库名

2.2.3 修改索引库

  • 索引库一旦创建,无法修改 mapping – 倒排索引结构本身不复杂,但是一旦改变数据结构(如分词器),整个倒排索引就都需要重新创建!
  • 无法修改mapping中已有的字段,但是却允许添加新的字段到mapping中 – 不会对倒排索引产生影响;
  • 语法格式:
PUT /索引库名/_mapping
{
  "properties": {
    "新字段名":{
      "type": "integer"
    }
  }
}

2.2.4 删除索引库

  • 语法:
    • 请求方式:DELETE
    • 请求路径:/索引库名
    • 请求参数:无
  • 格式:DELETE /索引库名

2.2.5 总结

  • 索引库操作:
    • 创建索引库:PUT /索引库名
    • 查询索引库:GET /索引库名
    • 删除索引库:DELETE /索引库名
    • 添加字段:PUT /索引库名/_mapping

3 文档操作

  • 文档 – 数据库表中的一行数据

3.1 新增文档

  • 语法:
POST /索引库名/_doc/文档id
{
    "字段1": "值1",
    "字段2": "值2",
    "字段3": {
        "子属性1": "值3",
        "子属性2": "值4"
    },
    // ...
}

3.2 查询文档

  • 语法:GET /{索引库名称}/_doc/{id}

3.3 删除文档

  • 语法:DELETE /{索引库名}/_doc/id值

3.4 修改文档

  • 全量修改:直接覆盖原来的文档,本质是根据指定 id 删除文档然后新增一个相同 id 的文档;
    • 语法:
    PUT /{索引库名}/_doc/文档id
    {
        "字段1": "值1",
        "字段2": "值2",
        // ... 略
    }
    
  • 增量修改:修改文档中的部分字段:
    • 语法:
    POST /{索引库名}/_update/文档id
    {
        "doc": {
             "字段名": "新的值",
        }
    }
    

3.5 总结

  • 文档操作:
    • 创建文档:POST /{索引库名}/_doc/文档id { json文档 }
    • 查询文档:GET /{索引库名}/_doc/文档id
    • 删除文档:DELETE /{索引库名}/_doc/文档id
    • 修改文档:
      • 全量修改:PUT /{索引库名}/_doc/文档id { json文档 }
      • 增量修改:POST /{索引库名}/_update/文档id { "doc": {字段}}

4 RestAPI – RestClient 操作索引库

  • ES 官方提供的用于操作 ES 的各种不同语言的客户端;
  • 本质:组装 DSL 语句,通过 http 请求 发送给 ES;
  • Java Rest Client 包括两种:
    • Java Low Level Rest Client
    • Java High Level Rest Client (使用)

4.0 初始化 RestClient

  • 初始化 RestHighLevelClient 类对象,建立与elasticsearch的连接:
    • 1)引入es的RestHighLevelClient依赖:
    <dependency>
        <groupId>org.elasticsearch.client</groupId>
        <artifactId>elasticsearch-rest-high-level-client</artifactId>
    </dependency>
    
    • 2)因为SpringBoot默认的ES版本是7.6.2,需要覆盖默认的ES版本:
    <properties>
        <java.version>1.8</java.version>
        <elasticsearch.version>7.12.1</elasticsearch.version>
    </properties>
    
    • 3)初始化RestHighLevelClient:
    RestHighLevelClient client = new RestHighLevelClient(RestClient.builder(
            HttpHost.create("http://ES安装IP:9200")
    ));
    

4.1 创建索引库

  • 操作步骤:
    • 1)创建Request对象。因为是创建索引库的操作,因此Request是CreateIndexRequest。
    • 2)添加请求参数,其实就是DSL的JSON参数部分。因为json字符串很长,这里是定义了静态字符串常量MAPPING_TEMPLATE,让代码看起来更加优雅。
    • 3)发送请求,client.indices()方法的返回值是IndicesClient类型,封装了所有与索引库操作有关的方法。
@Test
void createHotelIndex() throws IOException {
    // 1.创建Request对象
    CreateIndexRequest request = new CreateIndexRequest("hotel");
    // 2.准备请求的参数:DSL语句
    request.source(MAPPING_TEMPLATE, XContentType.JSON);
    // 3.发送请求
    client.indices().create(request, RequestOptions.DEFAULT);
}

4.2 删除索引库

  • 操作步骤:
    • 1)创建Request对象。这次是DeleteIndexRequest对象
    • 2)准备参数。这里是无参
    • 3)发送请求。改用delete方法
@Test
void testDeleteHotelIndex() throws IOException {
    // 1.创建Request对象
    DeleteIndexRequest request = new DeleteIndexRequest("hotel");
    // 2.发送请求
    client.indices().delete(request, RequestOptions.DEFAULT);
}

4.3 判断索引库是否存在

  • 判断是否存在 – 本质就是查询
  • 操作步骤:
    • 1)创建Request对象。这次是GetIndexRequest对象
    • 2)准备参数。这里是无参
    • 3)发送请求。改用exists方法
@Test
void testExistsHotelIndex() throws IOException {
    // 1.创建Request对象
    GetIndexRequest request = new GetIndexRequest("hotel");
    // 2.发送请求
    boolean exists = client.indices().exists(request, RequestOptions.DEFAULT);
    // 3.输出
    System.err.println(exists ? "索引库已经存在!" : "索引库不存在!");
}

4.4 总结

  • JavaRestClient操作的核心是用client.indices()方法来获取索引库的操作对象
  • 操作索引库的基本步骤:
    • 0.初始化RestHighLevelClient
    • 1.创建XxxIndexRequest。XXX是Create、Get、Delete
    • 2.准备DSL( Create时需要,其它是无参)
    • 3.发送请求。调用RestHighLevelClient#indices().xxx()方法,xxx是create、exists、delete

5 RestClient 操作文档

5.1 新增文档

  • 操作步骤:
    • 1)创建Request对象
    • 2)准备请求参数,也就是DSL中的JSON文档
    • 3)发送请求 – 直接使用client.xxx()的API,不再需要client.indices()了
      在这里插入图片描述

5.2 查询文档

  • 操作步骤:
    • 1)准备Request对象。这次是查询,所以是GetRequest
    • 2)发送请求,得到结果。因为是查询,这里调用client.get()方法
    • 3)解析结果,就是对JSON做反序列化
      在这里插入图片描述

5.3 删除文档

  • 操作步骤:
    • 1)准备Request对象,因为是删除,这次是DeleteRequest对象。要指定索引库名和id
    • 2)准备参数,无参
    • 3)发送请求。因为是删除,所以是client.delete()方法
@Test
void testDeleteDocument() throws IOException {
    // 1.准备Request
    DeleteRequest request = new DeleteRequest("hotel", "61083");
    // 2.发送请求
    client.delete(request, RequestOptions.DEFAULT);
}

5.4 修改文档

  • 全量修改:本质是根据 id 先删除再新增,与新增的 API 完全一样 – 传入的 ID 已存在就是修改,不存在就是新增;
  • 增量修改:
  • 操作步骤:
    • 1)准备Request对象。这次是修改,所以是UpdateRequest
    • 2)准备参数。也就是JSON文档,里面包含要修改的字段
    • 3)更新文档。这里调用client.update()方法
      在这里插入图片描述

5.5 批量导入文档

  • 批量处理BulkRequest,其本质就是将多个普通的CRUD请求组合在一起发送;
  • add 方法,可以添加新增、修改、删除请求,链式添加
    在这里插入图片描述
  • 操作步骤:
  • 1)创建Request对象。这里是BulkRequest
  • 2)准备参数。批处理的参数,就是其它Request对象,这里就是多个IndexRequest(或者实际需要的操作,批量导入添加就是 IndexRequest)
  • 3)发起请求。这里是批处理,调用的方法为client.bulk()方法
    在这里插入图片描述

5.6 小结

  • 文档操作基本步骤:
    • 0.初始化RestHighLevelClient
    • 1.创建XxxRequest。XXX是Index、Get、Update、Delete、Bulk
    • 2.准备参数(Index、Update、Bulk时需要)
    • 3.发送请求。调用RestHighLevelClient#.xxx()方法,xxx是index、get、update、delete、bulk
    • 4.解析结果(Get时需要)

6 DSL 查询文档

6.1 DSL 查询分类

  • 基于 JSON 的 DSL(Domain Specific Language)进行文档的查询,查询类型包括:
    • 查询所有:查询出所有数据,一般测试用。例如:match_all
    • 全文检索(full text)查询:利用分词器对用户输入内容分词,然后去倒排索引库中匹配。例如:
      • match_query
      • multi_match_query
    • 精确查询:根据精确词条值查找数据,一般是查找keyword、数值、日期、boolean等类型字段。例如:
      • ids
      • range
      • term
    • 地理(geo)查询:根据经纬度查询。例如:
      • geo_distance
      • geo_bounding_box
    • 复合(compound)查询:复合查询可以将上述各种查询条件组合起来,合并查询条件。例如:
      • bool
      • function_score
  • 查询语法
GET /indexName/_search
{
  "query": {
    "查询类型": {
      "查询条件": "条件值"
    }
  }
}
  • 查询所有:
// 查询所有
GET /indexName/_search
{
  "query": {
    "match_all": {
    }
  }
}

6.2 全文检索查询

  • 全文检索查询分类:
    • match查询:单字段查询
    • multi_match查询:多字段查询,任意一个字段符合条件就算符合查询条件
  • match查询语法如下:
GET /indexName/_search
{
  "query": {
    "match": {
      "FIELD": "TEXT"
    }
  }
}
  • mulit_match语法如下:
GET /indexName/_search
{
  "query": {
    "multi_match": {
      "query": "TEXT",
      "fields": ["FIELD1", " FIELD12"]
    }
  }
}
  • 一般将用于查询的字段在创建索引的时候都 copy_to 复制到 all 字段,然后直接用单字段查询 all 字段即可。

6.3 精准查询

  • 精确查询不会对搜索条件分词,常见精确查询有:
    • term:根据词条精确值查询
    • range:根据值的范围查询

6.3.1 term 查询

  • 语法:
// term查询
GET /indexName/_search
{
  "query": {
    "term": {
      "FIELD": {
        "value": "VALUE"
      }
    }
  }
}

6.3.2 range 查询

  • 语法:
// range查询
GET /indexName/_search
{
  "query": {
    "range": {
      "FIELD": {
        "gte": 10, // 这里的gte代表大于等于,gt则代表大于
        "lte": 20 // lte代表小于等于,lt则代表小于
      }
    }
  }
}

6.4 地理坐标查询

  • 地理坐标查询:即根据经纬度查询;

6.4.1 矩形范围查询

  • 矩形范围查询:geo_bounding_box查询,查询坐标落在某个矩形范围的所有文档;查询时指定矩形的左上、右下两个点的坐标
  • 语法:
// geo_bounding_box查询
GET /indexName/_search
{
  "query": {
    "geo_bounding_box": {
      "FIELD": {
        "top_left": { // 左上点
          "lat": 31.1,
          "lon": 121.5
        },
        "bottom_right": { // 右下点
          "lat": 30.9,
          "lon": 121.7
        }
      }
    }
  }
}

6.4.2 附近查询

  • 附近查询:也即距离查询(geo_distance):查询到指定中心点小于某个距离值的所有文档;
  • 语法:
// geo_distance 查询
GET /indexName/_search
{
  "query": {
    "geo_distance": {
      "distance": "15km", // 半径
      "FIELD": "31.21,121.5" // 圆心
    }
  }
}

6.5 复合查询

  • 复合(compound)查询:将其它简单查询组合起来,实现更复杂的逻辑;
  • 常见复合查询分类:
    • function score:算分函数查询,可以控制文档相关性算分,控制文档排名;
    • bool query:布尔查询,利用逻辑关系组合多个其它的查询,实现复杂搜索

6.5.1 相关性算分

  • 打分算法:
    • 早期:TF-IDF算法;
    • 5.1版本后:BM25算法

6.5.2 算分函数查询

  • 语法:
    在这里插入图片描述
  • function score 查询的四部分内容:
    • 原始查询条件:query部分,基于这个条件搜索文档,并且基于BM25算法给文档打分,原始算分(query score)
    • 过滤条件:filter部分,符合该条件的文档才会重新算分
    • 算分函数:符合filter条件的文档要根据这个函数做运算,得到的函数算分(function score),有四种函数
      • weight:函数结果是常量
      • field_value_factor:以文档中的某个字段值作为函数结果
      • random_score:以随机数作为函数结果
      • script_score:自定义算分函数算法
    • 运算模式:算分函数的结果、原始查询的相关性算分,两者之间的运算方式,包括:
      • multiply:相乘
      • replace:用function score替换query score
      • 其它,例如:sum、avg、max、min
  • function score 运行流程
    在这里插入图片描述
  • function score query 定义的三要素:
    • 过滤条件:哪些文档要加分
    • 算分函数:如何计算function score
    • 加权方式:function score 与 query score如何运算

6.5.3 布尔查询

  • 布尔查询:一个或多个查询子句的组合;
  • 子查询:每一个子句;
  • 子查询的组合方式:
    • must:必须匹配每个子查询,类似“与”
    • should:选择性匹配子查询,类似“或”
    • must_not:必须不匹配,不参与算分,类似“非”
    • filter:必须匹配,不参与算分
  • 语法示例:
GET /hotel/_search
{
  "query": {
    "bool": {
      "must": [
        {"term": {"city": "上海" }}
      ],
      "should": [
        {"term": {"brand": "皇冠假日" }},
        {"term": {"brand": "华美达" }}
      ],
      "must_not": [
        { "range": { "price": { "lte": 500 } }}
      ],
      "filter": [
        { "range": {"score": { "gte": 45 } }}
      ]
    }
  }
}
  • 搜索时,参与打分的字段越多,查询的性能越差。

7 搜索结果处理

7.1 排序

  • es 默认根据相关度算分排序;
  • 支持自定义方式对搜索结果排序,支持的字段类型有:keyword 类型、数值类型、地理坐标类型、日期类型

7.1.1 普通字段排序

  • 使用于 keyword、数值、日期类型排序
  • 语法:
GET /indexName/_search
{
  "query": {
    "match_all": {}
  },
  "sort": [
    {
      "FIELD": "desc"  // 排序字段、排序方式ASC、DESC
    }
  ]
}
  • 排序条件是数组,可以写多个排序条件这样当前面条件相等时按照后面的条件排序

7.1.2 地理坐标排序

  • 含义:指定目标坐标点,计算每一个文档中指定字段(geo_point 类型)的坐标到目标点的距离,根据该距离排序;
  • 语法:
GET /indexName/_search
{
  "query": {
    "match_all": {}
  },
  "sort": [
    {
      "_geo_distance" : {
          "FIELD" : "纬度,经度", // 文档中geo_point类型的字段名、目标坐标点
          "order" : "asc", // 排序方式
          "unit" : "km" // 排序的距离单位
      }
    }
  ]
}

7.2 分页

7.2.1 基本分页

  • 基本语法:
GET /hotel/_search
{
  "query": {
    "match_all": {}
  },
  "from": 0, // 分页开始的位置,默认为0
  "size": 10, // 期望获取的文档总数
  "sort": [
    {"price": "asc"}
  ]
}

7.2.2 深度分页问题

在这里插入图片描述

  • 深度分页问题:分页深度较大时,集群部署中,需要从每个节点的从开头到 from + size 的所有文档,聚合到一起然后重新排序选择前 from + size 个,最后截取 from 到第 from + size 个文档——这会对内存和 CPU 会产生非常大的压力,es 会禁止 from + size 超过 10000 的请求;
  • from + size
    • 优点:支持随机翻页
    • 缺点:深度分页问题,默认查询上限(from + size)是10000
    • 场景:百度、京东、谷歌、淘宝这样的随机翻页搜索
  • 解决方案:
    • after search:分页时需要排序,原理是从上一次的排序值开始,查询下一页数据。官方推荐使用的方式。
      • 优点:没有查询上限(单次查询的size不超过10000)
      • 缺点:只能向后逐页查询,不支持随机翻页
      • 场景:没有随机翻页需求的搜索,例如手机向下滚动翻页
    • scroll:原理是将排序后的文档id形成快照,保存在内存。官方已经不推荐使用
      • 优点:没有查询上限(单次查询的size不超过10000)
      • 缺点:会有额外内存消耗,并且搜索结果是非实时的
      • 场景:海量数据的获取和迁移。从ES7.1开始不推荐,建议用 after search方案。

7.3 高亮

7.3.1 高亮原理

  • 高亮显示:搜索时,将搜索的关键字在结果中高亮显示;
  • 高亮显示的步骤:
    • 1)给文档中的所有关键字都添加一个标签,例如<em>标签
    • 2)页面给<em>标签编写CSS样式

7.3.2 实现高亮

  • 高亮语法:
GET /hotel/_search
{
  "query": {
    "match": {
      "FIELD": "TEXT" // 查询条件,高亮一定要使用全文检索查询
    }
  },
  "highlight": {
    "fields": { // 指定要高亮的字段
      "FIELD": {
        "pre_tags": "<em>",  // 用来标记高亮字段的前置标签
        "post_tags": "</em>" // 用来标记高亮字段的后置标签
      }
    }
  }
}
  • 高亮是对关键字高亮,因此搜索条件必须带有关键字,而不能是范围这样的查询。
  • 默认情况下,高亮的字段,必须与搜索指定的字段一致,否则无法高亮
  • 如果要对非搜索字段高亮,则需要添加一个属性:required_field_match=false

7.4 总结

  • 查询 DSL 是一个包含多个属性的 JSON 对象:
    • query:查询条件
    • from和size:分页条件
    • sort:排序条件
    • highlight:高亮条件
      在这里插入图片描述

8 RestClient 查询文档

  • 基本步骤:
    • 1)准备 request 对象;
    • 2)准备请求参数;
    • 3)发起请求;
    • 4)解析响应

8.1 快速入门

  • 利用 RestClient 实现 match_all 查询

8.1.1 发起查询请求

在这里插入图片描述

  • 请求发起步骤:
    • 第一步,创建SearchRequest对象,指定索引库名
    • 第二步,利用request.source()构建DSL,DSL中可以包含查询、分页、排序、高亮等
      • query():代表查询条件,利用QueryBuilders.matchAllQuery()构建一个match_all查询的DSL
    • 第三步,利用client.search()发送请求,得到响应
  • 关键 API:
    • request.source:
      在这里插入图片描述
    • QueryBuilders:
      在这里插入图片描述

8.1.2 解析响应

在这里插入图片描述

  • 返回结果是 JSON 字符串,结构为:
  • hits:命中的结果
    • total:总条数,其中的value是具体的总条数值
    • max_score:所有结果中得分最高的文档的相关性算分
    • hits:搜索结果的文档数组,其中的每个文档都是一个json对象
      • _source:文档中的原始数据,也是json对象
  • 解析顺序:
    • SearchHits:通过response.getHits()获取,就是JSON中的最外层的hits,代表命中的结果
      • SearchHits -- getTotalHits().value:获取总条数信息
      • SearchHits -- getHits():获取SearchHit数组,也就是文档数组
        • SearchHit -- getSourceAsString():获取文档结果中的_source,也就是原始的json文档数据

8.1.3 小结

查询基本步骤:

  1. 创建SearchRequest对象
  2. 准备Request.source(),也就是DSL
    ① QueryBuilders来构建查询条件
    ② 传入Request.source() 的 query() 方法
  3. 发送请求,得到结果
  4. 解析结果(参考JSON结果,从外到内,逐层解析)

8.2 match查询

  • 全文检索的 match 和 multi_match 查询与 match_all 的API 基本一致,只是查询条件有差异:
    在这里插入图片描述
  • API :
    在这里插入图片描述

8.3 精确查询

  • 精确查询分类:
    • term:词条精确匹配
    • range:范围查询
  • API :
    在这里插入图片描述

8.4 布尔查询

在这里插入图片描述

8.5 排序、分页

  • 搜索结果的排序、分页与 query 是同级的参数,使用 request.source() 设置;
  • API :
    在这里插入图片描述

8.6 高亮

  • 高亮代码的差异:
    • 查询的DSL:其中除了查询条件,还需要添加高亮条件,同样是与query同级。
    • 结果解析:结果除了要解析_source文档数据,还要解析高亮结果

8.6.1 高亮请求构建

  • 高亮查询查询条件必须使用全文检索查询,并且要有关键字搜索;
  • API :
    在这里插入图片描述

8.6.2 高亮结果解析

  • 高亮结果与查询文档结果默认分离:
    • 第一步:从结果中获取 source.hit.getSourceAsString(),这部分是非高亮结果的 json 字符串,需反序列化为对象
    • 第二步:获取高亮结果 hit.getHighlightFields(),是一个Map,key是高亮字段名称,值是HighlightField对象,代表高亮值
    • 第三步:从map中根据高亮字段名称,获取高亮字段值对象HighlightField
    • 第四步:从HighlightField中获取Fragments,并转为字符串,得到真正的高亮字符串
    • 第五步:用高亮的结果替换第一步反序列化对象中的飞高亮结果
      在这里插入图片描述

9 案例

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值