之前写过一篇SpringData ES 关于字段名和索引中的列名字不一致导致的查询问题,顺便深入学习下Spring Data Elasticsearch。
 
Spring Data Elasticsearch是Spring Data针对Elasticsearch的实现。

它跟Spring Data一样,提供了Repository接口,我们只需要定义一个新的接口并继承这个Repository接口,然后就可以注入这个新的接口使用了。
 
定义接口:

@Repository
public interface TaskRepository extends ElasticsearchRepository<Task, String> { }

注入接口进行使用:

@Autowired
private TaskRepository taskRepository;....
taskRepository.save(task);

Repository接口的代理生成
 
上面的例子中TaskRepository是个接口,而我们却直接注入了这个接口并调用方法;很明显,这是错误的。

其实SpringData ES内部基于这个TaskRepository接口构造一个SimpleElasticsearchRepository,真正被注入的是这个SimpleElasticsearchRepository。

这个过程是如何实现的呢?  来分析一下。

ElasticsearchRepositoriesAutoConfiguration自动化配置类会导入ElasticsearchRepositoriesRegistrar这个ImportBeanDefinitionRegistrar。

ElasticsearchRepositoriesRegistrar继承自AbstractRepositoryConfigurationSourceSupport,是个ImportBeanDefinitionRegistrar接口的实现类,会被Spring容器调用registerBeanDefinitions进行自定义bean的注册。

ElasticsearchRepositoriesRegistrar委托给RepositoryConfigurationDelegate完成bean的解析。

整个解析过程可以分3个步骤:

  1. 找出模块中的org.springframework.data.repository.Repository接口的实现类或者org.springframework.data.repository.RepositoryDefinition注解的修饰类,并会过滤掉org.springframework.data.repository.NoRepositoryBean注解的修饰类。找出后封装到RepositoryConfiguration中
  2. 遍历这些RepositoryConfiguration,然后构造成BeanDefinition并注册到Spring容器中。需要注意的是这些RepositoryConfiguration会以beanClass为ElasticsearchRepositoryFactoryBean这个类的方式被注册,并把对应的Repository接口当做构造参数传递给ElasticsearchRepositoryFactoryBean,还会设置相应的属性比如elasticsearchOperations、evaluationContextProvider、namedQueries、repositoryBaseClass、lazyInitqueryLookupStrategyKey
  3. ElasticsearchRepositoryFactoryBean被实例化的时候设置对应的构造参数和属性。设置完毕以后调用afterPropertiesSet方法(实现了InitializingBean接口)。在afterPropertiesSet方法内部会去创建RepositoryFactorySupport类,并进行一些初始化,比如namedQueries、repositoryBaseClass等。然后通过这个RepositoryFactorySupport的getRepository方法基于Repository接口创建出代理类,并使用AOP添加了几个MethodInterceptor
