写点什么

Okio—— 更加高效易用的 IO 库,一线互联网架构师 Android 框架体系架构

用户头像
Android架构
关注
发布于: 刚刚

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("\n");


}


bufferedSink.close();


}


类似于读文件使用SourceBufferedSource, 写文件的话,则是使用的SinkBufferedSink,同样的在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("\n");


}


}


其中的换行符\n,Okio 没有提供单独的 api 方法,而是要你手动写,因为这个跟操作系统有关,不过你可以使用System.lineSeparator()来代替\n,这个方法在 Windows 上返回的是"\r\n"在 UNIX 上返回的是"\n"。


在上面的代码中,对writeUtf8()进行了四次调用, 这样要比下面的代码更高效,因为虚拟机不必对临时字符串进行创建和垃圾回收。


sink.writeUtf8(entry.getKey() + "=" + entry.getValue() + "\n");


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();

UTF-8

在上面的代码中,可以看到使用的基本都是带 UTF-8 的读写方法。Okio 推荐优先使用 UTF-8 的方法,why UTF-8? 这是因为 UTF-8 在世界各地都已标准化,而在早期的计算机系统中有许多不兼容的字符编码如:ISO-8859-1、ShiftJIS、 ASCII、EBCDIC 等,使用 UTF-8 可以避免这些问题。


如果你需要使用其他编码的字符集,可以使用readString()writeString(),这两个方法可以指定字符编码参数,但在大多数情况下应该只使用带 UTF-8 的方法。


在编码字符串时,需要特别注意字符串的表达形式和编码方式。当字形有重音或其他装饰时,情况可能会有点复杂。尽管在 I/O 中读写字符串时使用的都是 UTF-8,但是当在内存中,Java 字符串使用的是已过时的 UTF-16 进行编码的。这是一种糟糕的编码格式,因为它对大多数字符使用 16-bit char,但有些字符不适合。特别是大多数的表情符号使用的是两个 Java 字符, 这时就会出现一个问题: String.length()返回的结果是 utf-16 字符的数量,而不是字体原本的字符数量。


| | Café ? | Cafe? ? |


| --: | :-- | :-- |


| Form | 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 无需你关心这些问题,从而可以将关注点放在数据本身的使用上。但是当你确实需要处理这些低级的 UTF-8 字符串问题时,也有一些方便的 API 来处理,如使用utf8.size()可以计算字符串按钮 UTF-8 形式编码的字节数(但是并不会实际编码),使用bufferedsource.readutf8codepoint()读取单个可变长度的Code Point,使用bufferedsink.writeutf8codepoint()写入一个Code Point


public final class ExploreCharsets {


public void run() throws Exception {


dumpStringData("Café \uD83C\uDF69"); // NFC: é is one code point.


dumpStringData("Cafe? \uD83C\uDF69"); // NFD: e is one code point, its accent is another.


}


public void dumpStringData(String s) throws IOException {


System.out.println(" " + s);


System.out.println(" String.length: " + s.length());


System.out.println("String.codePointCount: " + s.codePointCount(0, s.length()));


System.out.println(" Utf8.size: " + Utf8.size(s));


System.out.println(" UTF-8 bytes: " + ByteString.encodeUtf8(s).hex());


readUtf8CodePoint(s);


System.out.println();


}


private void readUtf8CodePoint(String s) throws IOException {


Buffer buffer = new Buffer();


buffer.writeString(s, Charset.forName("utf-8"));


Source source = Okio.source(buffer.inputStream());


BufferedSource bufferedSource = Okio.buffer(source);


int i = -1;


StringBuilder sb = new StringBuilder();


while (!bufferedSource.exhausted() && (i = bufferedSource.readUtf8CodePoint()) != -1) {


sb.append((char) i).append("---");


}


System.out.println(" readUtf8CodePoint: " + sb.toString());


bufferedSource.close();


}


public static void main(String... args) throws Exception {


new ExploreCharsets().run();


}


}

