Okio的使用简介
.
简介
Okio 是由square公司开发的用于IO读取。补充了Java.io
和java.nio
的不足,以便能够更加方便,快速的访问、存储和处理数据。内部的读写操作是在内存中进行的。是OkHttp的底层IO库。
.
Okio的核心类
ByteStrings
: 是不可变的字节序列。它会自动将自己编码和解码为十六进制、base64
和utf-8
。
Buffers
: 是一个可变的字节序列。像Arraylist一样,你不需要预先设置缓冲区的大小。你可以将缓冲区读写为一个队列:将数据写到队尾,然后从队头读取。
Source
: 类似于java的Inputstream
(输入流),但也有所区别。
Sink
: 类似于java的Outputstream
(输入流),但也有所区别。
.
Okio的流与Java的区别 :
超时(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接口,因此测试代码简单明了。
.
.
添加的Okio的依赖
在项目的 build.gradle
中添加下面的依赖:
dependencies {implementation("com.squareup.okio:okio:2.10.0")
}
.
.
Okio的使用
.
1. 将数据写到文件中
@Throws(IOException::class)
fun writeFile(file: File) {file.sink().buffer().use { sink ->//循环的向文件中写入数据for ((key, value) in System.getenv()) {//数据格式:`key = value + 换行`sink.writeUtf8(key)sink.writeUtf8("=")sink.writeUtf8(value)sink.writeUtf8(system.lineeseseparator())//等价于(但前者性能更优)//因为VM不必创建和回收临时字符串//sink.writeUtf8(key + "=" + value + "\n"); }sink.close()}
}//在main()中调用方法来向文件中写入数据
fun main() {var file:File = File("文件路径")if(!file.exists()){try {file.createNewFile();} catch (IOException e) {e.printStackTrace();}}//向文件中写入数据writeFile(file)
}
注:
在进行数据的写入的时候sink.writeUtf8()
方法只会在一行上输入不会自动换行,所以需要我们手动插入换行符;在大多数的程序都是所以"\n"
来作为换行符的。在极少数情况下,你可以使用system.lineeseseparator()
来代替"\n"
;它在Windows上返回"\r\n"
,在其他操作系统上返回"\n"
。
.
2. 逐行读取文件
@Throws(IOException::class)
fun readLinesFile(file: File) {file.source().use { fileSource ->fileSource.buffer().use { bufferedFileSource ->while (true) {//读取文件的内容val line = bufferedFileSource.readUtf8Line() ?: break//将文件的内容打印到控制台println(line) }bufferedFileSource.close()}}
}//在main()中调用方法来读取指定的文件
fun main() {var file:File = File("文件路径")if(!file.exists()){return;}//读取已经readLinesFile(file)
}
注:
使用readUtf8Line()
方法来读取文件的所有数据,在读取数据的时候,直到读取下一个数据为\n
,\r\n
或文件的结尾前。它都以字符串的形式返回读取到的数据,并在最后省略定界符。当遇到空行时,该方法将返回一个空字符串。如果文件读取完成,将返回null。
.
3. 把二进制数据写入文件中
下面的示例代码是按照 BMP文件格式 对文件进行编码:
//提供给外部调用的写入方法@Throws(IOException::class)fun writeBitmap(bitmap: Bitmap, file: File) {file.sink().buffer().use { sink -> encode(bitmap, sink) }}@Throws(IOException::class)fun encode(bitmap: Bitmap, sink: BufferedSink) {val height = bitmap.heightval width = bitmap.widthval bytesPerPixel = 3val rowByteCountWithoutPadding = bytesPerPixel * widthval rowByteCount = (rowByteCountWithoutPadding + 3) / 4 * 4val pixelDataSize = rowByteCount * heightval bmpHeaderSize = 14val dibHeaderSize = 40// BMP Headersink.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 Headersink.writeIntLe(dibHeaderSize)sink.writeIntLe(width)sink.writeIntLe(height)sink.writeShortLe(1) // Color plane count.sink.writeShortLe(bytesPerPixel * Byte.SIZE_BITS)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 (y in height - 1 downTo 0) {for (x in 0 until width) {sink.writeByte(bitmap.blue(x, y))sink.writeByte(bitmap.green(x, y))sink.writeByte(bitmap.red(x, y))}// Padding for 4-byte alignment.for (p in rowByteCountWithoutPadding until rowByteCount) {sink.writeByte(0)}
}fun main() {val bitmap = Bitmap数据var file:File = File("文件路径")if(!file.exists()){try {file.createNewFile();} catch (IOException e) {e.printStackTrace();}}writeBitmap(bitmap, file)
}
说明:
代码中对文件按照BMP的格式写入二进制数据,这会生成一个
bmp
格式的图片文件,BMP格式要求每行以4字节开始,所以代码中加了很多0来做字节对齐。
.
.
使用Okio进行Socket通信
下面代码是基于Okio来实现的本地Socket客户端,并与服务器的Socket进行通信。
//Socket服务的代理类
class SocksProxyServer {//创建线程池private val executor = Executors.newCachedThreadPool()//创建Socket服务private lateinit var serverSocket: ServerSocket//创建Set集合private val openSockets: MutableSet<Socket> = Collections.newSetFromMap(ConcurrentHashMap())//启动Socket服务@Throws(IOException::class)fun start() {serverSocket = ServerSocket(0)executor.execute { acceptSockets() }}//关闭Socket服务@Throws(IOException::class)fun shutdown() {serverSocket.close()executor.shutdown()}fun proxy(): Proxy = Proxy(Proxy.Type.SOCKS,InetSocketAddress.createUnresolved("localhost", serverSocket.localPort))//连接Socket远程服务private fun acceptSockets() {try {while (true) {val from = serverSocket.accept()openSockets.add(from)executor.execute { handleSocket(from) }}} catch (e: IOException) {println("shutting down: $e")} finally {for (socket in openSockets) {socket.close()}}}//连接Socket服务private fun handleSocket(fromSocket: Socket) {try {val fromSource = fromSocket.source().buffer()val fromSink = fromSocket.sink().buffer()// Read the hello.val socksVersion = fromSource.readByte().toInt() and 0xffif (socksVersion != VERSION_5) throw ProtocolException()val methodCount = fromSource.readByte().toInt() and 0xffvar foundSupportedMethod = falsefor (i in 0 until methodCount) {val method: Int = fromSource.readByte().toInt() and 0xfffoundSupportedMethod = foundSupportedMethod or (method == METHOD_NO_AUTHENTICATION_REQUIRED)}if (!foundSupportedMethod) throw ProtocolException()// Respond to hello.fromSink.writeByte(VERSION_5).writeByte(METHOD_NO_AUTHENTICATION_REQUIRED).emit()// Read a command.val version = fromSource.readByte().toInt() and 0xffval command = fromSource.readByte().toInt() and 0xffval reserved = fromSource.readByte().toInt() and 0xffif (version != VERSION_5 || command != COMMAND_CONNECT || reserved != 0) {throw ProtocolException()}// Read an address.val addressType = fromSource.readByte().toInt() and 0xffval inetAddress = when (addressType) {ADDRESS_TYPE_IPV4 -> InetAddress.getByAddress(fromSource.readByteArray(4L))ADDRESS_TYPE_DOMAIN_NAME -> {val domainNameLength: Int = fromSource.readByte().toInt() and 0xffInetAddress.getByName(fromSource.readUtf8(domainNameLength.toLong()))}else -> throw ProtocolException()}val port = fromSource.readShort().toInt() and 0xffff// Connect to the caller's specified host.val toSocket = Socket(inetAddress, port)openSockets.add(toSocket)val localAddress = toSocket.localAddress.addressif (localAddress.size != 4) throw ProtocolException()// Write the reply.fromSink.writeByte(VERSION_5).writeByte(REPLY_SUCCEEDED).writeByte(0).writeByte(ADDRESS_TYPE_IPV4).write(localAddress).writeShort(toSocket.localPort).emit()// Connect sources to sinks in both directions.val toSink = toSocket.sink()executor.execute { transfer(fromSocket, fromSource, toSink) }val toSource = toSocket.source()executor.execute { transfer(toSocket, toSource, fromSink) }} catch (e: IOException) {fromSocket.close()openSockets.remove(fromSocket)println("connect failed for $fromSocket: $e")}}/***从source读取数据并将其写入sink*/private fun transfer(sourceSocket: Socket, source: Source, sink: Sink) {try {val buffer = Buffer()var byteCount: Longwhile (source.read(buffer, 8192L).also { byteCount = it } != -1L) {sink.write(buffer, byteCount)sink.flush()}} catch (e: IOException) {println("transfer failed from $sourceSocket: $e")} finally {sink.close()source.close()sourceSocket.close()openSockets.remove(sourceSocket)}}
}fun main() {//定义Socket代理对象并初始化val proxyServer = SocksProxyServer()//启动Socket服务proxyServer.start()//连接Socket远程服务器val url = URL("https://publicobject.com/helloworld.txt")val connection = url.openConnection(proxyServer.proxy())//接收Socket远程服务器返回的数据connection.getInputStream().source().buffer().use { source ->while (true) {//读取文件的内容val line = source.readUtf8Line() ?: break//将文件的内容打印到控制台println(line) }source.close()}//关闭Socket服务proxyServer.shutdown()
}
说明:
通过上述代码就可以实现与远程服务器的Socket进行通信,完成客户端与服务器的长链接通信。
.
.
Hashing(哈希散列)
Okio 可以对字节字符串
,Buffer
,source输入流
,sink输出流
等进行哈希加密。
下面介绍Okio支持加密哈希函数:
MD5: 128位(16字节)加密哈希。它既不安全又是过时的,因为它的逆向成本很低!之所以提供此哈希,是因为它在安全性较低的系统中使用比较非常流行并且方便。
SHA-1: 160位(20字节)加密散列。最近的研究表明,创建SHA-1碰撞是可行的。考虑从sha-1升级到sha-256。
SHA-256: 256位(32字节)加密哈希。SHA-256被广泛理解,逆向操作成本较高。这是大多数系统应该使用的哈希。
SHA-512: 512位(64字节)加密哈希。逆向操作成本很高。
1. 对字节字符串进行加密
println("ByteString")
val byteString = readByteString(File("文件路径"))
println(" md5: " + byteString.md5().hex())
println(" sha1: " + byteString.sha1().hex())
println(" sha256: " + byteString.sha256().hex())
println(" sha512: " + byteString.sha512().hex())@Throws(IOException::class)fun readByteString(file: File): ByteString {return file.source().buffer().use { it.readByteString() }}
2. 对Buffer进行加密
println("Buffer")
val buffer = readBuffer(File("文件路径"))
println(" md5: " + buffer.md5().hex())
println(" sha1: " + buffer.sha1().hex())
println(" sha256: " + buffer.sha256().hex())
println(" sha512: " + buffer.sha512().hex())@Throws(IOException::class)fun readBuffer(file: File): Buffer {return file.source().use { source ->Buffer().also { it.writeAll(source) }}}
3. 对source
输入流进行加密
println("HashingSource")
sha256(File("文件路径").source()).use { hashingSource ->hashingSource.buffer().use { source ->source.readAll(blackholeSink())println(" sha256: " + hashingSource.hash.hex())}
}
4. 对sink
输出流进行加密
println("HashingSink")
sha256(blackholeSink()).use { hashingSink ->hashingSink.buffer().use { sink ->File("文件路径").source().use { source ->sink.writeAll(source)sink.close() // Emit anything buffered.println(" sha256: " + hashingSink.hash.hex())}}
}
5. 支持秘钥和hash值加密
println("HMAC")
val byteString = readByteString(File("文件路径"))
val secret = "7065616e7574627574746572".decodeHex()秘钥
println("hmacSha256: " + byteString.hmacSha256(secret).hex())
补充:
你可以从
ByteString
,Buffer
,HashingSource
, 和HashingSink
中生成HMAC
,但Okio没有为MD5
实现HMAC。
.
.
加密和解密
Okio可以通过使用使用
Okio.cipherSink(Sink,Cipher)
或Okio.cipherSource(Source,Cipher)
让区块加密算法对Stream进行加密或解密。以下示例显示了
AES加密
的典型用法,其中key和iv参数都应为16个字节长度:
对Sink
输入流进行加密
fun encryptAes(bytes: ByteString, file: File, key: ByteArray, iv: ByteArray) {val cipher = Cipher.getInstance("AES/CBC/PKCS5Padding")cipher.init(Cipher.ENCRYPT_MODE, SecretKeySpec(key, "AES"), IvParameterSpec(iv))//解密val cipherSink = file.sink().cipherSink(cipher)cipherSink.buffer().use { it.write(bytes) }
}
对Source
输入流进行解密
fun decryptAesToByteString(file: File, key: ByteArray, iv: ByteArray): ByteString {val cipher = Cipher.getInstance("AES/CBC/PKCS5Padding")cipher.init(Cipher.DECRYPT_MODE, SecretKeySpec(key, "AES"), IvParameterSpec(iv))//解密val cipherSource = file.source().cipherSource(cipher)return cipherSource.buffer().use { it.readByteString()}
}
.
.
参考资料
-
okio官网
-
okio 的使用及源码分析