【OpenFeign 】OpenFeign 的常用配置

news/2024/9/27 23:34:37

1  前言

上节我们看了下 OpenFeign 里的重试,在从源码的角度看它的执行原理的时候,又意外的遇到了一个【OpenFeign 】OpenFeign 下未开启重试,服务却被调用了两次 的问题的分析,后面我们又看了重试器的入场和执行时机,那么本节我们看看 OpenFeign 的一些常用配置,以及全局配置和想对某个 Feign 单独配置的方法。

官网地址以及官网的配置,大家也可以去看看。

2  环境准备

在前面的 Feign 下,我增加了一个 StockFeign,这样来测测单独针对某个 Feign 的配置:

3  配置相关

3.1  配置优先级

在了解配置之前,要先知道配置的关系。

配置也是分层次的,比如有一个全局的配置,有可以针对某个 Feign 单独的配置,配置取值优先级采用的是就近策略,也就是这个 Feign 有自己的配置了,就用自己的,没有的话就用默认的。

这块逻辑体现在:

// FeignClientFactoryBean 在初始化 Feign.Builder 的时候
protected void configureFeign(FeignContext context, Feign.Builder builder) {FeignClientProperties properties = beanFactory != null? beanFactory.getBean(FeignClientProperties.class): applicationContext.getBean(FeignClientProperties.class);FeignClientConfigurer feignClientConfigurer = getOptional(context,FeignClientConfigurer.class);setInheritParentContext(feignClientConfigurer.inheritParentConfiguration());if (properties != null && inheritParentContext) {if (properties.isDefaultToProperties()) {configureUsingConfiguration(context, builder);// 先设置 default 的
            configureUsingProperties(properties.getConfig().get(properties.getDefaultConfig()),builder);// 再设置自己特有的 properties.getConfig().get(contextId)
            configureUsingProperties(properties.getConfig().get(contextId), builder);}else {// 先设置 default 的
            configureUsingProperties(properties.getConfig().get(properties.getDefaultConfig()),builder);// 再设置自己特有的 properties.getConfig().get(contextId)
            configureUsingProperties(properties.getConfig().get(contextId), builder);configureUsingConfiguration(context, builder);}}else {configureUsingConfiguration(context, builder);}
}

配置设置参考:

# 设置默认的配置的名称 默认是 default 一般我们都不会动它
feign.client.default-config = default
# 设置默认的属性值
# 格式:feign.client.config.default.属性名 = 属性值
feign.client.config.default.read-timeout=1000
# 设置某个 Feign 特有的 contextId 就是@FeignClient 的 contextId,contextId为空的话取 name属性的值
feign.client.config.feign的contextId.read-timeout=1000

3.2  配置的相关类

(1)FeignClientProperties

