文章大纲
- 引言
- 一、Okio 概述
- 二、Okio的核心元素
- 1、[Okio的两种数据类型](https://square.github.io/okio/#bytestrings-and-buffers)
- 1.1、ByteStrings ——不可变的字节序列
- 1.2、Buffers——可变的字节序列
- 2、Okio的Stream流类型
- 2.1、Source——Okio中的InputStream
- 2.2、Sink——Okio中的OutputStream
- 2.3、Okio 的Stream 和JDK的Stream的对比
- 三、Okio 的简单使用
- 1、BufferedSource读文本文件
- 2、序列化和反序列化
- 2.1、将对象序列化为`ByteString`
- 2.2、将ByteString 反序列化为对象
- 3、BufferedSink写文本文件
- 4、写二进制的字节序列
- 5、Socket 通信中使用Okio
- 6、Hash哈希算法和加解密
- 6.1、从ByteBufferString中生成对应的加密“hash"串
- 6.2、从Buffer中生成对应的加密“hash"串
- 6.3、从Source输入流中生成对应的加密“hash"串
- 6.4、从Sink输出流中生成对应的加密“hash"串
引言
OKHttp相比做Java 和Android 的无所不知吧,早期通过Gradle 引入OKHttp时都需要额外引入Okio的库,最早使用时和大多数一样我也没有去关注,直到后面慢慢喜欢去追根结底,深入到各种框架的核心中才发现Okio是一个宝库,不仅仅是从提供给我们的高效性能,设计思想也是有很多值得借鉴的,最后给个建议抛弃传统Java的IO、NIO吧,无论你是Java还是Android都应该尽快拥抱Okio。文章整理摘自Okio官网文档以后更多精选系列文章以高度完整的系列形式发布在公众号,真正的形成一套完整的技术栈,欢迎关注,目前第一个系列是关于Android系统启动系列的文章,大概有二十几篇干货:
基于 OKio 1.x
一、Okio 概述
Okio最初是OkHttp的一个组件,作为OKHttp内部使用的IO库,可以说是一个比JDK原生io和nio更高效的优秀组件,Okio 底层采用对象池复用技术避免了频繁的GC,而且对IO对象进行了高度的抽象(有点类似socket的设计思想),使得访问、存储和处理数据变得更加高效和便捷。
二、Okio的核心元素
1、Okio的两种数据类型
Okio是进行了对数据类型进行了高度的抽象,主要就是围绕两种类型构建的:
- ByteStrings
- Buffers
所有数据的操作都抽象统一到对应的API中,也正是因为他们内部的一些设计使得Okio相对于传统IO 更节省CPU和内存资源。
1.1、ByteStrings ——不可变的字节序列
ByteString
类实现原JDK生的Serializable, Comparable接口,本质就是一个不可变的字节序列,对于字符串数据对应的最基本类型就是String
,ByteString
相当于是更聪明的String
,因为它自带灵活的编码和解码为十六进制、base64和utf-8机制
当将用UTF-8将字符串编码为ByteString时,内部会对该字符串进行缓存处理,在解码时就可以直接从缓存中获取。
1.2、Buffers——可变的字节序列
Buffer
类实现Okio自定义的BufferedSource, BufferedSink, Cloneable, ByteChannel接口,本质是是一个可变的字节序列。与Arraylist类似,不需要预先设置缓冲区的大小,可以将缓冲区看成一个队列结构读写,将数据写到队尾,然后从队头读取。Buffer
是作为片段的链表实现的,当将数据从一个缓冲区移动到另一个缓冲区时,它会重新分配片段的持有关系,而不是跨片段复制数据。这对多线程特别有用,尤其与网络交互的子线程可以与工作线程交换数据,而无需任何复制或多余的操作。
2、Okio的Stream流类型
Okio对于Stream流类型也是进行了高度的抽象,所有流都可以用以下两种类型来表示:
- Source
- Sink
可以看成是所有的流的基类。
2.1、Source——Okio中的InputStream
Supplies a stream of bytes. Use this interface to read data from wherever it’s located: from the network, storage, or a buffer in memory.
public interface Sink extends Closeable, Flushable {
/** Removes {@code byteCount} bytes from {@code source} and appends them to this. */
void write(Buffer source, long byteCount) throws IOException;
/** Pushes all buffered bytes to their final destination. */
@Override void flush() throws IOException;
/** Returns the timeout for this sink. */
Timeout timeout();
/**
* Pushes all buffered bytes to their final destination and releases the
* resources held by this sink. It is an error to write a closed sink. It is
* safe to close a sink more than once.
*/
@Override void close() throws IOException;
}
当我们需要读数据时就可以直接调用Source类及其子类BufferedSource的方法即可。BufferedSource的设计有点类似于JDK原生的I/O架构设计,相当于是一个Source的装饰者角色,当通过Okio.buffer(fileSource)来得到Source输入流时,在方法内部首先会生成一个实现了BufferedSource接口的RealBufferedSource类对象,二RealBufferedSource内部持有Buffer缓冲对象可使IO速度更快。
2.2、Sink——Okio中的OutputStream
Receives a stream of bytes. Use this interface to write data wherever it’s needed: to the network, storage, or a buffer in memory. Sinks may be layered to transform received data, such as to compress, encrypt, throttle, or add protocol framing.
public interface Source extends Closeable {
/**
* Removes at least 1, and up to {@code byteCount} bytes from this and appends
* them to {@code sink}. Returns the number of bytes read, or -1 if this
* source is exhausted.
*/
long read(Buffer sink, long byteCount) throws IOException;
/** Returns the timeout for this source. */
Timeout timeout();
/**
* Closes this source and releases the resources held by this source. It is an
* error to read a closed source. It is safe to close a source more than once.
*/
@Override void close() throws IOException;
}
当我们需要写数据时就可以直接调用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之间也是如此。但它们之间还是存在一些区别的:
-
超时(Timeouts),与
java.io
的socket字流不同,Okio流支持对底层I/O超时机制的访问,其read()
和write()
方法都给予超时机制。 -
易于实现且安全,
Source
只定义了三个方法——read()、close()和timeout(),避免了调用java.io.InputStream#available available()
方法或单字节读取操作
时可能会导致未知的隐患。Source avoids the impossible-to-implement java.io.InputStream#available available() method. Instead callers specify how many bytes BufferedSource#require() method.
-
使用便捷,虽然Okio 的流基类接口
Source
和Sink
的实现只有三种方法,其子接口Bufferedsource
和Bufferedsink
定了丰富的API,除了支持用户自己扩展实现之外,内部也是封装了绝大部分类型的流操作对应的实现类供用户直接调用,使得一些类似C/C++的偏移操作变得很简单等等。 -
调用者不必显著区分字节流和字符流的操作,都是数据,可以用字节、UTF-8字符串、big-endian的32位整数、little-endian的短整数等任何形式进行读写操作,不需要像JDK 中额外的流转换操作,只需直接调用对应的方法即可。
三、Okio 的简单使用
1、BufferedSource读文本文件
- 通过Okio.source(file)方法得到Source输入流
- 再把原始输入流包装成BufferedSource
- 调用BufferedSource的方法,比如以行形式读取则调用readLines。
public void readLines(File file) throws IOException {
//不论try-catch中使用的资源是自己创造的还是java内置的类型,try-with-resources都是一个能够确保资源能被正确地关闭的强大方法。
try (Source fileSource = Okio.source(file);
BufferedSource bufferedSource = Okio.buffer(fileSource)) {
while (true) {
String line = bufferedSource.readUtf8Line();
if (line == null) break;
if (line.contains("square")) {
System.out.println(line);
}
}
}
}
另一种形式:
public void readLines(File file) throws IOException {
try (BufferedSource source = Okio.buffer(Okio.source(file))) {
for (String line; (line = source.readUtf8Line()) != null; ) {
if (line.contains("square")) {
System.out.println(line);
}
}
}
}
try-with-source是jdk1.7开始提供的语法糖,在try语句()里面的资源对象,jdk会自动调用它的close方法去关闭它,就不用自己在finally中去手动关闭了, 即便try里有多个资源对象也是没有影响。但是在android里面使用的话,会提示你要求API level最低为19才可以。
一般说来readUtf8Line()方法就可以用于读取适用于大多数文件,但对于某些特例可以考虑使用readUtf8LineStrict()功能大同小异,区别在于readUtf8LineStrict()要求每一行都以**\n或\r\n**结尾。如果在这之前遇到文件结尾,它将抛出一个EOFException。
在JDK 1.7后可以调用 System.lineSeparator()方法自动获取对应系统下的换行标志符。
- \n——Unix 系下的行结束标志
- \r\r——Window系下的行结束标志
The returned string will have at most {@code limit} UTF-8 bytes, and the maximum number
of bytes scanned is {@code limit + 2}. If {@code limit == 0} this will always throw an {@code EOFException} because no bytes will be scanned.public void readLines(File file) throws IOException {
try (BufferedSource source = Okio.buffer(Okio.source(file))) {
while (!source.exhausted()) {
/**
* The returned string will have at most {@code limit} UTF-8 bytes, and the maximum number
of bytes scanned is {@code limit + 2}. If {@code limit == 0} this will always throw an {@code EOFException} because no bytes will be scanned.
* Buffer buffer = new Buffer();
* buffer.writeUtf8("12345\r\n");
* // This will throw! There must be \r\n or \n at the limit or before it.
* buffer.readUtf8LineStrict(4);
* // No bytes have been consumed so the caller can retry.
* assertEquals("12345", buffer.readUtf8LineStrict(5));
*/
String line = source.readUtf8LineStrict(1024L);
System.out.println(line);
}
}
}
除了读文本文件,也可以通过ObjectOutputStream读取序列化后对象
2、序列化和反序列化
2.1、将对象序列化为ByteString
Okio的Buffer替代JDK的ByterrayOutputstream,再从Buffer中获得输出流对象,并通过JDK的ObjectOutputStream将输出流写入到到buffer缓冲区当中,当你向Buffer中写数据时,总是会写到缓冲区的末尾(由于内部的数据结构决定的),最后通过buffer对象的readByteString()从缓冲区读取一个ByteString对象,此时会从缓冲区的头部开始读取,readByteString()方法(可指定要读取的字节数,但如果不指定,则读取全部内容)
private ByteString serialize(Object o) throws IOException {
Buffer buffer = new Buffer();
try (ObjectOutputStream objectOut = new ObjectOutputStream(buffer.outputStream())) {
objectOut.writeObject(o);
}
return buffer.readByteString();
}
通过以上方法可以将一个对象进行序列化并得到的ByteString对象,直接调用**ByteString#base64()**方法就可以进行Base64编码:
Point point = new Point(8.0, 15.0);
ByteString pointBytes = serialize(point);
System.out.println(pointBytes.base64());
Okio把这个base64 处理后得到的字符串称为Golden value,对Golden value 进行ByteString#decodeBase64
ByteString goldenBytes = ByteString.decodeBase64(pointBytes.base64());
2.2、将ByteString 反序列化为对象
就可以从Golden value中得到原始的ByteString,再通过原始的ByteString构造JDK的ObjectInputStream对象并通过其readObject方法反序列化就可以得到对象
private Object deserialize(ByteString byteString) throws IOException, ClassNotFoundException {
Buffer buffer = new Buffer();
buffer.write(byteString);
try (ObjectInputStream objectIn = new ObjectInputStream(buffer.inputStream())) {
return objectIn.readObject();
}
}
简而言之就是可以通过Golden value来确保序列化后和反序列化的对象是一致的。
Point point = new Point(8.0, 15.0);
ByteString pointBytes = serialize(point);
String glodenValue =pointBytes.base64();
ByteString goldenBytes = ByteString.decodeBase64(glodenValue);
Point decoded = (Point) deserialize(goldenBytes);
assertEquals(new Point(8.0, 15.0), decoded);//true
Okio序列化与JDK原生序列化有一个明显的区别就是GodenValue可以在不同客户端之间兼容(只要序列化和反序列化的Class是相同的)。比如我在PC端使用Okio序列化一个User对象生成的GodenValue字符串,这个字符串你拿到手机端照样可以反序列化出来User对象。
3、BufferedSink写文本文件
- 通过Okio.sink()方法得到Sink输出流
- 再把原始输出流包装成BufferedSink
- 调用BufferedSink的write系列方法
public void writeEnv(File file) throws IOException {
try (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");
System.lineSeparator()
}
}
}
其他用法和设计思想参考读,write系列方法大多数都是带UTF-8编码的读写方法,Okio推荐优先使用UTF-8的方法,因为UTF-8在世界各地都已标准化。但还需要使用其他编码的字符集,可以使用readString() 和writeString()来指定字符编码参数,但在大多数情况下应该只使用带UTF-8的方法。
4、写二进制的字节序列
Okio中无论是任何类型的写操作都是由BufferedSink 发起,而且相对于传统的IO,Okio 写二进制字节序列尤其方便,实现了类似C/C++中的指针位移后再插入的功能,也因此引入了几个概念:
-
The width of each field——要写入的字节的数量,但Okio没有写入部分字节数的机制,但如果需要的话,需要自己在写之前对字节进行位移等运算。
-
The endianness of each field——计算机中对于大于一个字节的二进制序列都是有分为大小端排序的之分的,字节的顺序是从最高位到最低位(大端 big endian),还是从最低位到最高位(小端 little endian)。
Okio中针对小端排序的方法都带有
Le
的后缀;而没有后缀的方法默认是大端排序的 -
Signed vs Unsigned——有符号和无符号,Java中除了char是无符号的基础类型,剩下的基础类型都是有符号的,因此可直接把一个“无符号”字节像255(int类型)传入到
writeByte()
和writeShort()
方法,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 |
以下这个例子,基本上就是对于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来做字节对齐。另外编码其他二进制的格式非常相似,一些值得注意的点:
- 使用Golden values编写测试,对于确认程序的预期结果可以使调试更容易。
- 使用Utf8.size()方法计算编码字符串的字节长度。
- 使用Float.floatToIntBits()和Double.doubleToLongBits()来编码浮点型的数值。
5、Socket 通信中使用Okio
网络通信的本质就是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型的基本类型。
6、Hash哈希算法和加解密
哈希散列函数应用广泛,如HTTPS证书、Git提交、BitTorrent完整性检查和区块链块等都使用到加密散列, 良好地使用哈希可以提高应用程序的性能、隐私性、安全性和简单性。每个加密哈希函数接受一个可变长度的字节输入流,并生成一个长度固定的字符串值,称之为“hash”串。哈希函数具有以下重要特性:
- 确定性——每个输入总是产生相同的输出。
- 统一:每个输出的字节字符串的可能性相同。很难找到或创建产生相同输出的不同输入对。即“碰撞”。
- 不可逆:知道输出并不能帮助你找到输入。
- 易于理解:哈希在很多环境中都已被实现并且被严格理解。
Okio自带一些常见的加密哈希函数:
- MD5——128位(16字节)加密哈希,它既不安全又是过时的,因为它的逆向成本很低!之所以提供此哈希,是因为它在安全性较低的系统中使用比较非常流行并且方便。
- SHA-1——160位(20字节)加密散列。创建SHA-1碰撞是可行的,考虑从SHA-1升级到SHA-256。
- SHA-256——256位(32字节)加密哈希。SHA-256被广泛理解,逆向操作成本较高。这是大多数系统应该使用的哈希。
- SHA-512——512位(64字节)加密哈希。逆向操作成本很高。
6.1、从ByteBufferString中生成对应的加密“hash"串
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());
6.2、从Buffer中生成对应的加密“hash"串
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());
6.3、从Source输入流中生成对应的加密“hash"串
try (HashingSource hashingSource = HashingSource.sha256(Okio.source(file));
BufferedSource source = Okio.buffer(hashingSource)) {
source.readAll(Okio.blackhole());
System.out.println(" sha256: " + hashingSource.hash().hex());
}
6.4、从Sink输出流中生成对应的加密“hash"串
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。