Okio是围绕这两种类型构建的,它们将大量功能打包到一个简单的API中:Sources and Sinks
ByteString
是不可变的字节序列。对于字符数据,最基本的就是String
。而ByteString
就像是String
的兄弟一般,它使得将二进制数据作为一个变量值变得容易。这个类很聪明:它知道如何将自己编码和解码为十六进制、base64和utf-8。Buffer
是一个可变的字节序列。像Arraylist一样,你不需要预先设置缓冲区的大小。你可以将缓冲区读写为一个队列:将数据写到队尾,然后从队头读取。
在内部,ByteString
和Buffer
做了一些聪明的事情来节省CPU和内存。如果您将UTF-8字符串编码为ByteString
,它会缓存对该字符串的引用,这样,如果您稍后对其进行解码,就不需要做任何工作。
Buffer
是作为片段的链表实现的。当您将数据从一个缓冲区移动到另一个缓冲区时,它会重新分配片段的持有关系,而不是跨片段复制数据。这对多线程特别有用:与网络交互的子线程可以与工作线程交换数据,而无需任何复制或多余的操作。
java.io
设计的一个优雅部分是如何对流进行分层来处理加密和压缩等转换。Okio有自己的stream类型:Source
和Sink
,分别类似于java的Inputstream
和Outputstream
,但是有一些关键区别:
- 超时(Timeouts)。流提供了对底层I/O超时机制的访问。与
java.io
的socket字流不同,read()
和write()
方法都给予超时机制。- 易于实施。
source
只声明了三个方法:read()
、close()
和timeout()
。没有像available()
或单字节读取这样会导致正确性和性能意外的危险操作。- 使用方便。虽然
source
和sink
的实现只有三种方法可写,但是调用方可以实现Bufferedsource
和Bufferedsink
接口, 这两个接口提供了丰富API能够满足你所需的一切。- 字节流和字符流之间没有人为的区别。都是数据。你可以以字节、UTF-8字符串、big-endian的32位整数、little-endian的短整数等任何你想要的形式进行读写;不再有
InputStreamReader
!- 易于测试。
Buffer
类同时实现了BufferedSource
和BufferedSink
接口,因此测试代码简单明了。
Sources 和 Sinks分别与InputStream
和OutputStream
交互操作。你可以将任何Source
看做InputStream
,也可以将任何InputStream
当做Source
。对于Sink
和Outputstream
也是如此。
public void readLines(File file) throws IOException {
Source fileSource = Okio.source(file);
BufferedSource bufferedSource = Okio.buffer(fileSource);
for (String line; (line = bufferedSource.readUtf8Line()) != null; ) {
System.out.println(line);
}
bufferedSource.close();
}
这个示例代码是用来读取文本文件的,Okio通过Okio.source(File)
的方式来读取文件流,它返回的是一个Source对象,但是Source对象的方法是比较少的(只有3个),因此Okio提供了一个装饰者对象接口BufferedSource
,通过Okio.buffer(fileSource)
来生成,这个方法内部实际会生成一个RealBufferedSource
类对象,RealBufferedSource
内部持有Buffer缓冲对象可使IO速度更快,该类实现了BufferedSource
接口,而BufferedSource
接口提供了大量丰富的接口方法:try-with-source
语法,上面示例代码可以写成下面这样:
public void readLines(File file) throws IOException {
try (BufferedSource bufferedSource = Okio.buffer(Okio.source(file))) {
for (String line; (line = bufferedSource.readUtf8Line()) != null; ) {
System.out.println(line);
}
}
}
try-with-source
是jdk1.7开始提供的语法糖,在try
语句()里面的资源对象,jdk最终会自动调用它的close
方法去关闭它, 即便try
里有多个资源对象也是可以的,这样就不用你手动去关闭资源了。但是在android里面使用的话,会提示你要求API level最低为19
才可以。
readUtf8Line()
方法适用于大多数文件。对于某些用例,还可以考虑使用readUtf8LineStrict()
。类似readUtf8Line()
,但它要求每一行都以
或
结尾。如果在这之前遇到文件结尾,它将抛出一个EOFException
。它还允许设置一个字节限制来防止错误的输入。
public void readLines(File file) throws IOException {
try (BufferedSource source = Okio.buffer(Okio.source(file))) {
while (!source.exhausted()) {
String line = source.readUtf8LineStrict(1024L);
System.out.println(line);
}
}
}
public void writeEnv(File file) throws IOException {
Sink fileSink = Okio.sink(file);
BufferedSink bufferedSink = Okio.buffer(fileSink);
for (Map.Entry<String, String> entry : System.getenv().entrySet()) {
bufferedSink.writeUtf8(entry.getKey());
bufferedSink.writeUtf8("=");
bufferedSink.writeUtf8(entry.getValue());
bufferedSink.writeUtf8("
");
}
bufferedSink.close();
}
类似于读文件使用Source
和BufferedSource
, 写文件的话,则是使用的Sink
和 BufferedSink
,同样的在BufferedSink
接口中也提供了丰富的接口方法:Okio.buffer(fileSink)
内部返回的实现对象是一个RealBufferedSink
类的对象, 跟RealBufferedSource
一样它也是一个装饰者对象,具备Buffer
缓冲功能。同样,以上代码可以使用jdk的try-with-source
语法获得更加简便的写法:
public void writeEnv(File file) throws IOException {
try (BufferedSink sink = Okio.buffer(Okio.sink(file))) {
sink.writeUtf8("啊啊啊")
.writeUtf8("=")
.writeUtf8("aaa")
.writeUtf8("
");
}
}
其中的换行符
,Okio没有提供单独的api方法,而是要你手动写,因为这个跟操作系统有关,不过你可以使用System.lineSeparator()
来代替
,这个方法在Windows上返回的是"
"
在UNIX上返回的是"
"。
writeUtf8()
进行了四次调用, 这样要比下面的代码更高效,因为虚拟机不必对临时字符串进行创建和垃圾回收。
sink.writeUtf8(entry.getKey() + "=" + entry.getValue() + "
");
Gzip压缩和读取
//zip压缩
GzipSink gzipSink = new GzipSink(Okio.sink(file));
BufferedSink bufferedSink = Okio.buffer(gzipSink);
bufferedSink.writeUtf8("this is zip file");
bufferedSink.flush();
bufferedSink.close();
//读取zip
GzipSource gzipSource = new GzipSource(Okio.source(file));
BufferedSource bufferedSource = Okio.buffer(gzipSource);
String s = bufferedSource.readUtf8();
bufferedSource.close();
readString()
和 writeString()
,这两个方法可以指定字符编码参数,但在大多数情况下应该只使用带UTF-8的方法。
在编码字符串时,需要特别注意字符串的表达形式和编码方式。当字形有重音或其他装饰时,情况可能会有点复杂。尽管在I/O中读写字符串时使用的都是UTF-8,但是当在内存中,Java字符串使用的是已过时的UTF-16进行编码的。这是一种糟糕的编码格式,因为它对大多数字符使用 16-bit char
,但有些字符不适合。特别是大多数的表情符号使用的是两个Java字符, 这时就会出现一个问题: String.length()返回的结果是utf-16字符的数量,而不是字体原本的字符数量。