天天看點

Okio學習之官方文檔翻譯Okio學習之官方文檔翻譯ByteStrings and Buffers【位元組字元串及緩沖區】Sources and Sinks【來源與接收器】Presentations【示範文稿】Recipes【菜單】ReleasesR8 / ProGuardLicense【許可證】

Okio學習之官方文檔翻譯

連結: Okio官方文檔連接配接

Okio是一個對

java.io

java.nio

的補充庫。它使我們對資料的通路、存儲和加工更加容易。

它最初是作為 OkHttp的一個元件庫,OkHttp是Android中包含的功能強大的HTTP用戶端。 它已被很好地鍛煉,并準備解決新問題。

ByteStrings and Buffers【位元組字元串及緩沖區】

Okio是圍繞兩種類型建構的,它們将大量功能內建到了簡單的API中:

  • ByteString 是不可變的位元組序列。對于字元資料,

    String

    是基礎。

    ByteString

    是String失散已久的兄弟,它很容易将二進制資料視為數值。 此類是符合人體工程學的類:它知道如何以十六進制、base64和UTF-8編碼和解碼自身。
  • Buffer是一個可變的位元組序列。與

    ArrayList

    一樣,您不需要提前調整緩沖區的大小。作為隊列讀寫緩沖區:将資料寫到緩沖區的末尾,然後從緩沖區的前端讀取資料。沒有義務管理職位、限制或能力。

在内部,

ByteString

Buffer

做了一些聰明的事情來節省CPU和記憶體。如果您将一個UTF-8字元串編碼為

ByteString

,它會緩存一個對該字元串的引用,這樣如果您稍後解碼它,就不用做任何工作了。

Buffer

被實作為段的連結清單。當您将資料從一個緩沖區移動到另一個緩沖區時,它将重新配置設定段的所有權,而不是跨緩沖區複制資料。這種方法對多線程程式特别有用:與網絡對話的線程可以與工作線程交換資料,而無需任何複制或儀式。

Sources and Sinks【來源與接收器】

java.io

一個優雅的設計部分是如何對流進行分層以進行諸如加密和壓縮之類的轉換。 Okio包括自己的稱為Source 和Sink 的流類型,它們的工作方式類似于

InputStream

OutputStream

,但有一些主要差別:

  • 逾時. 流提供對基礎I / O機制逾時的通路。 與

    java.io

    套接字流不同,

    read()

    write()

    調用都允許逾時。
  • 容易實作.

    Source

    定義了三個方法:

    read()

    ,

    close()

    , and

    timeout()

    . 不會出現像

    available()

    或單位元組讀取這樣的會導緻正确性和性能意外的危險。
  • 易于使用. 實作

    Source

    Sink

    類的時候雖然隻有三個方法需要重寫,調用者通過BufferedSource 和BufferedSink 接口被賦予豐富的API 。這些接口可在一個類中為您提供所需的一切。
  • 位元組流和字元流之間沒有人為的差別. 都是資料。以位元組、UTF-8字元串、大端32位整數、小端短字元的形式讀寫;無論您想要什麼。不再有

    InputStreamReader

  • 容易測試.

    Buffer

    同時實作了

    BufferedSource

    and

    BufferedSink

    接口,讓你的測試代碼是簡單明了。

Sources and sinks 與

InputStream

OutputStream

可進行互相操作。 您可以将任何

Source

視為

InputStream

,也可以将任何

InputStream

視為

Source

。 對于

Sink

OutputStream

同樣。

Presentations【示範文稿】

一些“Ok”庫 (幻燈片): :介紹Okio和用它編寫的三個庫。

解碼二進制資料的秘密 (幻燈片): 資料編碼是如何工作的,Okio是如何做到的。

Ok的多平台! (幻燈片): 我們如何将Okio的實作語言從Java更改為Kotlin。

Recipes【菜單】

我們編寫了一些食譜,展示了如何解決Okio的常見問題。 通讀它們以了解一切如何協同工作。 随意剪切并粘貼這些示例; 這就是他們的目的。

