当前位置: 首页 > news >正文

Android IO 框架 Okio 的实现原理,到底哪里 OK?

本文已收录到 AndroidFamily,技术和职场问题,请关注公众号 [彭旭锐] 提问。

前言

大家好,我是小彭。

今天,我们来讨论一个 Square 开源的 I/O 框架 Okio,我们最开始接触到 Okio 框架还是源于 Square 家的 OkHttp 网络框架。那么,OkHttp 为什么要使用 Okio,它相比于 Java 原生 IO 有什么区别和优势?今天我们就围绕这些问题展开。

本文源码基于 Okio v3.2.0。


思维导图


1. 说一下 Okio 的优势?

相比于 Java 原生 IO 框架,我认为 Okio 的优势主要体现在 3 个方面:

  • 1、精简且全面的 API: 原生 IO 使用装饰模式,例如使用 BufferedInputStream 装饰 FileInputStream 文件输入流,可以增强流的缓冲功能。但是原生 IO 的装饰器过于庞大,需要区分字节、字符流、字节数组、字符数组、缓冲等多种装饰器,而这些恰恰又是最常用的基础装饰器。相较之下,Okio 直接在 BufferedSource 和 BufferedSink 中聚合了原生 IO 中所有基础的装饰器,使得框架更加精简;

  • 2、基于共享的缓冲区设计: 由于 IO 系统调用存在上下文切换的性能损耗,为了减少系统调用次数,应用层往往会采用缓冲区策略。但是缓冲区又会存在副作用,当数据从一个缓冲区转移到另一个缓冲区时需要拷贝数据,这种内存中的拷贝显得没有必要。而 Okio 采用了基于共享的缓冲区设计,在缓冲区间转移数据只是共享 Segment 的引用,而减少了内存拷贝。同时 Segment 也采用了对象池设计,减少了内存分配和回收的开销;

  • 3、超时机制: Okio 弥补了部分 IO 操作不支持超时检测的缺陷,而且 Okio 不仅支持单次 IO 操作的超时检测,还支持包含多次 IO 操作的复合任务超时检测。

下面,我们将从这三个优势展开分析:


2. 精简的 Okio 框架

先用一个表格总结 Okio 框架中主要的类型:

类型描述
Source输入流
Sink输出流
BufferedSource缓存输入流接口,实现类是 RealBufferedSource
BufferedSink缓冲输出流接口,实现类是 RealBufferedSink
Buffer缓冲区,由 Segment 链表组成
Segment数据片段,多个片段组成逻辑上连续数据
ByteStringString 类
Timeout超时控制

2.1 Source 输入流 与 Sink 输出流

在 Java 原生 IO 中有四个基础接口,分别是:

  • 字节流: InputStream 输入流和 OutputStream 输出流;
  • 字符流: Reader 输入流和 Writer 输出流。

而在 Okio 更加精简,只有两个基础接口,分别是:

  • 流: Source 输入流和 Sink 输出流。

Source.kt

interface Source : Closeable {// 从输入流读取数据到 Buffer 中(Buffer 等价于 byte[] 字节数组)// 返回值:-1:输入内容结束@Throws(IOException::class)fun read(sink: Buffer, byteCount: Long): Long// 超时控制(详细分析见后续文章)fun timeout(): Timeout// 关闭流@Throws(IOException::class)override fun close()
}

Sink.java

actual interface Sink : Closeable, Flushable {// 将 Buffer 的数据写入到输出流中(Buffer 等价于 byte[] 字节数组)@Throws(IOException::class)actual fun write(source: Buffer, byteCount: Long)// 清空输出缓冲区@Throws(IOException::class)actual override fun flush()// 超时控制(详细分析见后续文章)actual fun timeout(): Timeout// 关闭流@Throws(IOException::class)actual override fun close()
}

2.2 InputStream / OutputStream 与 Source / Sink 互转

在功能上,InputStream - Source 和 OutputStream - Sink 分别是等价的,而且是相互兼容的。结合 Kotlin 扩展函数,两种接口之间的转换会非常方便:

  • source(): InputStream 转 Source,实现类是 InputStreamSource;
  • sink(): OutputStream 转 Sink,实现类是 OutputStreamSink;

