Okio для Kotlin: полное руководство с примерами

Онлайн-тренажер Python для начинающих

Изучайте Python легко и без перегрузки теорией. Решайте практические задачи с автоматической проверкой, получайте подсказки на русском языке и пишите код прямо в браузере — без необходимости что-либо устанавливать.

Начать курс

Okio для Kotlin: Полное руководство по I/O библиотеке от Square

Okio — это современная библиотека ввода-вывода (I/O) для Kotlin и Java, разработанная компанией Square. Она создана как альтернатива стандартным потокам Java (java.io, java.nio) и предлагает более удобный, производительный и безопасный API для работы с данными. Okio решает ключевые проблемы стандартных I/O библиотек: громоздкий код, сложность работы с буферами, неэффективное копирование данных и отсутствие поддержки современных парадигм программирования.

Зачем нужна Okio? Во-первых, она предоставляет концепцию Source (источник) и Sink (приёмник) вместо InputStream/OutputStream. Это делает код более читаемым и предсказуемым. Во-вторых, библиотека использует собственные буферы (Buffer), которые работают быстрее стандартных за счёт оптимизации копирования и управления памятью. В-третьих, Okio идеально интегрируется с Kotlin: поддерживает корутины, extension-функции и null-safety. Библиотека является фундаментом для таких популярных проектов, как OkHttp (HTTP-клиент) и Retrofit (REST-клиент), что подтверждает её надёжность и производительность.

Okio особенно полезна при разработке сетевых приложений, работе с файловыми системами, сжатии данных, сериализации и любых сценариях, где требуется эффективная работа с потоками данных. Она позволяет писать меньше кода, избегать типичных ошибок (например, утечки ресурсов) и получать более высокую производительность по сравнению с традиционными подходами.

Установка

Okio распространяется через Maven Central. Для подключения к проекту на Kotlin (Gradle) добавьте зависимость в файл build.gradle.kts:

// build.gradle.kts
dependencies {
    implementation("com.squareup.okio:okio:3.9.0")
}

Если вы используете Maven, добавьте в pom.xml:

<dependency>
    <groupId>com.squareup.okio</groupId>
    <artifactId>okio</artifactId>
    <version>3.9.0</version>
</dependency>

Для платформы Android убедитесь, что минимальный SDK — 21 или выше. Okio версии 3.x требует Kotlin 1.6+ и Java 8+.

Быстрый старт

Минимальный рабочий пример — чтение данных из файла и запись в другой файл:

import okio.*
import java.io.File

fun main() {
    val sourceFile = File("input.txt")
    val sinkFile = File("output.txt")

    // Создаём Source и Sink
    val source: Source = sourceFile.source()
    val sink: Sink = sinkFile.sink()

    // Буфер для временного хранения данных
    val buffer = Buffer()

    // Читаем из source в буфер, затем пишем из буфера в sink
    source.readAll(buffer)
    sink.write(buffer, buffer.size)

    // Закрываем ресурсы
    source.close()
    sink.close()

    println("Данные скопированы из ${sourceFile.name} в ${sinkFile.name}")
}

Этот код копирует содержимое одного файла в другой. Обратите внимание: мы используем source() и sink() — extension-функции для File, которые возвращают Source и Sink соответственно. Буфер Buffer выступает как временное хранилище, минимизируя количество системных вызовов.

Основные методы, функции и классы

Ниже представлены ключевые компоненты Okio. Для каждого приведена сигнатура, описание и пример использования.

1. Buffer — класс для работы с буферизованными данными

Сигнатура: class Buffer : BufferedSource, BufferedSink

Описание: Основной класс для хранения и манипуляции данными в памяти. Реализует как BufferedSource, так и BufferedSink, что позволяет одновременно читать и писать. Буфер организован как сегментированная структура, что ускоряет операции копирования.

import okio.Buffer

