Okio 文档翻译
此为 Okio 文档翻译,文档地址:
Okio
Okio 对 java.io
与 java.nio
进行了封装,通过使用 Okio 这个库,你可以很方便地进行数据 访问,存储和处理。Okio 最初是作为 Okhttp
的一个模块。但现在它独立了出来,我们可以单独使用其来简化 IO 操作。
ByteStrings and Buffers
Okio 中规定了两种 对象,这两种对象封装了大量的功能 :
- ByteString 是一个不可变的 bytes 类型的序列(类似 byte 数组),对于 字符数据,它可以起到类似 String 的功能,但其封装了解码和编码的相关操作,能够快速进行 String 与 ByteArray 之间的转换。支持 Hex, base64 和 UTF-8 等编码。
- Buffer 是一个可变的 bytes 类型的序列,类似
ArrayList
,你可以从 Buffer 尾部写数据,从头部读数据。而不需要管理 nio 中 Buffer 的 positions, limits 等指针 。
ByteString
和 Buffer
内部进行了一些优化。例如,如果你使用 ByteString 来编码一个 UTF-8 的字符串,它会保存对原字符串的引用,当你下次解码时,直接返回字符串的引用。
Buffer
是 segments 的链表,当你想要将 Buffer 中的数据进行复制时,可直接将 segments 的引用进行复制,而不复制数据本身。
Sources and Sinks
在 java.io
中一个优雅的地方是如何将 streams 分层进行传输,例如压缩和传输一体化。Okio 规定了自己的流类型,Source 和 Sink,类似 InputStream 与 OutputStream,但他们有以下本质区别:
- Timeouts. Source 和 Sink 都提供了超时的 api,而
java.io
中这些方法都是阻塞方法。 - Easy to implement. Source 接口只规定了三个方法:read() ,close() 和 timeout() .没有像 available() 或者单字节读取这种可能会有歧义和性能损失的接口 。
- Easy to use.虽然 Source 与 Sink 只规定了简单的三个方法,不过调用者会得到一个 BufferedSource 或 BufferedSink 接口,这两者有丰富的 Api ,可以实现各种你需要的功能。
- No artificial distinction between byte streams and char streams. 字符流与字节流都是数据,你可以以 bytes, UTF-8 strings, bit/little-endian 32-bit integer 来读取你想要的数据。而在
java.io
中,字节流与字符流是分开的。 - Easy to test. Buffer 类实现了 BufferedSource 与 BufferedSink 接口因此测试代码是很简单和简洁的。
Source 与 Sink 可以与 InputStream 与 OutputStream 相互交互。可以相互转换。
Presentations
A Few “Ok” Libraries (slides): An introduction to Okio and three libraries written with it.
Decoding the Secrets of Binary Data (slides): How data encoding works and how Okio does it.
Ok Multiplatform! (slides): How we changed Okio’s implementation language from Java to Kotlin.
Requirements
Okio 2.x supports Android 4.0.3+ (API level 15+) and Java 7+.
Okio 3.x supports Android 4.0.3+ (API level 15+) and Java 8+.
Okio depends on the Kotlin standard library. It is a small library with strong backward-compatibility.
Recipes
我们写了一些用例来展示如何解决 Okio 使用中的一些问题。你可以随意复制剪切他们,因为这就是他们的作用。
Read a text file line-by-line
使用 Okio.source(File)
来开启一个 Source 来读取一个文件。该方法返回了一个 Source 接口。这个接口非常小,用处有限,因此,我们需要使用 buffer 包装该 source,这有两个好处
- It makes the API more powerful.
- It makes your program run faster
每一个 Source 开启后,都需要关闭,代码中需要手动关闭。
在 Kotlin 中,除了 Okio.source(File)
,还可以使用扩展方法 file.source()
来开启一个 Source,同时可以使用 kotlin 中 use 方法来自动关闭流:
@Throws(IOException::class)
fun readLines(file: File) {
file.source().use { fileSource ->
fileSource.buffer().use { bufferedFileSource ->
while (true) {
val line = bufferedFileSource.readUtf8Line() ?: break
if ("square" in line) {
println(line)
}
}
}
}
}
readUtf8Line()
方法会读取一整行的数据,直到遇到行标志符,例如 \n
, \r\n
或者文件的末尾 ,这里会将行标志符去掉。当读到一个空行时,会返回空字符串,而当没有数据可以读时,会返回 null 。
而在 Kotlin 中,我们可以将 source.readUtf8Line()
的调用封装到 generateSequence
的构造器中,一旦返回 null,循环就会结束:
@Throws(IOException::class)
fun readLines(file: File) {
file.source().buffer().use { source ->
generateSequence { source.readUtf8Line() }
.filter { line -> "square" in line }
.forEach(::println)
}
}
readUtf8Line()
适合解析接大多数文件,但在某些情况你也可以使用 readUtf8LineStrict()
,该方法只有遇到 \n
或者 \r\n
才会返回,如果在此之前遇到了文件末尾,则会直接抛出一个 EOFException
。此外,该方法也可以限制读取的字节数来限制输入:
@Throws(IOException::class)
fun readLines(file: File) {
file.source().buffer().use { source ->
while (!source.exhausted()) {
val line = source.readUtf8LineStrict(1024)
if ("square" in line) {
println(line)
}
}
}
}
Write a text file
以上我们使用了 Source 与 BufferedSource 来读取一个文件。这里我们使用 Sink 与 BufferedSink 来写入一个文件。这里将功能封装到 BufferedSink 的原因与 BufferedSource 相同。
BufferedSink 没有提供写入一行的 api,你需要手动写入换行符,你可以使用 System.lineSeparator()
来获取当前系统的换行符。在 Windows 中它会返回 \r\n
,而在其他系统中会返回 \n
。
在 Kotlin 中,我们可以通过内联扩展方法来写出更加紧凑的代码:
@Throws(IOException::class)
fun writeEnv(file: File) {
file.sink().buffer().use { sink ->
for ((key, value) in System.getenv()) {
sink.writeUtf8(key)
sink.writeUtf8("=")
sink.writeUtf8(value)
sink.writeUtf8("\n")
}
}
}
这里我们调用了四次 writeUtf8
,相比于以下的写法,这种调用多次的写法能防止 jvm 创建多个字符串对象:
sink.writeUtf8(entry.getKey() + "=" + entry.getValue() + "\n"); // Slower!
UTF-8
在上面的 api 中,您可以看到 Okio 非常喜欢UTF-8。早期的计算机系统遭受了许多不兼容的字符编码: ISO-8859-1、 ShiftJIS、 ASCII、 EBCDIC等。编写支持多种字符集的软件非常糟糕,我们甚至没有表情符号!今天我们很幸运,世界上所有地方都标准化了 UTF-8,在之后的系统中很少使用其他字符集。
你可以使用 readString()
与 writeString()
方法来按照指定字符集获取数据。该方法需要手动传入字符集。在当今的程序中大多数都是使用 UTF-8 字符集。
在编码字符串时,您需要注意字符串表示和编码的不同方式。当符号具有重音或其他装饰时,可以将其表示为单个复杂代码点 (é) 或后跟修饰符 (´) 的简单代码点 (e) 。当整个符号是一个单独的代码点,叫做 NFC ;当它是倍数时,它是 NFD。
尽管我们在 I/O 中读取或写入字符串时使用 UTF-8,但当它们在内存中时,Java 字符串使用一种被称为 UTF-16 的过时字符编码。它是一种糟糕的编码,因为它对大多数字符使用16位字符,但有些字符不适合。特别是,大多数表情符号使用两个Java字符。这是有问题的,因为String.length() 返回一个令人惊讶的结果: UTF-16 字符的数量,而不是符号的自然数量。
Café 🍩 | Café 🍩 | |
---|---|---|
From | NFC | NFD |
Code Points | c a f é ␣ 🍩 | c a f e ´ ␣ 🍩 |
UTF-8 bytes | 43 61 66 c3a9 20 f09f8da9 | 43 61 66 65 cc81 20 f09f8da9 |
String.codePointCount | 6 | 7 |
String.length | 7 | 8 |
Utf8.size | 10 | 11 |
在大多数情况下,Okio 可以让您忽略这些问题,专注于您的数据。但是当您需要它们时,有一些方便的 api 来处理低级 UTF-8 字符串。
使用 Utf8.size()
计算将字符串编码为 UTF-8 而不进行实际编码所需的字节数。这对于像协议缓冲区这样以长度为前缀的编码很方便。
Use BufferedSource.readUtf8CodePoint()
to read a single variable-length code point, and BufferedSink.writeUtf8CodePoint()
to write one.
使用 BufferedSource.readUtf8CodePoint()
与 BufferedSink.writeUtf8CodePoint()
读取或写入单个变长代码点。
Golden Values
Okio 喜欢测试。这个库经过了大量的测试,它的特性在测试应用程序代码时通常很有帮助。我们发现一个非常好用的测试模式是 golden value 测试。这种测试是为了测试旧版应用编码的数据能否被新版的应用解码。
我们使用 Java Serialization 为数据编码。尽管我们必须承认 Java Serialization 是一种好的编码系统,但应用程序还是更倾向于使用其他编码,比如 JSON 或 protobuf 。有一个方法可以接受实例化一个 object,并转换为 ByteString:
@Throws(IOException::class)
private fun serialize(o: Any?): ByteString {
val buffer = Buffer()
ObjectOutputStream(buffer.outputStream()).use { objectOut ->
objectOut.writeObject(o)
}
return buffer.readByteString()
}
看着调用了很少方法,但这里的过程还是比较复杂
- 我们创建了一个 buffer 作为序列化数据存放的容器,这里 buffer 类似 ByteArrayOutputStream 。
- 我们调用 buffer.outputStream() 获取其 OutputStream 对象,当向该 OutputStream 写入数据时,相当于向 buffer 末尾写入数据 。
- 我们创建一个 ObjectOutputStream 并装饰我们获取到的 OutputStream ,然后写入我们的 Object。这里使用 use 来自动关闭缓冲流,但关闭一个 buffer 的 OutputStream 不会做任何事情。
- 最后我们从 buffer 中调用 readByteString() 来读取 byte string 。这个方法允许我们传入读取的字节大小,缺省则表示读取全部数据。这里总是从 buffer 的起点开始读。
调用我们刚刚写的 serialize() 方法,并打印一个 golden value
val point = Point(8.0, 15.0)
val pointBytes = serialize(point)
println(pointBytes.base64())
最终我们打印 ByteString 的 base64 值,因为 base64 是一种比较紧凑的格式。以下为打印的值:
rO0ABXNyAB5va2lvLnNhbXBsZXMuR29sZGVuVmFsdWUkUG9pbnTdUW8rMji1IwIAAkQAAXhEAAF5eHBAIAAAAAAAAEAuAAAAAAAA
这就是我们的 golden value,我们可以将其作为一个测试用例嵌入进我们的测试用例代码中,构造 ByteString:
val goldenBytes = ("rO0ABXNyACRva2lvLnNhbXBsZXMuS290bGluR29sZGVuVmFsdWUkUG9pbnRF9yaY7cJ9EwIAA" + "kQAAXhEAAF5eHBAIAAAAAAAAEAuAAAAAAAA").decodeBase64()
之后是将该 ByteString 解码回对象,首先还是创建一个 Buffer,然后在其中写入我们的 ByteString,最终在调用 buffer.inputStream() 获取 inputStream 再用 ObjectInputStream 装饰,最终反序列化该对象:
@Throws(IOException::class, ClassNotFoundException::class)private fun deserialize(byteString: ByteString): Any? { val buffer = Buffer() buffer.write(byteString) ObjectInputStream(buffer.inputStream()).use { objectIn -> return objectIn.readObject() }}
现在我们可以使用该 golden value 来测试该解码器
val goldenBytes = ("rO0ABXNyACRva2lvLnNhbXBsZXMuS290bGluR29sZGVuVmFsdWUkUG9pbnRF9yaY7cJ9EwIAA" + "kQAAXhEAAF5eHBAIAAAAAAAAEAuAAAAAAAA").decodeBase64()!!val decoded = deserialize(goldenBytes) as PointassertEquals(point, decoded)
Write a binary file
对 二进制文件 进行编码与 对文本文件进行编码一样。对这两种情况,Okio 使用同样的 Api,BufferedSink 与 BufferedSource 。这对于同时包含字节和字符数据的二进制格式很方便。
写二进制数据比写文本更危险,因为如果出错,通常很难诊断。要避免这些错误,就要小心以下陷阱:
- The width of each field. 这是使用的字节数。Okio不包含发送部分字节的机制。如果你需要,则你需要在发送之前自己进行位移或修饰等操作。
- The endianness of each field. 所有大于一个字节的字段都需要注意其字节端,是大端还是小端。Okio 中,Le 开头的方法都是小端,没有前缀的方法为大端。
- Signed vs. Unsigned. 在 Java 中没有 unsigned 的类型(除了 char ,因此处理符号的问题通常需要在业务中完成。Okio 提供了 writeByte() 和 writeShort() 方法来简单的处理这些事,参数类型为 int 类型。你可以传入一个无符号数数据直接强转成的 int,例如 255 。Okio 会自动处理。
Method | Width | Endianness | Value | Encoded Value |
---|---|---|---|---|
writeByte | 1 | 3 | 03 | |
writeShort | 2 | big | 3 | 00 03 |
writeInt | 4 | big | 3 | 00 00 00 03 |
writeLong | 8 | big | 3 | 00 00 00 00 00 00 00 03 |
writeShortLe | 2 | little | 3 | 03 00 |
writeIntLe | 4 | little | 3 | 03 00 00 00 |
writeLongLe | 8 | little | 3 | 03 00 00 00 00 00 00 00 |
writeByte | 1 | Byte.MAX_VALUE | 7f | |
writeShort | 2 | big | Short.MAX_VALUE | 7f ff |
writeInt | 4 | big | Int.MAX_VALUE | 7f ff ff ff |
writeLong | 8 | big | Long.MAX_VALUE | 7f ff ff ff ff ff ff ff |
writeShortLe | 2 | little | Short.MAX_VALUE | ff 7f |
writeIntLe | 4 | little | Int.MAX_VALUE | ff ff ff 7f |
writeLongLe | 8 | little | Long.MAX_VALUE | ff ff ff ff ff ff ff 7f |
该代码按照 BMP 文件格式对位图进行编码。
@Throws(IOException::class)fun encode(bitmap: Bitmap, sink: BufferedSink) { val height = bitmap.height val width = bitmap.width val bytesPerPixel = 3 val rowByteCountWithoutPadding = bytesPerPixel * width val rowByteCount = (rowByteCountWithoutPadding + 3) / 4 * 4 val pixelDataSize = rowByteCount * height val bmpHeaderSize = 14 val dibHeaderSize = 40 // BMP Header sink.writeUtf8("BM") // ID. sink.writeIntLe(bmpHeaderSize + dibHeaderSize + pixelDataSize) // File size. sink.writeShortLe(0) // Unused. sink.writeShortLe(0) // Unused. sink.writeIntLe(bmpHeaderSize + dibHeaderSize) // Offset of pixel data. // DIB Header sink.writeIntLe(dibHeaderSize) sink.writeIntLe(width) sink.writeIntLe(height) sink.writeShortLe(1) // Color plane count. sink.writeShortLe(bytesPerPixel * Byte.SIZE_BITS) sink.writeIntLe(0) // No compression. sink.writeIntLe(16) // Size of bitmap data including padding. sink.writeIntLe(2835) // Horizontal print resolution in pixels/meter. (72 dpi). sink.writeIntLe(2835) // Vertical print resolution in pixels/meter. (72 dpi). sink.writeIntLe(0) // Palette color count. sink.writeIntLe(0) // 0 important colors. // Pixel data. for (y in height - 1 downTo 0) { for (x in 0 until width) { sink.writeByte(bitmap.blue(x, y)) sink.writeByte(bitmap.green(x, y)) sink.writeByte(bitmap.red(x, y)) } // Padding for 4-byte alignment. for (p in rowByteCountWithoutPadding until rowByteCount) { sink.writeByte(0) } }}
解码过程中最困难的部分是 bmp 文件格式的填充。该格式规定每行以 4 字节的边界开始,因此需要添加 0 。
其他二进制格式的编码通常非常相似。这里给出一些建议:
- 用 golden value 编写测试。确保程序发出预期的结果,这可以使调试更容易。
- 使用 Utf8.size() 计算已编码字符串的字节数。这对于长度前缀格式是必不可少的。
- 使用 Float.floatToIntBits() 和 Double.doubleToLongBits() 对浮点值进行编码。
Communicate on a Socket
通过网络发送和接收数据有点像写和读文件。我们使用 BufferedSink 对输出进行编码,使用 BufferedSource 对输入进行解码。与文件一样,网络协议可以是文本、二进制或两者的混合。但是在网络和文件系统之间也有一些实质性的区别。
对于文件,您可以直接读写,操作系统会帮我们处理并发,但对于网络,存在并发处理。有些协议是轮流处理的:写请求、读响应、重复。你可以用一个线程来实现这种协议。在其他协议中,您可以同时读写。通常情况下,您需要一个专用线程来进行读取。对于写线程,可以使用专用线程,也可以使用同步线程,这样多个线程就可以共享一个接收器。 Okio 的流对于并发使用并不安全。
接收缓冲区出站数据,以减少 I/O 操作。这是有效的,但这意味着您必须手动调用 flush() 来传输数据。通常,面向消息的协议在每条消息之后刷新。注意,当缓冲数据超过某个阈值时,Okio 将自动刷新。这是为了节省内存,您不应该在交互协议中依赖它。
Okio 内部是通过 java.io.Socket
实现通讯。创建服务器或客户端的 Socket,然后使用 Okio.source(socket) 进行读取,使用 Okio.sink(socket) 进行写入。同时你还可以使用 SSLSocket。并且我们建议你使用 SSLSocket。
调用 socket .close() 可以立即关闭该连接。同时所有它的 Source 与 Sink 立即无法使用,并抛出 IOException。同时还可以为所有 Socket 配置超时。此外还可以通过 source 与 sink 的公开方法来设置超时时间。即使使用其他流来装饰,这个 API 也可以工作。
以下用例实现了一个 SOCKS 代理服务器:
val fromSocket: Socket = ...val fromSource = fromSocket.source().buffer()val fromSink = fromSocket.sink().buffer()
创建 source 与 sink 的 api 与文件类似。注意,一旦你创建了 source 与 sink,则不能在使用该 Socket 的 inputStream 与 outputStream 。否则会出现冲突。
val buffer = Buffer()var byteCount: Longwhile (source.read(buffer, 8192L).also { byteCount = it } != -1L) { sink.write(buffer, byteCount) sink.flush()}
上面的循环将数据从源复制到接收器,在每次读取后进行刷新。如果我们不需要刷新,我们可以用一个调用 BufferedSink.writeAll(Source) 来替换这个循环。
read() 的 8192 参数是在返回之前读取的最大字节数。我们可以在这里传递任何值,但我们喜欢 8kib,因为这是 Okio 在单个系统调用中可以做到的最大值。大多数时候,应用程序代码不需要处理这样的限制!
val addressType = fromSource.readByte().toInt() and 0xffval port = fromSource.readShort().toInt() and 0xffff
Okio 使用有符号类型,如 byte 和 short,但协议通常需要无符号值。按位&操作符是Java将有符号值转换为无符号值的首选习惯用法。以下是bytes、shorts 和 int 的备忘单:
Type | Signed Range | Unsigned Range | Signed to Unsigned |
---|---|---|---|
byte | -128..127 | 0..255 | int u = s & 0xff; |
short | -32,768..32,767 | 0..65,535 | int u = s & 0xffff; |
int | -2,147,483,648..2,147,483,647 | 0..4,294,967,295 | long u = s & 0xffffffffL; |
Java 没有可以表示无符号长类型的基本类型。
Hashing
作为 Java 程序员,我们工作中经常接触到 哈希。我们在前面介绍了hashCode() 方法,我们知道需要重写这个方法,否则会发生不可预见的坏事情。接下来我们会看到 LinkedHashMap 及其好友。它们建立在hashCode()方法之上,以组织数据以实现快速检索。
在其他地方,我们有加密哈希函数。这些东西到处都在用。HTTPS 证书、Git 提交、BitTorrent 完整性检查和区块链块都使用加密哈希。良好地使用哈希可以提高应用程序的性能、私密性、安全性和简洁性。
每个加密哈希函数接受一个变长输入字节流,并产生一个固定长度的字节字符串值,称为“哈希”。哈希函数有这些重要的特性:
- Deterministic:每个输入总是产生相同的输出
- Uniform:每个输出字节串的可能性是相等的。要找到或创建产生相同输出的不同输入对是非常困难的。这被称为“碰撞”。
- Non-reversible:知道输出并不能帮助你找到输入。注意,如果您知道一些可能的输入,您可以计算它的哈希值,看看它们的哈希值是否匹配。
- Well-known:哈希 在任何地方都被实现并被严格理解。
好的哈希函数计算起来非常便宜(几十微秒),而反向计算(千万亿次)则非常昂贵。计算和数学的稳步发展使得一度伟大的哈希函数变得不那么昂贵。在选择哈希函数时,要注意不是所有函数都是一样的。 Okio 支持这些常见的加密哈希函数:
- MD5:128位(16字节)加密哈希。它既不安全又过时。之所以提供此散列,是因为它很流行,而且便于在不安全敏感的遗留系统中使用。
- SHA-1:160位(20字节)的加密哈希。最近证明了创建SHA-1碰撞是可行的。考虑从SHA-1升级到SHA-256。
- SHA-256:256位(32字节)加密哈希。SHA-256得到了广泛的理解,而且要反向使用也很昂贵。这是大多数系统应该使用的散列。
- SHA-512 :512位(64字节)加密哈希。想要逆转是很昂贵的。
计算 Hash 值需要先创建一个长度指定的 ByteString,计算后可以调用 hex() 方法获取其十六进制表示。
val byteString = readByteString(File("README.md"))println(" md5: " + byteString.md5().hex())println(" sha1: " + byteString.sha1().hex())println(" sha256: " + byteString.sha256().hex())println(" sha512: " + byteString.sha512().hex())
也可也使用 Buffer
val buffer = readBuffer(File("README.md"))println(" md5: " + buffer.md5().hex())println(" sha1: " + buffer.sha1().hex())println(" sha256: " + buffer.sha256().hex())println(" sha512: " + buffer.sha512().hex())
source 与 sink 也可以实现:
sha256(blackholeSink()).use { hashingSink -> file.source().buffer().use { source -> source.readAll(hashingSink) println(" sha256: " + hashingSink.hash.hex()) }}
sha256(blackholeSink()).use { hashingSink -> hashingSink.buffer().use { sink -> file.source().use { source -> sink.writeAll(source) sink.close() // Emit anything buffered. println(" sha256: " + hashingSink.hash.hex()) } }}
Okio 还支持 HMAC (哈希消息验证码),它结合了一个秘密和一个哈希。应用程序使用 HMAC 进行数据完整性和身份验证。
val secret = "7065616e7574627574746572".decodeHex()println("hmacSha256: " + byteString.hmacSha256(secret).hex())
与哈希一样,您可以从ByteString、Buffer、HashingSource和HashingSink生成HMAC。注意 Okio 没有为 MD5 实现 HMAC。
其中 Okio使用 Java 的 Java .security. messagedigest 用于加密哈希,javax.crypto.Mac 用于HMAC。
Encryption and Decryption
使用 Okio.Cipher (Sink, Cipher) 或 Okio.cipherSource(Source, Cipher) 通过块密码加密或解密流。
调用者负责使用所选算法、密钥和特定于算法的附加参数(如初始化向量)初始化加密或解密密码。下面的示例显示了 AES 加密的典型用法,其中 key 和 iv 参数都应该是 16 字节长。
fun encryptAes(bytes: ByteString, file: File, key: ByteArray, iv: ByteArray) { val cipher = Cipher.getInstance("AES/CBC/PKCS5Padding") cipher.init(Cipher.ENCRYPT_MODE, SecretKeySpec(key, "AES"), IvParameterSpec(iv)) val cipherSink = file.sink().cipherSink(cipher) cipherSink.buffer().use { it.write(bytes) }}fun decryptAesToByteString(file: File, key: ByteArray, iv: ByteArray): ByteString { val cipher = Cipher.getInstance("AES/CBC/PKCS5Padding") cipher.init(Cipher.DECRYPT_MODE, SecretKeySpec(key, "AES"), IvParameterSpec(iv)) val cipherSource = file.source().cipherSource(cipher) return cipherSource.buffer().use { it.readByteString() }}
File System Examples
Okio 最近获得了一个多平台文件系统 API。这些示例可以在JVM、native 和 Node.js 平台上工作。在下面的例子中 fileSystem 是 fileSystem 的一个实例,例如 fileSystem。系统或 FakeFileSystem。
读取 readme.md 文件的所有数据
val path = "readme.md".toPath()val entireFileString = fileSystem.read(path) { readUtf8()}
读取 thumbnail.png 的数据并加载到 ByteString:
val path = "thumbnail.png".toPath()val entireFileByteString = fileSystem.read(path) { readByteString()}
读取 /etc/hosts 文件,并按行写入 List<String>
val path = "/etc/hosts".toPath()val allLines = fileSystem.read(path) { generateSequence { readUtf8Line() }.toList()}
读取 index.html 文件中第一个 <html>
之前的数据:
val path = "index.html".toPath()val untilHtmlTag = fileSystem.read(path) { val htmlTag = indexOf("<html>".encodeUtf8()) if (htmlTag != -1L) readUtf8(htmlTag) else null}
使用 ByteString 写入文件:
val path = "data.bin".toPath()fileSystem.write(path) { val byteString = "68656c6c6f20776f726c640a".decodeHex() write(byteString)}
将 List<String>
中数据按行写入:
val path = "readme.md".toPath()val lines = listOf( "Hello, World", "------------", "", "This is a sample file.", "")fileSystem.write(path) { for (line in lines) { writeUtf8(line) writeUtf8("\n") }}
Releases
Our change log has release history.
repositories { maven { url = uri("https://oss.sonatype.org/content/repositories/snapshots/") }}dependencies { implementation("com.squareup.okio:okio:3.0.0-alpha.9")}