当前位置: 首页 > news >正文

Elasticsearch分布式搜索引擎入门

1. 认识Elasticsearch

Elasticsearch结合kibana,Logstash,Beats,是一整套技术栈,被叫做ELK。被广泛应用于在日志数据分析,实时监控等领域。

Elasticsearch对海量数据的搜索时非常快的,远远快于传统的关系型数据库,为什么呢?这取决于它底层采用了一个特殊的索引:倒排索引

  • 文档:每条数据就是一个文档
  • 词条:文档按照语义分成的词语

        传统的关系型数据库通常会给主键值一个聚簇索引(以MySQL为例),真实数据在聚簇索引的叶子节点上,如果通过主键值来做查询,那速度会非常的快。但是模糊查询就不行了,只能全表扫描。而Elasticsearch不仅给给每一个文档的id设置索引,还会给词条来做一个索引。在一次模糊查询过程中,首先会给模糊的词语进行分词,然后根据这个分词去词条里找,因为词条时做过索引的,所以查询会非常块,这时会拿到词条所属的文档id,再拿这些id去文档中查找,文档的id也是做过索引的,所以也是非常快的,于是只有两次查询,就会得到想要的结果。

        那怎么分词呢?需要下载分词的插件,我下载的是IK分词器,下载可以去github下载,且最好和你用的Elasticsearch版本一致。我是用的docker部署的es,并且指定了插件挂载的目录,这里不再赘述。

IK分词有两种模式,一种是ik_smart,另一种是ik_max_word

IK分词器允许我们配置拓展词典来增加自定义的词库:

在ik插件目录下的config目录下有个文件 IKAnalyzer.cfg.xml

        在配置自己的扩展词典的时候,指定一个文件,这个文件就得在当前目录下,文件的每一个词占一行。

下面对Elasticsearch进行和MySQL的对比,以达到对基础概念有一定的理解:

Elasticsearch和MySQL的对比
MySQLElasticsearch说明
TableIndex

索引(index),就是文档的集合,类似数据库

的表(table)

RowDocument

文档(Document),就是一条条的数据,类似

数据库的行(row),文档都是json格式

ColumnField字段(Field),就是JSON文档中的字段,类似数据库的列
SchemaMappingMapping(映射)是索引中文档的约束,例如字段型约束。类似数据库的表结构(Schema)
SQLDSLDSL是Elasticsearch提供的JSON风格的请求语句,用来定义搜索条件

2.Elasticsearch基础操作

        2.1Mapping映射属性和索引库操作

Mapping是对索引库中文档的约束,常见的Mapping属性包括:

  • type:字段数据类型,常见的简单类型有:

    • 字符串:text(可分词的文本)、keyword(精确值,例如:品牌、国家、ip地址)

    • 数值:longintegershortbytedoublefloat

    • 布尔:boolean

    • 日期:date

    • 对象:object

  • index:是否创建索引,默认为true

  • analyzer:使用哪种分词器

  • properties:该字段的子字段

2-1-1:创建索引库和映射示例:

PUT /索引库名称
{"mappings": {"properties": {"字段名":{"type": "text","analyzer": "ik_smart"},"字段名2":{"type": "keyword","index": "false"},"字段名3":{"properties": {"子字段": {"type": "keyword"}}},// ...略}}
}

2-1-2:Elasticsearch的增删改查都遵循restful风格,所以查询就是

GET /资源名

2-1-3:删除就是

DELETE /资源名

2-1-4:而修改其实只能在原有基础上新增:

PUT /索引库名/_mapping
{"properties": {"新字段名":{"type": "integer"}}
}
  • 创建索引库:PUT /索引库名

  • 查询索引库:GET /索引库名

  • 删除索引库:DELETE /索引库名

  • 修改索引库,添加字段:PUT /索引库名/_mapping

        在设计索引库与字段映射的时候,要考虑两个方面,一个是这个字段是否在在页面展示,一个是是否参与搜索。

2.2 文档操作

# 新增文档
POST /索引库名/_doc/文档id
{"字段1": "值1","字段2": "值2","字段3": {"子属性1": "值3","子属性2": "值4"},
}#查询文档
GET /{索引库名称}/_doc/{id}#删除文档
DELETE /{索引库名}/_doc/id值#修改文档有两种方式:
# 1.全量修改,先删除原来的文档,在太添加新的,直接覆盖原来的文档;所以这个修改也可以达到和新增文档一样的效果
PUT /{索引库名}/_doc/文档id
{"字段1": "值1","字段2": "值2",// ... 略
}# 2.局部修改
POST /{索引库名}/_update/文档id
{"doc": {"字段名": "新的值",}
}

批处理文档操作:

POST _bulk
{ "index" : { "_index" : "test", "_id" : "1" } }
{ "field1" : "value1" }{ "delete" : { "_index" : "test", "_id" : "2" } }{ "create" : { "_index" : "test", "_id" : "3" } }
{ "field1" : "value3" }{ "update" : {"_id" : "1", "_index" : "test"} }
{ "doc" : {"field2" : "value2"} }
  • index代表新增操作

    • _index:指定索引库名

    • _id指定要操作的文档id

    • { "field1" : "value1" }:则是要新增的文档内容

  • delete代表删除操作

    • _index:指定索引库名

    • _id指定要操作的文档id

  • update代表更新操作

    • _index:指定索引库名

    • _id指定要操作的文档id

    • { "doc" : {"field2" : "value2"} }:要更新的文档字段

3.初始化RestClient

第一步,引依赖

<dependency><groupId>org.elasticsearch.client</groupId><artifactId>elasticsearch-rest-high-level-client</artifactId>
</dependency>

第二步,因为SpringBoot默认的ES版本是7.17.10,所以我们需要覆盖默认的ES版本,我使用的版本是7.12.1:

  <properties><maven.compiler.source>11</maven.compiler.source><maven.compiler.target>11</maven.compiler.target><elasticsearch.version>7.12.1</elasticsearch.version></properties>

第三步:初始化RestHighLevelClient:

RestHighLevelClient client = new RestHighLevelClient(RestClient.builder(HttpHost.create("http://192.168.150.101:9200")
));

3.1:基于JavaClient创建索引库

@Test
void testCreateIndex() throws IOException {// 准备request对象CreateIndexRequest request = new CreateIndexRequest("items");// 准备请求参数request.source(MAPPING_TEMPLATE, XContentType.JSON);// 发送请求client.indices().create(request, RequestOptions.DEFAULT);
}

其中MAPPING_TEMPLATE就是json格式创建索引库的字符串,如下

private static final String MAPPING_TEMPLATE = "{\n" +"  \"mappings\": {\n" +"    \"properties\": {\n" +"      \"id\":{\n" +"        \"type\": \"keyword\"\n" +"      },\n" +"      \"name\":{\n" +"        \"type\": \"text\",\n" +"        \"analyzer\": \"ik_smart\"\n" +"      },\n" +"      \"price\":{\n" +"        \"type\": \"integer\"\n" +"      },\n" +"      \"image\":{\n" +"        \"type\": \"keyword\",\n" +"        \"index\": false\n" +"      },\n" +"      \"category\":{\n" +"        \"type\": \"keyword\"\n" +"      },\n" +"      \"brand\":{\n" +"        \"type\": \"keyword\"\n" +"      },\n" +"      \"sold\":{\n" +"        \"type\": \"integer\"\n" +"      },\n" +"      \"comment_count\":{\n" +"        \"type\": \"integer\",\n" +"        \"index\": false\n" +"      },\n" +"      \"isAD\":{\n" +"        \"type\": \"boolean\"\n" +"      },\n" +"      \"update_time\":{\n" +"        \"type\": \"date\"\n" +"      }\n" +"    }\n" +"  }\n" +"}";

查询索引库是否存在

@Test
void testGetIndex() throws IOException {//获取request对象GetIndexRequest request = new GetIndexRequest("items");//查询这个索引库是否存在boolean exists = client.indices().exists(request, RequestOptions.DEFAULT);System.out.println("exists = " + exists);
}

删除索引库

@Test
void testDeleteIndex() throws IOException {//获取request对象DeleteIndexRequest request = new DeleteIndexRequest("items");//删除索引库client.indices().delete(request, RequestOptions.DEFAULT);
}

3.2 基于JavaClient创建一条文档,也叫全量修改

@Test
void testIndexDoc() throws IOException {//准备数据Item item = itemService.getById(561178L);ItemDoc itemDoc = BeanUtil.copyProperties(item, ItemDoc.class);String jsonStr = JSONUtil.toJsonStr(itemDoc);//获取request对象IndexRequest request = new IndexRequest("items").id(itemDoc.getId());//准备请求参数request.source(jsonStr, XContentType.JSON);//发送请求client.index(request, RequestOptions.DEFAULT);
}

获取一条文档

@Test
void testGetDoc() throws IOException {//获取request对象GetRequest request = new GetRequest("items").id("561178");//发出请求GetResponse response = client.get(request, RequestOptions.DEFAULT);//拿到源信息String json = response.getSourceAsString();ItemDoc itemDoc = JSONUtil.toBean(json, ItemDoc.class);System.out.println("itemDoc = " + itemDoc);
}

删除一条文档

@Test
void testDeleteDoc() throws IOException {//获取request对象DeleteRequest request = new DeleteRequest("items").id("561178");//发出请求client.delete(request, RequestOptions.DEFAULT);
}

局部修改

@Test
void testUpdateDocument() throws IOException {// 1.准备RequestUpdateRequest request = new UpdateRequest("items", "561178");// 2.准备请求参数request.doc("price", 58800,"commentCount", 1);// 3.发送请求client.update(request, RequestOptions.DEFAULT);
}

4.DSL语句

        4-1:叶子查询

  • 全文检索查询(Full Text Queries):利用分词器对用户输入搜索条件先分词,得到词条,然后再利用倒排索引搜索词条。例如:

    • match:  #全文检索查询的一种,会对用户输入内容分词,然后去倒排索引库检索

    • multi_match   #与match类似,只不过允许同时查询多个字段

GET /{索引库名}/_search
{"query": {"match": {"字段名": "搜索条件"}}
}GET /{索引库名}/_search
{"query": {"multi_match": {"query": "搜索条件","fields": ["字段1", "字段2"]}}
}

精确查询(Term-level queries):不对用户输入搜索条件分词,根据字段内容精确值匹配。但只能查找keyword、数值、日期、boolean类型的字段。例如:

  • ids

  • term     # 这个是精确查询,基于分词表里的精确查询

  • range   

GET /{索引库名}/_search
{"query": {"term": {"字段名": {"value": "搜索条件"}}}
}GET /{索引库名}/_search
{"query": {"range": {"字段名": {"gte": {最小值},"lte": {最大值}}}}
}

地理坐标查询:用于搜索地理位置,搜索方式很多,例如:

  • geo_bounding_box:按矩形搜索

  • geo_distance:按点和半径搜索

4-2:复合查询

  • 第一类:基于逻辑运算组合叶子查询,实现组合条件,例如

    • bool

  • 第二类:基于某种算法修改查询时的文档相关性算分,从而改变文档排名。例如:

    • function_score

    • dis_max

从elasticsearch5.1开始,采用的相关性打分算法是BM25算法,公式如下:

        基于这套公式,就可以判断出某个文档与用户搜索的关键字之间的关联度,还是比较准确的。但是,在实际业务需求中,常常会有竞价排名的功能。不是相关度越高排名越靠前,而是掏的钱多的排名靠前。

要想认为控制相关性算分,就需要利用elasticsearch中的function score 查询了。

基本语法

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的运行流程如下:

  • 1)根据原始条件查询搜索文档,并且计算相关性算分,称为原始算分(query score)

  • 2)根据过滤条件,过滤文档

  • 3)符合过滤条件的文档,基于算分函数运算,得到函数算分(function score)

  • 4)将原始算分(query score)和函数算分(function score)基于运算模式做运算,得到最终结果,作为相关性算分。

