Okio—— 更加高效易用的IO库,软件开发常见面试题

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.

// 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 {

BitmapEncoder encoder = new BitmapEncoder();

Bitmap bitmap = encoder.generateGradient();

encoder.encode(bitmap, new File(“gradient.bmp”));

}

}

代码中对文件按照BMP的格式写入二进制数据,这会生成一个bmp格式的图片文件,BMP格式要求每行以4字节开始,所以代码中加了很多0来做字节对齐。

编码其他二进制的格式非常相似。一些值得注意的点:

  • 使用Golden values编写测试,对于确认程序的预期结果可以使调试更容易。

  • 使用Utf8.size()方法计算编码字符串的字节长度。这对于length-prefixed格式必不可少。

  • 使用Float.floatToIntBits()Double.doubleToLongBits()来编码浮点型的数值。

使用Socket进行通信

通过网络发送和接收数据有点像文件的读写。Okio使用BufferedSink对输出进行编码,使用BufferedSource对输入进行解码。与文件一样,网络协议可以是文本、二进制或两者的混合。但是网络和文件系统之间也有一些实质性的区别。

当你有一个文件对象,你只可以选择读或者写,但是网络与之不同的是可以同时进行读和写!在有一些协议中,处理这个问题的方式是轮流的进行:写入请求、读取响应、重复以上操作。你可以用一个单线程来实现这种协议。而在其他协议中,你可以同时进行读写。通常你需要一个专门的线程来读取数据。对于写入数据,你可以使用专门线程或者使用synchronized,以便多个线程可以共享一个Sink。Okio的流在并发情况下使用是不安全的。

对于Okio的Sinks缓冲区,必须手动调用flush()来传输数据,以最小化I/O操作。通常,面向消息的协议会在每条消息之后刷新。注意,当缓冲数据超过某个阈值时,Okio将自动刷新。但这只是为了节省内存,不能依赖它进行协议交互。

Okio是基于java.io.socket建立连接的,当你通过socket创建服务器或客户端后,可以使用Okio.source(Socket)进行读取,使用Okio.sink(Socket)进行写入。这些API也同样适用于SSLSocket

在任意线程中想要取消socket连接可以调用Socket.close()方法,这将导致 sources 和 sinks 对象立即抛出IOException而失败。Okio中可以为所有的socket操作配置超时限制,但并不需要你去调用Socket的方法来设置超时:SourceSink会提供超时的接口。即使流对象被装饰,这个API也能工作。

Okio官方Demo中编写了一个 简单的Socket代理服务 来示例完整的网络交互操作,下面是其中的部分代码截取:

private void handleSocket(final Socket fromSocket) {

try {

final BufferedSource fromSource = Okio.buffer(Okio.source(fromSocket));

final BufferedSink fromSink = Okio.buffer(Okio.sink(fromSocket));

//…

//…

} catch (IOException e) {

}

}

可以看到通过Socket创建sources 和 sinks的方式与通过文件创建的方式一样,都是先通过Okio.source()拿到Socket对应的SourceSink对象,然后通过Okio.buffer()获取对应的装饰者缓冲对象。在Okio中,一旦你为Socket对象创建了Source 或者 Sink,那么你就不能再使用InputStreamOutputStream 了。

Buffer buffer = new Buffer();

for (long byteCount; (byteCount = source.read(buffer, 8192L)) != -1; ) {

sink.write(buffer, byteCount);

sink.flush();

}

以上代码中,循环从source中读取数据写入到sink当中,并调用flush()进行刷新,如果你不需要每次写数据都进行flush(),那么for循环里的两句可以使用BufferedSink.writeAll(Source)一行代码来代替。

你会发现,在read()方法中传递了一个8192作为读取的字节数,其实这里可以传任何数字,但是Okio更喜欢用8 kib,因为这是Okio在单个系统调用中所能处理的最大值。大多数时候应用程序代码不需要处理这样的限制!

int addressType = fromSource.readByte() & 0xff;

int port = fromSource.readShort() & 0xffff;

自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数初中级安卓工程师,想要提升技能,往往是自己摸索成长,但自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年最新Android移动开发全套学习资料》送给大家,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
img
img
img
img

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频
如果你觉得这些内容对你有帮助,可以添加下面V无偿领取!(备注Android)
img

最后

其实Android开发的知识点就那么多,面试问来问去还是那么点东西。所以面试没有其他的诀窍,只看你对这些知识点准备的充分程度。so,出去面试时先看看自己复习到了哪个阶段就好。

上面分享的百度、腾讯、网易、字节跳动、阿里等公司2021年的高频面试题,博主还把这些技术点整理成了视频和PDF(实际上比预期多花了不少精力),包含知识脉络 + 诸多细节,由于篇幅有限,上面只是以图片的形式给大家展示一部分。

Android学习PDF+学习视频+面试文档+知识点笔记

【Android思维脑图(技能树)】

知识不体系?这里还有整理出来的Android进阶学习的思维脑图,给大家参考一个方向。

【Android高级架构视频学习资源】

较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频**
如果你觉得这些内容对你有帮助,可以添加下面V无偿领取!(备注Android)
[外链图片转存中…(img-qkUNcFY8-1710670451371)]

最后

其实Android开发的知识点就那么多,面试问来问去还是那么点东西。所以面试没有其他的诀窍,只看你对这些知识点准备的充分程度。so,出去面试时先看看自己复习到了哪个阶段就好。

上面分享的百度、腾讯、网易、字节跳动、阿里等公司2021年的高频面试题,博主还把这些技术点整理成了视频和PDF(实际上比预期多花了不少精力),包含知识脉络 + 诸多细节,由于篇幅有限,上面只是以图片的形式给大家展示一部分。

Android学习PDF+学习视频+面试文档+知识点笔记

【Android思维脑图(技能树)】

知识不体系?这里还有整理出来的Android进阶学习的思维脑图,给大家参考一个方向。

[外链图片转存中…(img-oYzTpQXX-1710670451371)]

【Android高级架构视频学习资源】

**Android部分精讲视频领取学习后更加是如虎添翼!**进军BATJ大厂等(备战)!现在都说互联网寒冬,其实无非就是你上错了车,且穿的少(技能),要是你上对车,自身技术能力够强,公司换掉的代价大,怎么可能会被裁掉,都是淘汰末端的业务Curd而已!现如今市场上初级程序员泛滥,这套教程针对Android开发工程师1-6年的人员、正处于瓶颈期,想要年后突破自己涨薪的,进阶Android中高级、架构师对你更是如鱼得水,赶快领取吧!

  • 22
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值