// 遍历基于第1步条件得到的RepositoryConfiguration集合
for (RepositoryConfiguration<? extends RepositoryConfigurationSource> configuration : extension.getRepositoryConfigurations(configurationSource, resourceLoader, inMultiStoreMode)) {// 构造出BeanDefinitionBuilderBeanDefinitionBuilder definitionBuilder = builder.build(configuration);extension.postProcess(definitionBuilder, configurationSource);if (isXml) {// 设置elasticsearchOperations属性 extension.postProcess(definitionBuilder, (XmlRepositoryConfigurationSource) configurationSource); } else { // 设置elasticsearchOperations属性 extension.postProcess(definitionBuilder, (AnnotationRepositoryConfigurationSource) configurationSource); } // 使用命名策略生成bean的名字 AbstractBeanDefinition beanDefinition = definitionBuilder.getBeanDefinition(); String beanName = beanNameGenerator.generateBeanName(beanDefinition, registry); if (LOGGER.isDebugEnabled()) { LOGGER.debug(REPOSITORY_REGISTRATION, extension.getModuleName(), beanName, configuration.getRepositoryInterface(), extension.getRepositoryFactoryClassName()); } beanDefinition.setAttribute(FACTORY_BEAN_OBJECT_TYPE, configuration.getRepositoryInterface()); // 注册到Spring容器中 registry.registerBeanDefinition(beanName, beanDefinition); definitions.add(new BeanComponentDefinition(beanDefinition, beanName)); } // build方法 public BeanDefinitionBuilder build(RepositoryConfiguration<?> configuration) { Assert.notNull(registry, "BeanDefinitionRegistry must not be null!"); Assert.notNull(resourceLoader, "ResourceLoader must not be null!"); // 得到factoryBeanName,这里会使用extension.getRepositoryFactoryClassName()去获得 // extension.getRepositoryFactoryClassName()返回的正是ElasticsearchRepositoryFactoryBean String factoryBeanName = configuration.getRepositoryFactoryBeanName(); factoryBeanName = StringUtils.hasText(factoryBeanName) ? factoryBeanName : extension.getRepositoryFactoryClassName(); // 基于factoryBeanName构造BeanDefinitionBuilder BeanDefinitionBuilder builder = BeanDefinitionBuilder.rootBeanDefinition(factoryBeanName); builder.getRawBeanDefinition().setSource(configuration.getSource()); // 设置ElasticsearchRepositoryFactoryBean的构造参数,这里是对应的Repository接口 // 设置一些的属性值 builder.addConstructorArgValue(configuration.getRepositoryInterface()); builder.addPropertyValue("queryLookupStrategyKey", configuration.getQueryLookupStrategyKey()); builder.addPropertyValue("lazyInit", configuration.isLazyInit()); builder.addPropertyValue("repositoryBaseClass", configuration.getRepositoryBaseClassName()); NamedQueriesBeanDefinitionBuilder definitionBuilder = new NamedQueriesBeanDefinitionBuilder( extension.getDefaultNamedQueryLocation()); if (StringUtils.hasText(configuration.getNamedQueriesLocation())) { definitionBuilder.setLocations(configuration.getNamedQueriesLocation()); } builder.addPropertyValue("namedQueries", definitionBuilder.build(configuration.getSource())); // 查找是否有对应Repository接口的自定义实现类 String customImplementationBeanName = registerCustomImplementation(configuration); // 存在自定义实现类的话,设置到属性中 if (customImplementationBeanName != null) { builder.addPropertyReference("customImplementation", customImplementationBeanName); builder.addDependsOn(customImplementationBeanName); } RootBeanDefinition evaluationContextProviderDefinition = new RootBeanDefinition( ExtensionAwareEvaluationContextProvider.class); evaluationContextProviderDefinition.setSource(configuration.getSource()); // 设置一些的属性值 builder.addPropertyValue("evaluationContextProvider", evaluationContextProviderDefinition); return builder; } // RepositoryFactorySupport的getRepository方法,获得Repository接口的代理类 public <T> T getRepository(Class<T> repositoryInterface, Object customImplementation) { // 获取Repository的元数据 RepositoryMetadata metadata = getRepositoryMetadata(repositoryInterface); // 获取Repository的自定义实现类 Class<?> customImplementationClass = null == customImplementation ? null : customImplementation.getClass(); // 根据元数据和自定义实现类得到Repository的RepositoryInformation信息类 // 获取信息类的时候如果发现repositoryBaseClass是空的话会根据meta中的信息去自动匹配 // 具体匹配过程在下面的getRepositoryBaseClass方法中说明 RepositoryInformation information = getRepositoryInformation(metadata, customImplementationClass); // 验证 validate(information, customImplementation); // 得到最终的目标类实例,会通过repositoryBaseClass去查找 Object target = getTargetRepository(information); // 创建代理工厂 ProxyFactory result = new ProxyFactory(); result.setTarget(target); result.setInterfaces(new Class[] { repositoryInterface, Repository.class }); // 进行aop相关的设置 result.addAdvice(SurroundingTransactionDetectorMethodInterceptor.INSTANCE); result.addAdvisor(ExposeInvocationInterceptor.ADVISOR); if (TRANSACTION_PROXY_TYPE != null) { result.addInterface(TRANSACTION_PROXY_TYPE); } // 使用RepositoryProxyPostProcessor处理 for (RepositoryProxyPostProcessor processor : postProcessors) { processor.postProcess(result, information); } if (IS_JAVA_8) { // 如果是JDK8的话,添加DefaultMethodInvokingMethodInterceptor result.addAdvice(new DefaultMethodInvokingMethodInterceptor()); } // 添加QueryExecutorMethodInterceptor result.addAdvice(new QueryExecutorMethodInterceptor(information, customImplementation, target)); // 使用代理工厂创建出代理类,这里是使用jdk内置的代理模式 return (T) result.getProxy(classLoader); } // 目标类的获取 protected Class<?> getRepositoryBaseClass(RepositoryMetadata metadata) { // 如果Repository接口属于QueryDsl,抛出异常。目前还不支持 if (isQueryDslRepository(metadata.getRepositoryInterface())) { throw new IllegalArgumentException("QueryDsl Support has not been implemented yet."); } // 如果主键是数值类型的话,repositoryBaseClass为NumberKeyedRepository if (Integer.class.isAssignableFrom(metadata.getIdType()) || Long.class.isAssignableFrom(metadata.getIdType()) || Double.class.isAssignableFrom(metadata.getIdType())) { return NumberKeyedRepository.class; } else if (metadata.getIdType() == String.class) { // 如果主键是String类型的话,repositoryBaseClass为SimpleElasticsearchRepository return SimpleElasticsearchRepository.class; } else if (metadata.getIdType() == UUID.class) { // 如果主键是UUID类型的话,repositoryBaseClass为UUIDElasticsearchRepository return UUIDElasticsearchRepository.class; } else { // 否则报错 throw new IllegalArgumentException("Unsupported ID type " + metadata.getIdType()); } }

ElasticsearchRepositoryFactoryBean是一个FactoryBean接口的实现类,getObject方法返回的上面提到的getRepository方法返回的代理对象;getObjectType方法返回的是对应Repository接口类型。

我们文章一开始提到的注入TaskRepository的时候,实际上这个对象是ElasticsearchRepositoryFactoryBean类型的实例,只不过ElasticsearchRepositoryFactoryBean实现了FactoryBean接口,所以注入的时候会得到一个代理对象,这个代理对象是由jdk内置的代理生成的,并且它的target对象是SimpleElasticsearchRepository(主键是String类型)。
 
 
SpringData ES中ElasticsearchOperations的介绍
 
ElasticsearchTemplate实现了ElasticsearchOperations接口。

ElasticsearchOperations接口是SpringData对Elasticsearch操作的一层封装,比如有创建索引createIndex方法、获取索引的设置信息getSetting方法、查询对象queryForObject方法、分页查询方法queryForPage、删除文档delete方法、更新文档update方法等等。

ElasticsearchTemplate是具体的实现类,它有这些属性:

// elasticsearch提供的基于java的客户端连接接口。java对es集群的操作使用这个接口完成
private Client client;
// 一个转换器接口,定义了2个方法,分别可以获得MappingContext和ConversionService
// MappingContext接口用于获取所有的持久化实体和这些实体的属性
// ConversionService目前在SpringData ES中没有被使用 private ElasticsearchConverter elasticsearchConverter; // 内部使用EntityMapper完成对象到json字符串和json字符串到对象的映射。默认使用jackson完成映射,可自定义 private ResultsMapper resultsMapper; // 查询超时时间 private String searchTimeout;

Client接口在ElasticsearchAutoConfiguration自动化配置类里被构造:

@Bean
@ConditionalOnMissingBean
public Client elasticsearchClient() { try { return createClient(); } catch (Exception ex) { throw new IllegalStateException(ex); } }

ElasticsearchTemplate、ElasticsearchConverter以及SimpleElasticsearchMappingContext在ElasticsearchDataAutoConfiguration自动化配置类里被构造:

@Bean
@ConditionalOnMissingBean
public ElasticsearchTemplate elasticsearchTemplate(Client client, ElasticsearchConverter converter) { try { return new ElasticsearchTemplate(client, converter); } catch (Exception ex) { throw new IllegalStateException(ex); } } @Bean @ConditionalOnMissingBean public ElasticsearchConverter elasticsearchConverter( SimpleElasticsearchMappingContext mappingContext) { return new MappingElasticsearchConverter(mappingContext); } @Bean @ConditionalOnMissingBean public SimpleElasticsearchMappingContext mappingContext() { return new SimpleElasticsearchMappingContext(); }

需要注意的是这个bean被自动化配置类构造的前提是它们在Spring容器中并不存在。
 
Repository的调用过程
 
以自定义的TaskRepository的save方法为例,大致的执行流程如下所示:

SimpleElasticsearchRepository的save方法具体的分析在SpringData ES 关于字段名和索引中的列名字不一致导致的查询问题中分析过。

像自定义的Repository查询方法,或者Repository接口的自定义实现类的操作这些底层,可以去QueryExecutorMethodInterceptor中查看,大家有兴趣的可以自行查看源码。

http://spring4all.com/article/17

最近工作中使用了Spring Data Elasticsearch。发生它存在一个问题:

Document对应的POJO的属性跟es里面文档的字段名字不一样,这样Repository里面编写自定义的查询方法就会查询不出结果。

比如有个Person类,它有2个属性goodFace和goodAt。这2个属性在es的索引里对应的字段表为good_face和good_at:

1
2
3
4
5
6
7
8
9
10
11
@Document(replicas = 1, shards = 1, type = "person", indexName = "person")
@Getter
@Setter
@JsonNaming(PropertyNamingStrategy.SnakeCaseStrategy.class)
public class Person {
@Id
private String id;
private String name;
private boolean goodFace;
private String goodAt;
}

Repository中的自定义查询:

1
2
3
4
5
@Repository
public interface PersonRepository extends ElasticsearchRepository<Person, String> {
List<Person> findByGoodFace(boolean isGoodFace);
List<Person> findByName(String name);
}

方法findByGoodFace是查询不出结果的,而findByName是ok的。

为什么findByGoodFace不行而findByName可以呢,来探究一下。

Person类的name属性跟ES中的字段名是一模一样的,而goodFace字段在ES中的字段是good_face(因为我们使用了SnakeCaseStrategy策略)。

所以产生这个问题的原因在于ES中文档的字段名跟POJO中的字段名不统一造成的。

但是我们使用PersonRepository的save方法保存文档的时候属性和字段是可以对上的。

那为什么使用repository的save方法能对应上文档和字段,而自定义的find方法却不行呢?

ES是使用jackson来完成POJO到json的映射关系的。

在Person类上使用@JsonNaming注解完成POJO和json的映射,我们使用了SnakeCaseStrategy策略,这个策略会把属性从驼峰方式改成小写带下划线的方式。

比如goodAt属性映射的时候就会变成good_at,good_face变成good_face,name变成name。

Spring Data Elasticsearch把对ES的操作封装成了一个ElasticsearchOperations接口。比如queryForObject、queryForPage、count、queryForList方法。

ElasticsearchOperations接口目前有一个实现类ElasticsearchTemplate。

ElasticsearchTemplate内部有个ResultsMapper属性,这个ResultsMapper目前只有一个实现类DefaultResultMapper,DefaultResultMapper内部使用DefaultEntityMapper完成映射。DefaultEntityMapper是个EntityMapper接口的实现类,它的定义如下:

1
2
3
4
public interface EntityMapper {
public String mapToString(Object object) throws IOException;
public <T> T mapToObject(String source, Class<T> clazz) throws IOException;
}

方法很明白:对象到json字符串的转换和json字符串倒对象的转换。

DefaultEntityMapper内部使用jackson的ObjectMapper完成。

自定义的Repository继承自ElasticsearchRepository,最后会使用代理映射成SimpleElasticsearchRepository。

SimpleElasticsearchRepository内部有个属性ElasticsearchOperations用于完成与ES的交互。

我们看下SimpleElasticsearchRepository的save方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
@Override
public <S extends T> S save(S entity) {
Assert.notNull(entity, "Cannot save 'null' entity.");
// createIndexQuery方法会构造一个IndexQuery,然后调用ElasticsearchOperations的index方法
elasticsearchOperations.index(createIndexQuery(entity));
elasticsearchOperations.refresh(entityInformation.getIndexName());
return entity;
}
// ElasticsearchTemplate的index方法
@Override
public String index(IndexQuery query) {
// 调用prepareIndex方法构造一个IndexRequestBuilder
String documentId = prepareIndex(query).execute().actionGet().getId();
// 设置保存文档的id
if (query.getObject() != null) {
setPersistentEntityId(query.getObject(), documentId);
}
return documentId;
}
private IndexRequestBuilder prepareIndex(IndexQuery query) {
try {
// 从@Document注解中得到索引的名字
String indexName = isBlank(query.getIndexName()) ? retrieveIndexNameFromPersistentEntity(query.getObject()
.getClass())[0] : query.getIndexName();
// 从@Document注解中得到索引的类型
String type = isBlank(query.getType()) ? retrieveTypeFromPersistentEntity(query.getObject().getClass())[0]
: query.getType();
IndexRequestBuilder indexRequestBuilder = null;
if (query.getObject() != null) { // save方法这里保存的object就是POJO
// 得到id字段
String id = isBlank(query.getId()) ? getPersistentEntityId(query.getObject()) : query.getId();
if (id != null) { // 如果设置了id字段
indexRequestBuilder = client.prepareIndex(indexName, type, id);
} else { // 如果没有设置id字段
indexRequestBuilder = client.prepareIndex(indexName, type);
}
// 使用ResultsMapper映射POJO到json字符串
indexRequestBuilder.setSource(resultsMapper.getEntityMapper().mapToString(query.getObject()));
} else if (query.getSource() != null) { // 如果自定义了source属性,直接赋值
indexRequestBuilder = client.prepareIndex(indexName, type, query.getId()).setSource(query.getSource());
} else { // 没有设置object属性或者source属性,抛出ElasticsearchException异常
throw new ElasticsearchException("object or source is null, failed to index the document [id: " + query.getId() + "]");
}
if (query.getVersion() != null) { // 设置版本
indexRequestBuilder.setVersion(query.getVersion());
indexRequestBuilder.setVersionType(EXTERNAL);
}
if (query.getParentId() != null) { // 设置parentId
indexRequestBuilder.setParent(query.getParentId());
}
return indexRequestBuilder;
} catch (IOException e) {
throw new ElasticsearchException("failed to index the document [id: " + query.getId() + "]", e);
}
}

save方法使用ResultsMapper完成了POJO到json的转换,所以save方法保存成功对应的文档数据:

1
indexRequestBuilder.setSource(resultsMapper.getEntityMapper().mapToString(query.getObject()));

自定义的findByGoodFace方法:

由于是Repository中的自定义方法,会被Spring Data通过代理进行构造,内部还是用了AOP,最终在QueryExecutorMethodInterceptor中并解析成ElasticsearchPartQuery这个RepositoryQuery接口的实现类,然后调用execute方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
@Override
public Object execute(Object[] parameters) {
ParametersParameterAccessor accessor = new ParametersParameterAccessor(queryMethod.getParameters(), parameters);
CriteriaQuery query = createQuery(accessor);
if(tree.isDelete()) { // 如果是删除方法
Object result = countOrGetDocumentsForDelete(query, accessor);
elasticsearchOperations.delete(query, queryMethod.getEntityInformation().getJavaType());
return result;
} else if (queryMethod.isPageQuery()) { // 如果是分页查询
query.setPageable(accessor.getPageable());
return elasticsearchOperations.queryForPage(query, queryMethod.getEntityInformation().getJavaType());
} else if (queryMethod.isStreamQuery()) { // 如果是流式查询
Class<?> entityType = queryMethod.getEntityInformation().getJavaType();
if (query.getPageable() == null) {
query.setPageable(new PageRequest(0, 20));
}
return StreamUtils.createStreamFromIterator((CloseableIterator<Object>) elasticsearchOperations.stream(query, entityType));
} else if (queryMethod.isCollectionQuery()) { // 如果是集合查询
if (accessor.getPageable() == null) {
int itemCount = (int) elasticsearchOperations.count(query, queryMethod.getEntityInformation().getJavaType());
query.setPageable(new PageRequest(0, Math.max(1, itemCount)));
} else {
query.setPageable(accessor.getPageable());
}
return elasticsearchOperations.queryForList(query, queryMethod.getEntityInformation().getJavaType());
} else if (tree.isCountProjection()) { // 如果是count查询
return elasticsearchOperations.count(query, queryMethod.getEntityInformation().getJavaType());
}
// 单个查询
return elasticsearchOperations.queryForObject(query, queryMethod.getEntityInformation().getJavaType());
}

findByGoodFace方法是个集合查询,最终会调用ElasticsearchOperations的queryForList方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
@Override
public <T> List<T> queryForList(CriteriaQuery query, Class<T> clazz) {
// 调用queryForPage方法
return queryForPage(query, clazz).getContent();
}
@Override
public <T> Page<T> queryForPage(CriteriaQuery criteriaQuery, Class<T> clazz) {
// 查询解析器进行语法的解析
QueryBuilder elasticsearchQuery = new CriteriaQueryProcessor().createQueryFromCriteria(criteriaQuery.getCriteria());
QueryBuilder elasticsearchFilter = new CriteriaFilterProcessor().createFilterFromCriteria(criteriaQuery.getCriteria());
SearchRequestBuilder searchRequestBuilder = prepareSearch(criteriaQuery, clazz);
if (elasticsearchQuery != null) {
searchRequestBuilder.setQuery(elasticsearchQuery);
} else {
searchRequestBuilder.setQuery(QueryBuilders.matchAllQuery());
}
if (criteriaQuery.getMinScore() > 0) {
searchRequestBuilder.setMinScore(criteriaQuery.getMinScore());
}
if (elasticsearchFilter != null)
searchRequestBuilder.setPostFilter(elasticsearchFilter);
if (logger.isDebugEnabled()) {
logger.debug("doSearch query:\n" + searchRequestBuilder.toString());
}
SearchResponse response = getSearchResponse(searchRequestBuilder
.execute());
// 最终的结果是用ResultsMapper进行映射
return resultsMapper.mapResults(response, clazz, criteriaQuery.getPageable());
}

自定义的方法使用ElasticsearchQueryCreator去创建CriteriaQuery,内部做一些词法的分析,有了CriteriaQuery之后,使用CriteriaQueryProcessor基于Criteria构造了QueryBuilder,最后使用QueryBuilder去做rest请求得到es的查询结果。这些过程中是没有用到ResultsMapper,而只是用反射得到POJO的属性,只有在得到查询结果后才会用ResultsMapper去做映射。

如果出现了这种情况,解决方案目前有两种:

1.使用repository的search方法,参数可以是QueryBuilder或者SearchQuery

1
2
3
4
personRepository.search(
QueryBuilders.boolQuery()
.must(QueryBuilders.termQuery("good_face", true))
)

2.使用@Query注解

1
2
@Query("{\"bool\" : {\"must\" : {\"term\" : {\"good_face\" : \"?0\"}}}}")
List<Person> findByGoodFace(boolean isGoodFace);

暂时发现这两种解决方法,不知还有否更好的解决方案。http://fangjian0423.github.io/2017/05/24/spring-data-es-query-problem/

SpringData ES中一些底层原理的分析相关推荐

  1. Es区域查找底层原理vsRedis Geo区域查找的实现区别

    es区域查找底层原理 Api如何调用 映射 PUT /test {"mappings":{"properties":{"name":{&qu ...

  2. es中ElasticsearchRepository 的原理和使用

    把数据存储到es中,有两种方式一种是 ElasticsearchRepository 接口,另一种是ElasticsearchTemplate接口,今天我们主要分析ElasticsearchRepos ...

  3. Hadoop2.6.0中YARN底层状态机实现分析

    版权声明:本文为博主原创文章,未经博主允许不得转载. https://blog.csdn.net/beliefer/article/details/51190842 前言 Hadoop2.x.x版本的 ...

  4. SpringData ES中字段名和索引中的列名字不一致导致的无法查询数据的解决方法

    为什么80%的码农都做不了架构师?>>>    用SpringDataElasticsearch查询数据的时候可以将实体中的字段名与ES中mapping中field的名字起成一样的名 ...

  5. Spring Security 中的执行原理流程分析

    1.简介 Spring Security是一个能够为基于Spring的企业应用系统提供声明式的安全访问控制解决方案的安全框架.它提供了一组可以在Spring应用上下文中配置的Bean,充分利用了Spr ...

  6. python字典内存分析_(一)Python入门-3序列:18字典-核心底层原理-内存分析-查找值对象过程...

    一:根据键查找"键值对"的底层过程 明白一个键值对是如何存储到数组中的,根据键对象取到值对象,理解起来就 简单了. >>> a.get("name&qu ...

  7. Silverlight游戏设计(Game Design):(一)游戏中斜视角的原理与分析

    "暗黑破坏神3"将采用45度斜视角设置这篇数月前貌似毫不起眼的新闻一直让我记忆深刻,不断的尝试去理解大师级的暗黑团队为何放弃目前主流的纯3D路线而回归到类2D,B大的一句话道破天机 ...

  8. elasticsearch原理_ElasticSearch读写底层原理及性能调优

    ES写入/查询底层原理 1. Elasticsearch写入数据流程 客户端随机选择一个ES集群中的节点,发送POST/PUT请求,被选择的节点为协调节点(coordinating node) 协调节 ...

  9. elasticsearch最大节点数_ElasticSearch读写底层原理及性能调优

    ES写入/查询底层原理 1. Elasticsearch写入数据流程 客户端随机选择一个ES集群中的节点,发送POST/PUT请求,被选择的节点为协调节点(coordinating node) 协调节 ...

  10. java Map及其实现类的底层原理

    文章目录 一.Map接口及其多个实现类的对比 二.Map中存储的key-value特点 三.HashMap在JDK7中的底层原理 四.HashMap在JDK8中的底层原理 五.HashMap在JDK7 ...

最新文章

  1. 从 25 倍稀释下的蘑菇街期权说起
  2. pat德才论(java)
  3. struts中如何查看配置文件中是否存在某个返回值
  4. rest api如何创建_REST:创建资源
  5. JS天气插件(最全)
  6. BZOJ1901: Zju2112 Dynamic Rankings
  7. 今天带你详细了解各组件原理
  8. java代码_Java 代码优化核心建议
  9. html 字符转换 ascii,HTML ASCII
  10. 【日常】DES加密算法python实现_以密码编码学与网络安全——原理与实践(第六版)课后习题3.11为例
  11. 阳春三月,放飞希望---希望今年不再碌碌无为
  12. 快递(EMS)增值业务解决方案
  13. VirtualBox 安装虚拟机步骤
  14. xctf攻防世界 MISC高手进阶区 打野
  15. 小米手机运行linux,在小米4手机安装Linux deploy,然后安装kali linux,但过程中出现如图所示問題...
  16. Notification的功能与使用案例
  17. C语言实现简单的ai麻将对局(较大工程,持续更新ing)
  18. 当“AI”对上“AI”,骚扰电话能否被扼杀在“苗芽”
  19. [Android]我的第一个手机应用
  20. 雷军的猪已经落地了,那人工智能的猪该飞去哪?

热门文章

  1. 【JZOJ4743】【NOIP2016提高A组模拟9.2】积木
  2. ROS Learning-008 beginner_Tutorials ROS话题
  3. VS 2012 No exports were found that match the constraint 解决办法
  4. 079 pandas模块
  5. spring整体架构
  6. python 基本数据类型及其功能-2-字符串 重点命令
  7. [转帖]Windows下cwRsyncServer双机连续同步部署
  8. 函数(八)-函数和匿名函数
  9. 解决servlet中get方式中中文乱码问题(二):装饰者模式使用
  10. Java IO-InputStream家族 -装饰者模式