Read a text file line-by-line (Java/Kotlin)【逐行讀取文本檔案(Java / Kotlin)】

使用

Okio.source(File)

打開源流以讀取檔案。傳回的

Source

接口非常小,用途有限。是以我們用buffer包裝source 。這有兩個好處:

  • 它使API更強大。與

    Source

    提供的基本方法不同,

    BufferedSource

    有幾十種方法可以簡潔地解決最常見的問題。
  • 它使你的程式運作得更快。緩沖允許Okio用更少的I/O操作完成更多的工作。

每個打開的

Source

都需要關閉。打開流的代碼負責確定流已關閉。

Java

在這裡,我們使用Java的

try

塊自動關閉源。

public void readLines(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;

      if (line.contains("square")) {
        System.out.println(line);
      }
    }

  }
}
           

Kotlin

請注意,靜态

Okio

方法成為擴充函數(

Okio.source(file)=> file.source()

),并

use

用于自動關閉流:

@Throws(IOException::class)
fun readLines(file: File) {
  file.source().use { fileSource ->
    fileSource.buffer().use { bufferedFileSource ->
      while (true) {
        val line = bufferedFileSource.readUtf8Line() ?: break
        if ("square" in line) {
          println(line)
        }
      }
    }
  }
}
           

readUtf8Line()

API讀取所有的資料,直到下一個行分隔符-要麼

\n

\r\n

或檔案的末尾。它以字元串形式傳回該資料,最後省略定界符。當遇到空行時,該方法将傳回一個空字元串。如果沒有更多要讀取的資料,它将傳回null。

可以通過内聯

fileSource

變量并使用奇特的

for

循環(而不是

while

)來更簡潔地編寫上述程式:

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);
      }
    }
  }
}
           

在Kotlin中,我們可以包裝

source.readUtf8Line()

轉換為

generateSequence

builder,以建立一個在傳回null後将結束的行序列。另外,由于擴充功能,轉換流很容易:

@Throws(IOException::class)
fun readLines(file: File) {
  file.source().buffer().use { source ->
    generateSequence { source.readUtf8Line() }
      .filter { line -> "square" in line }
      .forEach(::println)
  }
}
           

readUtf8Line()

方法适用于解析大多數檔案。 對于某些用例,您還可以考慮

readUtf8LineStrict()

。 後者與前者相似,但是要求每行以

\ n

\ r \ n

結尾。 如果在此之前遇到檔案末尾,它将抛出

EOFException

。 嚴格的變體還允許位元組限制以防止格式錯誤的輸入。

public void readLines(File file) throws IOException {
  try (BufferedSource source = Okio.buffer(Okio.source(file))) {
    while (!source.exhausted()) {
      String line = source.readUtf8LineStrict(1024L);
      if (line.contains("square")) {
        System.out.println(line);
      }
    }
  }
}
           

這是用Kotlin編寫的類似示例:

@Throws(IOException::class)
fun readLines(file: File) {
  file.source().buffer().use { source ->
    while (!source.exhausted()) {
      val line = source.readUtf8LineStrict(1024)
      if ("square" in line) {
        println(line)
      }
    }
  }
}
           

Write a text file(Java/Kotlin) 【編寫文本檔案】

在上面,我們使用

Source

BufferedSource

來讀取檔案。在編寫時,我們使用

Sink

BufferedSink

。緩沖的優點是相同的:功能更強大的API和更好的性能。

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");
    }

  }
}
           

沒有API可以編寫一行輸入;相反,我們手動插入自己的換行符。大多數程式應将寫死

"\n"

作為換行符。在極少數情況下,您可以使用

System.lineSeparator()

代替

"\n"

:他在Windows上傳回

"\r\n"

,在其他平台傳回

"\n"

我們可以通過内聯

fileSink

變量并利用方法鍊來更緊湊地編寫上述程式:

Java

public void writeEnv(File file) throws IOException {
  try (BufferedSink sink = Okio.buffer(Okio.sink(file))) {
    for (Map.Entry<String, String> entry : System.getenv().entrySet()) {
      sink.writeUtf8(entry.getKey())
          .writeUtf8("=")
          .writeUtf8(entry.getValue())
          .writeUtf8("\n");
    }
  }
}
           