fun main() {
    val buffer = Buffer()

    // Записываем строку в буфер
    buffer.writeUtf8("Hello, Okio!")
    println("Размер буфера: ${buffer.size}") // 12

    // Читаем строку из буфера
    val content = buffer.readUtf8()
    println("Содержимое: $content") // Hello, Okio!

    // Буфер пуст после чтения
    println("Буфер пуст: ${buffer.exhausted()}") // true
}

2. Source — интерфейс для чтения данных

Сигнатура: interface Source : Closeable

Описание: Базовый интерфейс для всех источников данных (файлы, сетевые потоки и т.д.). Содержит метод read(Buffer, Long) для чтения данных в буфер. В отличие от InputStream, Source не выбрасывает IOException на каждый чих, а использует более строгую систему исключений.

import okio.*
import java.io.File

fun main() {
    val file = File("example.txt")
    file.writeText("Kotlin & Okio")

    val source: Source = file.source()
    val buffer = Buffer()

    // Читаем до 1024 байт в буфер
    val bytesRead = source.read(buffer, 1024)
    println("Прочитано байт: $bytesRead") // 14

    val content = buffer.readUtf8()
    println("Содержимое: $content") // Kotlin & Okio

    source.close()
}

3. Sink — интерфейс для записи данных

Сигнатура: interface Sink : Closeable, Flushable

Описание: Базовый интерфейс для всех приёмников данных. Метод write(Buffer, Long) записывает данные из буфера. Sink гарантирует, что данные будут записаны атомарно (или будет выброшено исключение).

import okio.*
import java.io.File

fun main() {
    val file = File("output.txt")
    val sink: Sink = file.sink()
    val buffer = Buffer()

    buffer.writeUtf8("Запись через Okio!")
    sink.write(buffer, buffer.size)
    sink.close()

    println("Файл создан: ${file.readText()}") // Запись через Okio!
}

4. BufferedSource — буферизованный источник

Сигнатура: interface BufferedSource : Source

Описание: Расширяет Source, добавляя методы для чтения строк, чисел, байтовых массивов и других типов данных с использованием внутреннего буфера. Это основной интерфейс для удобного чтения данных.

import okio.*
import java.io.File

fun main() {
    val file = File("data.bin")
    file.writeBytes(byteArrayOf(0x48, 0x65, 0x6C, 0x6C, 0x6F)) // "Hello"

    val source: BufferedSource = file.source().buffer()
    
    // Читаем байт
    val byte = source.readByte()
    println("Первый байт: ${byte.toChar()}") // H

    // Читаем строку до конца
    val rest = source.readUtf8()
    println("Остаток: $rest") // ello

    source.close()
}

5. BufferedSink — буферизованный приёмник

Сигнатура: interface BufferedSink : Sink

Описание: Расширяет Sink, предоставляя удобные методы для записи строк, чисел, байтовых массивов и других типов. Буферизация уменьшает количество операций ввода-вывода.

import okio.*
import java.io.File

fun main() {
    val file = File("output.bin")
    val sink: BufferedSink = file.sink().buffer()

    sink.writeByte(0x48) // 'H'
    sink.writeUtf8("ello, Okio!")
    sink.writeInt(2024) // 4 байта
    sink.close()

    println("Размер файла: ${file.length()}") // 17 (1 + 12 + 4)
}

6. Okio.source() и Okio.sink() — фабричные методы

Сигнатура: fun InputStream.source(): Source
fun OutputStream.sink(): Sink

Описание: Extension-функции, преобразующие стандартные Java потоки в Source/Sink. Позволяют интегрировать Okio с существующим кодом.

import okio.*
import java.io.ByteArrayInputStream
import java.io.ByteArrayOutputStream

fun main() {
    val inputData = "Данные из InputStream".toByteArray()
    val inputStream = ByteArrayInputStream(inputData)
    val outputStream = ByteArrayOutputStream()

    // Преобразуем InputStream в Source
    val source: Source = inputStream.source()
    val sink: Sink = outputStream.sink()
    val buffer = Buffer()

    source.readAll(buffer)
    sink.write(buffer, buffer.size)

    source.close()
    sink.close()

    println("Результат: ${String(outputStream.toByteArray())}") // Данные из InputStream
}

