Okio的使用简介

Okio的使用简介

.

简介

Okio 是由square公司开发的用于IO读取。补充了Java.iojava.nio的不足,以便能够更加方便,快速的访问、存储和处理数据。内部的读写操作是在内存中进行的。是OkHttp的底层IO库。

.

Okio的核心类

  • ByteStrings: 是不可变的字节序列。它会自动将自己编码和解码为十六进制、base64utf-8

  • Buffers 是一个可变的字节序列。像Arraylist一样,你不需要预先设置缓冲区的大小。你可以将缓冲区读写为一个队列:将数据写到队尾,然后从队头读取。

  • Source: 类似于java的Inputstream(输入流),但也有所区别。

  • Sink 类似于java的Outputstream(输入流),但也有所区别。

.

Okio的流与Java的区别 :

  • 超时(Timeouts): 流提供了对底层I/O超时机制的访问。与java.io的socket字流不同,read()write()方法都给予超时机制。

  • 易于实施: source 只声明了三个方法:read()close()timeout()。没有像available()或单字节读取这样会导致正确性和性能意外的危险操作。

  • 使用方便: 虽然sourcesink的实现只有三种方法可写,但是调用方可以实现BufferedsourceBufferedsink接口, 这两个接口提供了丰富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 可以对字节字符串Buffersource输入流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()
  }
}

.

.

参考资料

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值