Kotlin

@Throws(IOException::class)
fun writeEnv(file: File) {
  file.sink().buffer().use { sink ->
    for ((key, value) in System.getenv()) {
      sink.writeUtf8(key)
      sink.writeUtf8("=")
      sink.writeUtf8(value)
      sink.writeUtf8("\n")
    }
  }
}
           

在上面的代碼中,我們對

writeUtf8()

方法進行了四次調用。進行四次調用比下面的代碼更有效,因為VM不必建立和垃圾收集臨時字元串。

sink.writeUtf8(entry.getKey() + "=" + entry.getValue() + "\n"); // Slower!
           

UTF-8(Java / Kotlin)

在以上API中,您可以看到Okio非常喜歡UTF-8。早期的計算機系統遇到了許多不相容的字元編碼:ISO-8859-1,ShiftJIS,ASCII,EBCDIC等。編寫支援多種字元集的軟體太糟糕了,我們甚至沒有表情符号!今天,我們很幸運,世界各地都已在UTF-8上實作了标準化,而在遺留系統中很少使用其他字元集。

如果您需要另一個字元集,

readString()

并且

writeString()

在那裡就可以找到。這些方法要求您指定字元集。否則,您可能會意外地建立隻能由本地計算機讀取的資料。大多數程式應僅使用UTF-8方法。

編碼字元串時,您需要注意字元串的表示和編碼的方式不同。當一個字形有重音或其他裝飾時,它可以表示為一個單一的複雜代碼點(

é

),也可以表示為一個簡單的代碼點(

e

),後跟它的修飾符(

´

)。當整個字形是一個稱為NFC的代碼點時;當它是多個時,它是NFD。

盡管每當我們在I / O中讀寫字元串時都使用UTF-8,但是當它們在記憶體中時,Java字元串會使用過時的字元編碼UTF-16。這是一種錯誤的編碼,因為它對

char

大多數字元使用16位,但是有些字元不合适。特别是,大多數表情符号使用兩個Java字元。這是有問題的,因為

String.length()

傳回的結果令人驚訝:UTF-16字元數不是字形的自然數。

Café 🍩 Café 🍩
Form NFC NFD
Code Points

c a f é ␣ 🍩

c a f e ´ ␣ 🍩

UTF-8 bytes

43 61 66 c3a9 20 f09f8da9

43 61 66 65 cc81 20 f09f8da9

String.codePointCount 6 7
String.length 7 8
Utf8.size 10 11

在大多數情況下,Okio可以讓您忽略這些問題并專注于資料。但是,當您需要它們時,還有一些使用友善的API來處理低級UTF-8字元串。

使用

Utf8.size()

計數的位元組數需要将字元串編碼為UTF-8,而無需實際編碼它。這在諸如協定緩沖區之類的帶有長度字首的編碼中非常友善。

使用

BufferedSource.readUtf8CodePoint()

讀取單個可變長度代碼點,并且

BufferedSink.writeUtf8CodePoint()

代碼點寫一個。

Golden Values(Java / Kotlin)【黃金值】

Okio喜歡測試。該庫本身已經過嚴格的測試,并且具有測試應用程式代碼時通常有用的功能。我們發現非常有用的一種模式是“黃金價值”測試。此類測試的目的是确認目前程式可以安全地解碼使用程式的早期版本編碼的資料。

我們将通過使用Java序列化對值進行編碼來說明這一點。盡管我們必須否認Java序列化是一個糟糕的編碼系統,并且大多數程式應該更喜歡JSON或protobuf之類的其他格式!無論如何,這是一個擷取對象,對其進行序列化并将結果傳回為的方法

ByteString

Java

private ByteString serialize(Object o) throws IOException {
  Buffer buffer = new Buffer();
  try (ObjectOutputStream objectOut = new ObjectOutputStream(buffer.outputStream())) {
    objectOut.writeObject(o);
  }
  return buffer.readByteString();
}
           

Kotlin

