Android IO 框架 Okio 的实现原理,如何检测超时?
本文已收录到 AndroidFamily,技术和职场问题,请关注公众号 [彭旭锐] 提问。
前言
大家好,我是小彭。
在上一篇文章里,我们聊到了 Square 开源的 I/O 框架 Okio 的三个优势:精简且全面的 API、基于共享的缓冲区设计以及超时机制。前两个优势已经分析过了,今天我们来分析 Okio 的超时检测机制。
本文源码基于 Okio v3.2.0。
思维导图:

1. 认识 Okio 的超时机制
超时机制是一项通用的系统设计,能够避免系统长时间阻塞在某些任务上。例如网络请求在超时时间内没有响应,客户端就会提前中断请求,并提示用户某些功能不可用。
1.1 说一下 Okio 超时机制的优势
先思考一个问题,相比于传统 IO 的超时有什么优势呢?我认为主要体现在 2 个方面:
- 优势 1 - Okio 弥补了部分 IO 操作不支持超时检测的缺陷:
Java 原生 IO 操作是否支持超时,完全取决于底层的系统调用是否支持。例如,网络 Socket 支持通过 setSoTimeout API 设置单次 IO 操作的超时时间,而文件 IO 操作就不支持,使用原生文件 IO 就无法实现超时。
而 Okio 是统一在应用层实现超时检测,不管系统调用是否支持超时,都能提供统一的超时检测机制。
- 优势 2 - Okio 不仅支持单次 IO 操作的超时检测,还支持包含多次 IO 操作的复合任务超时检测:
Java 原生 IO 操作只能实现对单次 IO 操作的超时检测,无法实现对包含多次 IO 操作的复合任务超时检测。例如,OkHttp 支持配置单次 connect、read 或 write 操作的超时检测,还支持对一次完整 Call 请求的超时检测,有时候单个操作没有超时,但串联起来的完整 call 却超时了。
而 Okio 超时机制和 IO 操作没有强耦合,不仅支持对 IO 操作的超时检测,还支持非 IO 操作的超时检测,所以这种复合任务的超时检测也是可以实现的。
1.2 Timeout 类的作用
Timeout 类是 Okio 超时机制的核心类,Okio 对 Source 输入流和 Sink 输出流都提供了超时机制,我们在构造 InputStreamSource 和 OutputStreamSink 这些流的实现类时,都需要携带 Timeout 对象:
Source.kt
interface Source : Closeable {// 返回超时控制对象fun timeout(): Timeout...
}
Sink.kt
actual interface Sink : Closeable, Flushable {// 返回超时控制对象actual fun timeout(): Timeout...
}
Timeout 类提供了两种配置超时时间的方式(如果两种方式同时存在的话,Timeout 会优先采用更早的截止时间):
- 1、timeoutNanos 任务处理时间: 设置处理单次任务的超时时间,
最终触发超时的截止时间是任务的 startTime + timeoutNanos;
- 2、deadlineNanoTime 截止时间: 直接设置未来的某个时间点,多个任务整体的超时时间点。
Timeout.kt
// hasDeadline 这个属性显得没必要
private var hasDeadline = false // 是否设置了截止时间点
private var deadlineNanoTime = 0L // 截止时间点(单位纳秒)
private var timeoutNanos = 0L // 处理单次任务的超时时间(单位纳秒)
创建 Source 和 Sink 对象时,都需要携带 Timeout 对象:
JvmOkio.kt
// ----------------------------------------------------------------------------
// 输入流
// ----------------------------------------------------------------------------fun InputStream.source(): Source = InputStreamSource(this, Timeout() /*Timeout 对象*/)// 文件输入流
fun File.source(): Source = InputStreamSource(inputStream(), Timeout.NONE)// Socket 输入流
fun Socket.source(): Source {val timeout = SocketAsyncTimeout(this)val source = InputStreamSource(getInputStream(), timeout /*携带 Timeout 对象*/)// 包装为异步超时return timeout.source(source)
}// ----------------------------------------------------------------------------
// 输出流
// ----------------------------------------------------------------------------fun OutputStream.sink(): Sink = OutputStreamSink(this, Timeout() /*Timeout 对象*/)// 文件输出流
fun File.sink(append: Boolean = false): Sink = FileOutputStream(this, append).sink()// Socket 输出流
fun Socket.sink(): Sink {val timeout = SocketAsyncTimeout(this)val sink = OutputStreamSink(getOutputStream(), timeout /*携带 Timeout 对象*/)// 包装为异步超时return timeout.sink(sink)
}
在 Timeout 类的基础上,Okio 提供了 2 种超时机制:
- Timeout 是同步超时
- AsyncTimeout 是异步超时
Okio 框架