7. Buffer.writeUtf8() и Buffer.readUtf8() — работа со строками

Сигнатура: fun writeUtf8(string: String): BufferedSink
fun readUtf8(): String

Описание: Методы для записи и чтения строк в кодировке UTF-8. Okio автоматически обрабатывает кодировку, что исключает ошибки, связанные с разными кодировками.

import okio.Buffer

fun main() {
    val buffer = Buffer()

    buffer.writeUtf8("Привет, мир!")
    buffer.writeUtf8(" Hello, World!")

    // Читаем всю строку
    val fullString = buffer.readUtf8()
    println(fullString) // Привет, мир! Hello, World!

    // Альтернатива: читаем до определённого размера
    buffer.writeUtf8("1234567890")
    val part = buffer.readUtf8(5)
    println(part) // 12345
}

8. Timeout — управление таймаутами

Сигнатура: class Timeout
fun timeout(): Timeout (метод Source/Sink)

Описание: Позволяет устанавливать таймауты на операции чтения/записи. Okio использует собственный механизм таймаутов, который работает корректно даже с прерыванием потоков.

import okio.*
import java.io.File
import kotlin.concurrent.thread

fun main() {
    val file = File("slow.txt")
    file.writeText("Данные")

    val source: Source = file.source()
    // Устанавливаем таймаут 100 мс
    source.timeout().timeout(100, java.util.concurrent.TimeUnit.MILLISECONDS)

    val buffer = Buffer()
    try {
        source.readAll(buffer)
        println("Прочитано: ${buffer.readUtf8()}")
    } catch (e: Exception) {
        println("Таймаут или ошибка: ${e.message}")
    } finally {
        source.close()
    }
}

9. GzipSink и GzipSource — сжатие данных

Сигнатура: class GzipSink(sink: Sink) : Sink
class GzipSource(source: Source) : Source

Описание: Декораторы, добавляющие сжатие/распаковку GZIP к любому Sink/Source. Позволяют легко работать со сжатыми данными без ручного управления буферами.

import okio.*
import java.io.File

fun main() {
    val originalFile = File("data.txt")
    originalFile.writeText("Повторяющиеся данные. Повторяющиеся данные. ")

    // Сжатие
    val gzipFile = File("data.gz")
    val sink = GzipSink(gzipFile.sink())
    val buffer = Buffer()
    buffer.writeAll(originalFile.source())
    sink.write(buffer, buffer.size)
    sink.close()

    println("Исходный размер: ${originalFile.length()}, сжатый: ${gzipFile.length()}")

    // Распаковка
    val source = GzipSource(gzipFile.source())
    val decompressed = Buffer()
    source.readAll(decompressed)
    source.close()

    println("Распаковано: ${decompressed.readUtf8()}")
}

10. HashingSource и HashingSink — вычисление хешей

Сигнатура: class HashingSource(source: Source, hashFunction: HashFunction)
class HashingSink(sink: Sink, hashFunction: HashFunction)

Описание: Декораторы, которые вычисляют хеш (MD5, SHA-1, SHA-256) данных "на лету" во время чтения или записи. Полезно для проверки целостности данных.

import okio.*
import java.io.File

fun main() {
    val file = File("document.pdf")
    if (!file.exists()) {
        println("Файл не найден, создаём тестовый")
        file.writeText("Тестовый документ для хеширования")
    }

    // Читаем файл и вычисляем SHA-256
    val source = file.source()
    val hashingSource = HashingSource(source, HashFunction.SHA256)
    val buffer = Buffer()
    hashingSource.readAll(buffer)
    hashingSource.close()

    val hash = hashingSource.hash()
    println("SHA-256 хеш файла: $hash")
    // Пример: 5e884898da28047151d0e56f8dc6292773603d0d6aabbdd62a11ef721d1542d8
}

Реальный практический пример

Разработаем утилиту для резервного копирования с шифрованием и сжа

Рекомендаций