Java Okio-更加高效易用的IO库
轉載請標明出處:http://blog.csdn.net/zhaoyanjun6/article/details/119997762
本文出自【趙彥軍的博客】
Java IO流學習總結一:輸入輸出流
Java IO流學習總結二:File
Java IO流學習總結三:緩沖流-BufferedInputStream、BufferedOutputStream
Java IO流學習總結四:緩沖流-BufferedReader、BufferedWriter
Java IO流學習總結五:轉換流-InputStreamReader、OutputStreamWriter
Java IO流學習總結六:ByteArrayInputStream、ByteArrayOutputStream
Java IO流學習總結七:Commons IO 2.5-FileUtils
2021年 Java Okio-更加高效易用的IO庫
文章目錄
- okio簡介
- 使用讀取數據
- 練習1:讀取文本
- BufferedSource
- 寫文件 Sink
- 綜合演練1:文本讀寫
- 綜合演練2:文件復制
- 對象序列化/反序列化
- 對象序列化/反序列化
- 序列化數據傳輸
- 序列化數據轉成base64字符串
- 數據Hash
- 實戰(zhàn):文本做md5計算
- 實戰(zhàn):文件做md5計算
- 數據加密 AES
- AES 簡介
- OKio 支持的AES加密
okio簡介
Okio是一個庫,它補充了java.io和java.nio,使訪問、存儲和處理數據變得更加容易。
OkHttp的的 io 功能就是 OKio 提供的,OkHttp是Android中包含的一個功能強大的HTTP客戶端。
github地址:https://github.com/square/okio
api主頁:https://square.github.io/okio/
maven地址:https://mvnrepository.com/artifact/com.squareup.okio/okio
依賴引入
implementation group: 'com.squareup.okio', name: 'okio', version: '2.10.0'Okio定義了自己的一套繼承鏈,Source對應InputStream, Sink對應OutputStream,這樣對比就不難理解了,看一下接口的定義。
public interface Source extends Closeable {long read(Buffer sink, long byteCount) throws IOException;Timeout timeout();@Override void close() throws IOException; }public interface Sink extends Closeable, Flushable {void write(Buffer source, long byteCount) throws IOException;@Override void flush() throws IOException;Timeout timeout();@Override void close() throws IOException; }使用讀取數據
練習1:讀取文本
/*** 讀取文件中的文本* @param file* @throws IOException*/public void readFile(File file) throws IOException {try (Source fileSource = Okio.source(file)) {Buffer buffer = new Buffer();fileSource.read(buffer, 1024);while (true) {String line = buffer.readUtf8Line();if (line == null) break;System.out.println(line);}}}可以看到Source 就相當于 InputStream , 我們看看 Okio.source(file) 的源碼
/** Returns a source that reads from `file`. */ @Throws(FileNotFoundException::class) fun File.source(): Source = inputStream().source()再看看 inputStream() 方法
@kotlin.internal.InlineOnly public inline fun File.inputStream(): FileInputStream {return FileInputStream(this) }是不是很熟悉,還是 Java IO 那一套。只不過是封裝了一層。
我們看看如何創(chuàng)建一個 Source ,它提供了幾個方法
Okio.source(InputStream input) Okio.source(File file) Okio.source(Socket socket)還有一點,okio 最新版本都用kotlin重新實現了,我們看看,上面的代碼如果用 kotlin 寫是什么樣子的。
/*** 讀取文件中的文本* @param file* @throws IOException*/@Throws(IOException::class)fun readFile(file: File) {file.source().use { fileSource ->val buffer = Buffer()fileSource.read(buffer, 1024)while (true) {val line = buffer.readUtf8Line() ?: breakprintln(line)}}}是不是簡介了不少。
我們用了一個 Buffer() 類做緩沖類,我們看看它的繼承關系:
class Buffer : BufferedSource, BufferedSink, Cloneable, ByteChannel {注意事項:
我們定義了 fileSource.read(buffer, 1024) , 這句話的含義是從文件讀數據存入 Buffer 中,最大存入 1024 個字節(jié),返回的是字節(jié)個數,如果文件讀完了,返回 -1 。
所以完整讀取文本文件的代碼為:
public void readFile(File file) throws IOException {try (Source fileSource = Okio.source(file)) {Buffer buffer = new Buffer();while (fileSource.read(buffer, 1024) != -1) {while (true) {//一次讀一行String line = buffer.readUtf8Line();if (line == null) break;System.out.println(line);}}}}readUtf8Line()一次讀一行,有沒有方法一次把 buffer 里面內容讀完,也有的 buffer.readUtf8() , 所以代碼可以改成如下
/*** 讀取文件中的文本** @param file* @throws IOException*/public void readFile(File file) throws IOException {try (Source fileSource = Okio.source(file)) {Buffer buffer = new Buffer();fileSource.read(buffer, 1024);while (fileSource.read(buffer, 1024) != -1) {//一次讀完String line = buffer.readUtf8();System.out.println(line);}}}BufferedSource
BufferedSource 是一個帶有緩沖功能的 Source , 說白了BufferedSource 其實就是內置了一個 Buffer
上面的代碼就可以改成:
/*** 讀取文件中的文本* @param file* @throws IOException*/public void readFile(File file) throws IOException {try (Source fileSource = Okio.source(file)) {BufferedSource bufferedSource = Okio.buffer(fileSource);while (true) {String line = bufferedSource.readUtf8Line();if (line == null) break;System.out.println(line);}}}readUtf8Line() API讀取所有數據,直到下一行分隔符\n、\r\n或文件結尾。它以字符串形式返回該數據,省略結尾的分隔符。當遇到空行時,該方法將返回一個空字符串。如果沒有更多的數據可讀取,它將返回null。
所以我們可以進一步優(yōu)化代碼如下:
/*** 讀取文件中的文本** @param file* @throws IOException*/public void readFile(File file) throws IOException {try (Source fileSource = Okio.source(file)) {BufferedSource bufferedSource = Okio.buffer(fileSource);String line;while ((line = bufferedSource.readUtf8Line()) != null) {System.out.println(line);}}}所以我們也可以優(yōu)化成
/*** 讀取文件中的文本** @param file* @throws IOException*/public void readFile(File file) throws IOException {try (Source fileSource = Okio.source(file); BufferedSource bufferedSource = Okio.buffer(fileSource)) {String line = bufferedSource.readUtf8();System.out.println(line);}}最后我們可以封裝一個方法來讀取文本文件
/*** 讀取文件中的文本** @param file* @throws IOException*/public String readFile(File file) throws IOException {try (Source fileSource = Okio.source(file); BufferedSource bufferedSource = Okio.buffer(fileSource)) {return bufferedSource.readUtf8();}}甚至可以更近一步,再簡介一下:
/*** 讀取文件中的文本** @param file* @throws IOException*/public String readFile(File file) throws IOException {try (BufferedSource bufferedSource = Okio.buffer(Okio.source(file))) {return bufferedSource.readUtf8();}}kotlin 版本如下:
@Throws(IOException::class)fun readFile(file: File): String? {file.source().buffer().use { bufferedSource ->return bufferedSource.readUtf8()}}是不是很簡潔。
寫文件 Sink
如果文件不存在,會自動創(chuàng)建文件
/*** 把文本寫入文件* @param file* @throws IOException*/public void writeFile(String content, File file) throws IOException {try (Sink sink = Okio.sink(file); BufferedSink bufferedSink = Okio.buffer(sink)) {bufferedSink.write(content.getBytes()); //寫字節(jié)}}如何創(chuàng)建一個 Sink ?
Okio.sink(File file) Okio.sink(OutputStream outputStream) Okio.sink(Socket sink)除此之外,還有其他寫數據方法:
bufferedSink.write(byte[] bytes) bufferedSink.writeUtf8(String string) bufferedSink.writeAll(Source source) bufferedSink.write(ByteString byteString)綜合演練1:文本讀寫
我們來實現一個讀文本,然后再寫文本的代碼
public class Test {public static void main(String[] args) {Test test = new Test();try {//從文件讀取文本String string = test.readFile(new File("/Users/xmly/workspace/web1/src/main/aa.txt"));//寫文本到文件test.writeFile(string, new File("/Users/xmly/workspace/web1/src/main/aa1.txt"));} catch (IOException e) {e.printStackTrace();}}/*** 讀取文件中的文本** @param file* @throws IOException*/public String readFile(File file) throws IOException {try (BufferedSource bufferedSource = Okio.buffer(Okio.source(file))) {return bufferedSource.readUtf8();}}/*** 把文本寫入文件** @param file* @throws IOException*/public void writeFile(String content, File file) throws IOException {try (Sink sink = Okio.sink(file); BufferedSink bufferedSink = Okio.buffer(sink)) {bufferedSink.write(content.getBytes());}} }綜合演練2:文件復制
public class Test {public static void main(String[] args) {Test test = new Test();try {//從文件讀取文本File file1 = new File("/Users/xmly/Desktop/1234.jpeg");File file2 = new File("/Users/xmly/workspace/web1/src/main/aa1.jpeg");test.copyFile(file1, file2);} catch (IOException e) {e.printStackTrace();}}/*** 復制文件** @param file1* @param file2* @throws IOException*/public void copyFile(File file1, File file2) throws IOException {try (BufferedSource bufferedSource = Okio.buffer(Okio.source(file1)); BufferedSink bufferedSink = Okio.buffer(Okio.sink(file2))) {bufferedSink.writeAll(bufferedSource);}} }相比java IO , Okio 簡直是神器,太簡潔,太牛B了
對象序列化/反序列化
序列化:把對象轉成二進制數據,可以存儲到本地,也可以網絡傳輸
反序列化:把二進制數據轉換成對象
通常我們把對象轉換成 json 格式的數據,然后再把 json 轉換成對象,這種情況也屬于對象的序列化/對象的反序列化
對象序列化/反序列化
定義 User 對象
public class User implements Serializable {int id;String name; }定義序列化方法和反序列化方法
/*** 對象序列化 (把對象轉成ByteString對象)** @param ob* @return* @throws IOException*/public ByteString serialize(Object ob) throws IOException {Buffer buffer = new Buffer();try (ObjectOutputStream outputStream = new ObjectOutputStream(buffer.outputStream())) {outputStream.writeObject(ob);}return buffer.readByteString();}/*** 對象反序列化 (把ByteString轉成對象)** @param byteString* @return*/public Object deserialize(ByteString byteString) throws IOException, ClassNotFoundException {Buffer buffer = new Buffer();buffer.write(byteString);try (ObjectInputStream objectInputStream = new ObjectInputStream(buffer.inputStream())) {return objectInputStream.readObject();}}我們跑一下,驗證一下結果:
try {User user = new User();user.id = 100;user.name = "zhaoyanjun";//序列號ByteString byteString = serialize(user);//反序列化User user1 = (User) deserialize(byteString);System.out.println("user = id:" + user1.id + " name:" + user1.name);} catch (IOException | ClassNotFoundException e) {e.printStackTrace(); }輸出結果:
user = id:100 name:zhaoyanjun結果證明,我們已經成功實現了對象的序列化和反序列化。
序列化數據傳輸
在上面,我們已經把對象序列化了,下面我們把序列化的數據保存到本地文件,然后再把本地文件中二進制數據取出來。其實底層都是操作二進制數據
/*** 把ByteString寫入文件** @param byteString* @param file* @throws IOException*/public void writeByteStringToFile(ByteString byteString, File file) throws IOException {try (BufferedSink bufferedSink = Okio.buffer(Okio.sink(file))) {bufferedSink.write(byteString);}}/*** 從文件讀數據* @param file* @return* @throws IOException*/public ByteString readByteStringFromFile(File file) throws IOException {try (BufferedSource source = Okio.buffer(Okio.source(file))) {return source.readByteString();}}這就完成了序列化數據傳輸。
最后,我們看一下 ByteString 寫入文件后,是什么樣子的?
序列化數據轉成base64字符串
我們知道序列化后,變成 ByteString ,是個二進制數據,寫入文件或者發(fā)給服務器還好,如果要想直接發(fā)給同事,就很難了。
其實也有解決的辦法,把二進制數據轉換為 base64 編碼的字符串就可以了。如下:
//把ByteString轉為base64編碼的字符串 String base = byteString.base64();//把base64編碼的字符串轉成ByteString ByteString newByteString = ByteString.decodeBase64(base);數據Hash
我們這里說的數據 hash 并不是特指 java 中的 hashCode 方法。而是一種數據處理形式,可以理解為 特征處理 , 就是 把任意長度的輸入通過散列算法變換成固定長度的輸出
Hash 的特性:
- 輸入域無窮,輸出域有限。
- 輸入參數確定,經過hash函數映射出的返回值一樣。
- 輸入域上的值經過函數值映射后會幾乎均等的分布在輸出域上
- 不可逆
常見的 Hash 算法:
- MD5
- SHA-1
- SHA-256
- SHA-512
ByteString 的 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());buffers 的 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());實戰(zhàn):文本做md5計算
思路:
- 第一步:把文本轉換成 ByteString
- 第二步:計算 ByteString md5值
我們來驗證一下:
System.out.println("md5:" + md5("周五下班了"));輸出結果:
md5:e0a0fd4fe31a1ff28b0085e168ca4aefmd5 其他實現方式:http://blog.csdn.net/zhaoyanjun6/article/details/120874209
實戰(zhàn):文件做md5計算
/*** 從文件讀數據** @param file* @return* @throws IOException*/public ByteString readByteStringFromFile(File file) throws IOException {try (BufferedSource source = Okio.buffer(Okio.source(file))) {return source.readByteString();}}/*** 計算文件md5值** @param file* @return*/public String md5(File file) {try {return readByteStringFromFile(file).md5().hex();} catch (IOException e) {e.printStackTrace();}return "";}我做了測試,非常好用,這里就不貼結果了
md5 其他實現方式:http://blog.csdn.net/zhaoyanjun6/article/details/120874209
數據加密 AES
AES 簡介
如果你對AES加密算法不是很了解,請移步到我的另外一篇博客:
AES加密 — 詳解
OKio 支持的AES加密
我們封裝一個方法,來實現 aes 加解密。
/*** AES加密** @param bytes* @param file* @param key 秘鑰* @param iv 偏移量* @throws GeneralSecurityException* @throws IOException*/void encryptAes(ByteString bytes, File file, byte[] key, byte[] iv)throws GeneralSecurityException, IOException {Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");cipher.init(Cipher.ENCRYPT_MODE, new SecretKeySpec(key, "AES"), new IvParameterSpec(iv));try (BufferedSink sink = Okio.buffer(Okio.cipherSink(Okio.sink(file), cipher))) {sink.write(bytes);}}/*** AES解密** @param file* @param key 秘鑰* @param iv 偏移量* @return* @throws GeneralSecurityException* @throws IOException*/ByteString decryptAesToByteString(File file, byte[] key, byte[] iv)throws GeneralSecurityException, IOException {Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");cipher.init(Cipher.DECRYPT_MODE, new SecretKeySpec(key, "AES"), new IvParameterSpec(iv));try (BufferedSource source = Okio.buffer(Okio.cipherSource(Okio.source(file), cipher))) {return source.readByteString();}}我們再來貼一下 Kotlin 的實現
//加密 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) } }//解密 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()} }我們寫一個測試demo跑一下代碼
public class Test {public static void main(String[] args) {Test test = new Test();try {//加密密碼(16位)String key = "zhaoyanjunzhaoy1";//偏移量(16位)String iv = "1234567890123456";//帶加密內容ByteString message = ByteString.encodeString("今天是周一,很開心", Charset.forName("UTF-8"));//加密字符串,并且把加密后的內容寫入文件File file = new File("/Users/xmly/workspace/web1/src/main/aa2.txt");test.encryptAes(message, file, key.getBytes("utf-8"), iv.getBytes());//解密,把文件內容解密出來ByteString sb = test.decryptAesToByteString(file, key.getBytes("utf-8"), iv.getBytes());System.out.println("解密后內容:" + sb.string(Charset.forName("UTF-8")));} catch (IOException | GeneralSecurityException e) {e.printStackTrace();}} }運行結果如下:
解密后內容:今天是周一,很開心看到,我們已經把文件成功解密了
我們再看看,字符串 ”今天是周一,很開心“ 加密后的文件內容是什么樣的.
可以看到是亂碼,無法識別,說明加密成功。
總結
以上是生活随笔為你收集整理的Java Okio-更加高效易用的IO库的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Kotlin by属性委托
- 下一篇: AES加密 — 详解