@Throws(IOException::class)
private fun serialize(o: Any?): ByteString {
  val buffer = Buffer()
  ObjectOutputStream(buffer.outputStream()).use { objectOut ->
    objectOut.writeObject(o)
  }
  return buffer.readByteString()
}
           

這裡有很多事情。

  1. 我們建立一個緩沖區作為序列化資料的儲存空間。這是的便捷替代品

    ByteArrayOutputStream

  2. 我們向緩沖區請求其輸出流。寫入緩沖區或其輸出流總是将資料追加到緩沖區的末尾。
  3. 我們建立一個

    ObjectOutputStream

    (用于Java序列化的編碼API)并編寫我們的對象。try塊負責為我們關閉流。請注意,關閉緩沖區無效。
  4. 最後,我們從緩沖區讀取一個位元組字元串。該

    readByteString()

    方法允許我們指定要讀取的位元組數。在這裡,我們沒有指定計數來讀取整個内容。從緩沖區讀取資料總是消耗緩沖區前端的資料。

有了我們

serialize()

友善的方法,我們就可以計算和列印黃金值了。

Java

Point point = new Point(8.0, 15.0);
ByteString pointBytes = serialize(point);
System.out.println(pointBytes.base64());
           

Kotlin

val point = Point(8.0, 15.0)
val pointBytes = serialize(point)
println(pointBytes.base64())
           

我們以base64列印

ByteString

,因為它是一種緊湊的格式,适合嵌入在測試用例中。程式将輸出以下内容:

rO0ABXNyAB5va2lvLnNhbXBsZXMuR29sZGVuVmFsdWUkUG9pbnTdUW8rMji1IwIAAkQAAXhEAAF5eHBAIAAAAAAAAEAuAAAAAAAA
           

那就是我們的黃金價值!我們可以再次使用base64将其嵌入測試用例中,以将其轉換成一個

ByteString

Java

ByteString goldenBytes = ByteString.decodeBase64("rO0ABXNyAB5va2lvLnNhbXBsZ"
    + "XMuR29sZGVuVmFsdWUkUG9pbnTdUW8rMji1IwIAAkQAAXhEAAF5eHBAIAAAAAAAAEAuA"
    + "AAAAAAA");
           

Kotlin

val goldenBytes = ("rO0ABXNyACRva2lvLnNhbXBsZXMuS290bGluR29sZGVuVmFsdWUkUG9pbnRF9yaY7cJ9EwIAA" +
  "kQAAXhEAAF5eHBAIAAAAAAAAEAuAAAAAAAA").decodeBase64()
           

下一步是将

ByteString

反序列化到類中。此方法與上面的

serialize()

方法相反:我們将位元組字元串附加到緩沖區,然後使用

ObjectInputStream

使用它:

Java

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();
  }
}
           

Kotlin

@Throws(IOException::class, ClassNotFoundException::class)
private fun deserialize(byteString: ByteString): Any? {
  val buffer = Buffer()
  buffer.write(byteString)
  ObjectInputStream(buffer.inputStream()).use { objectIn ->
    return objectIn.readObject()
  }
}
           

現在我們可以針對黃金值測試解碼器:

Java

ByteString goldenBytes = ByteString.decodeBase64("rO0ABXNyAB5va2lvLnNhbXBsZ"
    + "XMuR29sZGVuVmFsdWUkUG9pbnTdUW8rMji1IwIAAkQAAXhEAAF5eHBAIAAAAAAAAEAuA"
    + "AAAAAAA");
Point decoded = (Point) deserialize(goldenBytes);
assertEquals(new Point(8.0, 15.0), decoded);
           

Kotlin

val goldenBytes = ("rO0ABXNyACRva2lvLnNhbXBsZXMuS290bGluR29sZGVuVmFsdWUkUG9pbnRF9yaY7cJ9EwIAA" +
  "kQAAXhEAAF5eHBAIAAAAAAAAEAuAAAAAAAA").decodeBase64()!!
val decoded = deserialize(goldenBytes) as Point
assertEquals(point, decoded)
           

通過這個測試,我們可以在不破壞相容性的情況下更改

Point

類的序列化。

