Okio 使用教程:从入门到精通
先来看一张 Okio 的整体架构图,建立直觉:

第一章:为什么要用 Okio?
在学 API 之前,先看看 Java 原生 IO 有哪些痛点。
Java IO 的典型写法(读取一个文件的所有行):
java
// Java IO:冗长、容易出错、资源管理繁琐
List<String> lines = new ArrayList<>();
BufferedReader reader = null;
try {
reader = new BufferedReader(
new InputStreamReader(
new FileInputStream("data.txt"), "UTF-8"
)
);
String line;
while ((line = reader.readLine()) != null) {
lines.add(line);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
if (reader != null) {
try { reader.close(); } catch (IOException ignored) {}
}
}
Okio 的等效写法:
java
// Okio:简洁、安全、链式
try (BufferedSource source = Okio.buffer(Okio.source(new File("data.txt")))) {
String line;
while ((line = source.readUtf8Line()) != null) {
lines.add(line);
}
}
核心差异体现在:冗余的包装层消失了、编码声明消失了、finally 消失了。
第二章:入门 --- 基本读写
添加依赖
groovy
// build.gradle (Kotlin/Java 项目)
implementation("com.squareup.okio:okio:3.9.0")
2.1 写文件
java
import okio.*;
import java.io.File;
// ✅ Okio 写法
File file = new File("output.txt");
try (BufferedSink sink = Okio.buffer(Okio.sink(file))) {
sink.writeUtf8("Hello, Okio!\n");
sink.writeUtf8("第二行内容\n");
// try-with-resources 会自动 flush + close
}
// ❌ Java IO 等效写法(需要6行)
try (PrintWriter pw = new PrintWriter(
new OutputStreamWriter(
new FileOutputStream(file), "UTF-8"))) {
pw.println("Hello, Java IO!");
pw.println("第二行内容");
}
2.2 读文件
java
// ✅ Okio 读文件
try (BufferedSource source = Okio.buffer(Okio.source(new File("output.txt")))) {
// 一次性读取全部内容为 String
String content = source.readUtf8();
System.out.println(content);
}
// ✅ 逐行读取
try (BufferedSource source = Okio.buffer(Okio.source(new File("output.txt")))) {
String line;
while ((line = source.readUtf8Line()) != null) {
System.out.println(line);
}
}
2.3 读写字节数组
java
// ✅ 写入字节
byte[] bytes = {0x48, 0x65, 0x6C, 0x6C, 0x6F}; // "Hello"
try (BufferedSink sink = Okio.buffer(Okio.sink(new File("bytes.bin")))) {
sink.write(bytes);
sink.writeInt(42); // 写一个 int(4字节,大端)
sink.writeLong(1000L); // 写一个 long(8字节)
}
// ✅ 读取字节
try (BufferedSource source = Okio.buffer(Okio.source(new File("bytes.bin")))) {
byte[] buf = source.readByteArray(5); // 读5个字节
int n = source.readInt(); // 读4字节 int
long l = source.readLong(); // 读8字节 long
System.out.println(new String(buf) + ", " + n + ", " + l);
// 输出: Hello, 42, 1000
}
第三章:核心概念 --- Buffer(零拷贝的秘密)
Buffer 是 Okio 的心脏,理解它才能理解 Okio 的性能优势。

Buffer 直接使用示例:
java
// Buffer 同时实现了 BufferedSource 和 BufferedSink,可以直接在内存里读写
Buffer buffer = new Buffer();
// 写入
buffer.writeUtf8("Hello");
buffer.writeInt(42);
buffer.writeByte(0xFF);
System.out.println(buffer.size()); // 10 (5 + 4 + 1)
// 读取(消费型------读了就没了)
System.out.println(buffer.readUtf8(5)); // "Hello"
System.out.println(buffer.readInt()); // 42
// 对比 Java NIO ByteBuffer:需要手动管理 position/limit/flip,非常容易出错
java.nio.ByteBuffer bb = java.nio.ByteBuffer.allocate(64);
bb.put("Hello".getBytes());
bb.putInt(42);
bb.flip(); // 忘记 flip 是 NIO 最常见 bug!
byte[] dst = new byte[5];
bb.get(dst);
System.out.println(new String(dst)); // "Hello"
System.out.println(bb.getInt()); // 42
第四章:进阶 --- Source 和 Sink 的装饰器模式
Okio 和 Java IO 一样支持流的包装,但更直观:
4.1 GZip 压缩/解压
java
// ✅ Okio:写 GZip 压缩文件
File gzFile = new File("data.gz");
try (BufferedSink sink = Okio.buffer(new GzipSink(Okio.sink(gzFile)))) {
sink.writeUtf8("这是要压缩的大文本内容...\n");
sink.writeUtf8("Okio 内置 GzipSink/GzipSource\n");
}
// ✅ Okio:读 GZip 文件
try (BufferedSource source = Okio.buffer(new GzipSource(Okio.source(gzFile)))) {
System.out.println(source.readUtf8());
}
// ❌ Java IO 等效(更繁琐)
try (BufferedWriter bw = new BufferedWriter(
new OutputStreamWriter(
new GZIPOutputStream(
new FileOutputStream("data.gz")), "UTF-8"))) {
bw.write("这是要压缩的大文本内容...");
}
4.2 哈希计算(HashingSink / HashingSource)
这是 Okio 独有的能力,Java IO 没有对应的简洁实现:
java
// 在写入数据的同时计算 MD5/SHA256,不需要读两遍数据!
HashingSink hashingSink = HashingSink.md5(Okio.blackhole());
// Okio.blackhole() 是一个丢弃所有数据的 Sink(类似 /dev/null)
try (BufferedSink sink = Okio.buffer(hashingSink)) {
sink.writeUtf8("计算这段文字的 MD5");
}
ByteString md5 = hashingSink.hash();
System.out.println("MD5: " + md5.hex());
// 同样可以 wrap 文件 Sink,边写边算 hash
HashingSink fileSink = HashingSink.sha256(Okio.sink(new File("output.txt")));
try (BufferedSink sink = Okio.buffer(fileSink)) {
sink.writeUtf8("文件内容");
}
System.out.println("SHA-256: " + fileSink.hash().hex());
// Java 原生等效(需要手动用 MessageDigest,且要读两遍或自己 wrap)
MessageDigest md = MessageDigest.getInstance("MD5");
byte[] data = "计算这段文字的 MD5".getBytes("UTF-8");
byte[] digest = md.digest(data);
// 还需要手动转 hex...
4.3 ForwardingSink / ForwardingSource(拦截器模式)
这是实现监控、限速等功能的关键:
java
// 自定义一个带进度回调的 Sink
public class ProgressSink extends ForwardingSink {
private long bytesWritten = 0;
private final long totalBytes;
private final ProgressListener listener;
public ProgressSink(Sink delegate, long totalBytes, ProgressListener listener) {
super(delegate);
this.totalBytes = totalBytes;
this.listener = listener;
}
@Override
public void write(Buffer source, long byteCount) throws IOException {
super.write(source, byteCount);
bytesWritten += byteCount;
listener.onProgress(bytesWritten, totalBytes);
}
}
// 使用:带进度的文件下载
long fileSize = 1024 * 1024; // 1MB
Sink fileSink = Okio.sink(new File("download.bin"));
ProgressSink progressSink = new ProgressSink(fileSink, fileSize, (done, total) -> {
System.out.printf("进度: %.1f%%%n", done * 100.0 / total);
});
try (BufferedSink sink = Okio.buffer(progressSink)) {
// ... 写入下载数据
}
第五章:ByteString --- 不可变字节序列
ByteString 是 Okio 的另一个利器,Java 原生没有对应物(只有 byte[],但它是可变的且没有工具方法):
java
// 创建 ByteString
ByteString bs1 = ByteString.encodeUtf8("Hello Okio");
ByteString bs2 = ByteString.decodeHex("48656c6c6f"); // "Hello"
ByteString bs3 = ByteString.of(new byte[]{0x01, 0x02, 0x03});
// 丰富的工具方法
System.out.println(bs1.utf8()); // "Hello Okio"
System.out.println(bs1.hex()); // 十六进制字符串
System.out.println(bs1.base64()); // Base64 编码
System.out.println(bs1.md5().hex()); // MD5 哈希
System.out.println(bs1.sha256().hex());// SHA-256 哈希
// 截取子串(不拷贝数据,共享底层 byte[])
ByteString sub = bs1.substring(0, 5);
System.out.println(sub.utf8()); // "Hello"
// 不可变:适合当 HashMap key、缓存、常量
// ✅ 线程安全,可以在多线程间共享
// 对比 Java 原生 byte[](每次操作都需要手动转换)
byte[] javaBytes = "Hello Okio".getBytes("UTF-8");
String hex = javax.xml.bind.DatatypeConverter.printHexBinary(javaBytes); // 还需要引入额外包
String b64 = java.util.Base64.getEncoder().encodeToString(javaBytes); // Java 8 才有
第六章:精通 --- Pipe、超时机制与网络应用
6.1 Pipe(进程内管道)
Pipe 是生产者-消费者模式的神器,Java NIO 的 Pipe 更复杂:
java
// 创建一个缓冲 1MB 的管道
Pipe pipe = new Pipe(1024 * 1024);
// 生产者线程:向管道写入数据
Thread producer = new Thread(() -> {
try (BufferedSink sink = Okio.buffer(pipe.sink())) {
for (int i = 0; i < 100; i++) {
sink.writeUtf8("数据块 " + i + "\n");
sink.flush(); // 确保消费者能读到
}
} catch (IOException e) {
e.printStackTrace();
}
});
// 消费者线程:从管道读取数据
Thread consumer = new Thread(() -> {
try (BufferedSource source = Okio.buffer(pipe.source())) {
String line;
while ((line = source.readUtf8Line()) != null) {
System.out.println("读到: " + line);
}
} catch (IOException e) {
e.printStackTrace();
}
});
producer.start();
consumer.start();
6.2 超时机制(Timeout)
这是 Okio 对 Java IO 的重要改进。Java IO 的 Socket.setSoTimeout 只能设读超时,Okio 提供更细粒度的控制:
java
// 带超时的 Socket 通信
Socket socket = new Socket("example.com", 80);
// 创建带超时的 Source
Source rawSource = Okio.source(socket);
// AsyncTimeout 可以异步地在超时时关闭 socket
AsyncTimeout timeout = new AsyncTimeout() {
@Override
protected void timedOut() {
try { socket.close(); } catch (IOException ignored) {}
}
};
timeout.timeout(30, TimeUnit.SECONDS);
Source timeoutSource = timeout.source(rawSource);
try (BufferedSource source = Okio.buffer(timeoutSource)) {
// 如果 30 秒内没读到数据,会自动关闭 socket 并抛出 IOException
String response = source.readUtf8();
}
// Okio Timeout 还支持 deadline(截止时间)
Timeout t = new Timeout();
t.deadline(5, TimeUnit.SECONDS); // 整个操作最多 5 秒
t.timeout(2, TimeUnit.SECONDS); // 每次读写最多 2 秒
6.3 实战:高效的文件拷贝
java
// Okio 最高效的文件拷贝
public static void copyFile(File src, File dst) throws IOException {
try (Source source = Okio.source(src);
Sink sink = Okio.sink(dst)) {
// buffer 内部会以 Segment 为单位移动数据,不做额外拷贝
Okio.buffer(sink).writeAll(source);
}
}
// 对比 Java NIO(Files.copy 更简洁,但无法插入拦截逻辑)
java.nio.file.Files.copy(src.toPath(), dst.toPath());
// 对比 Java IO(经典 while 循环拷贝)
try (InputStream in = new FileInputStream(src);
OutputStream out = new FileOutputStream(dst)) {
byte[] buf = new byte[8192]; // 需要自己管理缓冲区大小
int read;
while ((read = in.read(buf)) != -1) {
out.write(buf, 0, read);
}
}
第七章:与 OkHttp 结合(Okio 的实战场景)
Okio 最广泛的使用场景之一就是 OkHttp,理解这个有助于你在网络层写出高质量代码:
java
OkHttpClient client = new OkHttpClient();
// 上传文件(用 Okio 的 RequestBody)
RequestBody fileBody = new RequestBody() {
@Override
public MediaType contentType() {
return MediaType.get("application/octet-stream");
}
@Override
public void writeTo(BufferedSink sink) throws IOException {
// sink 就是 Okio 的 BufferedSink!直接用 Okio API 写
try (Source source = Okio.source(new File("upload.bin"))) {
sink.writeAll(source);
}
}
};
// 下载文件(用 Okio 的 ResponseBody)
Request request = new Request.Builder().url("https://example.com/file").build();
try (Response response = client.newCall(request).execute()) {
ResponseBody body = response.body();
// body.source() 就是 Okio 的 BufferedSource!
try (BufferedSink fileSink = Okio.buffer(Okio.sink(new File("download.bin")))) {
fileSink.writeAll(body.source());
}
}
第八章:全面对比总结
来看一张完整的对比总结图:


学习路径总结
按照以下顺序练习,循序渐进:
- 入门 :用
Okio.buffer(Okio.source/sink)替换你项目里的BufferedReader/Writer,感受 API 的简洁。 - 理解 Buffer :在内存中直接
new Buffer()做序列化/反序列化的练习,体会它和ByteBuffer的差异。 - 进阶装饰 :用
GzipSink + HashingSink做压缩+校验的流水线,理解装饰器链。 - 自定义拦截 :继承
ForwardingSink实现进度监控或限速 Sink,理解 Okio 的扩展点。 - 精通 :研究
AsyncTimeout和Pipe,在网络编程中(配合 OkHttp)发挥 Okio 的全部威力。
如果你想深入某一个章节(比如 Kotlin 协程版 Okio、二进制协议解析,或者源码分析),随时告诉我!
Okio是否只能用于Android吗?
Okio 完全不限于 Android,它是一个纯 JVM 库(同时也支持 Kotlin Multiplatform),普通的 IntelliJ Kotlin/Java 项目完全可以用。
Okio 支持的平台:
- JVM(普通 Java/Kotlin 项目、Spring Boot、命令行工具等)
- Android(这只是 JVM 平台的一个子集)
- Kotlin/Native(iOS、macOS、Linux)
- Kotlin/JS
所以它只是"因为 OkHttp 和 Android 生态而出名",但本质上是一个跨平台的通用 IO 库。
在普通 IntelliJ Kotlin 项目里添加依赖:
如果用 Gradle(Kotlin DSL):
kotlin
// build.gradle.kts
dependencies {
implementation("com.squareup.okio:okio:3.9.0")
}
如果用 Gradle(Groovy DSL):
groovy
// build.gradle
dependencies {
implementation 'com.squareup.okio:okio:3.9.0'
}
如果用 Maven:
xml
<dependency>
<groupId>com.squareup.okio</groupId>
<artifactId>okio</artifactId>
<version>3.9.0</version>
</dependency>
添加完同步一下 Gradle/Maven,就可以直接 import okio.* 使用了,和 Android 项目里的用法完全一致。