7、Search APIs
7.1、SearchSourceBuilder
控制搜索行为的大多数选项都可以在SearchSourceBuilder上设置,它或多或少包含与Rest API的搜索请求体中的选项相当的选项。
SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
//查询语句
sourceBuilder.query(QueryBuilders.termQuery("user", "kimchy"));
//from
sourceBuilder.from(0);
//size
sourceBuilder.size(5);
//超时
sourceBuilder.timeout(new TimeValue(60, TimeUnit.SECONDS));
7.1.1、排序(SortBuilder)
SearchSourceBuilder允许添加一个或多个SortBuilder实例。有四种特殊的实现:
- FieldSortBuilder:字段排序
- GeoDistanceSortBuilder:Geo距离排序
- ScoreSortBuilder:评分排序
- ScriptSortBuilder:脚本排序
//评分排序
sourceBuilder.sort(new ScoreSortBuilder().order(SortOrder.DESC));
//字段排序
sourceBuilder.sort(new FieldSortBuilder("id").order(SortOrder.ASC));
7.1.2、字段过滤
//不获取_source字段
sourceBuilder.fetchSource(false);
//通过includes和exclude过滤
String[] includeFields = new String[] {"title", "innerObject.*"};
String[] excludeFields = new String[] {"user"};
sourceBuilder.fetchSource(includeFields, excludeFields);
7.1.3、高亮
突出显示搜索结果可以通过在SearchSourceBuilder上设置HighlightBuilder来实现。通过添加一个或多个HighlightBuilder,可以为每个字段定义不同的突出显示行为。
//创建一个HighlightBuilder
HighlightBuilder highlightBuilder = new HighlightBuilder();
//创建一个高亮字段
HighlightBuilder.Field highlightTitle = new HighlightBuilder.Field("title");
//设置字段高亮显示类型
highlightTitle.highlighterType("unified");
//将字段高亮添加到高亮器中
highlightBuilder.field(highlightTitle);
//第二个高亮字段
HighlightBuilder.Field highlightUser = new HighlightBuilder.Field("user");
//添加到高亮器
highlightBuilder.field(highlightUser);
//高亮器加入到builder
sourceBuilder.highlighter(highlightBuilder);
7.1.4、聚合
聚合可以通过首先创建适当的AggregationBuilder,然后在SearchSourceBuilder上设置它来添加到搜索中
//创建一个聚合Builder,指定集合类型、字段
TermsAggregationBuilder aggregation = AggregationBuilders.terms("by_company")
.field("company.keyword");
//子聚合
aggregation.subAggregation(AggregationBuilders.avg("average_age")
.field("age"));
//加入到Builder
sourceBuilder.aggregation(aggregation);
7.1.5、Profile API
Profile API可用于分析特定搜索请求的查询和聚合的执行情况。为了使用它,profile标志必须在SearchSourceBuilder上设置为true:
SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
searchSourceBuilder.profile(true);
7.2、QueryBuilder
搜索查询是使用QueryBuilder对象创建的。QueryBuilder存在于Elasticsearch的查询DSL支持的每种搜索查询类型。
共有两种创建方式:
- 使用构造函数new一个
MatchQueryBuilder matchQueryBuilder = new MatchQueryBuilder("user", "kimchy");
matchQueryBuilder.fuzziness(Fuzziness.AUTO);
matchQueryBuilder.prefixLength(3);
matchQueryBuilder.maxExpansions(10);
- 使用QueryBuilders工具类创建
QueryBuilder matchQueryBuilder = QueryBuilders.matchQuery("user", "kimchy")
.fuzziness(Fuzziness.AUTO)
.prefixLength(3)
.maxExpansions(10);
7.2.1、简单查询
//查询所有
MatchAllQueryBuilder matchAllQueryBuilder = QueryBuilders.matchAllQuery();
//词项查询
TermQueryBuilder termQueryBuilder = QueryBuilders.termQuery("title", "hello");
//全文检索
QueryBuilders.matchQuery("content", "content")
.analyzer("english");//设置分词器
//分词查询
QueryBuilders.matchPhraseQuery("content", "content")
.slop(2);//指定slop
//前缀分词匹配
QueryBuilders.matchBoolPrefixQuery("conotnet", "con")
.maxExpansions(1);//最大匹配数
//多字段 搜索title、content字段中的hello
QueryBuilders.multiMatchQuery("hello", "title", "content")
.field("title", 1.2f);//指定字段权重
//query_string
QueryBuilders.queryStringQuery("(title:Reversus) OR (content:Nanette)")
.field("content", 1.2f);//指定字段权重
//query_string升级版:使用 +、|、- 代替 AND、OR、NOT 等。
QueryBuilders.simpleQueryStringQuery("lake + street")
.field("content");
//词项查询
QueryBuilders.termQuery("title.keyword", "hello");
//多词项查询
QueryBuilders.termsQuery("title.keyword", "value1", "value2");
//范围查询
QueryBuilders.rangeQuery("age")
.gte("18")
.lte("28");
//非空查询
QueryBuilders.existsQuery("content");
//前缀匹配
QueryBuilders.prefixQuery("content", "prefix_value");
//通配符查询
QueryBuilders.wildcardQuery("content", "h?ll*");
//正则查询
QueryBuilders.regexpQuery("content", "na.*");
//模糊查询
QueryBuilders.fuzzyQuery("title", "javo");
//多id查询
QueryBuilders.idsQuery()
.addIds("1", "2", "3");
7.2.2、复合查询
7.2.2.1、constant_score query——无关词频的查询
@Test
public void constant_score_query() throws IOException {
SearchRequest searchRequest = new SearchRequest("bank");
SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
/**
* constant_score query 无关词频的查询
* GET /bank/_search
* {
* "query": {
* "constant_score": {
* "filter": {
* "match": {
* "employer": "Chillium"
* }
* },
* "boost": 1.2
* }
* }
* }
*/
ConstantScoreQueryBuilder queryBuilder = QueryBuilders.constantScoreQuery(
QueryBuilders.matchQuery("employer", "Chillium")
);
sourceBuilder.query(queryBuilder);
searchRequest.source(sourceBuilder);
SearchResponse response = client.search(searchRequest, RequestOptions.DEFAULT);
System.out.println(response.toString());
}
7.2.2.2、bool query——逻辑组合查询
@Test
public void bool_query() throws IOException {
SearchRequest searchRequest = new SearchRequest("bank");
SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
/**
* bool query——逻辑组合查询
* GET /bank/_search
* {
* "query": {
* "bool": {
* "must": [
* {
* "match": {
* "address": "street"
* }
* }
* ],
* "should": [
* {
* "term": {
* "balance": {
* "value": 1
* }
* }
* }
* ],
* "must_not": [
* {
* "range": {
* "age": {
* "gte": 20,
* "lte": 39
* }
* }
* }
* ]
* }
* },
* "from": 0,
* "size": 2
* }
*/
BoolQueryBuilder queryBuilder = QueryBuilders.boolQuery()
.must(QueryBuilders.matchQuery("address", "street"))
.should(QueryBuilders.termQuery("balance", 1))
.mustNot(QueryBuilders.rangeQuery("age").gte(20).lte(39));
sourceBuilder.query(queryBuilder).from(0).size(2);
searchRequest.source(sourceBuilder);
SearchResponse response = client.search(searchRequest, RequestOptions.DEFAULT);
System.out.println(response.toString());
}
7.2.2.3、dis_max query——分离最大化查询
@Test
public void dis_max_query() throws IOException {
SearchRequest searchRequest = new SearchRequest("blog");
SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
/**
* dis_max query——分离最大化查询
* GET /blog/_search
* {
* "query": {
* "dis_max": {
* "tie_breaker": 0.7,
* "boost": 1.2,
* "queries": [
* {
* "match": {
* "title": "java解决方案"
* }
* },
* {
* "match": {
* "content": "java解决方案"
* }
* }
* ]
* }
* }
* }
*/
QueryBuilder queryBuilder = QueryBuilders.disMaxQuery()
.tieBreaker(0.7f)
.boost(1.2f)
.add(QueryBuilders.matchQuery("title", "java解决方案"))
.add(QueryBuilders.matchQuery("content", "java解决方案"));
sourceBuilder.query(queryBuilder);
searchRequest.source(sourceBuilder);
SearchResponse response = client.search(searchRequest, RequestOptions.DEFAULT);
System.out.println(response.toString());
}
7.2.2.4、function_score query——自定义评分
@Test
public void function_score_query() throws IOException {
SearchRequest searchRequest = new SearchRequest("blog");
SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
/**
* function_score query——自定义评分
* GET /blog/_search
* {
* "query": {
* "function_score": {
* "query": {
* "match": {
* "title": "java"
* }
* },
* "functions": [
* {
* "weight": 10
* }
* ]
* }
* }
* }
*/
WeightBuilder weightBuilder = ScoreFunctionBuilders.weightFactorFunction(10);
FunctionScoreQueryBuilder.FilterFunctionBuilder[] filterFunctionBuilders = new FunctionScoreQueryBuilder.FilterFunctionBuilder[1];
filterFunctionBuilders[0] = new FunctionScoreQueryBuilder.FilterFunctionBuilder(weightBuilder);
QueryBuilder queryBuilder = QueryBuilders.functionScoreQuery(
QueryBuilders.matchQuery("title", "java"),
filterFunctionBuilders
);
sourceBuilder.query(queryBuilder);
searchRequest.source(sourceBuilder);
SearchResponse response = client.search(searchRequest, RequestOptions.DEFAULT);
System.out.println(response.toString());
}
7.2.2.5、boosting query——子句加权查询
@Test
public void boosting_query() throws IOException {
SearchRequest searchRequest = new SearchRequest("blog");
SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
/**
* boosting query——子句加权查询
* GET /blog/_search
* {
* "query": {
* "boosting": {
* "positive": {
* "match": { "title": "java" }
* },
* "negative": {
* "term": { "votes": 10 }
* },
* "negative_boost": 0.5
* }
* }
* }
*/
QueryBuilder queryBuilder = QueryBuilders.boostingQuery(
QueryBuilders.matchQuery("title", "java"),
QueryBuilders.termQuery("votes", 10)
).negativeBoost(0.5f);
sourceBuilder.query(queryBuilder);
searchRequest.source(sourceBuilder);
SearchResponse response = client.search(searchRequest, RequestOptions.DEFAULT);
System.out.println(response.toString());
}
7.2.2.6、has_child query——子查父
@Test
public void has_child_query() throws IOException {
SearchRequest searchRequest = new SearchRequest("stu_class");
SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
/**
* has_child query——子查父
* GET /stu_class/_search
* {
* "query": {
* "has_child": {
* "type": "student",
* "query": {
* "match": {
* "name": "wangwu"
* }
* }
* }
* }
* }
*/
HasChildQueryBuilder queryBuilder = new HasChildQueryBuilder(
"student",
QueryBuilders.matchQuery("name", "wangwu"),
ScoreMode.Max
);
sourceBuilder.query(queryBuilder);
searchRequest.source(sourceBuilder);
SearchResponse response = client.search(searchRequest, RequestOptions.DEFAULT);
System.out.println(response.toString());
}
7.2.2.7、has_parent query——父查子
@Test
public void has_parent_query() throws IOException {
SearchRequest searchRequest = new SearchRequest("stu_class");
SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
/**
* has_parent query——父查子
* GET /stu_class/_search
* {
* "query": {
* "has_parent": {
* "parent_type": "class",
* "query": {
* "match": {
* "name": "一班"
* }
* }
* }
* }
* }
*/
HasParentQueryBuilder queryBuilder = new HasParentQueryBuilder(
"class",
QueryBuilders.matchQuery("name", "一班"),
false
);
sourceBuilder.query(queryBuilder);
searchRequest.source(sourceBuilder);
SearchResponse response = client.search(searchRequest, RequestOptions.DEFAULT);
System.out.println(response.toString());
}
7.2.2.8、parent_id——父id查子文档
@Test
public void parent_id() throws IOException {
SearchRequest searchRequest = new SearchRequest("stu_class");
SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
/**
* parent_id——父id查子文档
* GET /stu_class/_search
* {
* "query": {
* "parent_id": {
* "type": "student",
* "id": 2
* }
* }
* }
*/
ParentIdQueryBuilder queryBuilder = new ParentIdQueryBuilder(
"student",
"2"
);
sourceBuilder.query(queryBuilder);
searchRequest.source(sourceBuilder);
SearchResponse response = client.search(searchRequest, RequestOptions.DEFAULT);
System.out.println(response.toString());
}
7.2.2.9、nested query——嵌套查询
@Test
public void nested_query() throws IOException {
SearchRequest searchRequest = new SearchRequest("movies");
SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
/**
* nested query——嵌套查询
* GET /movies/_search
* {
* "query": {
* "nested": {
* "path": "actors",
* "query": {
* "bool": {
* "must": [
* {
* "match": {
* "actors.name": "张国荣"
* }
* },
* {
* "match": {
* "actors.gender": "男"
* }
* }
* ]
* }
* }
* }
* }
* }
*/
QueryBuilder queryBuilder = QueryBuilders.nestedQuery(
"actors",
QueryBuilders.boolQuery()
.must(QueryBuilders.matchQuery("actors.name", "张国荣"))
.must(QueryBuilders.matchQuery("actors.gender", "男")),
ScoreMode.Max
);
sourceBuilder.query(queryBuilder);
searchRequest.source(sourceBuilder);
SearchResponse response = client.search(searchRequest, RequestOptions.DEFAULT);
System.out.println(response.toString());
}
7.2.3、地理位置查询
7.2.3.1、geo_distance query
@Test
public void geo_distance_query() throws IOException {
SearchRequest searchRequest = new SearchRequest("geo");
SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
/**
* geo_distance query
* GET /geo/_search
* {
* "query": {
* "bool": {
* "must": [
* {
* "match_all": {}
* }
* ],
* "filter": [
* {
* "geo_distance": {
* "distance": "600km",
* "location": {
* "lat": 34.288991865037524,
* "lon": 108.9404296875
* }
* }
* }
* ]
* }
* }
* }
*/
QueryBuilder queryBuilder = QueryBuilders.boolQuery()
.must(QueryBuilders.matchAllQuery())
.filter(
QueryBuilders.geoDistanceQuery("location")
.distance("600km")
.point(34.288991865037524d, 108.9404296875d)
);
sourceBuilder.query(queryBuilder);
searchRequest.source(sourceBuilder);
SearchResponse response = client.search(searchRequest, RequestOptions.DEFAULT);
System.out.println(response.toString());
}
7.2.3.2、geo_bounding_box query
@Test
public void geo_bounding_box_query() throws IOException {
SearchRequest searchRequest = new SearchRequest("geo");
SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
/**
* geo_bounding_box_query
* GET /geo/_search
* {
* "query": {
* "bool": {
* "must": [
* {
* "match_all": {}
* }
* ],
* "filter": [
* {
* "geo_bounding_box": {
* "location": {
* "top_right": {
* "lat": 32.0639555946604,
* "lon": 118.78967285156249
* },
* "bottom_left": {
* "lat": 29.98824461550903,
* "lon": 122.20642089843749
* }
* }
* }
* }
* ]
* }
* }
* }
*/
QueryBuilder queryBuilder = QueryBuilders.boolQuery()
.must(QueryBuilders.matchAllQuery())
.filter(
QueryBuilders.geoBoundingBoxQuery("location")
.setCornersOGC(
new GeoPoint(29.98824461550903d, 122.20642089843749d),
new GeoPoint(32.0639555946604d, 118.78967285156249d)
)
);
sourceBuilder.query(queryBuilder);
searchRequest.source(sourceBuilder);
SearchResponse response = client.search(searchRequest, RequestOptions.DEFAULT);
System.out.println(response.toString());
}
7.2.3.3、geo_polygon query
@Test
public void geo_polygon_query() throws IOException {
SearchRequest searchRequest = new SearchRequest("geo");
SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
/**
* geo_polygon query
* GET /geo/_search
* {
* "query": {
* "bool": {
* "must": [
* {
* "match_all": {}
* }
* ],
* "filter": [
* {
* "geo_polygon": {
* "location": {
* "points": [
* {
* "lat": 31.793755581217674,
* "lon": 113.8238525390625
* },
* {
* "lat": 30.007273923504556,
* "lon": 114.224853515625
* },
* {
* "lat": 30.007273923504556,
* "lon": 114.8345947265625
* }
* ]
* }
* }
* }
* ]
* }
* }
* }
*/
List<GeoPoint> list = new ArrayList<>();
list.add(new GeoPoint(31.793755581217674d, 113.8238525390625d));
list.add(new GeoPoint(30.007273923504556d, 114.224853515625));
list.add(new GeoPoint(30.007273923504556d, 114.8345947265625d));
QueryBuilder queryBuilder = QueryBuilders.boolQuery()
.must(QueryBuilders.matchAllQuery())
.filter(
QueryBuilders.geoPolygonQuery(
"location",
list
)
);
sourceBuilder.query(queryBuilder);
searchRequest.source(sourceBuilder);
SearchResponse response = client.search(searchRequest, RequestOptions.DEFAULT);
System.out.println(response.toString());
}
7.2.3.4、geo_shape query
@Test
public void geo_shape_query() throws IOException {
SearchRequest searchRequest = new SearchRequest("geo_shape");
SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
/**
* geo_shape query
* GET /geo_shape/_search
* {
* "query": {
* "bool": {
* "must": [
* {
* "match_all": {}
* }
* ],
* "filter": [
* {
* "geo_shape": {
* "location": {
* "shape": {
* "type": "envelope",
* "coordinates": [
* [
* 106.5234375,
* 36.80928470205937
* ],
* [
* 115.3344726625,
* 32.24997445586331
* ]
* ]
* },
* "relation": "within"
* }
* }
* }
* ]
* }
* }
* }
*/
QueryBuilder queryBuilder = QueryBuilders.boolQuery()
.must(QueryBuilders.matchAllQuery())
.filter(
QueryBuilders.geoShapeQuery(
"location",
new Rectangle(106.5234375, 115.3344726625, 36.80928470205937,32.24997445586331 )
).relation(ShapeRelation.WITHIN)
);
sourceBuilder.query(queryBuilder);
searchRequest.source(sourceBuilder);
SearchResponse response = client.search(searchRequest, RequestOptions.DEFAULT);
System.out.println(response.toString());
}
7.2.4、特殊查询
7.2.4.1、more_like_this query——基于内容的推荐
@Test
public void more_like_this_query() throws IOException{
SearchRequest searchRequest = new SearchRequest("bank");
SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
/**
* more_like_this query——基于内容的推荐
* GET /bank/_search
* {
* "query": {
* "more_like_this": {
* "fields": [
* "address"
* ],
* "like": "madison street",
* "min_term_freq": 1,
* "max_query_terms": 12
* }
* }
* }
*/
QueryBuilder queryBuilder = QueryBuilders.moreLikeThisQuery(
new String[]{"address"},
new String[]{"madison street"},
null
).minTermFreq(1).maxQueryTerms(12);
sourceBuilder.query(queryBuilder);
searchRequest.source(sourceBuilder);
SearchResponse response = client.search(searchRequest, RequestOptions.DEFAULT);
System.out.println(response.toString());
}
7.2.4.2、script query——脚本查询
@Test
public void script_query() throws IOException {
SearchRequest searchRequest = new SearchRequest("bank");
SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
/**
* script query——脚本查询
* GET /bank/_search
* {
* "query": {
* "bool": {
* "filter": [
* {
* "script": {
* "script": {
* "lang": "painless",
* "source": "doc['age'].value > 39"
* }
* }
* }
* ]
* }
* }
* }
*/
QueryBuilder queryBuilder = QueryBuilders.boolQuery()
.filter(
QueryBuilders.scriptQuery(
new Script(
ScriptType.INLINE,
"painless",
"doc['age'].value > 39",
new HashMap<>()
)
)
);
sourceBuilder.query(queryBuilder);
searchRequest.source(sourceBuilder);
SearchResponse response = client.search(searchRequest, RequestOptions.DEFAULT);
System.out.println(response.toString());
}
7.3、Search API
@Test
public void search_test() throws IOException {
//最基本的形式:
//创建SearchRequest。如果没有参数,这将对所有索引运行。
SearchRequest searchRequest = new SearchRequest("bank");
//构建搜索参数
SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
//添加一个match_all查询
searchSourceBuilder.query(QueryBuilders.matchAllQuery());
//添加到请求
searchRequest.source(searchSourceBuilder);
//同步请求
SearchResponse searchResponse = client.search(searchRequest, RequestOptions.DEFAULT);
}
7.3.1、提交请求
同步提交:
SearchResponse searchResponse = client.search(searchRequest, RequestOptions.DEFAULT);
异步提交:
ActionListener<SearchResponse> listener = new ActionListener<SearchResponse>() {
@Override
public void onResponse(SearchResponse searchResponse) {
//成功
}
@Override
public void onFailure(Exception e) {
//失败
}
};
client.searchAsync(searchRequest, RequestOptions.DEFAULT, listener);
7.3.2、响应(SearchResponse)
7.3.2.1、普通响应信息
普通响应信息:
//响应
//Http状态码
RestStatus status = searchResponse.status();
//花费的时间
TimeValue took = searchResponse.getTook();
//是否提前中止
Boolean terminatedEarly = searchResponse.isTerminatedEarly();
//是否超时
boolean timedOut = searchResponse.isTimedOut();
//搜索影响的总分片数
int totalShards = searchResponse.getTotalShards();
//搜索成功的分片数
int successfulShards = searchResponse.getSuccessfulShards();
//搜索失败的分片数
int failedShards = searchResponse.getFailedShards();
//分片上的失败信息
for (ShardSearchFailure failure : searchResponse.getShardFailures()) {
// failures should be handled here
System.out.println(failure.getMessage());
}
7.3.2.2、命中的结果信息
响应中包含的SearchHits:
//搜索结果
SearchHits hits = searchResponse.getHits();
//SearchHits提供了关于所有命中的全局信息,如命中总数或最高分数:
TotalHits totalHits = hits.getTotalHits();
//总的命中数
long numHits = totalHits.value;
//命中数是准确的(equal_to),还是总数的下限(greater_than_equal_to)
TotalHits.Relation relation = totalHits.relation;
//最高评分
float maxScore = hits.getMaxScore();
//遍历单个搜索结果
SearchHit[] searchHits = hits.getHits();
for (SearchHit hit : searchHits) {
//SearchHit提供访问基本信息,如索引、文档ID和每个搜索命中的分数:
String index = hit.getIndex();
String id = hit.getId();
float score = hit.getScore();
//以简单的JSON-String返回文档源。
String sourceAsString = hit.getSourceAsString();
//以map键/值对映射的形式返回文档源。常规字段由字段名作为关键字,并包含字段值。
//多值字段作为对象列表返回,嵌套对象作为另一个map键/值映射返回。
Map<String, Object> sourceAsMap = hit.getSourceAsMap();
String documentTitle = (String) sourceAsMap.get("title");
List<Object> users = (List<Object>) sourceAsMap.get("user");
Map<String, Object> innerObject = (Map<String, Object>) sourceAsMap.get("innerObject");
}
7.3.2.3、高亮信息
//如果请求包含高亮字段,hit对象提供了对HighlightField实例的字段名映射的访问,
//每个实例包含一个或多个高亮文本片段:
for (SearchHit hit : hits.getHits()) {
Map<String, HighlightField> highlightFields = hit.getHighlightFields();
//获取title字段的高亮显示
HighlightField highlight = highlightFields.get("title");
//获取一个或多个包含突出显示的字段内容的片段
Text[] fragments = highlight.fragments();
String fragmentString = fragments[0].string();
}
7.3.2.4、聚合信息
//通过首先获取聚合树的根,即aggregs对象,然后按名称获取聚合,可以从SearchResponse中检索聚合
Aggregations aggregations = searchResponse.getAggregations();
//根据名称,获取by_company terms聚合
Terms byCompanyAggregation = aggregations.get("by_company");
//获取Elastic相关的桶
Terms.Bucket elasticBucket = byCompanyAggregation.getBucketByKey("Elastic");
//从该桶获取average_age子聚合
Avg averageAge = elasticBucket.getAggregations().get("average_age");
//获取值
double avg = averageAge.getValue();
//注意,如果通过名称访问聚合,需要根据你请求的聚合类型指定聚合接口,否则将抛出ClassCastException:
Range range = aggregations.get("by_company");
//还可以将所有聚合作为由聚合名称键控的映射来访问。在这种情况下,需要显式地转换到适当的聚合接口:
Map<String, Aggregation> aggregationMap = aggregations.getAsMap();
Terms companyAggregation = (Terms) aggregationMap.get("by_company");
//也可以将所有顶级聚合作为列表返回:
List<Aggregation> aggregationList = aggregations.asList();
//可以遍历所有的聚合,然后决定如何根据它们的类型进一步处理它们:
for (Aggregation agg : aggregations) {
String type = agg.getType();
if (type.equals(TermsAggregationBuilder.NAME)) {
Terms.Bucket bucket = ((Terms) agg).getBucketByKey("Elastic");
long numberOfDocs = bucket.getDocCount();
}
}
7.4、Search Scroll API
Scroll API可用于从搜索请求中检索大量结果。
@Test
public void search_scroll() throws IOException {
//1.初始化搜索滚动上下文
SearchRequest searchRequest = new SearchRequest("bank");
SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
searchSourceBuilder.query(QueryBuilders.matchQuery("address", "street"))
.size(2);//一次检索多少结果
searchRequest.source(searchSourceBuilder);
//设置滚动间隔
searchRequest.scroll(TimeValue.timeValueMinutes(1L));
SearchResponse searchResponse = client.search(searchRequest, RequestOptions.DEFAULT);
//读取返回的滚动id,它指向正在保持活动状态的搜索上下文,并将在下一个搜索滚动调用中用到它
String scrollId = searchResponse.getScrollId();
//第一批结果
SearchHits hits = searchResponse.getHits();
SearchHit[] searchHits = hits.getHits();
for (SearchHit hit : searchHits) {
System.out.println(hit.getSourceAsString());
}
//第二步:设置返回的scrollId和新的滚动间隔,延续滚动上下文
SearchScrollRequest scrollRequest = new SearchScrollRequest(scrollId);
scrollRequest.scroll(TimeValue.timeValueSeconds(30));
SearchResponse searchScrollResponse = client.scroll(scrollRequest, RequestOptions.DEFAULT);
//读取新的滚动id,它指向正在保持活动的搜索上下文,并将在下一个搜索滚动调用中使用
scrollId = searchScrollResponse.getScrollId();
//第二批结果
hits = searchScrollResponse.getHits();
searchHits = hits.getHits();
for (SearchHit hit : searchHits) {
System.out.println(hit.getSourceAsString());
}
//第三步:重复第二步
scrollRequest.scrollId(scrollId);
scrollRequest.scroll(TimeValue.timeValueSeconds(30));
searchScrollResponse = client.scroll(scrollRequest, RequestOptions.DEFAULT);
//读取滚动id
scrollId = searchScrollResponse.getScrollId();
searchHits = hits.getHits();
for (SearchHit hit : searchHits) {
System.out.println(hit.getSourceAsString());
}
//最后:清除滚动上下文
ClearScrollRequest clearScrollRequest = new ClearScrollRequest();
clearScrollRequest.addScrollId(scrollId);
ClearScrollResponse clearScrollResponse = client.clearScroll(clearScrollRequest, RequestOptions.DEFAULT);
System.out.println(clearScrollResponse.isSucceeded());
}
7.4.1、异步请求
ActionListener<SearchResponse> scrollListener =
new ActionListener<SearchResponse>() {
@Override
public void onResponse(SearchResponse searchResponse) {
}
@Override
public void onFailure(Exception e) {
}
};
client.scrollAsync(scrollRequest, RequestOptions.DEFAULT, scrollListener);
7.4.2、完整示例
@Test
public void scroll_example() throws IOException {
final Scroll scroll = new Scroll(TimeValue.timeValueMinutes(1L));
SearchRequest searchRequest = new SearchRequest("posts");
searchRequest.scroll(scroll);
SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
searchSourceBuilder.query(matchQuery("title", "Elasticsearch"));
searchRequest.source(searchSourceBuilder);
SearchResponse searchResponse = client.search(searchRequest, RequestOptions.DEFAULT);
String scrollId = searchResponse.getScrollId();
SearchHit[] searchHits = searchResponse.getHits().getHits();
while (searchHits != null && searchHits.length > 0) {
SearchScrollRequest scrollRequest = new SearchScrollRequest(scrollId);
scrollRequest.scroll(scroll);
searchResponse = client.scroll(scrollRequest, RequestOptions.DEFAULT);
scrollId = searchResponse.getScrollId();
searchHits = searchResponse.getHits().getHits();
}
ClearScrollRequest clearScrollRequest = new ClearScrollRequest();
clearScrollRequest.addScrollId(scrollId);
ClearScrollResponse clearScrollResponse = client.clearScroll(clearScrollRequest, RequestOptions.DEFAULT);
boolean succeeded = clearScrollResponse.isSucceeded();
}
7.5、Multi-Search API
multiSearch API在单个http请求中并行执行多个搜索请求。
@Test
public void multi_search() throws IOException {
//MultiSearchRequest是空的,可以添加所有想要执行的搜索
MultiSearchRequest request = new MultiSearchRequest();
//第一个请求
SearchRequest firstSearchRequest = new SearchRequest("bank");
SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
searchSourceBuilder.query(QueryBuilders.matchQuery("address", "street"));
firstSearchRequest.source(searchSourceBuilder);
request.add(firstSearchRequest);
//第二个请求
SearchRequest secondSearchRequest = new SearchRequest("person");
searchSourceBuilder = new SearchSourceBuilder();
searchSourceBuilder.query(QueryBuilders.termQuery("name", "tom"));
secondSearchRequest.source(searchSourceBuilder);
request.add(secondSearchRequest);
//发送请求
MultiSearchResponse response = client.msearch(request, RequestOptions.DEFAULT);
}
7.5.1、响应(MultiSearchResponse)
MultiSearchResponse.Item[] responses = response.getResponses();
for (MultiSearchResponse.Item item : responses) {
//每个item都包含一个searchResponse
SearchResponse searchResponse = item.getResponse();
System.out.println(searchResponse.toString());
}
7.6、Search Template API
搜索模板API允许从基于mustache语言的模板执行搜索,也允许预览呈现的模板。
7.6.1、Mustache模板语法
Mustache 是一种轻量级的模板语言,它的语法简洁易懂。以下是 Mustache 的语法规则:
- 变量替换
Mustache 用 {{
和}}
表示变量的替换,例如:
Hello, {{name}}!
在渲染时,{{name}} 会被替换成相应的值,例如:
Hello, John!
- 列表迭代
使用{{#}}
和 {{/}}
来表示列表迭代,例如:
<ul>
{{#items}}
<li>{{.}}</li>
{{/items}}
</ul>
在渲染时,items列表中的每个元素都会被替换成相应的 <li>
元素。
- 条件判断
使用 {{#}}
和 {{/}
来表示条件判断,例如:
{{#show}}
This is shown!
{{/show}}
如果 show 的值为真,则会显示 This is shown!。
- 反转条件判断
使用 {{^}}
和 {{/}}
来表示反转条件判断,例如:
{{^hide}}
This is shown!
{{/hide}}
如果 hide 的值为假,则会显示 This is shown!。
- HTML转义
在变量替换时,Mustache 会自动对 HTML 进行转义,以防止 XSS 攻击。如果想要显示 HTML 标签,可以使用三个大括号 {{{
和}}}
来表示不转义的变量替换,例如:
{{{html}}}
- 注释
使用{{! comment }}
来表示注释,例如:
{{! This is a comment. }}
7.6.2、模板搜索
@Test
public void search_template() throws IOException {
SearchTemplateRequest request = new SearchTemplateRequest();
request.setRequest(new SearchRequest("bank"));
//模板类型
request.setScriptType(ScriptType.INLINE);
//模板定义搜索源的结构。它作为字符串传递,因为mustache模板并不总是有效的JSON。
request.setScript(
"{" +
" \"query\": { \"match\" : { \"{{field}}\" : \"{{value}}\" } }," +
" \"size\" : \"{{size}}\"" +
"}");
//填充参数
Map<String, Object> scriptParams = new HashMap<>();
scriptParams.put("field", "address");
scriptParams.put("value", "street");
scriptParams.put("size", 2);
request.setScriptParams(scriptParams);
SearchTemplateResponse response = client.searchTemplate(request, RequestOptions.DEFAULT);
System.out.println(response.toString());
}
7.6.3、注册模板
搜索模板可以通过存储脚本API提前注册。注意,存储的脚本API在高级REST客户端中还不可用,因此在本例中我们使用低级REST客户端。
@Test
public void registered_template() throws IOException {
Request scriptRequest = new Request("POST", "_scripts/bank_search");
scriptRequest.setJsonEntity(
"{" +
" \"script\": {" +
" \"lang\": \"mustache\"," +
" \"source\": {" +
" \"query\": { \"match\" : { \"{{field}}\" : \"{{value}}\" } }," +
" \"size\" : \"{{size}}\"" +
" }" +
" }" +
"}");
//低级客户端注册
Response scriptResponse = restClient.performRequest(scriptRequest);
//请求中不需要提供内联脚本,可以直接引用这个已经注册的模板
SearchTemplateRequest request = new SearchTemplateRequest();
request.setRequest(new SearchRequest("bank"));
//模板类型
request.setScriptType(ScriptType.STORED);
request.setScript("bank_search");
//填充参数
Map<String, Object> params = new HashMap<>();
params.put("field", "address");
params.put("value", "street");
params.put("size", 3);
request.setScriptParams(params);
//高级客户端调用
SearchTemplateResponse response = client.searchTemplate(request, RequestOptions.DEFAULT);
System.out.println(response.toString());
}
7.6.4、渲染模板、可选参数及响应
渲染模板:给定参数值,可以在不执行搜索的情况下呈现模板
request.setSimulate(true);
SearchTemplateResponse response = client.searchTemplate(request, RequestOptions.DEFAULT);
System.out.println(response.toString());
//没有搜索结果,只渲染模板
System.out.println(new String(response.getSource().array()));
//{"size":"2","query":{"match":{"address":"street"}}}
可选参数:
request.setExplain(true);
request.setProfile(true);
响应:SearchResponse
SearchTemplateResponse response = client.searchTemplate(request, RequestOptions.DEFAULT);
SearchResponse searchResponse = response.getResponse();
7.7、Multi-Search Template API
multiSearchTemplate API在单个http请求中并行执行多个搜索模板请求。
@Test
public void multi_search_template() throws IOException {
String [] searchTerms = {"street", "hello"};
MultiSearchTemplateRequest multiRequest = new MultiSearchTemplateRequest();
for (String searchTerm : searchTerms) {
SearchTemplateRequest request = new SearchTemplateRequest();
request.setRequest(new SearchRequest("bank"));
request.setScriptType(ScriptType.INLINE);
request.setScript(
"{" +
" \"query\": { \"match\" : { \"{{field}}\" : \"{{value}}\" } }," +
" \"size\" : \"{{size}}\"" +
"}");
Map<String, Object> scriptParams = new HashMap<>();
scriptParams.put("field", "address");
scriptParams.put("value", searchTerm);
scriptParams.put("size", 1);
request.setScriptParams(scriptParams);
multiRequest.add(request);
}
MultiSearchTemplateResponse response = client.msearchTemplate(multiRequest, RequestOptions.DEFAULT);
for (MultiSearchTemplateResponse.Item item : response.getResponses()) {
if (item.isFailure()) {
System.out.println(item.getFailureMessage());
} else {
SearchTemplateResponse searchTemplateResponse = item.getResponse();
System.out.println(searchTemplateResponse.toString());
}
}
}
7.8、Field Capabilities API
Field Capabilities API允许跨多个索引检索字段的功能。
FieldCapabilitiesRequest包含一个要获取功能的字段列表(应该返回),以及一个可选的目标索引列表。如果没有提供索引,请求将在所有索引上执行。
注意fields参数支持通配符表示法。例如,提供text_*
将导致返回与表达式匹配的所有字段。
@Test
public void field_capabilities() throws IOException {
FieldCapabilitiesRequest request = new FieldCapabilitiesRequest()
.fields("address")//检索的字段
.indices("bank", "person");//索引列表
//控制如何解析不可用的索引以及如何扩展通配符表达式。
request.indicesOptions(IndicesOptions.lenientExpandOpen());
FieldCapabilitiesResponse response = client.fieldCaps(request, RequestOptions.DEFAULT);
//获取address字段的响应
Map<String, FieldCapabilities> userResponse = response.getField("address");
//address字段包含type为text的所有索引
FieldCapabilities textCapabilities = userResponse.get("text");
//是否可搜索
boolean isSearchable = textCapabilities.isSearchable();
//是否可聚合
boolean isAggregatable = textCapabilities.isAggregatable();
//所有索引
String[] indices = textCapabilities.indices();
//不可搜索的索引
String[] nonSearchableIndices = textCapabilities.nonSearchableIndices();
//不可聚合的索引
String[] nonAggregatableIndices = textCapabilities.nonAggregatableIndices();
System.out.println(isSearchable);
System.out.println(isAggregatable);
System.out.println(indices);
System.out.println(nonSearchableIndices);
System.out.println(nonAggregatableIndices);
}
7.9、Ranking Evaluation API
Ranking Evaluation API允许在一组搜索请求上评估排名搜索结果的质量。给定针对每个搜索请求的手动评级文档集,排名评估执行多个搜索请求并计算信息检索指标,如返回结果的平均倒数排名、精度或折扣累积增益。
为了构建RankEvalRequest,首先需要创建一个评估规范(RankEvalSpec)。该规范需要定义将要计算的评估度量,以及每个搜索请求的评级文档列表。创建排名评估请求,然后将规范和目标索引列表作为参数
@Test
public void ranking_evaluation() throws IOException {
//为了构建RankEvalRequest,您首先需要创建一个评估规范(RankEvalSpec)。
//该规范需要定义将要计算的评估度量,以及每个搜索请求的评级文档列表。
//创建排名评估请求,然后将规范和目标索引列表作为参数:
//创建评估中使用的度量
EvaluationMetric metric = new PrecisionAtK();
List<RatedDocument> ratedDocs = new ArrayList<>();
//添加评级文档,由索引名称、id和评级指定
ratedDocs.add(new RatedDocument("bank", "1", 1));
//创建查询
SearchSourceBuilder searchQuery = new SearchSourceBuilder();
searchQuery.query(QueryBuilders.matchQuery("address", "street"));
//将前三个部分组合成一个RatedRequest
RatedRequest ratedRequest = new RatedRequest("bank_query", ratedDocs, searchQuery);
List<RatedRequest> ratedRequests = Arrays.asList(ratedRequest);
//创建排名评估规范
RankEvalSpec specification = new RankEvalSpec(ratedRequests, metric);
//创建排名评估请求
RankEvalRequest request = new RankEvalRequest(specification, new String[] { "bank" });
RankEvalResponse response = client.rankEval(request, RequestOptions.DEFAULT);
//通过执行请求返回的RankEvalResponse包含有关总体评估分数的信息、
//查询集合中每个单独搜索请求的分数、关于搜索命中的详细信息以及关于每个部分结果的度量计算的详细信息。
System.out.println(response.toString());
//综合评价结果
double evaluationResult = response.getMetricScore();
Map<String, EvalQueryQuality> partialResults = response.getPartialResults();
//按查询key入关键字的部分结果
EvalQueryQuality evalQuality = partialResults.get("bank_query");
//每个部分结果的度量分数
double qualityLevel = evalQuality.metricScore();
//评级搜索命中包含一个完全成熟的搜索命中
List<RatedSearchHit> hitsAndRatings = evalQuality.getHitsAndRatings();
RatedSearchHit ratedSearchHit = hitsAndRatings.get(2);
MetricDetail metricDetails = evalQuality.getMetricDetails();
String metricName = metricDetails.getMetricName();
PrecisionAtK.Detail detail = (PrecisionAtK.Detail) metricDetails;
}
7.10、Explain API
explain api计算查询和特定文档的分数解释。这可以提供有用的反馈,无论文档是否匹配特定查询。
一个ExplainRequest需要一个索引和一个id来指定一个特定的文档,以及一个由QueryBuilder表示的查询来针对它运行(构建查询的方式)。
@Test
public void explain_request() throws IOException {
//ExplainRequest需要一个索引和一个id来指定一个特定的文档,
//以及一个由QueryBuilder表示的查询
ExplainRequest request = new ExplainRequest("bank", "2");
request.query(QueryBuilders.matchQuery("address", "place"));
//可选参数:
// request.routing("routing");
// //preference
// request.preference("_local");
// //是否获取_sourece字段
// request.fetchSourceContext(new FetchSourceContext(true));
// //store_fields
// request.storedFields(new String[]{"age"});
ExplainResponse response = client.explain(request, RequestOptions.DEFAULT);
//被解释的索引id
String index = response.getIndex();
//被解析的文档id
String id = response.getId();
//被解析的文档是否存在
boolean exists = response.isExists();
//指示所解释的文档和所提供的查询之间是否存在匹配
//(如果lucene解释建模匹配,则返回true,否则返回false,则从后台的lucene解释中检索匹配)。
boolean match = response.isMatch();
//指示是否存在此请求的lucene解释。
boolean hasExplanation = response.hasExplanation();
//获取lucene解释对象(如果存在)。
Explanation explanation = response.getExplanation();
//如果检索到_source或存储字段,则获取GetResult对象。
GetResult getResult = response.getGetResult();
//GetResult内部包含两个映射,用于存储获取的_source和存储的字段
Map<String, Object> source = getResult.getSource();
Map<String, DocumentField> fields = getResult.getFields();
}
7.11、Count API
CountRequest用于执行查询并获取查询的匹配数。在CountRequest中使用的查询可以用与使用SearchSourceBuilder的SearchRequest中的查询类似的方式设置。
@Test
public void count() throws IOException {
//创建CountRequest。如果没有参数,这将对所有索引运行。
CountRequest countRequest = new CountRequest("bank");
// .routing("routing")//路由
// .indicesOptions(IndicesOptions.lenientExpandOpen())//索引不存在或扩展匹配
// .preference("_local");//分片
//创建一个查询
SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
searchSourceBuilder.query(QueryBuilders.termQuery("age", 22));
//添加到countrequest
countRequest.source(searchSourceBuilder);
CountResponse countResponse = client.count(countRequest, RequestOptions.DEFAULT);
//命中数量
long count = countResponse.getCount();
//HTTP状态码
RestStatus status = countResponse.status();
//是否提前中止
Boolean terminatedEarly = countResponse.isTerminatedEarly();
//分片级别的执行信息
int totalShards = countResponse.getTotalShards();
int skippedShards = countResponse.getSkippedShards();
int successfulShards = countResponse.getSuccessfulShards();
int failedShards = countResponse.getFailedShards();
for (ShardSearchFailure failure : countResponse.getShardFailures()) {
}
System.out.println(count);
}
8、Async Search APIs
8.1、Submit Async Search API
SubmitAsyncSearchRequest允许向集群提交异步搜索任务。必需的参数是定义搜索和目标索引的SearchSourceBuilder:
@Test
public void submit_async_search() throws IOException {
SearchSourceBuilder searchSource = new SearchSourceBuilder()
.query(QueryBuilders.matchAllQuery());
String[] indices = new String[] { "bank" };
SubmitAsyncSearchRequest request = new SubmitAsyncSearchRequest(searchSource, indices);
//参数
//等待的最小时间(默认为1秒)
request.setWaitForCompletionTimeout(TimeValue.timeValueSeconds(30));
//请求的过期时间(默认为5天)。
request.setKeepAlive(TimeValue.timeValueMinutes(15));
//控制如果请求在提供的wait_for_completion时间内完成,是否应该存储结果(默认:false)
request.setKeepOnCompletion(true);
//执行请求并以AsyncSearchResponse对象的形式获取响应。
AsyncSearchResponse response = client.asyncSearch().submit(request, RequestOptions.DEFAULT);
//SubmitAsyncSearchRequest的异步执行允许在提交请求返回时使用ActionListener回调。
//注意,这与提交的搜索请求的执行无关,后者总是异步执行的。然而,侦听器等待提交请求本身返回:
client.asyncSearch()
.submitAsync(request, RequestOptions.DEFAULT, new ActionListener<AsyncSearchResponse>() {
@Override
public void onResponse(AsyncSearchResponse asyncSearchResponse) {
}
@Override
public void onFailure(Exception e) {
}
});
//响应
//SearchResponse,如果还不可用,则为null
response.getSearchResponse();
//异步搜索请求的id,如果没有存储响应,则为空
response.getId();
//当响应包含部分结果时为True
response.isPartial();
//是否正在运行搜索
response.isRunning();
//创建响应的时间
response.getStartTime();
//响应到期的时间
response.getExpirationTime();
//失败元音,没有则为null
response.getFailure();
System.out.println(response.getId());//FnNWTmNhNi1FUXlPNFdISUZpUGVKMHcbd0kydHMzMVdTeVNjM0lEVE5LNlNFUTo1MDc4
System.out.println(response.getStartTime());//1698162470049
System.out.println(response.getExpirationTime());//1698163370049
}
8.2、Get Async Search API
GetAsyncSearchRequest允许通过其id获取正在运行的异步搜索任务。必选参数是正在运行的异步搜索的id:
@Test
public void get_async_search() throws IOException {
GetAsyncSearchRequest request = new GetAsyncSearchRequest("FnNWTmNhNi1FUXlPNFdISUZpUGVKMHcbd0kydHMzMVdTeVNjM0lEVE5LNlNFUTo1MDc4");
//请求在返回部分结果之前应该等待的最小时间(默认为不等待)。
request.setWaitForCompletion(TimeValue.timeValueSeconds(30));
//请求的过期时间(默认为无)
request.setKeepAlive(TimeValue.timeValueMinutes(15));
//同步执行
AsyncSearchResponse response = client.asyncSearch().get(request, RequestOptions.DEFAULT);
//异步执行
client.asyncSearch()
.getAsync(request, RequestOptions.DEFAULT, new ActionListener<AsyncSearchResponse>() {
@Override
public void onResponse(AsyncSearchResponse asyncSearchResponse) {
}
@Override
public void onFailure(Exception e) {
}
});
//响应
//SearchResponse,如果还不可用,则为null
response.getSearchResponse();
//异步搜索请求的id,如果没有存储响应,则为null
response.getId();
//当响应包含部分结果时为True
response.isPartial();
//在搜索还在进行的时候true
response.isRunning();
//创建响应的时间(从epoch算起的毫秒)
response.getStartTime();
//响应到期的时间(从epoch算起的毫秒)
response.getExpirationTime();
//获取失败原因,如果没有失败则为null
response.getFailure();
System.out.println(response.toString());
}
8.3、Delete Async Search API
DeleteAsyncSearchRequest允许使用其id删除正在运行的异步搜索任务。必选参数是正在运行的搜索的id
@Test
public void delete_async_search() throws IOException {
DeleteAsyncSearchRequest request = new DeleteAsyncSearchRequest("FnNWTmNhNi1FUXlPNFdISUZpUGVKMHcbd0kydHMzMVdTeVNjM0lEVE5LNlNFUTo1MDc4");
//同步提交
AcknowledgedResponse response = client.asyncSearch().delete(request, RequestOptions.DEFAULT);
//异步提交
client.asyncSearch()
.deleteAsync(request, RequestOptions.DEFAULT, new ActionListener<AcknowledgedResponse>() {
@Override
public void onResponse(AcknowledgedResponse acknowledgedResponse) {
}
@Override
public void onFailure(Exception e) {
}
});
//响应
System.out.println(response.isAcknowledged());
}
Java 面试宝典是大明哥全力打造的 Java 精品面试题,它是一份靠谱、强大、详细、经典的 Java 后端面试宝典。它不仅仅只是一道道面试题,而是一套完整的 Java 知识体系,一套你 Java 知识点的扫盲贴。
它的内容包括:
- 大厂真题:Java 面试宝典里面的题目都是最近几年的高频的大厂面试真题。
- 原创内容:Java 面试宝典内容全部都是大明哥原创,内容全面且通俗易懂,回答部分可以直接作为面试回答内容。
- 持续更新:一次购买,永久有效。大明哥会持续更新 3+ 年,累计更新 1000+,宝典会不断迭代更新,保证最新、最全面。
- 覆盖全面:本宝典累计更新 1000+,从 Java 入门到 Java 架构的高频面试题,实现 360° 全覆盖。
- 不止面试:内容包含面试题解析、内容详解、知识扩展,它不仅仅只是一份面试题,更是一套完整的 Java 知识体系。
- 宝典详情:https://www.yuque.com/chenssy/sike-java/xvlo920axlp7sf4k
- 宝典总览:https://www.yuque.com/chenssy/sike-java/yogsehzntzgp4ly1
- 宝典进展:https://www.yuque.com/chenssy/sike-java/en9ned7loo47z5aw
目前 Java 面试宝典累计更新 400+ 道,总字数 42w+。大明哥还在持续更新中,下图是大明哥在 2024-12 月份的更新情况:
想了解详情的小伙伴,扫描下面二维码加大明哥微信【daming091】咨询
同时,大明哥也整理一套目前市面最常见的热点面试题。微信搜[大明哥聊 Java]或扫描下方二维码关注大明哥的原创公众号[大明哥聊 Java] ,回复【面试题】 即可免费领取。