Write a binary file(Java / Kotlin)【編寫一個二進制檔案】

編碼二進制檔案與編碼文本檔案沒有什麼不同。

Okio使用相同的

BufferedSink

BufferedSource

位元組。對于包含位元組和字元資料的二進制格式來說很友善。

編寫二進制資料比編寫文本危險得多,因為如果您犯了一個錯誤,則通常很難診斷。謹慎處理以下陷阱,避免發生此類錯誤:

  • **每個字段的寬度。**這是使用的位元組數。Okio不包含發出部分位元組的機制。如果需要,則需要在寫入之前進行自己的移位和屏蔽。
  • **每個字段的位元組序。**具有一個以上位元組的所有字段都具有位元組順序:位元組的順序是從最高有效到最低(大位元組序)或者從最低有效到最高(小位元組序)。Okio将

    Le

    字尾用于小尾數法。沒有字尾的方法是big-endian。
  • **有符号與無符号。**Java沒有無符号基元類型(

    char

    !除外),是以,處理這一問題通常發生在應用程式層。為了使操作更簡單,Okio接受

    writeByte()

    writeShort()

    int

    類型 。你可以傳遞一個像255這樣的“無符号”位元組,Okio會做正确的事。
方法 寬度 位元組序 編碼值
writeByte 1個 3

03

writeShort 2 3

00 03

writeInt 4 3

00 00 00 03

寫長 8 3

00 00 00 00 00 00 00 03

writeShortLe 2 3

03 00

writeIntLe 4 3

03 00 00 00

寫長樂 8 3

03 00 00 00 00 00 00 00

writeByte 1個 Byte.MAX_VALUE

7f

writeShort 2 空頭。MAX_VALUE

7f ff

writeInt 4 整數MAX_VALUE

7f ff ff ff

寫長 8 Long.MAX_VALUE

7f ff ff ff ff ff ff ff

writeShortLe 2 空頭。MAX_VALUE

ff 7f

writeIntLe 4 整數MAX_VALUE

ff ff ff 7f

寫長樂 8 Long.MAX_VALUE

ff ff ff ff ff ff ff 7f

該代碼按照BMP檔案格式對位圖進行編碼。

Java

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);
    }
  }
}
           

Kotin

@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)
    }
  }
}
           

這個程式最棘手的部分是格式所需的填充。BMP格式要求每一行從4位元組的邊界開始,是以需要添加零來保持對齊。

編碼其他二進制格式通常非常相似。一些技巧:

  • 用黃金值編寫測試!确認您的程式發出了預期的結果可以使調試更加容易。
  • 使用

    Utf8.size()

    計算編碼序列的位元組數。這對于長度字首的格式至關重要。
  • 使用

    Float.floatToIntBits()

    Double.doubleToLongBits()

    編碼浮點值。

Communicate on a Socket (Java/Kotlin)【在套接字上通信】

通過網絡發送和接收資料有點像讀寫檔案。我們使用

BufferedSink

對輸出進行編碼,使用

BufferedSource

對輸入進行解碼。與檔案一樣,網絡協定可以是文本、二進制或兩者的混合。但是網絡和檔案系統之間也有一些實質性的差別。

有了一個檔案,你可以讀或寫,但有了網絡,你可以兩者兼得!有些協定輪流處理這個問題:寫請求,讀響應,重複。你可以用一個線程來實作這種協定。在其他協定中,你可以同時讀寫。通常你需要一個專門的線程來閱讀。對于編寫,您可以使用專用線程或使用

synchronized

以便多個線程可以共享一個接收器。Okio的流對于并發使用是不安全的。

接收緩沖區出站資料以最小化I/O操作。這是有效的,但這意味着您必須手動調用

flush()

來傳輸資料。通常面向消息的協定在每條消息之後重新整理。請注意,當緩沖資料超過某個門檻值時,Okio将自動重新整理。這是為了節省記憶體,你不應該依賴它來進行互動協定。

Okio建立在

java.io.Socket

接口為了連通性。将套接字建立為伺服器或用戶端,然後使用

Okio.source(Socket)

