| |
|
开发:
C++知识库
Java知识库
JavaScript
Python
PHP知识库
人工智能
区块链
大数据
移动开发
嵌入式
开发工具
数据结构与算法
开发测试
游戏开发
网络协议
系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程 数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁 |
-> 移动开发 -> 网络编程——使用更简洁且性能高效的Okio库来做IO和NIO -> 正文阅读 |
|
[移动开发]网络编程——使用更简洁且性能高效的Okio库来做IO和NIO |
引言OKHttp相比做Java 和Android 的无所不知吧,早期通过Gradle 引入OKHttp时都需要额外引入Okio的库,最早使用时和大多数一样我也没有去关注,直到后面慢慢喜欢去追根结底,深入到各种框架的核心中才发现Okio是一个宝库,不仅仅是从提供给我们的高效性能,设计思想也是有很多值得借鉴的,最后给个建议抛弃传统Java的IO、NIO吧,无论你是Java还是Android都应该尽快拥抱Okio。文章整理摘自Okio官网文档以后更多精选系列文章以高度完整的系列形式发布在公众号,真正的形成一套完整的技术栈,欢迎关注,目前第一个系列是关于Android系统启动系列的文章,大概有二十几篇干货:
一、Okio 概述Okio最初是OkHttp的一个组件,作为OKHttp内部使用的IO库,可以说是一个比JDK原生io和nio更高效的优秀组件,Okio 底层采用对象池复用技术避免了频繁的GC,而且对IO对象进行了高度的抽象(有点类似socket的设计思想),使得访问、存储和处理数据变得更加高效和便捷。 二、Okio的核心元素1、Okio的两种数据类型Okio是进行了对数据类型进行了高度的抽象,主要就是围绕两种类型构建的:
所有数据的操作都抽象统一到对应的API中,也正是因为他们内部的一些设计使得Okio相对于传统IO 更节省CPU和内存资源。 1.1、ByteStrings ——不可变的字节序列
1.2、Buffers——可变的字节序列
2、Okio的Stream流类型Okio对于Stream流类型也是进行了高度的抽象,所有流都可以用以下两种类型来表示:
可以看成是所有的流的基类。 2.1、Source——Okio中的InputStream
当我们需要读数据时就可以直接调用Source类及其子类BufferedSource的方法即可。BufferedSource的设计有点类似于JDK原生的I/O架构设计,相当于是一个Source的装饰者角色,当通过Okio.buffer(fileSource)来得到Source输入流时,在方法内部首先会生成一个实现了BufferedSource接口的RealBufferedSource类对象,二RealBufferedSource内部持有Buffer缓冲对象可使IO速度更快。 2.2、Sink——Okio中的OutputStream
当我们需要写数据时就可以直接调用Sink类及其子类BufferedSink的方法即可。BufferedSink的设计与BufferedSource类似,当通过Okio.buffer(fileSource)来得到Source输入流时,在方法内部首先会生成一个实现了BufferedSink接口的RealBufferedSink类对象,二RealBufferedSink内部持有Buffer缓冲对象可使IO速度更快。 2.3、Okio 的Stream 和JDK的Stream的对比Source和Sink 与 JDK中的InputStream和OutputStream可以互相操作,即可以将任何Source视为一个InputStream,也可以将任何InputStream视为一个Source;Sink和OutputStream之间也是如此。但它们之间还是存在一些区别的:
三、Okio 的简单使用1、BufferedSource读文本文件
另一种形式:
一般说来readUtf8Line()方法就可以用于读取适用于大多数文件,但对于某些特例可以考虑使用readUtf8LineStrict()功能大同小异,区别在于readUtf8LineStrict()要求每一行都以**\n或\r\n**结尾。如果在这之前遇到文件结尾,它将抛出一个EOFException。
除了读文本文件,也可以通过ObjectOutputStream读取序列化后对象 2、序列化和反序列化2.1、将对象序列化为
|
方法 | 宽度 | 字节排序 | 值 | 编码后的值 |
---|---|---|---|---|
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 |
以下这个例子,基本上就是对于Bitmap算法协议的解析和实现,通过Okio 快速实现。
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来做字节对齐。另外编码其他二进制的格式非常相似,一些值得注意的点:
网络通信的本质就是I/O操作,所以Okio 对于提升网络通信性能是十分重要的。Okio使用BufferedSink对输出进行编码,使用BufferedSource对输入进行解码。网络协议可以是文本、二进制或两者的混合。但是网络和文件系统之间有一些实质性的区别。对于一个文件对象,可以选择读或者写,但是网络则可以同时进行读和写,这就存在了线程安全的问题。因此在某些协议中,采用轮流方式工作——写入请求、读取响应、重复以上操作。涉及到多线程时,通常需要一个专门的线程来读取数据,再使用专门线程或者使用synchronized来写数据,如果多个线程可以共享一个Sink,就需要考虑线程安全问题,总之,默认情况下Okio的流在并发情况下使用是不安全的。虽然当缓冲数据超过某个阈值时,Okio将自动刷新,但这只是为了节省内存,不能依赖它进行协议交互。因此对于Okio的Sinks缓冲区,必须手动调用flush()来传输数据,以最小化I/O操作。Okio是基于java.io.socket建立连接的,当通过socket创建服务器或客户端后,可以使用Okio.source(Socket)进行读取,使用Okio.sink(Socket)进行写入(这些API也同样适用于SSLSocket)。在任意线程中可以调用Socket.close()方法关闭连接,这将导致 sources 和 sinks 对象立即抛出IOException而失败。Okio中可以为所有的socket操作配置超时限制,但并不需要你去调用Socket的方法来设置超时:Source 和 Sink会提供超时的接口。
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对应的Source或Sink对象,然后通过Okio.buffer()获取对应的装饰者缓冲对象。
在Okio中,一旦你为Socket对象创建了Source 或者 Sink,那么你就不能再使用InputStream 或 OutputStream 了。
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;
Okio使用的是有符号类型,如byte
和short
,但通常协议需要的是无符号的值,而在Java中将有符号的值转换为无符号值的首选方式,就是通过是按位与&
运算符。以下是字节、短整型和整型的互相转换表格
Type Java类型 | 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中没有能够表示无符号的long型的基本类型。
哈希散列函数应用广泛,如HTTPS证书、Git提交、BitTorrent完整性检查和区块链块等都使用到加密散列, 良好地使用哈希可以提高应用程序的性能、隐私性、安全性和简单性。每个加密哈希函数接受一个可变长度的字节输入流,并生成一个长度固定的字符串值,称之为“hash”串。哈希函数具有以下重要特性:
Okio自带一些常见的加密哈希函数:
ByteString byteString = readByteString(new File("README.md"));
System.out.println(" md5: " + byteString.md5().hex());
System.out.println(" sha1: " + byteString.sha1().hex());
System.out.println("sha256: " + byteString.sha256().hex());
System.out.println("sha512: " + byteString.sha512().hex());
Buffer buffer = readBuffer(new File("README.md"));
System.out.println(" md5: " + buffer.md5().hex());
System.out.println(" sha1: " + buffer.sha1().hex());
System.out.println("sha256: " + buffer.sha256().hex());
System.out.println("sha512: " + buffer.sha512().hex());
try (HashingSource hashingSource = HashingSource.sha256(Okio.source(file));
BufferedSource source = Okio.buffer(hashingSource)) {
source.readAll(Okio.blackhole());
System.out.println(" sha256: " + hashingSource.hash().hex());
}
try (HashingSink hashingSink = HashingSink.sha256(Okio.blackhole());
BufferedSink sink = Okio.buffer(hashingSink);
Source source = Okio.source(file)) {
sink.writeAll(source);
sink.close(); // Emit anything buffered.
System.out.println(" sha256: " + hashingSink.hash().hex());
}
Okio还支持HMAC
(Hash Message Authentication Code 哈希消息认证代码),它结合了一个秘钥值和一个hash值,应用程序可以使用HMAC进行数据完整性和身份验证。
Okio使用Java的java.security.MessageDigest用于加密散列和javax.crypto.Mac 生成HMAC。
ByteString secret = ByteString.decodeHex("7065616e7574627574746572");
System.out.println("hmacSha256: " + byteString.hmacSha256(secret).hex());
当然也可以从ByteString, Buffer, HashingSource, 和HashingSink生成HMAC,不过,Okio没有为MD5实现HMAC。
|
移动开发 最新文章 |
Vue3装载axios和element-ui |
android adb cmd |
【xcode】Xcode常用快捷键与技巧 |
Android开发中的线程池使用 |
Java 和 Android 的 Base64 |
Android 测试文字编码格式 |
微信小程序支付 |
安卓权限记录 |
知乎之自动养号 |
【Android Jetpack】DataStore |
|
上一篇文章 下一篇文章 查看所有文章 |
|
开发:
C++知识库
Java知识库
JavaScript
Python
PHP知识库
人工智能
区块链
大数据
移动开发
嵌入式
开发工具
数据结构与算法
开发测试
游戏开发
网络协议
系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程 数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁 |
360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 | -2024/11/23 20:02:04- |
|
网站联系: qq:121756557 email:121756557@qq.com IT数码 |