序列化和反序列化

将一个对象进行序列化并以ByteString的形式返回:


private ByteString serialize(Object o) throws IOException {


Buffer buffer = new Buffer();


try (ObjectOutputStream objectOut = new ObjectOutputStream(buffer.outputStream())) {


objectOut.writeObject(o);


}


return buffer.readByteString();


}


这里使用Buffer对象代替 java 的ByterrayOutputstream,然后从 buffer 中获得输出流对象,并通过ObjectOutputStream(来自 java 的 Api)写入对象到 buffer 缓冲区当中,当你向Buffer中写数据时,总是会写到缓冲区的末尾。


最后,通过 buffer 对象的readByteString()从缓冲区读取一个ByteString对象,这会从缓冲区的头部开始读取,readByteString()方法可以指定要读取的字节数,如果不指定,则读取全部内容。


我们利用上面的方法将一个对象进行序列化,并得到的ByteString对象按照base64格式进行输出:


Point point = new Point(8.0, 15.0);


ByteString pointBytes = serialize(point);


System.out.println(pointBytes.base64());


这样我们会得到输出的一串字符串:


rO0ABXNyADVjb20uZXhhbXBsZS5hc3VzLm15YXBwbGljYXRpb24ub2tpby5Hb2xkZW5WYWx1ZSRQb2ludIRwF2M5CCK2AgACRAABeEQAAXl4cEAgAAAAAAAAQC4AAAAAAAA=


Okio 将这个字符串称之为 Golden Value


接下来,我们尝试将这个字符串(Golden Value)反序列化为一个 Point 对象


首先转回ByteString对象:


ByteString goldenBytes = ByteString.decodeBase64("rO0ABXNyADVjb20uZXhhbXBsZS5hc3VzLm1"


+"5YXBwbGljYXRpb24ub2tpby5Hb2xkZW5WYWx1ZSRQb2ludIRwF2M5CCK2AgACRAABeEQAAXl4cEAgAAAAAAAAQC4AAAAAAAA=");


然后将ByteString对象反序列化:


private Object deserialize(ByteString byteString) throws IOException, ClassNotFoundException {


Buffer buffer = new Buffer();


buffer.write(byteString);


try (ObjectInputStream objectIn = new ObjectInputStream(buffer.inputStream())) {


Object result = objectIn.readObject();


if (objectIn.read() != -1) throw new IOException("Unconsumed bytes in stream");


return result;


}


}


public void run() throws Exception {


Point point = new Point(8.0, 15.0);


ByteString pointBytes = serialize(point);


System.out.println(pointBytes.base64());


ByteString goldenBytes = ByteString.decodeBase64("rO0ABXNyADVjb20uZXhhbXBsZS5hc3VzLm15YXBwbGljYXRpb24ub2tpby5Hb2xkZW5WYWx1ZSRQb2ludIRwF2M5CCK2AgACRAABeEQAAXl4cEAgAAAAAAAAQC4AAAAAAAA=");


Point decoded = (Point) deserialize(goldenBytes);


if (point.x == decoded.x || point.y == decoded.y) {


System.out.println("Equals");


}


}


输出:



这样我们可以在不破坏兼容性的情况下更改对象的序列化方式。


这个序列化与 Java 原生的序列化有一个明显的区别就是GodenValue可以在不同客户端之间兼容(只要序列化和反序列化的 Class 是相同的)。什么意思呢,比如我在 PC 端使用 Okio 序列化一个 User 对象生成的GodenValue字符串,这个字符串你拿到手机端照样可以反序列化出来 User 对象。

写二进制文件