2. Timeout 同步超时
Timeout 同步超时依赖于 Timeout#throwIfReached() 方法。
同步超时在每次执行任务之前,都需要先调用 Timeout#throwIfReached() 检查当前时间是否到达超时截止时间。如果超时则会直接抛出超时异常,不会再执行任务。
JvmOkio.kt
private class InputStreamSource(// 输入流private val input: InputStream,// 超时控制private val timeout: Timeout
) : Source {override fun read(sink: Buffer, byteCount: Long): Long {// 1、参数校验if (byteCount == 0L) return 0require(byteCount >= 0) { "byteCount < 0: $byteCount" }// 2、检查超时时间timeout.throwIfReached()// 3、执行输入任务(已简化)val bytesRead = input.read(...)return bytesRead.toLong()}...
}private class OutputStreamSink(// 输出流private val out: OutputStream,// 超时控制private val timeout: Timeout
) : Sink {override fun write(source: Buffer, byteCount: Long) {// 1、参数校验checkOffsetAndCount(source.size, 0, byteCount)// 2、检查超时时间timeout.throwIfReached()// 3、执行输入任务(已简化)out.write(...)...}...
}
看一眼 Timeout#throwIfReached 的源码。 可以看到,同步超时只考虑 “deadlineNanoTime 截止时间”,如果只设置 “timeoutNanos 任务处理时间” 是无效的,我觉得这个设计容易让开发者出错。
Timeout.kt
@Throws(IOException::class)
open fun throwIfReached() {if (Thread.interrupted()) {// 传递中断状态Thread.currentThread().interrupt() // Retain interrupted status.throw InterruptedIOException("interrupted")}if (hasDeadline && deadlineNanoTime - System.nanoTime() <= 0) {// 抛出超时异常throw InterruptedIOException("deadline reached")}
}
有必要解释所谓 “同步” 的意思:
同步超时就是指任务的 “执行” 和 “超时检查” 是同步的。当任务超时时,Okio 同步超时不会直接中断任务执行,而是需要检主动查超时时间(Timeout#throwIfReached)来判断是否发生超时,再决定是否中断任务执行。
这其实与 Java 的中断机制是非常相似的:
当 Java 线程的中断标记位置位时,并不是真的会直接中断线程执行,而是主动需要检查中断标记位(Thread.interrupted)来判断是否发生中断,再决定是否中断线程任务。所以说 Java 的线程中断机制是一种 “同步中断”。
可以看出,同步超时存在 “滞后性”:
因为同步超时需要主动检查,所以即使在任务执行过程中发生超时,也必须等到检查时才会发现超时,无法及时触发超时异常。因此,就需要异步超时机制。
同步超时示意图

3. AsyncTimeout 异步超时
-
异步超时监控进入: 异步超时在每次执行任务之前,都需要先调用
AsyncTimeout#enter()方法将 AsyncTimeout 挂载到超时队列中,并根据超时截止时间的先后顺序排序,队列头部的节点就是会最先超时的任务; -
异步超时监控退出: 在每次任务执行结束之后,都需要再调用
AsyncTimeout#exit()方法将 AsyncTimeout 从超时队列中移除。
注意: enter() 方法和 eixt() 方法必须成对存在。
AsyncTimeout.kt
open class AsyncTimeout : Timeout() {// 是否在等待队列中private var inQueue = false// 后续指针private var next: AsyncTimeout? = null// 超时截止时间private var timeoutAt = 0L// 异步超时监控进入fun enter() {check(!inQueue) { "Unbalanced enter/exit" }val timeoutNanos = timeoutNanos()val hasDeadline = hasDeadline()if (timeoutNanos == 0L && !hasDeadline) {return}inQueue = truescheduleTimeout(this, timeoutNanos, hasDeadline)}// 异步超时监控退出// 返回值:是否发生超时(如果节点不存在,说明被 WatchDog 线程移除,即发生超时)fun exit(): Boolean {if (!inQueue) return falseinQueue = falsereturn cancelScheduledTimeout(this)}// 在 WatchDog 线程调用protected open fun timedOut() {}companion object {// 超时队列头节点(哨兵节点)private var head: AsyncTimeout? = null// 分发超时监控任务private fun scheduleTimeout(node: AsyncTimeout, timeoutNanos: Long, hasDeadline: Boolean) {synchronized(AsyncTimeout::class.java) {// 首次添加监控时,需要启动 Watchdog 线程if (head == null) {// 哨兵节点head = AsyncTimeout()Watchdog().start()}// now:当前时间val now = System.nanoTime()// timeoutAt 超时截止时间:计算 now + timeoutNanos 和 deadlineNanoTime 的较小值if (timeoutNanos != 0L && hasDeadline) {node.timeoutAt = now + minOf(timeoutNanos, node.deadlineNanoTime() - now)} else if (timeoutNanos != 0L) {node.timeoutAt = now + timeoutNanos} else if (hasDeadline) {node.timeoutAt = node.deadlineNanoTime()} else {throw AssertionError()}// remainingNanos 超时剩余时间:当前时间距离超时发生的时间val remainingNanos = node.remainingNanos(now)var prev = head!!// 线性遍历超时队列,按照超时截止时间将 node 节点插入超时队列while (true) {if (prev.next == null || remainingNanos < prev.next!!.remainingNanos(now)) {node.next = prev.nextprev.next = node// 如果插入到队列头部,需要唤醒 WatchDog 线程if (prev === head) {(AsyncTimeout::class.java as Object).notify()}break}prev = prev.next!!}}}// 取消超时监控任务// 返回值:是否超时private fun cancelScheduledTimeout(node: AsyncTimeout): Boolean {synchronized(AsyncTimeout::class.java) {// 线性遍历超时队列,将 node 节点移除var prev = headwhile (prev != null) {if (prev.next === node) {prev.next = node.nextnode.next = nullreturn false}prev = prev.next}// 如果节点不存在,说明被 WatchDog 线程移除,即发生超时return true}}}
}
同时,在首次添加异步超时监控时,AsyncTimeout 内部会开启一个 WatchDog 守护线程,按照 “检测 - 等待” 模型观察超时队列的头节点:
-
如果发生超时,则将头节点移除,并回调
AsyncTimeout#timeOut()方法。这是一个空方法,需要由子类实现来主动取消任务; -
如果未发生超时,则 WatchDog 线程会计算距离超时发生的时间间隔,调用
Object#wait(时间间隔)进入限时等待。
需要注意的是: AsyncTimeout#timeOut() 回调中不能执行耗时操作,否则会影响后续检测的及时性。
有意思的是:我们会发现 Okio 的超时检测机制和 Android ANR 的超时检测机制非常类似,所以我们可以说 ANR 也是一种异步超时机制。
AsyncTimeout.kt
private class Watchdog internal constructor() : Thread("Okio Watchdog") {init {// 守护线程isDaemon = true}override fun run() {// 死循环while (true) {try {var timedOut: AsyncTimeout? = nullsynchronized(AsyncTimeout::class.java) {// 取头节点(Maybe wait)timedOut = awaitTimeout()// 超时队列为空,退出线程if (timedOut === head) {head = nullreturn}}// 超时发生,触发 AsyncTimeout#timedOut 回调timedOut?.timedOut()} catch (ignored: InterruptedException) {}}}
}companion object {// 超时队列为空时,再等待一轮的时间private val IDLE_TIMEOUT_MILLIS = TimeUnit.SECONDS.toMillis(60)private val IDLE_TIMEOUT_NANOS = TimeUnit.MILLISECONDS.toNanos(IDLE_TIMEOUT_MILLIS)@Throws(InterruptedException::class)internal fun awaitTimeout(): AsyncTimeout? {// Get the next eligible node.val node = head!!.next// 如果超时队列为空if (node == null) {// 需要再等待 60s 后再判断(例如在首次添加监控时)val startNanos = System.nanoTime()(AsyncTimeout::class.java as Object).wait(IDLE_TIMEOUT_MILLIS)return if (head!!.next == null && System.nanoTime() - startNanos >= IDLE_TIMEOUT_NANOS) {// 退出 WatchDog 线程head} else {// WatchDog 线程重新取一次null}}// 计算当前时间距离超时发生的时间var waitNanos = node.remainingNanos(System.nanoTime())// 未超时,进入限时等待if (waitNanos > 0) {// Waiting is made complicated by the fact that we work in nanoseconds,// but the API wants (millis, nanos) in two arguments.val waitMillis = waitNanos / 1000000LwaitNanos -= waitMillis * 1000000L(AsyncTimeout::class.java as Object).wait(waitMillis, waitNanos.toInt())return null}// 超时,将头节点移除head!!.next = node.nextnode.next = nullreturn node}
}
异步超时示意图

直接看代码不好理解,我们来举个例子:
4. 举例:OkHttp Call 的异步超时监控
在 OkHttp 中,支持配置一次完整的 Call 请求上的操作时间 callTimeout。一次 Call 请求包含多个 IO 操作的复合任务,使用传统 IO 是不可能监控超时的,所以需要使用 AsyncTimeout 异步超时。
在 OkHttp 的 RealCall 请求类中,就使用了 AsyncTimeout 异步超时:
-
1、开始任务: 在 execute() 方法中,调用
AsyncTimeout#enter()进入异步超时监控,再执行请求; -
2、结束任务: 在 callDone() 方法中,调用
AsyncTimeout#exit()退出异步超时监控。分析源码发现:callDone() 不仅在请求正常时会调用,在取消请求时也会回调,保证了 enter() 和 exit() 成对存在; -
3、超时回调: 在
AsyncTimeout#timeOut超时回调中,调用了 Call#cancel() 提前取消请求。Call#cancel() 会调用到 Socket#close(),让阻塞中的 IO 操作抛出 SocketException 异常,以达到提前中断的目的,最终也会走到 callDone() 执行 exit() 退出异步监控。
Call 超时监控示意图

RealCall
class RealCall(val client: OkHttpClient,/** The application's original request unadulterated by redirects or auth headers. */val originalRequest: Request,val forWebSocket: Boolean
) : Call {// 3、AsyncTimeout 超时监控private val timeout = object : AsyncTimeout() {override fun timedOut() {// 取消请求cancel()}}.apply {timeout(client.callTimeoutMillis.toLong(), MILLISECONDS)}// 取消请求override fun cancel() {if (canceled) return // Already canceled.canceled = trueexchange?.cancel()// 最终会调用 Socket#close()connectionToCancel?.cancel()eventListener.canceled(this)}// 1、请求开始(由业务层调用)override fun execute(): Response {// 1.1 异步超时监控进入timeout.enter()// 1.2 执行请求client.dispatcher.executed(this)return getResponseWithInterceptorChain()}// 2、请求结束(由 OkHttp 引擎层调用,包含正常和异常情况)// 除了 IO 操作在抛出异常后会走到 callDone(),在取消请求时也会走到 callDone()internal fun <E : IOException?> messageDone(exchange: Exchange,requestDone: Boolean, // 请求正常结束responseDone: Boolean, // 响应正常结束e: E): E {...if (callDone) {return callDone(e)}return e}private fun <E : IOException?> callDone(e: E): E {...// 检查是否超时val result = timeoutExit(e)if (e != null) {// 请求异常(包含超时异常)eventListener.callFailed(this, result!!)} else {// 请求正常结束eventListener.callEnd(this)}return result}private fun <E : IOException?> timeoutExit(cause: E): E {if (timeoutEarlyExit) return cause// 2.1 异步超时监控退出if (!timeout.exit()) return cause// 2.2 包装超时异常val e = InterruptedIOException("timeout")if (cause != null) e.initCause(cause)return e as E}
}
调用 Socket#close() 会让阻塞中的 IO 操作抛出 SocketException 异常:
Socket.java
// Any thread currently blocked in an I/O operation upon this socket will throw a {@link SocketException}.
public synchronized void close() throws IOException {synchronized(closeLock) {if (isClosed())return;if (created)impl.close();closed = true;}
}
Exchange 中会捕获 Socket#close() 抛出的 SocketException 异常:
Exchange.kt
private inner class RequestBodySink(delegate: Sink,/** The exact number of bytes to be written, or -1L if that is unknown. */private val contentLength: Long
) : ForwardingSink(delegate) {@Throws(IOException::class)override fun write(source: Buffer, byteCount: Long) {...try {super.write(source, byteCount)this.bytesReceived += byteCount} catch (e: IOException) {// Socket#close() 会抛出异常,被这里拦截throw complete(e)}}private fun <E : IOException?> complete(e: E): E {if (completed) return ecompleted = truereturn bodyComplete(bytesReceived, responseDone = false, requestDone = true, e = e)}
}fun <E : IOException?> bodyComplete(bytesRead: Long,responseDone: Boolean,requestDone: Boolean,e: E
): E {...// 回调到上面的 RealCall#messageDonereturn call.messageDone(this, requestDone, responseDone, e)
}
5. OkHttp 超时检测总结
先说一下 Okhttp 定义的 2 种颗粒度的超时:
- 第 1 种是在单次 connect、read 或 write 操作上的超时;
- 第 2 种是在一次完整的 call 请求上的超时,有时候单个操作没有超时,但连接起来的完整 call 却超时。
其实 Socket 支持通过 setSoTimeout API 设置单次操作的超时时间,但这个 API 无法满足需求,比如说 Call 超时是包含多个 IO 操作的复合任务,而且不管是 HTTP/1 并行请求还是 HTTP/2 多路复用,都会存在一个 Socket 连接上同时承载多个请求的情况,无法区分是哪个请求超时。
因此,OkHttp 采用了两种超时监测:
- 对于 connect 操作,OkHttp 继续使用 Socket 级别的超时,没有问题;
- 对于 call、read 和 write 的超时,OkHttp 使用一个 Okio 的异步超时机制来监测超时。
参考资料
- Github · Okio
- Okio 官网

相关文章:
Android IO 框架 Okio 的实现原理,如何检测超时?
本文已收录到 AndroidFamily,技术和职场问题,请关注公众号 [彭旭锐] 提问。 前言 大家好,我是小彭。 在上一篇文章里,我们聊到了 Square 开源的 I/O 框架 Okio 的三个优势:精简且全面的 API、基于共享的缓冲区设计以…...
简单介绍反射
1.定义Java的反射机制是在运行状态中,对于任意一个类,都知道这个类的所有属性和方法;对于任意一个对象,都能调用它的任意方法和属性,既然能拿到,我们就可以修改部分类型信息;这种动态获取信息的…...
PyTorch学习笔记:nn.MSELoss——MSE损失
PyTorch学习笔记:nn.MSELoss——MSE损失 torch.nn.MSELoss(size_average None,reduce None,reduction mean)功能:创建一个平方误差(MSE)损失函数,又称为L2损失: l(x,y)L{l1,…,lN}T,ln(xn−yn)2l(x,y)L…...
apache和nginx的TLS1.0和TLS1.1禁用处理方案
1、TLS1.0和TLS1.1是什么? TLS协议其实就是网络安全传输层协议,用于在两个通信应用程序之间提供保密性和数据完整性,TLS 1. 0 和TLS 1. 1 是分别是96 年和 06 年发布的老版协议。 2、为什么要禁用TLS1.0和TLS1.1传输协议 TLS1.0和TLS1.1协…...
K_A12_002 基于STM32等单片机采集光敏电阻传感器参数串口与OLED0.96双显示
K_A12_002 基于STM32等单片机采集光敏电阻传感器参数串口与OLED0.96双显示一、资源说明二、基本参数参数引脚说明三、驱动说明IIC地址/采集通道选择/时序对应程序:四、部分代码说明1、接线引脚定义1.1、STC89C52RC光敏电阻传感器模块1.2、STM32F103C8T6光敏电阻传感器模块五、基…...
《机器学习》学习笔记
第 2 章 模型评估与选择 2.1 经验误差与过拟合 精度:精度1-错误率。如果在 mmm 个样本中有 aaa 个样本分类错误,则错误率 Ea/mEa/mEa/m,精度 1−a/m1-a/m1−a/m。误差:一般我们把学习器的实际预测输出与样本的真实输出之间的差…...
前端卷算法系列(一)
前端卷算法系列(一) 两数之和 给定一个整数数组 nums 和一个整数目标值 target,请你在该数组中找出 和为目标值 target 的那 两个 整数,并返回它们的数组下标。 你可以假设每种输入只会对应一个答案。但是,数组中同…...
【机器学习】聚类算法(理论)
聚类算法(理论) 目录一、概论1、聚类算法的分类2、欧氏空间的引入二、K-Means算法1、算法思路2、算法总结三、DBSCAN算法1、相关概念2、算法思路3、算法总结四、实战部分一、概论 聚类分析,即聚类(Clustering)…...
Docker-用Jenkins发版Java项目-(1)Docke安装Jenkins
文章目录前言环境背景操作流程docker安装及jenkins软件安装jenkins配置登录配置安装插件及创建账号前言 学海无涯,旅“途”漫漫,“途”中小记,如有错误,敬请指出,在此拜谢! 最近新购得了M2的MAC,…...
java集合框架内容整理
主要内容集合框架体系ArrayListLinkedListHashSetTreeSetLinkedHashSet内部比较器和外部比较器哈希表的原理List集合List集合的主要实现类有ArrayList和LinkedList,分别是数据结构中顺序表和链表的实现。另外还包括栈和队列的实现类:Deque和Queue。• Li…...
win10系统安装Nginx
Nginx是一款自由的、开源的、高性能的HTTP服务器和反向代理服务器,同时也提供了IMAP/POP3/SMTP服务。 Nginx可以进行反向代理、负载均衡、HTTP服务器(动静分离)、正向代理等操作。因为最近在公司使用到了Nginx 第一步:下载Nginx …...
数据库学习笔记(2)——workbench和SQL语言
1、workbench简介: 登录客户端的两种方法 在cmd中,只能通过sql语句控制数据库;workbench其实就是一种图形化数据库管理工具,在workbench中既可以通过sql语句控制数据库,也可以通过图形化界面控制数据库。通过workbenc…...
测量学期末考试之名词解释总结
仅供自己参考,且范围不全面.大地水准面与处于静止平衡状态的平均海水面重合,并延伸通过陆地的水准面高程地面点到大地水准面的铅锤距离水准面处于静止状态的水面就是水准面高差两点的水准面之间的铅锤距离垂直角在铅锤面上,瞄准目标的倾斜视线…...
TDengine时序数据库的简单使用
最近学习了TDengine数据库,因为我们公司有硬件设备,设备按照每分钟,每十分钟,每小时上传数据,存入数据库。而这些数据会经过sql查询,统计返回展示到前端。但时间积累后现在数据达到了百万级数据,…...
记录每日LeetCode 2335.装满被子需要的最短总时长 Java实现
题目描述: 现有一台饮水机,可以制备冷水、温水和热水。每秒钟,可以装满 2 杯 不同 类型的水或者 1 杯任意类型的水。 给你一个下标从 0 开始、长度为 3 的整数数组 amount ,其中 amount[0]、amount[1] 和 amount[2] 分别表示需要…...
了解线程池newFixedTheadPool
什么是线程池 操作系统 能够进行运算 调度 的最小单位。线程池是一种多线程处理形式。 为什么引入线程池的概念 解决处理短时间任务时创建和销毁线程代价较大的弊端,可以使用线程池技术。 复用 饭店只有一个服务员和饭店有10个服务员 线程池的种类 newFixedThea…...
IP分片和TCP分段解析--之IP分片
本文目录什么是IP分片为什么会产生IP分片为什么要避免IP分片如何避免IP分片什么是IP分片 IP协议栈将TCP/UDP传输层要求它发送的,但长度大于发送端口MTU的一个数据包,分割成多个IP报文后分多次发送。这些分成多次发送的多个IP报文就是IP分片。 为什么会…...
物联网方向常见通信方式有哪些?
常用的有线通信方式有串口、以太网等。 1、串口 串口通信普及率高、成本低,但是组网能力差,只适合低速率和小数据量的通信 2、以太网接口(网线) 以太网(Ethernet)是目前最普遍的一种局域网 通信技术,它规定了包括 物理层的连线、电子信号和介质访问层协议的内容。 以太…...
windows wireshark抓到未加入组的组播消息
现象 在Windows上开启wireshark,抓到了大量地址为239.255.255.251的组播包。 同时,根据组播相关命令,调用netsh interface ipv4 show joins,显示当前并没加入 239.255.255.251 组播组。 解决 根据IGMP Snooping,I…...
【PTA Advanced】1156 Sexy Primes(C++)
目录 题目 Input Specification: Output Specification: Sample Input 1: Sample Output 1: Sample Input 2: Sample Output 2: 思路 代码 题目 Sexy primes are pairs of primes of the form (p, p6), so-named since "sex" is the Latin word for "…...
浅谈 React Hooks
React Hooks 是 React 16.8 引入的一组 API,用于在函数组件中使用 state 和其他 React 特性(例如生命周期方法、context 等)。Hooks 通过简洁的函数接口,解决了状态与 UI 的高度解耦,通过函数式编程范式实现更灵活 Rea…...
TDengine 快速体验(Docker 镜像方式)
简介 TDengine 可以通过安装包、Docker 镜像 及云服务快速体验 TDengine 的功能,本节首先介绍如何通过 Docker 快速体验 TDengine,然后介绍如何在 Docker 环境下体验 TDengine 的写入和查询功能。如果你不熟悉 Docker,请使用 安装包的方式快…...
利用ngx_stream_return_module构建简易 TCP/UDP 响应网关
一、模块概述 ngx_stream_return_module 提供了一个极简的指令: return <value>;在收到客户端连接后,立即将 <value> 写回并关闭连接。<value> 支持内嵌文本和内置变量(如 $time_iso8601、$remote_addr 等)&a…...
从零实现富文本编辑器#5-编辑器选区模型的状态结构表达
先前我们总结了浏览器选区模型的交互策略,并且实现了基本的选区操作,还调研了自绘选区的实现。那么相对的,我们还需要设计编辑器的选区表达,也可以称为模型选区。编辑器中应用变更时的操作范围,就是以模型选区为基准来…...
(二)原型模式
原型的功能是将一个已经存在的对象作为源目标,其余对象都是通过这个源目标创建。发挥复制的作用就是原型模式的核心思想。 一、源型模式的定义 原型模式是指第二次创建对象可以通过复制已经存在的原型对象来实现,忽略对象创建过程中的其它细节。 📌 核心特点: 避免重复初…...
什么是EULA和DPA
文章目录 EULA(End User License Agreement)DPA(Data Protection Agreement)一、定义与背景二、核心内容三、法律效力与责任四、实际应用与意义 EULA(End User License Agreement) 定义: EULA即…...
AI编程--插件对比分析:CodeRider、GitHub Copilot及其他
AI编程插件对比分析:CodeRider、GitHub Copilot及其他 随着人工智能技术的快速发展,AI编程插件已成为提升开发者生产力的重要工具。CodeRider和GitHub Copilot作为市场上的领先者,分别以其独特的特性和生态系统吸引了大量开发者。本文将从功…...
#Uniapp篇:chrome调试unapp适配
chrome调试设备----使用Android模拟机开发调试移动端页面 Chrome://inspect/#devices MuMu模拟器Edge浏览器:Android原生APP嵌入的H5页面元素定位 chrome://inspect/#devices uniapp单位适配 根路径下 postcss.config.js 需要装这些插件 “postcss”: “^8.5.…...
MySQL 8.0 事务全面讲解
以下是一个结合两次回答的 MySQL 8.0 事务全面讲解,涵盖了事务的核心概念、操作示例、失败回滚、隔离级别、事务性 DDL 和 XA 事务等内容,并修正了查看隔离级别的命令。 MySQL 8.0 事务全面讲解 一、事务的核心概念(ACID) 事务是…...
【MATLAB代码】基于最大相关熵准则(MCC)的三维鲁棒卡尔曼滤波算法(MCC-KF),附源代码|订阅专栏后可直接查看
文章所述的代码实现了基于最大相关熵准则(MCC)的三维鲁棒卡尔曼滤波算法(MCC-KF),针对传感器观测数据中存在的脉冲型异常噪声问题,通过非线性加权机制提升滤波器的抗干扰能力。代码通过对比传统KF与MCC-KF在含异常值场景下的表现,验证了后者在状态估计鲁棒性方面的显著优…...