比较不理解的是: Okio 没有提供 InputStreamSource 和 OutputStreamSink 转回 InputStream 和 OutputStream 的方法,而是需要先转换为 BufferSource 与 BufferSink,再转回 InputStream 和 OutputStream。

  • buffer(): Source 转 BufferedSource,Sink 转 BufferedSink,实现类分别是 RealBufferedSource 和 RealBufferedSink。

示例代码

// 原生 IO -> Okio
val source = FileInputStream(File("")).source()
val bufferSource = FileInputStream(File("")).source().buffer()val sink = FileOutputStream(File("")).sink()
val bufferSink = FileOutputStream(File("")).sink().buffer()// Okio -> 原生 IO
val inputStream = bufferSource.inputStream()
val outputStream = bufferSink.outputStream()

JvmOkio.kt

// InputStream -> Source
fun InputStream.source(): Source = InputStreamSource(this, Timeout())// OutputStream -> Sink
fun OutputStream.sink(): Sink = OutputStreamSink(this, Timeout())private class InputStreamSource(private val input: InputStream,private val timeout: Timeout
) : Source {override fun read(sink: Buffer, byteCount: Long): Long {if (byteCount == 0L) return 0require(byteCount >= 0) { "byteCount < 0: $byteCount" }try {// 同步超时监控(详细分析见后续文章)timeout.throwIfReached()// 读入 Bufferval tail = sink.writableSegment(1)val maxToCopy = minOf(byteCount, Segment.SIZE - tail.limit).toInt()val bytesRead = input.read(tail.data, tail.limit, maxToCopy)if (bytesRead == -1) {if (tail.pos == tail.limit) {// We allocated a tail segment, but didn't end up needing it. Recycle!sink.head = tail.pop()SegmentPool.recycle(tail)}return -1}tail.limit += bytesReadsink.size += bytesReadreturn bytesRead.toLong()} catch (e: AssertionError) {if (e.isAndroidGetsocknameError) throw IOException(e)throw e}}override fun close() = input.close()override fun timeout() = timeoutoverride fun toString() = "source($input)"
}private class OutputStreamSink(private val out: OutputStream,private val timeout: Timeout
) : Sink {override fun write(source: Buffer, byteCount: Long) {checkOffsetAndCount(source.size, 0, byteCount)var remaining = byteCount// 写出 Bufferwhile (remaining > 0) {// 同步超时监控(详细分析见后续文章)timeout.throwIfReached()// 取有效数据量和剩余输出量的较小值val head = source.head!!val toCopy = minOf(remaining, head.limit - head.pos).toInt()out.write(head.data, head.pos, toCopy)head.pos += toCopyremaining -= toCopysource.size -= toCopy// 指向下一个 Segmentif (head.pos == head.limit) {source.head = head.pop()SegmentPool.recycle(head)}}}override fun flush() = out.flush()override fun close() = out.close()override fun timeout() = timeoutoverride fun toString() = "sink($out)"
}

Okio.kt

// Source -> BufferedSource
fun Source.buffer(): BufferedSource = RealBufferedSource(this)// Sink -> BufferedSink
fun Sink.buffer(): BufferedSink = RealBufferedSink(this)

2.3 BufferSource 与 BufferSink

在 Java 原生 IO 中,为了减少系统调用次数,我们一般不会直接调用 InputStream 和 OutputStream,而是会使用 BufferedInputStreamBufferedOutputStream 包装类增加缓冲功能。

例如,我们希望采用带缓冲的方式读取字符格式的文件,则需要先将文件输入流包装为字符流,再包装为缓冲流:

Java 原生 IO 示例

// 第一层包装
FileInputStream fis = new FileInputStream(file);
// 第二层包装
InputStreamReader isr = new InputStreamReader(new FileInputStream(file), "UTF-8");
// 第三层包装
BufferedReader br = new BufferedReader(isr);
String line;
while ((line = br.readLine()) != null) {...
}
// 省略 close

同理,我们在 Okio 中一般也不会直接调用 Source 和 Sink,而是会使用 BufferedSourceBufferedSink 包装类增加缓冲功能:

Okio 示例

val bufferedSource = file.source()/*第一层包装*/.buffer()/*第二层包装*/
while (!bufferedSource.exhausted()) {val line = bufferedSource.readUtf8Line();...
}
// 省略 close

网上有资料说 Okio 没有使用装饰器模式,所以类结构更简单。 这么说其实不太准确,装饰器模式本身并不是缺点,而且从 BufferedSource 和 BufferSink 可以看出 Okio 也使用了装饰器模式。 严格来说是原生 IO 的装饰器过于庞大,而 Okio 的装饰器更加精简。

比如原生 IO 常用的流就有这么多:

  • 原始流: FileInputStream / FileOutputStream 与 SocketInputStream / SocketOutputStream;

  • 基础接口(区分字节流和字符流): InputStream / OutputStream 与 Reader / Writer;

  • 缓存流: BufferedInputStream / BufferedOutputStream 与 BufferedReader / BufferedWriter;

  • 基本类型: DataInputStream / DataOutputStream;

  • 字节数组和字符数组: ByteArrayInputStream / ByteArrayOutputStream 与 CharArrayReader / CharArrayWriter;

  • 此处省略一万个字。

原生 IO 框架

而这么多种流在 Okio 里还剩下多少呢?

  • 原始流: FileInputStream / FileOutputStream 与 SocketInputStream / SocketOutputStream;
  • 基础接口: Source / Sink;
  • 缓存流: BufferedSource / BufferedSink。

Okio 框架

就问你服不服?

而且你看哈,这些都是平时业务开发中最常见的基本类型,原生 IO 把它们都拆分开了,让问题复杂化了。反观 Okio 直接在 BufferedSource 和 BufferedSink 中聚合了原生 IO 中基本的功能,而不再需要区分字节、字符、字节数组、字符数组、基础类型等等装饰器,确实让框架更加精简。

BufferedSource.kt

actual interface BufferedSource : Source, ReadableByteChannel {actual val buffer: Buffer// 读取 Int@Throws(IOException::class)actual fun readInt(): Int// 读取 String@Throws(IOException::class)fun readString(charset: Charset): String...fun inputStream(): InputStream
}

BufferedSink.kt

actual interface BufferedSink : Sink, WritableByteChannel {actual val buffer: Buffer// 写入 Int@Throws(IOException::class)actual fun writeInt(i: Int): BufferedSink// 写入 String@Throws(IOException::class)fun writeString(string: String, charset: Charset): BufferedSink...fun outputStream(): OutputStream
}

2.4 RealBufferedSink 与 RealBufferedSource

BufferedSource 和 BufferedSink 还是接口,它们的真正的实现类是 RealBufferedSource 和 RealBufferedSink。可以看到,在实现类中会创建一个 Buffer 缓冲区,在输入和输出的时候,都会借助 “Buffer 缓冲区” 减少系统调用次数。

RealBufferedSource.kt

internal actual class RealBufferedSource actual constructor(// 装饰器模式@JvmField actual val source: Source
) : BufferedSource {// 创建输入缓冲区@JvmField val bufferField = Buffer()// 带缓冲地读取(全部数据)override fun readString(charset: Charset): String {buffer.writeAll(source)return buffer.readString(charset)}// 带缓冲地读取(byteCount)override fun readString(byteCount: Long, charset: Charset): String {require(byteCount)return buffer.readString(byteCount, charset)}
}

RealBufferedSink.kt

internal actual class RealBufferedSink actual constructor(// 装饰器模式@JvmField actual val sink: Sink
) : BufferedSink {// 创建输出缓冲区@JvmField val bufferField = Buffer()// 带缓冲地写入(全部数据)override fun writeString(string: String, charset: Charset): BufferedSink {buffer.writeString(string, charset)return emitCompleteSegments()}// 带缓冲地写入(beginIndex - endIndex)override fun writeString(string: String,beginIndex: Int,endIndex: Int,charset: Charset): BufferedSink {buffer.writeString(string, beginIndex, endIndex, charset)return emitCompleteSegments()}
}

至此,Okio 基本框架分析结束,用一张图总结:

Okio 框架


3. Okio 的缓冲区设计

3.1 使用缓冲区减少系统调用次数

在操作系统中,访问磁盘和网卡等 IO 操作需要通过系统调用来执行。系统调用本质上是一种软中断,进程会从用户态陷入内核态执行中断处理程序,完成 IO 操作后再从内核态切换回用户态。

可以看到,系统调用存在上下文切换的性能损耗。为了减少系统调用次数,应用层往往会采用缓冲区策略:

以 Java 原生 IO BufferedInputStream 为例,会通过一个 byte[] 数组作为数据源的输入缓冲,每次读取数据时会读取更多数据到缓冲区中:

  • 如果缓冲区中存在有效数据,则直接从缓冲区数据读取;
  • 如果缓冲区不存在有效数据,则先执行系统调用填充缓冲区(fill),再从缓冲区读取数据;
  • 如果要读取的数据量大于缓冲区容量,就会跳过缓冲区直接执行系统调用。

输出流 BufferedOutputStream 也类似,输出数据时会优先写到缓冲区,当缓冲区满或者手动调用 flush() 时,再执行系统调用写出数据。

伪代码

// 1. 输入
fun read(byte[] dst, int len) : Int {// 缓冲区有效数据量int avail = count - posif(avail <= 0) {if(len >= 缓冲区容量) {// 直接从输入流读取read(输入流 in, dst, len)}// 填充缓冲区fill(数据源 in, 缓冲区)}// 本次读取数据量,不超过可用容量int cnt = (avail < len) ? avail : len?read(缓冲区, dst, cnt)// 更新缓冲区索引pos += cntreturn cnt
}// 2. 输出
fun write(byte[] src, len) {if(len > 缓冲区容量) {// 先将缓冲区写出flush(缓冲区)// 直接写出数据write(输出流 out, src, len)}// 缓冲区剩余容量int left = 缓冲区容量 - countif(len > 缓冲区剩余容量) {// 先将缓冲区写出flush(缓冲区)}// 将数据写入缓冲区write(缓冲区, src, len)// 更新缓冲区已添加数据容量count += len
}

3.2 缓冲区的副作用

的确,缓冲区策略能有效地减少系统调用次数,不至于读取一个字节都需要执行一次系统调用,大多数情况下表现良好。 但考虑一种 “双流操作” 场景,即从一个输入流读取,再写入到一个输出流。回顾刚才讲的缓存策略,此时的数据转移过程为:

  • 1、从输入流读取到缓冲区;
  • 2、从输入流缓冲区拷贝到 byte[](拷贝)
  • 3、将 byte[] copy 到输出流缓冲区(拷贝);
  • 4、将输出流缓冲区写入到输出流。

如果这两个流都使用了缓冲区设计,那么数据在这两个内存缓冲区之间相互拷贝,就显得没有必要。

3.3 Okio 的 Buffer 缓冲区

Okio 当然也有缓冲区策略,如果没有就会存在频繁系统调用的问题。

Buffer 是 RealBufferedSource 和 RealBufferedSink 的数据缓冲区。虽然在实现上与原生 BufferedInputStream 和 BufferedOutputStream 不一样,但在功能上是一样的。区别在于:

  • 1、BufferedInputStream 中的缓冲区是 “一个固定长度的字节数组” ,数据从一个缓冲区转移到另一个缓冲区需要拷贝;

  • 2、Buffer 中的缓冲区是 “一个 Segment 双向循环链表” ,每个 Segment 对象是一小段字节数组,依靠 Segment 链表的顺序组成逻辑上的连续数据。这个 Segment 片段是 Okio 高效的关键。

Buffer.kt

actual class Buffer : BufferedSource, BufferedSink, Cloneable, ByteChannel {// 缓冲区(Segment 双向链表)@JvmField internal actual var head: Segment? = null// 缓冲区数据量@get:JvmName("size")actual var size: Long = 0Linternal setoverride fun buffer() = thisactual override val buffer get() = this
}

对比 BufferedInputStream:

BufferedInputStream.java

public class BufferedInputStream extends FilterInputStream {// 缓冲区的默认大小(8KB)private static int DEFAULT_BUFFER_SIZE = 8192;// 输入缓冲区(固定长度的数组)protected volatile byte buf[];// 有效数据起始位,也是读数据的起始位protected int pos;// 有效数据量,pos + count 是写数据的起始位protected int count;...
}

3.4 Segment 片段与 SegmentPool 对象池

Segment 中的字节数组是可以 “共享” 的,当数据从一个缓冲区转移到另一个缓冲区时,可以共享数据引用,而不一定需要拷贝数据。

Segment.kt

internal class Segment {companion object {// 片段的默认大小(8KB)const val SIZE = 8192// 最小共享阈值,超过 1KB 的数据才会共享const val SHARE_MINIMUM = 1024}// 底层数组@JvmField val data: ByteArra// 有效数据的起始位,也是读数据的起始位@JvmField var pos: Int = 0// 有效数据的结束位,也是写数据的起始位@JvmField var limit: Int = 0// 共享标记位@JvmField var shared: Boolean = false// 宿主标记位@JvmField var owner: Boolean = false// 后续指针@JvmField var next: Segment? = null// 前驱指针@JvmField var prev: Segment? = nullconstructor() {// 默认构造 8KB 数组(为什么默认长度是 8KB)this.data = ByteArray(SIZE)// 宿主标记位this.owner = true// 共享标记位this.shared = false}
}

另外,Segment 还使用了对象池设计,被回收的 Segment 对象会缓存在 SegmentPool 中。SegmentPool 内部维护了一个被回收的 Segment 对象单链表,缓存容量的最大值是 MAX_SIZE = 64 * 1024,也就相当于 8 个默认 Segment 的长度:

SegmentPool.kt

// object:全局单例
internal actual object SegmentPool {// 缓存容量actual val MAX_SIZE = 64 * 1024// 头节点private val LOCK = Segment(ByteArray(0), pos = 0, limit = 0, shared = false, owner = false)...
}

Segment 示意图


4. 总结

  • 1、Okio 将原生 IO 多种基础装饰器聚合在 BufferedSource 和 BufferedSink,使得框架更加精简;
  • 2、为了减少系统调用次数的同时,应用层 IO 框架会使用缓存区设计。而 Okio 使用了基于共享 Segment 的缓冲区设计,减少了在缓冲区间转移数据的内存拷贝;
  • 3、Okio 弥补了部分 IO 操作不支持超时检测的缺陷,而且 Okio 不仅支持单次 IO 操作的超时检测,还支持包含多次 IO 操作的复合任务超时检测。

关于 Okio 超时机制的详细分析,我们在 下一篇文章 里讨论。请关注。


参考资料

  • Github · Okio
  • Okio 官网
  • Okio 源码学习分析 —— 川峰 著
  • Okio 好在哪?—— MxsQ 著

相关文章:

Android IO 框架 Okio 的实现原理,到底哪里 OK?

本文已收录到 AndroidFamily&#xff0c;技术和职场问题&#xff0c;请关注公众号 [彭旭锐] 提问。 前言 大家好&#xff0c;我是小彭。 今天&#xff0c;我们来讨论一个 Square 开源的 I/O 框架 Okio&#xff0c;我们最开始接触到 Okio 框架还是源于 Square 家的 OkHttp 网络…...

一文讲解Linux 设备模型 kobject,kset

设备驱动模型 面试的时候&#xff0c;有面试官会问&#xff0c;什么是Linux 设备驱动模型&#xff1f;你要怎么回答&#xff1f; 这个问题&#xff0c;突然这么一问&#xff0c;可能你会愣住不知道怎么回答&#xff0c;因为Linux 设备驱动模型是一个比较整体的概念&#xff0…...

linux配置密码过期的安全策略(/etc/login.defs的解读)

长期不更换密码很容易导致密码被破解&#xff0c;而linux的密码过期安全策略主要在/etc/login.defs中配置。一、/etc/login.defs文件的参数解读1、/etc/login.defs文件的内容示例[rootlocalhost ~]# cat /etc/login.defs # # Please note that the parameters in this configur…...

c_character_string 字符串----我认真的弄明白了,也希望你们也是。

字符串 1. 字符串长度strlen 1.1strlen 函数介绍 size_t strlen ( const char * str );strlen ——string length strlen 的头文件是 #include <string.h> 参数指向的字符串必须要以 ‘\0’ 结束。 strlen 是求字符串长度的函数&#xff0c;统计的是字符串中\0之前出现…...

spring面试题 一

一、为了降低Java开发的复杂性&#xff0c;Spring采取了那4种关键策略 基于POJO的轻量级和最小侵入性编程&#xff1b; 通过依赖注入和面向接口实现松耦合&#xff1b; 基于切面和惯例进行声明式编程&#xff1b; 通过切面和模板减少样板式代码。 二、Spring框架的核心&am…...

C++中char *,char a[ ]的特殊应用

1.数组的本质 数组是多个元素的集合&#xff0c;在内存中分布在地址相连的单元中&#xff0c;所以可以通过其下标访问不同单元的元素。 2.指针 指针也是一种变量&#xff0c;只不过它的内存单元中保存的是一个标识其他位置的地址。 3.字符串常量的本质是它的第一个字符的地…...

【Windows10】电脑副屏无法调节屏幕亮度?解决方法

先说下情况&#xff0c;本人对显示器不太懂&#xff0c;属于小白 这个副屏无法调节的问题出现也已经很久了&#xff0c;但是之前亮度适合就无所谓&#xff0c;今天突然按了之后很亮&#xff0c;于是就找问题。 第一步&#xff0c;我直接百度&#xff0c;遇事不决&#xff0c;百…...

Paper简读 - ProGen2: Exploring the Boundaries of Protein Language Models

欢迎关注我的CSDN:https://spike.blog.csdn.net/ 本文地址:https://blog.csdn.net/caroline_wendy/article/details/128976102 ProGen2: Exploring the Boundaries of Protein Language Models ProGen2:探索蛋白质语言模型的边界Cumulative density:累积密度 Ligand:在生…...

leaflet 加载WKT数据(示例代码050)

第050个 点击查看专栏目录 本示例的目的是介绍演示如何在vue+leaflet中加载WKT文件,将图形显示在地图上。 直接复制下面的 vue+openlayers源代码,操作2分钟即可运行实现效果; 注意如果OpenStreetMap无法加载,请加载其他来练习 文章目录 示例效果配置方式示例源代码(共67行…...

设计模式-组合模式和建筑者模式详解

一. 组合模式 1. 背景 在现实生活中&#xff0c;存在很多“部分-整体”的关系&#xff0c;例如&#xff0c;大学中的部门与学院、总公司中的部门与分公司、学习用品中的书与书包、生活用品中的衣月艮与衣柜以及厨房中的锅碗瓢盆等。在软件开发中也是这样&#xff0c;例如&…...

Pcap文件的magic_number

1. 简述 pcap文件头中的magicNum是来标识pcap文件文件头和包头字节序和应用是否一致的。 在标准情况下为0xa1b2c3d4。如果相反则说明需要调换字节序。 一般格式 Global Header Packet Header Packet Data Packet Header Packet Data ....pcap文件头格式 typedef struct pca…...

MDS75-16-ASEMI三相整流模块MDS75-16

编辑-Z MDS75-16在MDS封装里采用的6个芯片&#xff0c;是一款工业焊机专用大功率整流模块。MDS75-16的浪涌电流Ifsm为920A&#xff0c;漏电流(Ir)为5mA&#xff0c;其工作时耐温度范围为-40~150摄氏度。MDS75-16采用GPP硅芯片材质&#xff0c;里面有6颗芯片组成。MDS75-16的电…...

基本TCP编程

1. 基本概念 TCP (即传输控制协议) 是一种面向连接的传输层协议,它能提供高可靠性通信 (即数据无误、数据无丢失、数据无失序、数据无重复到达的通信)。 2. 通信流程解析 TCP 通信的流程与打电话的过程相似,以下以一对情侣打电话的过程来展示TCP的通信流程: 其中服务端 …...

【沁恒WCH CH32V307V-R1开发板读取板载温度实验】

【沁恒WCH CH32V307V-R1开发板读取板载温度实验】1. 前言2. 软件配置2.1 安装MounRiver Studio3. ADC项目测试3.1 打开ADC工程3.2 编译项目4. 下载验证4.1 接线4.2 演示效果5. 小结1. 前言 ADC 模块包含 2 个 12 位的逐次逼近型的模拟数字转换器&#xff0c;最高 14MHz 的输入时…...

学习SpringCloudAlibaba(二)微服务的拆分与编写

目录 一、单体架构VS微服务架构 1.单体架构 ​&#xff08;1&#xff09;.单体架构的优点 &#xff08;2&#xff09;.单体架构的缺点 2.微服务架构 &#xff08;1&#xff09;微服务的特性 &#xff08;2&#xff09;微服务架构图 &#xff08;3&#xff09;微服务的优点 …...

通过对HashMap的源码分析解决部分关于HashMap的问题

HashMap第一次分配多大的空间我们查看resize&#xff08;)中的源码所以当我们没有传入默认容量的参数的时候&#xff0c;默认容量是16当传进一个20的初始参数时&#xff0c;数组的容量是多大所以当我们传入20的参数&#xff0c;这时创建的容量是32&#xff08;2^5&#xff09;对…...

【无标题】

Windeployqt 打包,缺少DLL 的原因分析,解决方法 很多同学使用工具windeployqt进行打包发布后,运行exe文件时,还是会出现下图所示的系统错误提示,这种情况就表示相关的DLL 库文件没有被正确打包。可是windeployqt明确显示运行正常啊,难道是QT自家的windeployqt这个工具有…...

渗透测试 -- 网站信息收集

数据来源 01 网站指纹识别 网站的最基本组成&#xff1a;服务器&#xff08;操作系统&#xff09;、中间件(web容器)、脚本语言&#xff08;php、java、...&#xff09;、数据库&#xff08;Mysql、...&#xff09;为什么要了解这些&#xff1f; 举个例子&#xff1a;发现了一…...

Windows 搭建ARM虚拟机 UOS系统

搭建环境安装虚拟机下载虚拟机QEMU&#xff0c;https://www.qemu.org/download/&#xff0c;目前最新版本是7.2.0。安装完成后&#xff0c;需要将qemu的安装路径设置到环境变量完成后运行cmd&#xff0c;测试环境变量配置完成。qemu-system-aarch64 --version如上截图所示&…...

day58每日温度_下一个更大元素1

力扣739.每日温度 题目链接&#xff1a;https://leetcode.cn/problems/daily-temperatures/ 思路 什么时候用单调栈呢&#xff1f; 通常是一维数组&#xff0c;要寻找任一个元素的右边或者左边第一个比自己大或者小的元素的位置&#xff0c;此时我们就要想到可以用单调栈了…...

超清遥感影像语义分割处理

分割出城市中的垃圾堆场,遂寻找到了 UrbanAtlas 2012 database 这个数据集和对应的 baseline baseline IoU for class 1: 0.5667 IoU for class 2: 0.3504 IoU for class 3: 0.0001 IoU for class 4: 0.0002 IoU for class 5: 0.4121 IoU for class 6: 0.0204 IoU for cla…...

RabbitMQ安装及配置

目录1.下载和安装1.1 下载1.2. 安装1.3 测试1.4 卸载管理界面2.1 添加用户2.2 创建Virtual Hosts2.3. 设置权限1.下载和安装 1.1 下载 1.下载Erlang的rpm包 RabbitMQ是Erlang语言编写&#xff0c;所以Erang环境必须要有&#xff0c;注&#xff1a;Erlang环境一定要与RabbitMQ…...

网络协议(四):网络互联模型、物理层、数据链路层

网络协议系列文章 网络协议(一)&#xff1a;基本概念、计算机之间的连接方式 网络协议(二)&#xff1a;MAC地址、IP地址、子网掩码、子网和超网 网络协议(三)&#xff1a;路由器原理及数据包传输过程 网络协议(四)&#xff1a;网络互联模型、物理层、数据链路层 目录一、网…...

请问有没有关于数据预测的方法?

利用数据对未来进行预测分析&#xff0c;虽然不能百分百的准确预测&#xff0c;但是有数据理论支撑的趋势预测是客观可靠的。当数据分析的目的涉及未来决策时&#xff0c;就可以大胆使用预测的思路和方法&#xff0c;如企业明年的战略计划、销售计划等&#xff0c;这对企业而言…...

[CVPR 2021] Your “Flamingo“ is My “Bird“: Fine-Grained, or Not

Contents Your "Flamingo" is My "Bird"MethodCooperation or Confrontation?Disentanglement and ReinforcementExperimentDiscussionsReferencesYour “Flamingo” is My “Bird” 对于如下图片,FGVC 的目标是让模型识别出 “flamingo”,但对于大部分…...

clickHouse笔记

1、介绍 clickHouse将数据进行分成多个partition&#xff0c;多个CUP进行进行单条的Query&#xff0c;不适合qps特别高的查询场景&#xff1b;适应场景&#xff1a;处理过的&#xff0c;字段特别大的宽表&#xff0c;进行统计&#xff0c;查询&#xff0c;聚合等数据分析场景 …...

10.jQuery中请求预处理 $.ajaxPrefilter()

在使用jQuery发起请求的时候($.get(),$.post().$ajax()都可以)会默认在请求前调用$.ajaxPrefilter()这个函数&#xff0c;我们可以利用这个来做一些事情 目录 1 定义API根路径 2 添加请求头 3 添加请求结束的回调函数 1 定义API根路径 这样后面每次请求就不用再写根路…...

【黄啊码】浅谈PHP入门|如何学习PHP

大家好&#xff0c;我是黄啊码&#xff0c;很多小白问我&#xff0c;怎么入门PHP&#xff0c;甚至连PHP能干嘛都不知道&#xff0c;今天啊码就具体来讲讲。 什么是PHP PHP是一种开源的通用脚本语言&#xff0c;用于创建动态网页和应用程序。它可以运行在服务器端&#xff0c;…...

人大金仓数据库的归档日志

归档日志 归档日志是非活动的WAL日志备份。通过使用归档日志&#xff0c;可以保留所有WAL日志的历史记录&#xff0c;当数据库处于ARCHIVELOG模式并进行日志切换时&#xff0c;后台进程archiver process会将WAL日志的内容保存到归档日志中&#xff0c;当数据库出现介质失败时&…...

C++:类和对象(上)

文章目录1 面向过程与面向对象的初步认识2 类的引入3 类的定义4 类的访问限定符及封装4.1 访问限定符4.2 封装5 类的实例化6 类对象模型6.1 如何计算类的大小6.2 类对象的存储方式猜测7 this指针7.1 this指针的引出7.2 this指针的特性8 C语言和C栈&#xff08;Stack&#xff09…...

做网站手机版/郑州搜索引擎优化

猴子数据分析 图解SQL面试题11 【题目】下面是学生的成绩表&#xff08;表名score&#xff0c;列名&#xff1a;学号、成绩&#xff09;&#xff0c;使用SQL查找所有至少连续出现3次的成绩。 例如&#xff0c;“成绩”这一列里84是连续出现3次的成绩。 【解题思路】1.条件1&…...

开原 铁岭网站建设/网络营销策划书包括哪些内容

原文地址&#xff1a;你必须知道的地理坐标系和投影坐标系 文章条理清晰&#xff0c;内容通俗易懂 还可以参考另一篇文章&#xff1a;GISer梳理的我国常用的坐标系及相关知识 1、基本概念 地理坐标系&#xff1a;为球面坐标。 参考平面地是椭球面&#xff0c;坐标单位&#xff…...

wordpress模板首页是哪个文件夹/搜索网站大全排名

当你遇到累进税率&#xff0c;你知道怎么算税额吗&#xff1f;刚刚学习税务的时候&#xff0c;小编对于这四个汉字一点都不陌生&#xff0c;但是这四个汉字这样的组合&#xff0c;看得一头雾水&#xff0c;累觉不爱。后来才知道在我国税收中&#xff0c;以累进税率计算税额是很…...

c 做网站怎么连接到别的网页/石家庄高级seo经理

实现同时运行多个线程工作&#xff0c;主要通过信号量的设置&#xff0c;但还是在一个CPU上执行,具体要实现的例子可以放在函数里执行&#xff0c;实现单核多并发,还等待什么...... #!/usr/bin/env python # -*- coding: utf-8 -*- import threading import time import random…...

个性网站制作/百度搜索引擎推广步骤

我有一个表单验证问题。下面是提交时发生的逻辑(至少是其中的一部分)。在for循环中&#xff0c;我们检查一个站点访问者可以注册的可能事件数组。如果用户没有检查任何事件(这些是复选框&#xff0c;因为用户可以注册多个事件)&#xff0c;我们应该在下面输入第二条if语句&…...

网站如何做银联在线支付/铁岭网站seo

今天搞树莓派&#xff0c;遇到/sys这个目录&#xff0c;不太清楚&#xff0c;先对/sys目录知识进行一个整理 首先&#xff0c;对 /sys目录下的各个子目录进行具体说明&#xff1a; /sys下的子目录 内容 /sys/devices 该目录下是全局设备结构体系&#xff0c;包含所有…...