讀取和

Okio.sink(Socket)

寫入。這些api也可以與

SSLSocket

一起工作。你應該使用SSL,除非你有很好的理由不使用!

取消一個Socket可通過任何線程調用

Socket.close()

;這将導緻其源和接收器立即失敗,并出現

IOException

。還可以為所有套接字操作配置逾時。您不需要對套接字的引用來調整逾時:

Source

and

Sink

直接暴露逾時。即使流被修飾,這個API也能工作。

作為與Okio聯網的完整示例,我們編寫了一個基本的SOCKS代理伺服器。一些重點:

Java

Socket fromSocket = ...
BufferedSource fromSource = Okio.buffer(Okio.source(fromSocket));
BufferedSink fromSink = Okio.buffer(Okio.sink(fromSocket));
           

Kotlin

val fromSocket: Socket = ...
val fromSource = fromSocket.source().buffer()
val fromSink = fromSocket.sink().buffer()
           

為套接字建立源和接收器與為檔案建立源和接收器相同。一旦為套接字建立了

Source

and

Sink

,就不能分别使用其

InputStream

OutputStream

Java

Buffer buffer = new Buffer();
for (long byteCount; (byteCount = source.read(buffer, 8192L)) != -1; ) {
  sink.write(buffer, byteCount);
  sink.flush();
}
           

Kotlin

val buffer = Buffer()
var byteCount: Long
while (source.read(buffer, 8192L).also { byteCount = it } != -1L) {
  sink.write(buffer, byteCount)
  sink.flush()
}
           

上面的循環将資料從源複制到接收器,每次讀取後都會重新整理。如果我們不需要重新整理,我們可以用一個調用來替換這個循環

BufferedSink.writeAll(Source)

read()

8192

參數是傳回之前要讀取的最大位元組數。我們可以在這裡傳遞任何值,但是我們喜歡8kib,因為這是Okio在單個系統調用中可以做的最大值。大多數時候應用程式代碼不需要處理這些限制!

Java

int addressType = fromSource.readByte() & 0xff;
int port = fromSource.readShort() & 0xffff;
           

Kotlin

val addressType = fromSource.readByte().toInt() and 0xff
val port = fromSource.readShort().toInt() and 0xffff
           

Okio使用帶符号的類型,例如

byte

short

,但是協定通常需要無符号的值。按位運算

&

符是Java首選的習慣用法,用于将有符号值轉換為無符号值。這是有關bytes, shorts, and ints的備忘單:

Type 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沒有可以表示無符号長型的原始類型。

Hashing (Java/Kotlin)【散列】

在Java程式員的生活中,我們受到雜湊演算法的轟炸。早先介紹了該

hashCode()

方法,我們知道需要重載一些東西,否則将發生無法預料的不良情況。後來我們顯示了

LinkedHashMap

它的朋友。這些基于該

hashCode()

方法來組織資料以便快速檢索。

在其他地方,我們具有加密哈希函數。這些到處都有使用。HTTPS證書,Git送出,BitTorrent完整性檢查和區塊鍊塊均使用加密哈希。良好地使用哈希可以提高應用程式的性能,隐私性,安全性和簡便性。

每個加密哈希函數都接受輸入位元組的可變長度流,并生成一個稱為“哈希”的固定長度位元組字元串值。哈希函數具有以下重要特征:

  • 确定性的:每個輸入總是産生相同的輸出。
  • 統一:每個輸出位元組字元串的可能性相等。很難找到或建立産生相同輸出的不同輸入對。這就是所謂的“碰撞”。
  • 不可逆的:知道輸出并不能幫助您找到輸入。請注意,如果您知道一些可能的輸入,則可以對它們進行散列以檢視其散列是否比對。
  • 衆所周知:哈希在任何地方都得到了實作,并且得到了嚴格的了解。