编码二进制文件与编码文本文件没有什么不同,Okio 使用相同的BufferedSinkBufferedSource字节。这对于同时包含字节和字符数据的二进制格式很方便。写二进制数据比写文本更容易出错,需要注意以下几点:


  • 字段的宽度 ,即字节的数量。Okio 没有释放部分字节的机制。如果你需要的话,需要自己在写操作之前对字节进行 shift 和 mask 运算。

  • 字段的字节顺序 , 所有多字节的字段都具有结束符:字节的顺序是从最高位到最低位(大字节 big endian),还是从最低位到最高位(小字节 little endian)。Okio 中针对小字节排序的方法都带有Le的后缀;而没有后缀的方法默认是大字节排序的。

  • 有符号和无符号,Java 没有无符号的基础类型(除了 char!)因此,在应用程序层经常会遇到这种情况。为方便使用,Okio 的writeByte()writeShort()方法可以接受int类型。你可以直接传递一个“无符号”字节像 255,Okio 会做正确的处理。


| 方法 | 宽度 | 字节排序 | 值 | 编码后的值 |


| :-- | :-: | :-: | :-: | :-- |


| 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文件格式 对文件进行编码:


public final class BitmapEncoder {


static final class Bitmap {


private final int[][] pixels;


Bitmap(int[][] pixels) {


this.pixels = pixels;


}


int width() {


return pixels[0].length;


}


int height() {


return pixels.length;


}


int red(int x, int y) {


return (pixels[y][x] & 0xff0000) >> 16;


}


int green(int x, int y) {


return (pixels[y][x] & 0xff00) >> 8;


}


int blue(int x, int y) {


return (pixels[y][x] & 0xff);


}


}


/**


  • Returns a bitmap that lights up red subpixels at the bottom, green subpixels on the right, and

  • blue subpixels in bottom-right.


*/


Bitmap generateGradient() {


int[][] pixels = new int[1080][1920];


for (int y = 0; y < 1080; y++) {


for (int x = 0; x < 1920; x++) {


int r = (int) (y / 1080f * 255);


int g = (int) (x / 1920f * 255);


int b = (int) ((Math.hypot(x, y) / Math.hypot(1080, 1920)) * 255);


pixels[y][x] = r << 16 | g << 8 | b;


}


}


return new Bitmap(pixels);


}


void encode(Bitmap bitmap, File file) throws IOException {


try (BufferedSink sink = Okio.buffer(Okio.sink(file))) {


encode(bitmap, sink);


}


}


/**


  • https://en.wikipedia.org/wiki/BMP_file_format


*/


void encode(Bitmap bitmap, BufferedSink sink) throws IOException {


int height = bitmap.height();


int width = bitmap.width();


int bytesPerPixel = 3;


int rowByteCountWithoutPadding = (bytesPerPixel * width);


int rowByteCount = ((rowByteCountWithoutPadding + 3) / 4) * 4;


int pixelDataSize = rowByteCount * height;


int bmpHeaderSize = 14;


int 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.


《Android学习笔记总结+最新移动架构视频+大厂安卓面试真题+项目实战源码讲义》
浏览器打开:qq.cn.hn/FTe 免费领取
复制代码


// DIB Header


sink.writeIntLe(dibHeaderSize);


sink.writeIntLe(width);


sink.writeIntLe(height);


sink.writeShortLe(1); // Color plane count.


sink.writeShortLe(bytesPerPixel * Byte.SIZE);


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 (int y = height - 1; y >= 0; y--) {


for (int x = 0; x < width; x++) {


sink.writeByte(bitmap.blue(x, y));


sink.writeByte(bitmap.green(x, y));


sink.writeByte(bitmap.red(x, y));


}


// Padding for 4-byte alignment.


for (int p = rowByteCountWithoutPadding; p < rowByteCount; p++) {


sink.writeByte(0);


}


}


}


public static void main(String[] args) throws Exception {

用户头像

Android架构

关注

还未添加个人签名 2021.10.31 加入

还未添加个人简介

评论

发布
暂无评论
Okio—— 更加高效易用的IO库,一线互联网架构师Android框架体系架构