发布:2023/2/24 16:01:25作者:管理员 来源:本站 浏览次数:795
简介
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.height
val width = bitmap.width
val bytesPerPixel = 3
val rowByteCountWithoutPadding = bytesPerPixel * width
val rowByteCount = (rowByteCountWithoutPadding + 3) / 4 * 4
val pixelDataSize = rowByteCount * height
val bmpHeaderSize = 14
val 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_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 0xff
if (socksVersion != VERSION_5) throw ProtocolException()
val methodCount = fromSource.readByte().toInt() and 0xff
var foundSupportedMethod = false
for (i in 0 until methodCount) {
val method: Int = fromSource.readByte().toInt() and 0xff
foundSupportedMethod = 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 0xff
val command = fromSource.readByte().toInt() and 0xff
val reserved = fromSource.readByte().toInt() and 0xff
if (version != VERSION_5 || command != COMMAND_CONNECT || reserved != 0) {
throw ProtocolException()
}
// Read an address.
val addressType = fromSource.readByte().toInt() and 0xff
val inetAddress = when (addressType) {
ADDRESS_TYPE_IPV4 -> InetAddress.getByAddress(fromSource.readByteArray(4L))
ADDRESS_TYPE_DOMAIN_NAME -> {
val domainNameLength: Int = fromSource.readByte().toInt() and 0xff
InetAddress.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.address
if (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: Long
while (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()
}
}
© Copyright 2014 - 2024 柏港建站平台 ejk5.com. 渝ICP备16000791号-4