// Feign客户端的基础配置选项,比如日志级别、重试策略、编码器和解码器的选择等。
@ConfigurationProperties("feign.client")
public class FeignClientProperties {

(2)FeignClientEncodingProperties

// 请求压缩相关 mimeTypes 支持的mime类型默认text/xml,application/xml,application/json  minRequestSize 边界超过多少进行请求压缩 默认2048
@ConfigurationProperties("feign.compression.request")
public class FeignClientEncodingProperties {

(3)FeignHttpClientProperties

// 这个配置类可能包含了一系列与HTTP客户端相关配置,如连接池大小、连接超时时间、读取超时时间等。使用Apache HttpClient或其他HTTP客户端时,这类配置是非常有用的。例如,当使用Apache HttpClient作为Feign的HTTP客户端时,可以通过此类配置来优化连接管理和性能。
@ConfigurationProperties(prefix = "feign.httpclient")
public class FeignHttpClientProperties {

(4)HTTP 连接池相关的两个

# feign.httpclient.enabled
# feign.okhttp.enabled
@ConditionalOnProperty(value = "feign.httpclient.enabled", matchIfMissing = true)
class HttpClientFeignLoadBalancedConfiguration {
@ConditionalOnProperty("feign.okhttp.enabled")
class OkHttpFeignLoadBalancedConfiguration {
// 熔断相关 feign.hystrix.enabled
protected static class HystrixFeignConfiguration {@ConditionalOnProperty(name = "feign.hystrix.enabled")public Feign.Builder feignHystrixBuilder() {return HystrixFeign.builder();}
}

4  常用配置

4.1  feign.client 相关的

设置默认配置的名称,默认就是 default 这个不建议设置,就用默认的即可。

feign.client.default-config = default

feign.client 可以设置默认的配置,也可以对某个 feign 设置最后都是保存在 FeignClientProperties 的 config 属性中。

private Map<String, FeignClientConfiguration> config = new HashMap<>();

设置默认的:feign.client.config.default.属性 = 属性值

设置某个的:feign.client.config.contextId.属性 = 属性值

FeignClientConfiguration 是 FeignClientProperties 的子类,是针对 feignClient 可以设置的一些属性,我们看一些常见的:

4.1.1  日志

(1)日志相关的:loggerLevel  Level有 NONE、BASIC、HEADERS、FULL 四个等级,不设置的话默认为 null

比如:feign.client.config.default.logger-level = full

它的作用点就是在执行过程中,判断当前的日志级别,来打印相应的信息:

// 摘自 SynchronousMethodHandler
Object executeAndDecode(RequestTemplate template, Options options) throws Throwable {Request request = targetRequest(template);// 判断日志级别 打印相应信息if (logLevel != Logger.Level.NONE) {logger.logRequest(metadata.configKey(), logLevel, request);}try {response = client.execute(request, options);...} catch (IOException e) {if (logLevel != Logger.Level.NONE) {logger.logIOException(metadata.configKey(), logLevel, e, elapsedTime(start));}...
}

4.1.2 连接、响应超时时间

connectTimeout 连接超时时间、readTimeout 响应超时时间(单位时间:毫秒)比如:

# 设置默认的响应时间 1 秒
feign.client.config.default.read-timeout=1000
# 设置 stockFeign 的响应时间 5 秒
feign.client.config.stockFeign.read-timeout=5000

它的作用点在:

在初始化 Feign.Builder 的时候取出配置的连接、响应超时时间并用 Request.Options 封装起来:

// Request.Options()
public Options() {// 默认连接超时 10 秒,响应超时 60 秒this(10, TimeUnit.SECONDS, 60, TimeUnit.SECONDS, true);
}
// 默认值来自于 Request.Options()
private int readTimeoutMillis = new Request.Options().readTimeoutMillis();
private int connectTimeoutMillis = new Request.Options().connectTimeoutMillis();
private boolean followRedirects = new Request.Options().isFollowRedirects();
// 如果配置了,取配置的没有的话取 Request.Options() 上边默认的
connectTimeoutMillis = config.getConnectTimeout() != null ? config.getConnectTimeout() : connectTimeoutMillis;
readTimeoutMillis = config.getReadTimeout() != null ? config.getReadTimeout() : readTimeoutMillis;
followRedirects = config.isFollowRedirects() != null ? config.isFollowRedirects() : followRedirects;
// 构建 new Request.Options
builder.options(new Request.Options(connectTimeoutMillis, TimeUnit.MILLISECONDS,readTimeoutMillis, TimeUnit.MILLISECONDS, followRedirects));

默认情况下请求是通过 HttpURLConnection 发送的,根据你的 Request.Options() 建立请求:

@Override
public Response execute(Request request, Options options) throws IOException {HttpURLConnection connection = convertAndSend(request, options);return convertResponse(connection, request);
}
HttpURLConnection convertAndSend(Request request, Options options) throws IOException {...connection.setConnectTimeout(options.connectTimeoutMillis());connection.setReadTimeout(options.readTimeoutMillis());
}

而对于 ApacheHttpClient 或者 okHttp 他们都是由 FeignHttpClientProperties即(feign.httpclient)来管理。

你看 HttpClient,最大连接数、连接超时等都是从 FeignHttpClientProperties 获取的:

@Bean
@ConditionalOnMissingBean(HttpClientConnectionManager.class)
public HttpClientConnectionManager connectionManager(ApacheHttpClientConnectionManagerFactory connectionManagerFactory,FeignHttpClientProperties httpClientProperties) {final HttpClientConnectionManager connectionManager = connectionManagerFactory.newConnectionManager(httpClientProperties.isDisableSslValidation(),httpClientProperties.getMaxConnections(),httpClientProperties.getMaxConnectionsPerRoute(),httpClientProperties.getTimeToLive(),httpClientProperties.getTimeToLiveUnit(), this.registryBuilder);this.connectionManagerTimer.schedule(new TimerTask() {@Overridepublic void run() {connectionManager.closeExpiredConnections();}}, 30000, httpClientProperties.getConnectionTimerRepeat());return connectionManager;
}
private CloseableHttpClient createClient(HttpClientBuilder builder,HttpClientConnectionManager httpClientConnectionManager,FeignHttpClientProperties httpClientProperties) {// 从 FeignHttpClientProperties 获取 连接超时时间 默认 2秒// public static final int DEFAULT_CONNECTION_TIMEOUT = 2000;RequestConfig defaultRequestConfig = RequestConfig.custom().setConnectTimeout(httpClientProperties.getConnectionTimeout()).setRedirectsEnabled(httpClientProperties.isFollowRedirects()).build();CloseableHttpClient httpClient = builder.setDefaultRequestConfig(defaultRequestConfig).setConnectionManager(httpClientConnectionManager).build();return httpClient;
}

你看 okHttp 一样的都是从 FeignHttpClientProperties 获取的:

@Bean
public okhttp3.OkHttpClient client(OkHttpClientFactory httpClientFactory,ConnectionPool connectionPool,FeignHttpClientProperties httpClientProperties) {Boolean followRedirects = httpClientProperties.isFollowRedirects();Integer connectTimeout = httpClientProperties.getConnectionTimeout();this.okHttpClient = httpClientFactory.createBuilder(httpClientProperties.isDisableSslValidation()).connectTimeout(connectTimeout, TimeUnit.MILLISECONDS).followRedirects(followRedirects).connectionPool(connectionPool).build();return this.okHttpClient;
}

有一点比较好奇的是 FeignHttpClientProperties 这些没有响应超时时间,连接池不能设置超时时间么?

其实连接池的响应超时时间、连接超时时间都是从 Request.Options 获取的,我们来看下:

ApacheHttpClient、OkHttpClient 都实现了 feign core包里的 Client 接口:

public interface Client {Response execute(Request request, Options options) throws IOException;
}

对于 ApacheHttpClient

public final class ApacheHttpClient implements Client {@Overridepublic Response execute(Request request, Request.Options options) throws IOException {HttpUriRequest httpUriRequest;try {httpUriRequest = toHttpUriRequest(request, options);...}}
}
HttpUriRequest toHttpUriRequest(Request request, Request.Options options)throws URISyntaxException {RequestBuilder requestBuilder = RequestBuilder.create(request.httpMethod().name());// per request timeouts 从 Request.Options 获取连接超时和响应超时RequestConfig requestConfig =(client instanceof Configurable ? RequestConfig.copy(((Configurable) client).getConfig()): RequestConfig.custom()).setConnectTimeout(options.connectTimeoutMillis()).setSocketTimeout(options.readTimeoutMillis()).build();requestBuilder.setConfig(requestConfig);URI uri = new URIBuilder(request.url()).build();...
}

对于 OkHttpClient

public Response execute(feign.Request input, Options options) throws IOException {okhttp3.OkHttpClient requestScoped;// 也是从 Options 来获取的if (this.delegate.connectTimeoutMillis() == options.connectTimeoutMillis() && this.delegate.readTimeoutMillis() == options.readTimeoutMillis() && this.delegate.followRedirects() == options.isFollowRedirects()) {requestScoped = this.delegate;} else {requestScoped = this.delegate.newBuilder().connectTimeout((long)options.connectTimeoutMillis(), TimeUnit.MILLISECONDS).readTimeout((long)options.readTimeoutMillis(), TimeUnit.MILLISECONDS).followRedirects(options.isFollowRedirects()).build();}Request request = toOkHttpRequest(input);okhttp3.Response response = requestScoped.newCall(request).execute();return toFeignResponse(response, input).toBuilder().request(input).build();
}

我这里拿 ApacheHttpClient 调试如下:

虽然 ApacheHttpClient 默认连接超时是 2秒,但是由于 Options 默认是 10秒,所以 ApacheHttpClient 被重置为了 10秒,并且我配置的响应超时 1秒也生效了:

所以不管对于连接池方式的还是默认的 HttpURLConnection,连接超时或者响应超时的配置都可以通过 feign.client.config 来做:

feign.client.config.default.read-timeout=1000
feign.client.config.default.connect-timeout=1000
或者
feign.client.config.某个contextId.read-timeout=1000
feign.client.config.某个contextId.connect-timeout=1000

4.1.3 Retryer 重试器

重试器我们这里就不看了吧,之前都看过了。

4.1.4 RequestInterceptor 拦截器

OpenFeign 在执行请求的时候,给我们提供了一个拦截器,来做一些自定义的处理。

public interface RequestInterceptor {void apply(RequestTemplate template);
}

它的执行时机如图,是在 SynchronousMethodHandler 的 executeAndDecode 执行请求时的 targetRequest 方法中执行拦截器的:

Request targetRequest(RequestTemplate template) {for (RequestInterceptor interceptor : requestInterceptors) {interceptor.apply(template);}return target.apply(template);
}

它的场景主要有:

  • 比如我们服务之间互相调用,要传递用户信息,通过从上下文取到当前用户标志塞到 RequestTemplate 的 header 中,下游服务再从 Header中解析获得。
  • 比如我们要看服务调用时的一些请求参数等信息,可以通过拦截器打印。
  • 比如服务之间的链路监控,也是通过将 TraceId 放置到 header 中,跟第一点方式原理类似。

4.1.5 默认请求头、请求参数

defaultRequestHeaders 默认的请求头、defaultQueryParameters 默认的路径参数

他俩的实现原理都是通过拦截器来实现的:

protected void configureUsingProperties(FeignClientProperties.FeignClientConfiguration config, Feign.Builder builder) {...if (Objects.nonNull(config.getDefaultRequestHeaders())) {// lambda 放置一个拦截器builder.requestInterceptor(requestTemplate -> requestTemplate.headers(config.getDefaultRequestHeaders()));}if (Objects.nonNull(config.getDefaultQueryParameters())) {// lambda 放置一个拦截器builder.requestInterceptor(requestTemplate -> requestTemplate.queries(config.getDefaultQueryParameters()));}...
}

4.1.6 编码器、解码器

encode 编码:就是在请求发出之前对参数进行编码

decode 解码:在接收到结果数据后对其进行解码

说实话,这块还真没研究过,这里就看一下这两者的执行时机:

4.1.6.1 encoder 编码时机

我们的方法处理器 SynchronousMethodHandler 有一个这样的属性:

private final RequestTemplate.Factory buildTemplateFromArgs;

它来源于 ReflectiveFeign 解析你的 Feign 的方法的时候会根据你方法请求方式以及参数来创建不同的 BuildTemplateByResolvingArgs(它实现了 RequestTemplate.Factory),并且放置编码器在里边:

public Map<String, MethodHandler> apply(Target target) {List<MethodMetadata> metadata = contract.parseAndValidateMetadata(target.type());Map<String, MethodHandler> result = new LinkedHashMap<String, MethodHandler>();// 遍历每个方法的原始信息for (MethodMetadata md : metadata) {BuildTemplateByResolvingArgs buildTemplate;// 当是存在 form 表单形式的提交if (!md.formParams().isEmpty() && md.template().bodyTemplate() == null) {buildTemplate =new BuildFormEncodedTemplateFromArgs(md, encoder, queryMapEncoder, target);// 其次是有请求体的时候} else if (md.bodyIndex() != null) {buildTemplate = new BuildEncodedTemplateFromArgs(md, encoder, queryMapEncoder, target);} else {// 剩余的走这里buildTemplate = new BuildTemplateByResolvingArgs(md, queryMapEncoder, target);}...}return result;
}

先看下类图关系:

然后 SynchronousMethodHandler 在执行的第一步就是构建 RequestTemplate,就会调用 buildTemplateFromArgs 的 create 方法:

@Override
public Object invoke(Object[] argv) throws Throwable {RequestTemplate template = buildTemplateFromArgs.create(argv);...
}

接着就会先进入父类 BuildTemplateByResolvingArgs 的 create 方法:

@Override
public RequestTemplate create(Object[] argv) {...// 解析RequestTemplate template = resolve(argv, mutable, varBuilder);...
}

解析方法 resolve 就会进入上边的两个编码过程:

// BuildFormEncodedTemplateFromArgs 的:
private static class BuildFormEncodedTemplateFromArgs extends BuildTemplateByResolvingArgs {private final Encoder encoder;@Overrideprotected RequestTemplate resolve(Object[] argv, RequestTemplate mutable, Map<String, Object> variables) {Map<String, Object> formVariables = new LinkedHashMap<String, Object>();for (Entry<String, Object> entry : variables.entrySet()) {if (metadata.formParams().contains(entry.getKey())) {formVariables.put(entry.getKey(), entry.getValue());}}try {encoder.encode(formVariables, Encoder.MAP_STRING_WILDCARD, mutable);} catch (EncodeException e) {throw e;} catch (RuntimeException e) {throw new EncodeException(e.getMessage(), e);}return super.resolve(argv, mutable, variables);}}
}
// BuildEncodedTemplateFromArgs 的:
private static class BuildEncodedTemplateFromArgs extends BuildTemplateByResolvingArgs {private final Encoder encoder;@Overrideprotected RequestTemplate resolve(Object[] argv, RequestTemplate mutable, Map<String, Object> variables) {Object body = argv[metadata.bodyIndex()];checkArgument(body != null, "Body parameter %s was null", metadata.bodyIndex());try {encoder.encode(body, metadata.bodyType(), mutable);} catch (EncodeException e) {throw e;} catch (RuntimeException e) {throw new EncodeException(e.getMessage(), e);}return super.resolve(argv, mutable, variables);}
}

4.1.6.1 decoder 解码时机

解码就是在 SynchronousMethodHandler 的 executeAndDecode 执行方法中,当响应结果回来后:

Object executeAndDecode(RequestTemplate template, Options options) throws Throwable {Request request = targetRequest(template);Response response;try {// 发送请求response = client.execute(request, options);response = response.toBuilder().request(request).requestTemplate(template).build();} catch (IOException e) {throw errorExecuting(request, e);}// 解码if (decoder != null)return decoder.decode(response, metadata.returnType());...
}

4.2  feign.httpclient 相关

4.3  feign.compression.request 相关

5  连接池

关于 OpenFeign 的请求,我们知道它是基于 Http 的,并且默认的情况下,它是不开启连接池的:

@Configuration(proxyBeanMethods = false)
class DefaultFeignLoadBalancedConfiguration {@Bean// 当没有别的 Client 的话走这里,也就是默认走这里
    @ConditionalOnMissingBeanpublic Client feignClient(CachingSpringLoadBalancerFactory cachingFactory,SpringClientFactory clientFactory) {// 客户端默认用的是 Client.Default (Client 是 feign 核心包里的 Default 是默认的实现)return new LoadBalancerFeignClient(new Client.Default(null, null), cachingFactory,clientFactory);}
}
// Client.Default
class Default implements Client {...@Overridepublic Response execute(Request request, Options options) throws IOException {// 每次请求都是用的 HttpURLConnection 当没有keep-alive的情况下,其实每次请求都会经历建立连接发送接收数据断开连接 影响性能HttpURLConnection connection = convertAndSend(request, options);return convertResponse(connection, request);}
}

5.1  Apache-HttpClient

如果要开启 Apache 的 HttpClient 作为 HTTP 客户端,首先引入依赖:

<dependency><groupId>io.github.openfeign</groupId><artifactId>feign-httpclient</artifactId>
</dependency>

并且开启配置即可:

feign.httpclient.enabled = true

原理来源于两个配置类:

@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(ApacheHttpClient.class)
@ConditionalOnProperty(value = "feign.httpclient.enabled", matchIfMissing = true)
@Conditional(HttpClient5DisabledConditions.class)
// 引入 HttpClientFeignConfiguration
@Import(HttpClientFeignConfiguration.class)
class HttpClientFeignLoadBalancedConfiguration {@Bean@ConditionalOnMissingBean(Client.class)public Client feignClient(CachingSpringLoadBalancerFactory cachingFactory,SpringClientFactory clientFactory, HttpClient httpClient) {// httpClient 来源于下面的配置类ApacheHttpClient delegate = new ApacheHttpClient(httpClient);return new LoadBalancerFeignClient(delegate, cachingFactory, clientFactory);}
}
// HttpClientFeignConfiguration
@Configuration(proxyBeanMethods = false)
@ConditionalOnMissingBean(CloseableHttpClient.class)
public class HttpClientFeignConfiguration {@Bean@ConditionalOnProperty(value = "feign.compression.response.enabled",havingValue = "false", matchIfMissing = true)public CloseableHttpClient httpClient(ApacheHttpClientFactory httpClientFactory,HttpClientConnectionManager httpClientConnectionManager,FeignHttpClientProperties httpClientProperties) {// 默认走这里创建 HttpClientthis.httpClient = createClient(httpClientFactory.createBuilder(),httpClientConnectionManager, httpClientProperties);return this.httpClient;}private CloseableHttpClient createClient(HttpClientBuilder builder,HttpClientConnectionManager httpClientConnectionManager,FeignHttpClientProperties httpClientProperties) {RequestConfig defaultRequestConfig = RequestConfig.custom().setConnectTimeout(httpClientProperties.getConnectionTimeout()).setRedirectsEnabled(httpClientProperties.isFollowRedirects()).build();CloseableHttpClient httpClient = builder.setDefaultRequestConfig(defaultRequestConfig).setConnectionManager(httpClientConnectionManager).build();return httpClient;}...
}

5.2  OkHttp

如果要开启 okHttp 作为 HTTP 客户端,也是先引入依赖:

<dependency><groupId>io.github.openfeign</groupId><artifactId>feign-okhttp</artifactId>
</dependency>

并且开启配置即可:

feign.okhttp.enabled = true

原理也是来源于两个配置类:

@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(OkHttpClient.class)
@ConditionalOnProperty("feign.okhttp.enabled")
// 引入 OkHttpFeignConfiguration
@Import(OkHttpFeignConfiguration.class)
class OkHttpFeignLoadBalancedConfiguration {@Bean@ConditionalOnMissingBean(Client.class)public Client feignClient(CachingSpringLoadBalancerFactory cachingFactory,SpringClientFactory clientFactory, okhttp3.OkHttpClient okHttpClient) {OkHttpClient delegate = new OkHttpClient(okHttpClient);return new LoadBalancerFeignClient(delegate, cachingFactory, clientFactory);}}
@Configuration(proxyBeanMethods = false)
@ConditionalOnMissingBean(okhttp3.OkHttpClient.class)
public class OkHttpFeignConfiguration {private okhttp3.OkHttpClient okHttpClient;@Bean@ConditionalOnMissingBean(ConnectionPool.class)public ConnectionPool httpClientConnectionPool(FeignHttpClientProperties httpClientProperties,OkHttpClientConnectionPoolFactory connectionPoolFactory) {Integer maxTotalConnections = httpClientProperties.getMaxConnections();Long timeToLive = httpClientProperties.getTimeToLive();TimeUnit ttlUnit = httpClientProperties.getTimeToLiveUnit();return connectionPoolFactory.create(maxTotalConnections, timeToLive, ttlUnit);}@Beanpublic okhttp3.OkHttpClient client(OkHttpClientFactory httpClientFactory,ConnectionPool connectionPool,FeignHttpClientProperties httpClientProperties) {Boolean followRedirects = httpClientProperties.isFollowRedirects();Integer connectTimeout = httpClientProperties.getConnectionTimeout();this.okHttpClient = httpClientFactory.createBuilder(httpClientProperties.isDisableSslValidation()).connectTimeout(connectTimeout, TimeUnit.MILLISECONDS).followRedirects(followRedirects).connectionPool(connectionPool).build();return this.okHttpClient;}@PreDestroypublic void destroy() {if (this.okHttpClient != null) {this.okHttpClient.dispatcher().executorService().shutdown();this.okHttpClient.connectionPool().evictAll();}}}

另外要注意的是,如果你的依赖中既包含 httpClient 又包含 okHttpClient 的话,默认是用的 httpClient

即使你设置了 feign.okhttp.enabled = true,也没用还是会用 httpClient,我调试发现是这样的。

@ConditionalOnProperty(value = "feign.httpclient.enabled", matchIfMissing = true)
class HttpClientFeignLoadBalancedConfiguration {

6  小结

好啦,本节就看到这里,有理解不对的地方欢迎指点。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.ryyt.cn/news/59671.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈,一经查实,立即删除!

相关文章

Splay 浅谈

Splay 树 定义 Splay 树是一个二叉平衡搜索树,它可以通过 Splay 操作 将一个结点旋转至根结点或者一个给定的结点的下一层,使得整棵树仍然满足二叉搜索树的性质。 Splay 树可以在均摊 \(O(\log n)\) 的时间内完成查找、插入、查询、删除等操作。二叉搜索树的定义:空树是一个…

从kmp到AC自动机

知道kmp的请跳过这一段 找到最清晰的解析 kmp 我看了约114514个解析才搞懂 如何求next 首先,next[i]本应表示0~i的字符串的最长相同前缀后缀的长度。 不过为了方便匹配,实际可以存最长相同前缀后缀时前缀最后一个的地址 听起来好绕 那这么说吧: 例如串 abaabaabaab next[0]=…

LinkedHashMap原理详解—从LRU缓存机制说起

写在前面 从一道Leetcode题目说起 首先,来看一下Leetcode里面的一道经典题目:146.LRU缓存机制,题目描述如下:请你设计并实现一个满足 LRU (最近最少使用) 缓存 约束的数据结构。 实现 LRUCache 类:LRUCache(int capacity) 以 正整数 作为容量 capacity 初始化 LRU 缓存 in…

微积分快速入门1部分:直觉

1 一分钟微积分:X射线和延时视觉 我们通常只看到图形、公式和情况的表面价值。微积分为我们提供了两种深入挖掘的超能力:X射线能看到图案中隐藏的部分。你不仅能看到树,还能知道它是由年轮组成的,在我们说话的同时,另一个年轮也在生长。延时视觉你能看到物体未来的运行轨迹…

torch.nn.Embedding的导入与导出

简介及导入转自:torch.nn.Embedding使用在RNN模型的训练过程中,需要用到词嵌入,使用torch.nn.Embedding可以快速的完成:只需要初始化torch.nn.Embedding(n,m)即可(n是单词总数,m是词向量的维度)(n是嵌入字典的大小,m是嵌入向量的维度。)。 注意: embedding开始是随机的…

第十七讲 为什么这些SQL语句逻辑相同,性能却差异巨大?

第十七讲: 为什么这些SQL语句逻辑相同,性能却差异巨大? 简概:引入: ​ 在 MySQL 中,有很多看上去逻辑相同,但性能却差异巨大的 SQL 语句。对这些语句使用不当的话,就会不经意间导致整个数据库的压力变大。我今天挑选了三个这样的案例和你分享。希望再遇到相似的问题时,…

2024-09-12 TypeError: Cannot read properties of undefined (reading 0) ==》检查未定义的对象or数组

TypeError: Cannot read properties of undefined (reading 0) ==》TypeError:无法读取undefined的属性(读取“0”) 请记住出现这种错误大多数都是因为你读取了未定义的对象或数组 排查结果:后端返回的id由原来的小写id改成了大写Id。 666,服了,哥们。

UNO 已知问题 在后台线程触发 SKXamlCanvas 的 Invalidate 且在 PaintSurface 事件抛出异常将炸掉应用

本文记录一个 UNO 已知问题,在 UNO 里面可以利用 SKXamlCanvas 对接 Skia 绘制到应用里面。如果此时在后台线程里面调用 SKXamlCanvas 的 Invalidate 触发界面的重新刷新,但在具体的执行绘制 PaintSurface 事件里面对外抛出异常,将会导致应用炸掉背景: 我准备在 UNO 里面将…