因此,其中的关键点是:

  • 过滤条件:决定哪些文档的算分被修改

  • 算分函数:决定函数算分的算法

  • 运算模式:决定最终算分结果

示例:给IPhone这个品牌的手机算分提高十倍,分析如下:

  • 过滤条件:品牌必须为IPhone

  • 算分函数:常量weight,值为10

  • 算分模式:相乘multiply

对应代码如下:

# 算发函数查询
GET /hotel/_search
{"query": {"function_score": {"query": {  .... }, // 原始查询,可以是任意条件"functions": [ // 算分函数{"filter": { // 满足的条件,品牌必须是Iphone"term": {"brand": "Iphone"}},"weight": 10 // 算分权重为2}],"boost_mode": "multipy" // 加权模式,求乘积}}
}

bool查询,即布尔查询。就是利用逻辑运算来组合一个或多个查询子句的组合。bool查询支持的逻辑运算有:

  • must:必须匹配每个子查询,类似“与”

  • should:选择性匹配子查询,类似“或”

  • must_not:必须不匹配,不参与算分,类似“非”

  • filter:必须匹配,不参与算分

bool查询的语法如下:

GET /items/_search
{"query": {"bool": {"must": [{"match": {"name": "手机"}}],"should": [{"term": {"brand": { "value": "vivo" }}},{"term": {"brand": { "value": "小米" }}}],"must_not": [{"range": {"price": {"gte": 2500}}}],"filter": [{"range": {"price": {"lte": 1000}}}]}}
}

        出于性能考虑,与搜索关键字无关的查询尽量采用must_not或filter逻辑运算,避免参与相关性算分。

        排序

        elasticsearch默认是根据相关度算分(_score)来排序,但是也支持自定义方式对搜索结果排序。不过分词字段无法排序,能参与排序字段类型有:keyword类型、数值类型、地理坐标类型、日期类型等。

GET /indexName/_search
{"query": {"match_all": {}},"sort": [{"排序字段": {"order": "排序方式asc和desc"}}]
}
        分页

        基础分页:elasticsearch 默认情况下只返回top10的数据。而如果要查询更多数据就需要修改分页参数了

GET /items/_search
{"query": {"match_all": {}},"from": 0, // 分页开始的位置,默认为0"size": 10,  // 每页文档数量,默认10"sort": [{"price": {"order": "desc"}}]
}

        深度分页

  • search after:分页时需要排序,原理是从上一次的排序值开始,查询下一页数据。官方推荐使用的方式。

不过一般没有深度分页的需求

 高亮显示

实现高亮的思路就是:

  • 用户输入搜索关键字搜索数据

  • 服务端根据搜索关键字到elasticsearch搜索,并给搜索结果中的关键字词条添加html标签

  • 前端提前给约定好的html标签添加CSS样式

GET /{索引库名}/_search
{"query": {"match": {"搜索字段": "搜索关键字"}},"highlight": {"fields": {"高亮字段名称": {"pre_tags": "<em>","post_tags": "</em>"}}}
}

5.Java对于DSL的API

        5.1:叶子查询

matchAll查询

@Test
void testMatchAll() throws IOException {// 1.创建RequestSearchRequest request = new SearchRequest("items");// 2.组织请求参数request.source().query(QueryBuilders.matchAllQuery());// 3.发送请求SearchResponse response = client.search(request, RequestOptions.DEFAULT);// 4.解析响应handleResponse(response);
}private void handleResponse(SearchResponse response) {SearchHits searchHits = response.getHits();// 1.获取总条数long total = searchHits.getTotalHits().value;System.out.println("共搜索到" + total + "条数据");// 2.遍历结果数组SearchHit[] hits = searchHits.getHits();for (SearchHit hit : hits) {// 3.得到_source,也就是原始json文档String source = hit.getSourceAsString();// 4.反序列化并打印ItemDoc item = JSONUtil.toBean(source, ItemDoc.class);System.out.println(item);}
}

代码解读

elasticsearch返回的结果是一个JSON字符串,结构包含:

  • hits:命中的结果

    • total:总条数,其中的value是具体的总条数值

    • max_score:所有结果中得分最高的文档的相关性算分

    • hits:搜索结果的文档数组,其中的每个文档都是一个json对象

      • _source:文档中的原始数据,也是json对象

因此,我们解析响应结果,就是逐层解析JSON字符串,流程如下:

  • SearchHits:通过response.getHits()获取,就是JSON中的最外层的hits,代表命中的结果

    • SearchHits     #getTotalHits().value:获取总条数信息

    • SearchHits      #getHits():获取SearchHit数组,也就是文档数组

      • SearchHit     #getSourceAsString():获取文档结果中的_source,也就是原始的json文档数据

match查询

@Test
void testMatch() throws IOException {// 1.创建RequestSearchRequest request = new SearchRequest("items");// 2.组织请求参数request.source().query(QueryBuilders.matchQuery("name", "脱脂牛奶"));// 3.发送请求SearchResponse response = client.search(request, RequestOptions.DEFAULT);// 4.解析响应handleResponse(response);
}

muti_match查询

@Test
void testMultiMatch() throws IOException {// 1.创建RequestSearchRequest request = new SearchRequest("items");// 2.组织请求参数request.source().query(QueryBuilders.multiMatchQuery("脱脂牛奶", "name", "category"));// 3.发送请求SearchResponse response = client.search(request, RequestOptions.DEFAULT);// 4.解析响应handleResponse(response);
}

range查询

@Test
void testRange() throws IOException {// 1.创建RequestSearchRequest request = new SearchRequest("items");// 2.组织请求参数request.source().query(QueryBuilders.rangeQuery("price").gte(10000).lte(30000));// 3.发送请求SearchResponse response = client.search(request, RequestOptions.DEFAULT);// 4.解析响应handleResponse(response);
}

term查询

@Test
void testTerm() throws IOException {// 1.创建RequestSearchRequest request = new SearchRequest("items");// 2.组织请求参数request.source().query(QueryBuilders.termQuery("brand", "华为"));// 3.发送请求SearchResponse response = client.search(request, RequestOptions.DEFAULT);// 4.解析响应handleResponse(response);
}

5.2:复合查询

@Test
void testBool() throws IOException {// 1.创建RequestSearchRequest request = new SearchRequest("items");// 2.组织请求参数// 2.1.准备bool查询BoolQueryBuilder bool = QueryBuilders.boolQuery();// 2.2.关键字搜索bool.must(QueryBuilders.matchQuery("name", "脱脂牛奶"));// 2.3.品牌过滤bool.filter(QueryBuilders.termQuery("brand", "德亚"));// 2.4.价格过滤bool.filter(QueryBuilders.rangeQuery("price").lte(30000));request.source().query(bool);// 3.发送请求SearchResponse response = client.search(request, RequestOptions.DEFAULT);// 4.解析响应handleResponse(response);
}

5.3:分页和排序

@Test
void testPageAndSort() throws IOException {int pageNo = 1, pageSize = 5;// 1.创建RequestSearchRequest request = new SearchRequest("items");// 2.组织请求参数// 2.1.搜索条件参数request.source().query(QueryBuilders.matchQuery("name", "脱脂牛奶"));// 2.2.排序参数request.source().sort("price", SortOrder.ASC);// 2.3.分页参数request.source().from((pageNo - 1) * pageSize).size(pageSize);// 3.发送请求SearchResponse response = client.search(request, RequestOptions.DEFAULT);// 4.解析响应handleResponse(response);
}

5.4:高亮

@Test
void testHighlight() throws IOException {// 1.创建RequestSearchRequest request = new SearchRequest("items");// 2.组织请求参数// 2.1.query条件request.source().query(QueryBuilders.matchQuery("name", "脱脂牛奶"));// 2.2.高亮条件request.source().highlighter(SearchSourceBuilder.highlight().field("name").preTags("<em>").postTags("</em>"));// 3.发送请求SearchResponse response = client.search(request, RequestOptions.DEFAULT);// 4.解析响应handleResponse(response);
}

代码解读:

  • 3、4步:从结果中获取_sourcehit.getSourceAsString(),这部分是非高亮结果,json字符串。还需要反序列为ItemDoc对象

  • 5步:获取高亮结果。hit.getHighlightFields(),返回值是一个Map,key是高亮字段名称,值是HighlightField对象,代表高亮值

  • 5.1步:从Map中根据高亮字段名称,获取高亮字段值对象HighlightField

  • 5.2步:从HighlightField中获取Fragments,并且转为字符串。这部分就是真正的高亮字符串了

  • 最后:用高亮的结果替换ItemDoc中的非高亮结果

//完整代码private void handleResponse(SearchResponse response) {SearchHits searchHits = response.getHits();// 1.获取总条数long total = searchHits.getTotalHits().value;System.out.println("共搜索到" + total + "条数据");// 2.遍历结果数组SearchHit[] hits = searchHits.getHits();for (SearchHit hit : hits) {// 3.得到_source,也就是原始json文档String source = hit.getSourceAsString();// 4.反序列化ItemDoc item = JSONUtil.toBean(source, ItemDoc.class);// 5.获取高亮结果Map<String, HighlightField> hfs = hit.getHighlightFields();if (CollUtils.isNotEmpty(hfs)) {// 5.1.有高亮结果,获取name的高亮结果HighlightField hf = hfs.get("name");if (hf != null) {// 5.2.获取第一个高亮结果片段,就是商品名称的高亮值String hfName = hf.getFragments()[0].string();item.setName(hfName);}}System.out.println(item);}
}

6.数据聚合

        聚合(aggregations)可以让我们极其方便的实现对数据的统计、分析、运算。

聚合常见的有三类:

  • 桶(Bucket聚合:用来对文档做分组(类似于MySQL的group by)

    • TermAggregation:按照文档字段值分组,例如按照品牌值分组、按照国家分组

    • Date Histogram:按照日期阶梯分组,例如一周为一组,或者一月为一组

  • 度量(Metric聚合:用以计算一些值,比如:最大值、最小值、平均值等

    • Avg:求平均值

    • Max:求最大值

    • Min:求最小值

    • Stats:同时求maxminavgsum

  • 管道(pipeline聚合:其它聚合的结果为基础做进一步运算

注意参加聚合的字段必须是keyword、日期、数值、布尔类型

GET /items/_search
{"size": 0, "aggs": {"category_agg": {"terms": {"field": "category","size": 20}}}
}

语法说明:

  • size:设置size为0,就是每页查0条,则结果中就不包含文档,只包含聚合

  • aggs:定义聚合

    • category_agg:聚合名称,自定义,但不能重复

      • terms:聚合的类型,按分类聚合,所以用term

        • field:参与聚合的字段名称

        • size:希望返回的聚合结果的最大数量

带条件的聚合

GET /items/_search
{"query": {"bool": {"filter": [{"term": {"category": "手机"}},{"range": {"price": {"gte": 300000}}}]}}, "size": 0, "aggs": {"brand_agg": {"terms": {"field": "brand","size": 20}}}
}

Metric聚合

GET /items/_search
{"query": {"bool": {"filter": [{"term": {"category": "手机"}},{"range": {"price": {"gte": 300000}}}]}}, "size": 0, "aggs": {"brand_agg": {"terms": {"field": "brand","size": 20},"aggs": {"stats_meric": {"stats": {"field": "price"}}}}}
}

可以看到我们在brand_agg聚合的内部,我们新加了一个aggs参数。这个聚合就是brand_agg的子聚合,会对brand_agg形成的每个桶中的文档分别统计。

  • stats_meric:聚合名称

    • stats:聚合类型,stats是metric聚合的一种

      • field:聚合字段,这里选择price,统计价格


Java客户端代码写数据聚合

聚合结果与搜索文档同一级别,因此需要单独获取和解析。具体解析语法如下:

完整代码如下

@Test
void testAgg() throws IOException {// 1.创建RequestSearchRequest request = new SearchRequest("items");// 2.准备请求参数BoolQueryBuilder bool = QueryBuilders.boolQuery().filter(QueryBuilders.termQuery("category", "手机")).filter(QueryBuilders.rangeQuery("price").gte(300000));request.source().query(bool).size(0);// 3.聚合参数request.source().aggregation(AggregationBuilders.terms("brand_agg").field("brand").size(5));// 4.发送请求SearchResponse response = client.search(request, RequestOptions.DEFAULT);// 5.解析聚合结果Aggregations aggregations = response.getAggregations();// 5.1.获取品牌聚合Terms brandTerms = aggregations.get("brand_agg");// 5.2.获取聚合中的桶List<? extends Terms.Bucket> buckets = brandTerms.getBuckets();// 5.3.遍历桶内数据for (Terms.Bucket bucket : buckets) {// 5.4.获取桶内keyString brand = bucket.getKeyAsString();System.out.print("brand = " + brand);long count = bucket.getDocCount();System.out.println("; count = " + count);}
}

相关文章:

  • Anaconda虚拟环境创建和配置以使用PyTorch和DGL
  • 机器学习课程学习周报十三
  • LLM - 使用 XTuner 指令微调 多模态大语言模型(InternVL2) 教程
  • OJ在线评测系统 后端 判题机模块预开发 架构分析 使用工厂模式搭建
  • 【在Linux世界中追寻伟大的One Piece】进程间通信
  • Rapid品牌SSL证书通配符单域名申请窍门
  • 背景图鼠标放上去切换图片过渡效果
  • docker笔记_数据卷、挂载
  • 2024年【烟花爆竹经营单位主要负责人】免费试题及烟花爆竹经营单位主要负责人考试技巧
  • (已解决)vscode如何选择python解释器
  • Docker 教程:如何查看容器的最后 300 行实时日志
  • docker修改默认存储路径和网段
  • pdf编辑转换器怎么用?分享9个pdf编辑、转换方法(纯干货)
  • CentOS Stream 9部署Redis
  • C语言中的一些小知识(三)
  • 【Leetcode】104. 二叉树的最大深度
  • 4月23日世界读书日 网络营销论坛推荐《正在爆发的营销革命》
  • 8年软件测试工程师感悟——写给还在迷茫中的朋友
  • CSS盒模型深入
  • extjs4学习之配置
  • java 多线程基础, 我觉得还是有必要看看的
  • JAVA 学习IO流
  • js 实现textarea输入字数提示
  • MobX
  • SAP云平台运行环境Cloud Foundry和Neo的区别
  • SegmentFault 技术周刊 Vol.27 - Git 学习宝典:程序员走江湖必备
  • socket.io+express实现聊天室的思考(三)
  • Vue全家桶实现一个Web App
  • Web标准制定过程
  • 读懂package.json -- 依赖管理
  • 基于遗传算法的优化问题求解
  • 每天一个设计模式之命令模式
  • 前端面试之CSS3新特性
  • 收藏好这篇,别再只说“数据劫持”了
  • 微信小程序--------语音识别(前端自己也能玩)
  • 优化 Vue 项目编译文件大小
  • 【云吞铺子】性能抖动剖析(二)
  • ​力扣解法汇总946-验证栈序列
  • ​你们这样子,耽误我的工作进度怎么办?
  • ​香农与信息论三大定律
  • #window11设置系统变量#
  • $redis-setphp_redis Set命令,php操作Redis Set函数介绍
  • $分析了六十多年间100万字的政府工作报告,我看到了这样的变迁
  • (2)(2.10) LTM telemetry
  • (26)4.7 字符函数和字符串函数
  • (6)添加vue-cookie
  • (附源码)计算机毕业设计SSM教师教学质量评价系统
  • (六)c52学习之旅-独立按键
  • (七)微服务分布式云架构spring cloud - common-service 项目构建过程
  • (强烈推荐)移动端音视频从零到上手(上)
  • (十八)SpringBoot之发送QQ邮件
  • (未解决)jmeter报错之“请在微信客户端打开链接”
  • (原创)Stanford Machine Learning (by Andrew NG) --- (week 9) Anomaly DetectionRecommender Systems...
  • (转)memcache、redis缓存
  • (转)原始图像数据和PDF中的图像数据