【Android】浅析OkHttp(1)
【Android】浅析OkHttp(1)
OkHttp
是一个高效、轻量级的 HTTP 客户端库,主要用于 Android 和 Java 应用开发。它不仅支持同步和异步的 HTTP 请求,还支持许多高级功能,如连接池、透明的 GZIP 压缩、响应缓存、WebSocket 等。OkHttp3 是 OkHttp 的第三个大版本,在稳定性和功能性方面进行了很多改进。
今天主要介绍OkHttp3。
OkHttp基本用法
使用前
配置gradle
compile 'com.squareup.okhttp3:okhttp:3.2.0'
compile 'com.squareup.okio:okio:1.7.0'
异步 GET 请求
最简单的 GET 请求,请求博客地址,代码如下所示:
Request.Builder requestBuilder = new Request.Builder().url("http://blog.csdn.net/itachi85");requestBuilder.method("GET", null);Request request = requestBuilder.build();OkHttpClient mOkHttpClient = new OkHttpClient();Call mcall = mOkHttpClient.newCall(request);mcall.enqueue(new Callback() {@Overridepublic void onFailure(Call call, IOException e) {}@Overridepublic void onResponse(Call call, Response response) throws IOException {String str = response.body().string();Log.d(TAG str); }});
其基本步骤就是创建 OkHttpClient、Request 和 Call,最后调用 Call 的 enqueue()方法。但是 每次这么写很麻烦,肯定是要进行封装的。需要注意的是 onResponse 回调并非在 UI 线程。如 果想要调用同步 GET 请求,可以调用 Call 的 execute 方法。
同步 GET 请求
代码示例如下:
OkHttpClient client = new OkHttpClient();// 创建Request对象
Request request = new Request.Builder().url("https://jsonplaceholder.typicode.com/posts").build();// 同步发送请求
try (Response response = client.newCall(request).execute()) {if (response.isSuccessful()) {// 打印响应内容System.out.println(response.body().string());} else {System.out.println("Request Failed");}
} catch (IOException e) {e.printStackTrace();
}
在同步请求中,execute()
方法会阻塞线程,直到服务器返回响应。这里 Response
通过 try-with-resources
自动关闭流。
异步 POST 请求
OkHttp 3 异步 POST 请求和 OkHttp 2.x 有一些差别,就是没有 FormEncodingBuilder 这个类, 替代它的是功能更加强大的 FormBody。这里访问淘宝 IP 库,代码如下所示:
RequestBody formBody = new FormBody.Builder().add("ip", "59.108.54.37").build();
Request request = new Request.Builder().url("http://ip.taobao.com/service/getIpInfo.php").post(formBody).build();
OkHttpClient mOkHttpClient = new OkHttpClient();
Call call = mOkHttpClient.newCall(request);
call.enqueue(new Callback() {@Overridepublic void onFailure(Call call, IOException e) {}@Overridepublic void onResponse(Call call, Response response) throws IOException {String str = response.body().string();Log.d(TAG, str);}
});
这与异步 GET 请求类似,只是多了用 FormBody 来封装请求的参数,并传递给 Request。
设置超时时间和缓存
和 OkHttp 2.x 有区别的是 OkHttp 3 不能通过 OkHttpClient 直接设置超时时间和缓存了,而 是通过 OkHttpClient.Builder 来设置。通过 builder 配置好 OkHttpClient 后用 builder.build()来返回 OkHttpClient。所以我们通常不会调用 new OkHttpClient()来得到 OkHttpClient,而是通过 builder.build()得到 OkHttpClient。另外,OkHttp 3 支持设置连接、写入和读取的超时时间,如下 所示:
File sdcache = getExternalCacheDir();
int cacheSize = 10 * 1024 * 1024;
OkHttpClient.Builder builder = new OkHttpClient.Builder().connectTimeout(15, TimeUnit.SECONDS).writeTimeout(20, TimeUnit.SECONDS).readTimeout(20, TimeUnit.SECONDS).cache(new Cache(sdcache.getAbsoluteFile(), cacheSize));
mOkHttpClient = builder.build();
取消请求
使用 call.cancel()可以立即停止一个正在执行的 call。当用户离开一个应用时或者跳到其他 界面时,使用 call.cancel()可以节约网络资源;另外,不管同步还是异步的 call 都可以取消,也 可以通过 tag 来同时取消多个请求。当构建一个请求时,使用 Request.Builder.tag(Object tag)来 分配一个标签,之后你就可以用 OkHttpClient.cancel(Object tag )来取消所有带有这个 tag 的 call。 具体代码如下所示:
private ScheduledExecutorService executor = Executors.newScheduledThreadPool(1);
private void cancel() {final Request request = new Request.Builder().url("http://www.baidu.com").cacheControl(CacheControl.FORCE_NETWORK)//1.build();Call call = null;call = mOkHttpClient.newCall(request);final Call finalCall = call;//100ms 后取消 callexecutor.schedule(new Runnable() {@Overridepublic void run() {finalCall.cancel();}}, 100, TimeUnit.MILLISECONDS);call.enqueue(new Callback() {@Overridepublic void onFailure(Call call, IOException e) {}@Overridepublic void onResponse(Call call, Response response) throws IOException {if (null != response.cacheResponse()) {String str = response.cacheResponse().toString();Log.d(TAG, "cache---" + str);} else {String str = response.networkResponse().toString();Log.d(TAG, "network---" + str);}}});
}
创建定时线程池,100 ms 后调用 call.cancel()来取消请求。为了能让请求耗时,在上面代码 注释 1 处设置每次请求都要请求网络,运行程序并且不断地调用 cancel 方法。Log 打印结果如 图 5-13 所示。
源码解析OkHttp
首先来学习一下 OkHttp 的请求网络流程。
OkHttp的请求网络流程
大致流程

首先创建出OkHttpClient,而后创建出Request对象,通过这两个对象,获取到Call对象。使用Call开始执行请求后,请求的任务会首先经过Dispatcher(分发器)进行任务的调配,而后Interceptors(拦截器)完成请求的过程。
分发器:内部维护队列以及线程池,完成请求调配。
拦截器:完成整个请求过程。
关于分发器Dispatcher
RunningAsyncCalls:正在执行的异步请求队列
ReadyAsyncCalls:等待执行的异步请求队列
RunningSyncCalls:正在执行的同步请求队列
关于拦截器Interceptor
重试重定向拦截器
桥接拦截器
缓存拦截器
连接拦截器
请求服务器拦截器
- 重试拦截器:负责判断用户是否取消了请求;获得结果之后,会根据响应码判断是否需要重定向,如果满足条件就会重启执行所有拦截器
- 桥接拦截器在交出之前,负责将Http协议必备的请求头加入其中;获得结果之后,调用保存cookie接口并解析GZIP数据
- 缓存拦截器,交出之前读取并判断是否使用缓存;获得结果之后判断是否缓存。
- 连接拦截器,交出之前负责找到或者新建一个链接,并获取对应的socket流,在获得结果之后,不进行额外处理。
- 请求服务器拦截器进行真正的与服务器的通信,向服务器发送数据,解析读取的相应数据
从execute/enqueue开始
当我们要请求网络的时候需要用 OkHttpClient.newCall(request)进行 execute 或者 enqueue 操 作;当调用 newCall 方法时,会调用如下代码:
@Override
public Call newCall(Request request) {return new RealCall(this, request);
}
其实际返回的是一个RealCall类。我们调用 enqueue 异步请求网络实际上是调用了 RealCall 的 enqueue 方法。查看 RealCall 的 enqueue 方法,如下所示:
void enqueue(Callback responseCallback, boolean forWebSocket) {synchronized (this) {if (executed) throw new IllegalStateException("Already Executed");executed = true;}client.dispatcher().enqueue(new AsyncCall(responseCallback, forWebSocket));
}
可以看到最终的请求是 dispatcher 来完成的,接下来就开始分析 dispatcher。
Dispatcher 任务调度
Dispatcher 主要用于控制并发的请求,它主要维护了以下变量:
接下来看看 Dispatcher 的构造方法,如下所示:
public Dispatcher(ExecutorService executorService) {this.executorService = executorService;}public Dispatcher() {}public synchronized ExecutorService executorService() {if (executorService == null) {executorService = new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60, TimeUnit.SECONDS, new SynchronousQueue<Runnable>(), Util.threadFactory("OkHttp Dispatcher", false));}return executorService;}
复习:
synchronized
关键字可以确保同一时间内只有一个线程可以访问共享资源,从而避免竞争条件(race condition)的问题。同步方法
可以将整个方法标记为
synchronized
,这样该方法在某一时刻只允许一个线程访问。同步代码块
可以使用
synchronized
块来同步特定的代码块,而不是整个方法。这样可以减少锁的持有时间,提升程序的并发性能。
Dispatcher 有两个构造方法,可以使用自己设定的线程池。如果没有设定线程池,则会在请 求网络前自己创建默认线程池。这个线程池类似于 CachedThreadPool,比较适合执行大量的耗 时比较少的任务。前面讲过,当调用 RealCall 的 enqueue 方法时,实际上是调用了 Dispatcher 的 enqueue 方法,它的代码如下所示:
synchronized void enqueue(AsyncCall call) {if (runningAsyncCalls.size() < maxRequests && runningCallsForHost(call) < maxRequestsPerHost) {runningAsyncCalls.add(call);executorService().execute(call);} else {readyAsyncCalls.add(call);}
}
当正在运行的异步请求队列中的数量小于 64 并且同一个主机的请求最大数小于 5 时,把请求 加载到runningAsyncCalls中并在线程池中执行,否则就加入到readyAsyncCalls中进行缓存等待。 线程池中传进来的参数是 AsyncCall,它是 RealCall 的内部类,其内部也实现了 execute 方法, 如下所示:
protected void execute() {boolean signalledCallback = false;try {...} catch (IOException e) {...} finally {client.dispatcher().finished(this);//1}
}
在上面代码注释 1 处,无论这个请求的结果如何,都会执行 client.dispatcher().finished(this), finished 方法如下所示:
synchronized void finished(AsyncCall call) {if (!runningAsyncCalls.remove(call)) throw new AssertionError("AsyncCall wasn't running!");promoteCalls();
}
finished 方法将此次请求从 runningAsyncCalls 移除后还执行了 promoteCalls 方法:
private void promoteCalls() {if (runningAsyncCalls.size() >= maxRequests) return; if (readyAsyncCalls.isEmpty()) return; for (Iterator<AsyncCall> i = readyAsyncCalls.iterator(); i.hasNext(); ) {AsyncCall call = i.next();if (runningCallsForHost(call) < maxRequestsPerHost) {i.remove();runningAsyncCalls.add(call);executorService().execute(call);}if (runningAsyncCalls.size() >= maxRequests) return; }
}
最关键的一点就是会从 readyAsyncCalls 取出下一个请求,加入 runningAsyncCalls 中并交由 线程池处理。好了,让我们再回到上面 AsyncCall 的 execute 方法:
@Override
protected void execute() {boolean signalledCallback = false;try {Response response = getResponseWithInterceptorChain(forWebSocket);//1if (canceled) {signalledCallback = true;responseCallback.onFailure(RealCall.this, new IOException("Canceled"));} else {signalledCallback = true;responseCallback.onResponse(RealCall.this, response);}} catch (IOException e) {if (signalledCallback) {logger.log(Level.INFO, "Callback failure for " + toLoggableString(), e);} else {responseCallback.onFailure(RealCall.this, e);}} finally {client.dispatcher().finished(this);}
}
在上面代码注释 1 处,getResponseWithInterceptorChain 方法返回了 Response,这是在请求网络。
Interceptor 拦截器
接下来查看 getResponseWithInterceptorChain 方法,如下所示:
private Response getResponseWithInterceptorChain(boolean forWebSocket) throws IOException {Interceptor.Chain chain = new ApplicationInterceptorChain(0, originalRequest,forWebSocket);return chain.proceed(originalRequest);
}
getResponseWithInterceptorChain 方法中创建了 ApplicationInterceptorChain,这是一个拦截器链。这个类也是 RealCall 的内部类,接下来执行了它的 proceed 方法:
public Response proceed(Request request) throws IOException {if (index < client.interceptors().size()) {Interceptor.Chain chain = new ApplicationInterceptorChain(index + 1, request, forWebSocket);//从拦截器列表中取出拦截器Interceptor interceptor = client.interceptors().get(index);Response interceptedResponse = interceptor.intercept(chain);//1if (interceptedResponse == null) {throw new NullPointerException("application interceptor " + interceptor + " returned null");}return interceptedResponse;}return getResponse(request, forWebSocket);
}
proceed 方法每次从拦截器列表中取出拦截器。当存在多个拦截器时都会在上面代码注释 1 处阻塞,并等待下一个拦截器的调用返回。下面分别以拦截器链中有一个、两个拦截器的场景加以模拟,如图 5-14 所示。
拦截器是一种能够监控、重写、重试调用的机制。通常情况下,拦截器用来添加、移除、 转换请求和响应的头部信息。比如将域名替换为 IP 地址,在请求头中添加 host 属性;也可以添 加我们应用中的一些公共参数,比如设备 id、版本号,等等。回到代码上来,查看最后一行 return getResponse(request, forWebSocket),如果没有更多拦截器的话,就会执行网络请求。现在查看 getResponse 方法做了什么,代码如下所示:
Response getResponse(Request request, boolean forWebSocket) throws IOException {...engine = new HttpEngine(client, request, false, false, forWebSocket, null, null, null);int followUpCount = 0;
while (true) {if (canceled) {engine.releaseStreamAllocation();throw new IOException("Canceled");}boolean releaseConnection = true;try {engine.sendRequest();engine.readResponse();releaseConnection = false;} catch (RequestException e) {throw e.getCause();} catch (RouteException e) {... }
}
getResponse 方法比较长,这里省略了一些代码,在此可以看到创建了 HttpEngine 类并且调 用了 HttpEngine 的 sendRequest 方法和 readResponse 方法。
缓存策略
首先来看看 HttpEngine 的 sendRequest 方法,如下:
public void sendRequest() throws RequestException, RouteException, IOException {if (cacheStrategy != null) return; // Already sent.if (httpStream != null) throw new IllegalStateException();Request request = networkRequest(userRequest);//获取 client 中的 Cache,同时 Cache 在初始化时会读取缓存目录中曾经请求过的所有信息InternalCache responseCache = Internal.instance.internalCache(client); Response cacheCandidate = responseCache != null? responseCache.get(request): null;//1long now = System.currentTimeMillis();cacheStrategy = new CacheStrategy.Factory(now, request, cacheCandidate).get();//网络请求networkRequest = cacheStrategy.networkRequest;//缓存的响应cacheResponse = cacheStrategy.cacheResponse;if (responseCache != null) {//记录当前请求是网络发起还是缓存发起responseCache.trackResponse(cacheStrategy);}if (cacheCandidate != null && cacheResponse == null) {closeQuietly(cacheCandidate.body()); }//不进行网络请求并且缓存不存在或者过期,则返回 504 错误if (networkRequest == null && cacheResponse == null) {userResponse = new Response.Builder().request(userRequest).priorResponse(stripBody(priorResponse)).protocol(Protocol.HTTP_1_1).code(504).message("Unsatisfiable Request (only-if-cached)").body(EMPTY_BODY).build();return;}// 不进行网络请求而且缓存可以使用,则直接返回缓存if (networkRequest == null) {userResponse = cacheResponse.newBuilder().request(userRequest).priorResponse(stripBody(priorResponse)).cacheResponse(stripBody(cacheResponse)).build();userResponse = unzip(userResponse);return;}//需要访问网络时boolean success = false;try {httpStream = connect();httpStream.setHttpEngine(this);...}}
上面的代码显然是在发送请求,但是最主要的是做了缓存的策略。上面代码注释 1 处的 cacheCandidate 是上次与服务器交互时缓存的 Response,这里的缓存均基于 Map。key 是请求中 url 的 md5,value 是在文件中查询到的缓存,页面置换基于 LRU 算法,我们现在只需要知道 cacheCandidate 是一个可以读取缓存 Header 的 Response 即可。根据 cacheStrategy 的处理得到了 networkRequest 和 cacheResponse 这两个值,根据这两个值的数据是否为 null 来进行进一步的处 理。在 networkRequest 和 cacheResponse 都为 null 的情况下,也就是不进行网络请求并且缓存 不存在或者过期,这时返回 504 错误;当 networkRequest 为 null 时也就是不进行网络请求,如 果缓存可以使用时则直接返回缓存,其他情况则请求网络。
接下来查看 HttpEngine 的 readResponse 方法,如下所示:

这个方法主要用来解析 HTTP 响应报头。如果有缓存并且可用,则用缓存的数据并更新缓存,否则就用网络请求返回的数据。再来查看上面代码注释 1 处的 validate 方法是如何判断缓存是否可用的:
private static boolean validate(Response cached, Response network) {//如果服务器返回 304,则缓存有效if (network.code() == HTTP_NOT_MODIFIED) {return true;}//通过缓存和网络请求响应中的 Last-Modified 来计算是否是最新数据。如果是,则缓存有效Date lastModified = cached.headers().getDate("Last-Modified");if (lastModified != null) {Date networkLastModified = network.headers().getDate("Last-Modified");if (networkLastModified != null&& networkLastModified.getTime() < lastModified.getTime()) {return true;}}return false;
}
如果缓存是有效的,则返回 304 Not Modified,否则直接返回 body。如果缓存过期或者强 制放弃缓存,则缓存策略全部交给服务器判断,客户端只需要发送条件 GET 请求即可。条件 GET 请求有两种方式:一种是 Last-Modified-Date,另一种是 ETag。这里采用了 Last-ModifiedDate,通过缓存和网络请求响应中的 Last-Modified 来计算是否是最新数据。如果是,则缓存有效。
失败重连
最后我们再回到 RealCall 的 getResponse 方法,如下所示:
Response getResponse(Request request, boolean forWebSocket) throwsIOException {...boolean releaseConnection = true;try {engine.sendRequest();engine.readResponse();releaseConnection = false;} catch (RequestException e) {throw e.getCause();} catch (RouteException e) {HttpEngine retryEngine = engine.recover(e.getLastConnectException(), null);//1if (retryEngine != null) {releaseConnection = false;engine = retryEngine;continue;}throw e.getLastConnectException();} catch (IOException e) {HttpEngine retryEngine = engine.recover(e, null);//2if (retryEngine != null) {releaseConnection = false;engine = retryEngine;continue;}throw e;} finally {if (releaseConnection) {StreamAllocation streamAllocation = engine.close();streamAllocation.release();}}...engine = new HttpEngine(client, request, false, false, forWebSocket, streamAllocation, null, response);
}}
在上面代码注释 1 和注释 2 处,当发生 IOException 或者 RouteException 时都会执行 HttpEngine 的 recover 方法,它的代码如下所示:
public HttpEngine recover(IOException e, Sink requestBodyOut) {if (!streamAllocation.recover(e, requestBodyOut)) {return null;}if (!client.retryOnConnectionFailure()) {return null;}StreamAllocation streamAllocation = close();return new HttpEngine(client, userRequest, bufferRequestBody, callerWritesRequestBody,forWebSocket,streamAllocation, (RetryableSink)requestBodyOut, priorResponse);
}
通过最后一行可以看到,其就是重新创建了 HttpEngine 并返回,用来完成重连。
相关文章:

【Android】浅析OkHttp(1)
【Android】浅析OkHttp(1) OkHttp 是一个高效、轻量级的 HTTP 客户端库,主要用于 Android 和 Java 应用开发。它不仅支持同步和异步的 HTTP 请求,还支持许多高级功能,如连接池、透明的 GZIP 压缩、响应缓存、WebSocke…...

Generate-on-Graph
目录 摘要1 引言2 相关工作4 不完整知识图谱问答(IKGQA)4.1 任务介绍4.2 数据集构建 5 Generate-on-Graph (GoG) 摘要 为了解决大型语言模型(LLMs)在知识不足和幻觉问题上的困扰,众多研究探索了将LLMs与知识图谱&…...

学习笔记——交换——STP(生成树)简介
一、技术背景 1、生成树技术背景 交换机单线路组网,存在单点故障(上左图),上行线路及设备都不具备冗余性,一旦链路或上行设备发生故障,业务将会中断。 为了使得网络更加健壮、更具有冗余性,将拓扑修改为(上右图)接入…...

【Linux从入门到精通一】操作系统概述与Linux初识
个人名片 🎓作者简介:java领域优质创作者 🌐个人主页:码农阿豪 📞工作室:新空间代码工作室(提供各种软件服务) 💌个人邮箱:[2435024119qq.com] 📱…...

Git 深度解析 —— 从基础到进阶
目录 1. Git 基础概念 1.1 版本控制 (Version Control) 1.2 分布式版本控制 (Distributed Version Control) 1.3 核心概念 1.4 Git 工作流程 2. Git 常用命令 2.1 初始化仓库 2.2 添加文件 2.3 提交修改 2.4 查看状态 2.5 查看历史记录 2.6 切换分支 2.7 创建分支…...

PCIE-变量总结
1.changed_speed_recovery: 表示链路双方已经将链路速率协商为更高的速率。 在configuration.complete状态下此变量会reset成0; 当前状态在recovery.rcvrlock状态: 在经过24ms的timeout之后,任何一个已经configured的lane&…...

【iOS】AFNetworing初步学习
文章目录 前言OC的网络请求步骤单例封装网络请求使用AFNetworking进行网络请求 前言 在暑假,学习了一些简单的网络请求的内容,本周学习了AFNetworking的基本使用,通过本篇博客进行一个简单的介绍。 OC的网络请求步骤 简单的网络请求主要有…...

【数据结构】堆的创建
Heap.h #include<stdio.h> #include<stdlib.h> #include<stdbool.h> #include<assert.h>//创建堆结构体 typedef int HPDateType; typedef struct Heap {HPDateType* a;int size;int capacity; }HP;//堆的初始化 void HPInit(HP* php);//堆的销毁 voi…...

Linux下Git操作
一、基本命令 1、创建 git 目录(工作区) mkdir gitcode 2、创建本地仓库,生成 .git 隐藏目录 git init 3、设置配置项 git config user.name "xxx" git config user.email "....." 4、查看配置项 git config -l …...

缺失d3dx9_42.dll如何修复,d3dx9_42.dll故障的6种修复方法分享
在电脑使用过程中,许多游戏玩家和软件用户可能都遇到过d3dx9_42.dll丢失的问题。这个问题会导致游戏或软件无法正常运行,给用户带来诸多不便。本文将详细解读d3dx9_42.dll丢失的原因、影响及解决方案,帮助大家顺利解决这个问题。 一、d3dx9_4…...

深入理解Android WebView的加载流程与事件回调
文章目录 一、WebView 加载流程时序图二、WebView 加载流程回调函数说明三、AwContents3.1 主要功能和职责3.2 架构和实现3.3 使用场景 四、利用WebView回调函数检测白屏4.1 使用onPageStarted和onPageFinished检测加载时间4.2 利用onReceivedError和onReceivedHttpError检测加…...

机器视觉相机自动对焦算法
第一,Brenner梯度法、 第二,Tenegrad梯度法、 第三,laplace梯度法、 第四,方差法、 第五,能量梯度法。 此实例通过使用Halcon实现5种清晰度算法函数: 1. 方差算法函数; 2. 拉普拉斯能量函数…...

StarTowerChain:开启去中心化创新篇章
官网: www.startower.fr 在当今创新驱动的时代,StarTowerChain 以其独特的去中心化创新模式,为我们带来了新的希望和机遇。去中心化,这个充满活力与创造力的理念,正引领着我们走向未来的创新之路。 StarTowerChain …...

SpringCloudStream使用StreamBridge实现延时队列
利用RabbitMQ实现消息的延迟队列 一、安装RabbitMQ 1、安装rabbitmq 安装可以看https://blog.csdn.net/qq_38618691/article/details/118223851,进行安装。 2、安装插件 安装完毕后,exchange是不支持延迟类型的,需要手动安装插件,需要和安装的rabbitmq版本一致 https:…...

MATLAB中head函数用法
目录 语法 说明 示例 显示矩阵的前八行 显示表的前三行 返回表的前八行 head函数的功能是获取数组或表的顶行。 语法 head(A) head(A,k) B head(___) 说明 head(A) 在命令行窗口中显示数组、表或时间表 A 的前八行,但不存储值。 head(A,k) 显示 A 的前 k …...

golang 基本数据类型
1. go语言的数据类型简介 golang的数据类型分为两大类,一类是基本数据类型和符合数据类型; 按照传递的内容分:传递本身数据和传递地址; golang和java很相似,都是值传递,不过分为传递的值和传递的地址&a…...

各种查询sql介绍
1. 关联查询(JOIN) 关联查询用于从多个表中检索数据。它基于两个或多个表之间的共同字段(通常是主键和外键)来组合数据。 内连接(INNER JOIN): sql SELECT a.name, b.order_date FROM custome…...

Guava防击穿回源-异步防击穿
异步防击穿策略 在高并发环境下,缓存击穿(Cache Stampede)是一种常见的问题。当缓存中的热点数据失效或未命中时,大量并发请求同时访问后端数据源(如数据库),可能导致后端系统压力骤增,甚至出现崩溃。为了有效防止这种情况,可以利用Guava提供的异步缓存加载机制(类似…...

人工智能正在扼杀云计算的可持续性
可持续性曾是公共云计算中备受推崇的优势。企业和云提供商大肆宣扬他们的绿色计划,推广采用可再生能源的数据中心,以减少碳足迹。 近几个月来,这个话题已悄然淡出人们的视线。罪魁祸首是什么?对人工智能功能的无限需求正在推动云…...

C# 条形码、二维码标签打印程序
1、条码标答打印主界面 2、打印设置 3、生成QR代码 private void GetBarcode_T(string lr) { QRCodeEncoder qrCodeEncoder = new QRCodeEncoder();//创建一个对象 qrCodeEncoder.QRCodeEncodeMode = QRCodeEncoder.ENCODE_MODE.BYTE; //设置编码测量…...

嵌入式入门学习——6Protues点亮数码管,认识位码和段码,分辨共阴还是共阳(数字时钟第一步)
0 系列文章入口 嵌入式入门学习——0快速入门,Let‘s Do It! 首先新建基于Arduino UNO的protues工程,见本系列第3篇文章 1 点“P”按钮找器件 2 输入“seg”或“digit”查找数码管器件 3 找到我们想要的6位7段数码管 4如图A、B…DP都是段码…...

poisson过程——随机模拟(Python和R实现)
Python实现 exponential()使用,自动poisson过程实现。 import numpy as np import matplotlib.pyplot as plt# Parameters lambda_rate 5 # rate parameter (events per time unit) T 10 # total time# Generate Poisson process times np.random.exponential(…...

100 种下划线 / 覆盖层动画 | 终极 CSS(层叠样式表)集合
还在为你的菜单项和链接寻找动画效果而感到疲惫吗? 不用再找了!这里列出了 100 多种不同的动画。从简单的到更复杂的,你肯定能找到自己想要的。 无需 SVG(可缩放矢量图形),无需 JavaScript(脚…...

华为ICT大赛2024-2025网络赛道考试分析
华为ICT大赛2024-2025正在报名中,网络赛道的同学如何备考,了解考试内容呢? 一、考试概况 华为ICT大赛分为4个赛段,分别为省赛初赛、省赛复赛、中国总决赛,全球总决赛。其中对应的能力级别分别如下: 省赛…...

linux 效率化 - 输入法 - fcitx5
安装 Fcitx5 1. 卸载 ibus 框架 由于 ibus 和 fcitx 可能会冲突,先卸载 ibus(暂未确认原因) sudo apt remove --purge ibus2. 安装 fcitx5 输入法框架 sudo apt update sudo apt install fcitx5 fcitx5-chinese-addons fcitx5-frontend-gtk…...

YOLOv11改进策略【卷积层】| 替换骨干网络 CVPR-2024 RepViT 轻量级的Vision Transformers架构
一、本文介绍 本文记录的是基于RepVit的YOLOv11轻量化改进方法研究。RepVit的网络结构借鉴ViT的设计理念,通过分离的token mixe和channel mixer减少推理时的计算和内存成本,同时减少扩展比率并增加宽度,降低延迟,并通过加倍通道来弥补参数大幅减少的问题,提高了准确性。本…...

一天认识一个硬件之路由器
今天来给大家分享一下路由器的知识,先来说一下什么是路由器,路由器是一种计算机网络设备,它的主要作用是在不同的网络之间转发数据包,实现数据的传输和共享,介绍完了什么是路由器,再来介绍一下路由器的定义…...

【scene_manager】与 MoveIt 机器人的规划场景进行交互
scene_manager Scene Manager包是由 Robotnik 创建的 ROS 包,旨在帮助构建和与 MoveIt 机器人的规划场景进行交互。 背景信息 MoveIt 规划场景 是一个用于存储机器人周围世界的表示(外部碰撞)以及机器人自身状态(内部碰撞和当…...

数据结构单向链表的插入和删除(一)
链表 一、链表结构: (物理存储结构上不连续,逻辑上连续;大小不固定)二、单链表:三、单项链表的代码实现:四、开发可用的链表:四、单链表的效率分析: 一、链表结构&#x…...

鸿蒙网络编程系列30-断点续传下载文件示例
1. 断点续传简介 在文件的下载中,特别是大文件的下载中,可能会出现各种原因导致的下载暂停情况,如果不做特殊处理,下次还需要从头开始下载,既浪费了时间,又浪费了流量。不过,HTTP协议通过Range…...