好的散列函數的計算成本非常低(幾十微秒),而反轉的成本也很高(千分之五)。計算和數學的穩步發展使得曾經偉大的散列函數變得更便宜。選擇哈希函數時,請注意并非所有函數都是相等的!Okio支援以下衆所周知的加密哈希函數:

  • MD5:128位(16位元組)加密哈希。它既不安全又過時,因為它的逆轉并不難!提供這個散列是因為它在不安全敏感的遺留系統中很流行,而且使用起來很友善。
  • SHA-1:160位(20位元組)加密哈希。最近的研究表明,制造SHA-1碰撞是可行的。考慮從SHA-1更新到SHA-256。
  • SHA-256:256位(32位元組)加密哈希。SHA-256被廣泛了解,而且逆轉成本高昂。這是大多數系統應該使用的哈希。
  • SHA-512:512位(64位元組)加密哈希。逆轉成本很高。

每個哈希都建立一個指定長度的

ByteString

。使用

hex()

獲得正常的人類可讀的形式。或将其保留為

ByteString

因為這是一種友善的模型類型!

Okio可以從位元組字元串生成加密哈希:

Java

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());
           

Kotlin

val byteString = readByteString(File("README.md"))
println("       md5: " + byteString.md5().hex())
println("      sha1: " + byteString.sha1().hex())
println("    sha256: " + byteString.sha256().hex())
println("    sha512: " + byteString.sha512().hex())
           

從緩沖區:

Java

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());
           

Kotlin

val buffer = readBuffer(File("README.md"))
println("       md5: " + buffer.md5().hex())
println("      sha1: " + buffer.sha1().hex())
println("    sha256: " + buffer.sha256().hex())
println("    sha512: " + buffer.sha512().hex())
           

從源進行流式處理時:

Java

try (HashingSink hashingSink = HashingSink.sha256(Okio.blackhole());
     BufferedSource source = Okio.buffer(Okio.source(file))) {
  source.readAll(hashingSink);
  System.out.println("sha256: " + hashingSink.hash().hex());
}
           

Kotlin

sha256(blackholeSink()).use { hashingSink ->
  file.source().buffer().use { source ->
    source.readAll(hashingSink)
    println("    sha256: " + hashingSink.hash.hex())
  }
}
           

流到接收器【sink】時:

Java

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());
}
           

Kotlin

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())
    }
  }
}
           

Okio還支援HMAC(哈希消息驗證碼),它結合了秘密和哈希。應用程式使用HMAC來實作資料完整性和身份驗證。

Java

ByteString secret = ByteString.decodeHex("7065616e7574627574746572");
System.out.println("hmacSha256: " + byteString.hmacSha256(secret).hex());
           

Kotlin

val secret = "7065616e7574627574746572".decodeHex()
println("hmacSha256: " + byteString.hmacSha256(secret).hex())
           

與哈希一樣,可以從

ByteString

Buffer

HashingSource

HashingSink

生成HMAC。注意,Okio沒有為MD5實作HMAC。Okio使用Java的

java.security.MessageDigest

對于加密哈希和

javax.crypto.Mac

對于HMAC。

Encryption and Decryption 【加密與解密】

使用

Okio.cipherSink(Sink, Cipher)

或者

Okio.cipherSource(Source, Cipher)

使用分組密碼對流進行加密或解密。

調用者負責使用所選算法、密鑰和特定于算法的附加參數(如初始化向量)初始化加密或解密密碼。 以下示例顯示了AES加密的典型用法,其中

key

iv

參數都應為16個位元組長。

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);
  }
}

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中,這些加密和解密方法是

Cipher

的擴充:

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()
  }
}
           

Releases

release版本的修改日志

implementation("com.squareup.okio:okio:2.9.0")
           

快照版本也是可用的

repositories {
    maven {
        url = uri("https://oss.sonatype.org/content/repositories/snapshots/")
    }
}

dependencies {
   implementation("com.squareup.okio:okio:2.9.0")
}
           

R8 / ProGuard

If you are using R8 or ProGuard add the options from this file.

# Animal Sniffer compileOnly dependency to ensure APIs are compatible with older versions of Java.

-dontwarn org.codehaus.mojo.animal_sniffer.*

License【許可證】

Copyright 2013 Square, Inc. Licensed under the Apache License, Version 2.0 (the “License”); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an “AS IS” BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.