A modern I/O library for Android, Java, and Kotlin Multiplatform
—
This document covers Okio's essential byte manipulation, buffered I/O, and streaming operations. These are the fundamental building blocks for all data processing in Okio.
ByteString represents an immutable sequence of bytes with built-in encoding, hashing, and comparison capabilities.
// From companion object
val empty = ByteString.EMPTY
val fromBytes = ByteString.of(0x48, 0x65, 0x6c, 0x6c, 0x6f) // "Hello"
// From String extensions
val fromString = "Hello, Okio!".encodeUtf8()
// From ByteArray extensions
val bytes = byteArrayOf(0x48, 0x65, 0x6c, 0x6c, 0x6f)
val fromByteArray = bytes.toByteString()
val fromByteArrayRange = bytes.toByteString(offset = 1, byteCount = 3)expect open class ByteString internal constructor(data: ByteArray) : Comparable<ByteString> {
// Properties
val size: Int
operator fun get(index: Int): Byte
// Text conversion
fun utf8(): String
// Encoding
fun base64(): String
fun base64Url(): String
fun hex(): String
// Hashing
fun md5(): ByteString
fun sha1(): ByteString
fun sha256(): ByteString
fun sha512(): ByteString
fun hmacSha1(key: ByteString): ByteString
fun hmacSha256(key: ByteString): ByteString
fun hmacSha512(key: ByteString): ByteString
// Case conversion
fun toAsciiLowercase(): ByteString
fun toAsciiUppercase(): ByteString
// Manipulation
fun substring(beginIndex: Int = 0, endIndex: Int = size): ByteString
fun toByteArray(): ByteArray
fun copyInto(offset: Int = 0, target: ByteArray, targetOffset: Int = 0, byteCount: Int = minOf(size - offset, target.size - targetOffset)): Int
// Comparison and search
fun rangeEquals(offset: Int, other: ByteString, otherOffset: Int = 0, byteCount: Int): Boolean
fun startsWith(prefix: ByteString): Boolean
fun endsWith(suffix: ByteString): Boolean
fun indexOf(other: ByteString, fromIndex: Int = 0): Int
fun lastIndexOf(other: ByteString, fromIndex: Int = size): Int
// Comparison
override fun compareTo(other: ByteString): Int
override fun equals(other: Any?): Boolean
override fun hashCode(): Int
override fun toString(): String
companion object {
val EMPTY: ByteString
fun of(vararg data: Byte): ByteString
fun read(inputStream: InputStream, byteCount: Int): ByteString
}
}// String to ByteString conversion
fun String.encodeUtf8(): ByteString
fun String.decodeBase64(): ByteString?
fun String.decodeHex(): ByteString
// ByteArray to ByteString conversion
fun ByteArray.toByteString(offset: Int = 0, byteCount: Int = size): ByteString// Creating and manipulating ByteStrings
val data = "Hello, Okio!".encodeUtf8()
println("Size: ${data.size}") // Size: 12
println("Base64: ${data.base64()}") // Base64: SGVsbG8sIE9raW8h
println("Hex: ${data.hex()}") // Hex: 48656c6c6f2c204f6b696f21
// Hashing
val hash = data.sha256()
println("SHA-256: ${hash.hex()}")
// Substring operations
val hello = data.substring(0, 5) // "Hello"
val okio = data.substring(7, 11) // "Okio"
// Search operations
val commaIndex = data.indexOf(",".encodeUtf8()) // 5
val startsWithHello = data.startsWith("Hello".encodeUtf8()) // trueBuffer is a mutable collection of bytes that implements both BufferedSource and BufferedSink, providing efficient byte array pooling and segment-based storage.
expect class Buffer() : BufferedSource, BufferedSink {
// Properties
var size: Long
override val buffer: Buffer // Returns self
// Basic operations
fun copyTo(out: Buffer, offset: Long = 0L, byteCount: Long): Buffer
fun copyTo(out: Buffer, offset: Long = 0L): Buffer // Overload for remaining bytes
fun completeSegmentByteCount(): Long
operator fun get(pos: Long): Byte
fun clear()
fun skip(byteCount: Long)
// Hashing (same as ByteString)
fun md5(): ByteString
fun sha1(): ByteString
fun sha256(): ByteString
fun sha512(): ByteString
fun hmacSha1(key: ByteString): ByteString
fun hmacSha256(key: ByteString): ByteString
fun hmacSha512(key: ByteString): ByteString
// Buffer management
fun copy(): Buffer
fun snapshot(): ByteString
fun snapshot(byteCount: Int): ByteString
// Advanced operations
fun readUnsafe(unsafeCursor: UnsafeCursor = UnsafeCursor()): UnsafeCursor
fun readAndWriteUnsafe(unsafeCursor: UnsafeCursor = UnsafeCursor()): UnsafeCursor
}class Buffer.UnsafeCursor {
var buffer: Buffer?
var readWrite: Boolean
var offset: Long
var data: ByteArray?
var start: Int
var end: Int
fun next(): Int
fun seek(offset: Long): Int
fun resizeBuffer(newSize: Long): Long
fun expandBuffer(minByteCount: Int): Long
}// Basic buffer operations
val buffer = Buffer()
buffer.writeUtf8("Hello")
buffer.writeUtf8(", ")
buffer.writeUtf8("Okio!")
println("Buffer size: ${buffer.size}") // 12
println("Content: ${buffer.readUtf8()}") // Hello, Okio!
// Copying data between buffers
val source = Buffer().writeUtf8("Source data")
val destination = Buffer()
source.copyTo(destination)
// Snapshot to immutable ByteString
val buffer2 = Buffer().writeUtf8("Test data")
val snapshot = buffer2.snapshot() // Creates ByteString without consuming bufferSource and Sink are the fundamental streaming interfaces in Okio.
interface Source : Closeable {
/**
* Removes at least 1, and up to byteCount bytes from this source and appends them to sink.
* Returns the number of bytes read, or -1 if this source is exhausted.
*/
fun read(sink: Buffer, byteCount: Long): Long
/**
* Returns the timeout for this source.
*/
fun timeout(): Timeout
/**
* Closes this source and releases the resources held by this source.
*/
override fun close()
}expect interface Sink : Closeable {
/**
* Removes exactly byteCount bytes from source and appends them to this sink.
*/
fun write(source: Buffer, byteCount: Long)
/**
* Pushes all buffered bytes to their final destination.
*/
fun flush()
/**
* Returns the timeout for this sink.
*/
fun timeout(): Timeout
/**
* Closes this sink and releases the resources held by this sink.
*/
override fun close()
}// Convert Source/Sink to buffered versions
fun Source.buffer(): BufferedSource
fun Sink.buffer(): BufferedSink
// Utility sinks
fun blackholeSink(): Sink
// Resource management
inline fun <T : Closeable?, R> T.use(block: (T) -> R): RBufferedSource provides an efficient interface for reading data with internal buffering.
expect sealed interface BufferedSource : Source {
// Buffer access
val buffer: Buffer
// State checking
fun exhausted(): Boolean
fun require(byteCount: Long) // Throws EOFException if not available
fun request(byteCount: Long): Boolean // Returns false if not available
// Byte reading
fun readByte(): Byte
fun readShort(): Short
fun readShortLe(): Short
fun readInt(): Int
fun readIntLe(): Int
fun readLong(): Long
fun readLongLe(): Long
// Number parsing
fun readDecimalLong(): Long
fun readHexadecimalUnsignedLong(): Long
// Skipping
fun skip(byteCount: Long)
// Bulk reading
fun readByteString(): ByteString
fun readByteString(byteCount: Long): ByteString
fun readByteArray(): ByteArray
fun readByteArray(byteCount: Long): ByteArray
// Array reading
fun read(sink: ByteArray): Int
fun read(sink: ByteArray, offset: Int, byteCount: Int): Int
fun readFully(sink: ByteArray)
fun readFully(sink: Buffer, byteCount: Long)
fun readAll(sink: Sink): Long
// String reading
fun readUtf8(): String
fun readUtf8(byteCount: Long): String
fun readUtf8Line(): String?
fun readUtf8LineStrict(): String
fun readUtf8LineStrict(limit: Long): String
fun readUtf8CodePoint(): Int
}expect sealed interface BufferedSource : Source {
// Selection
fun select(options: Options): Int
fun <T : Any> select(options: TypedOptions<T>): T?
// Search for bytes
fun indexOf(b: Byte): Long
fun indexOf(b: Byte, fromIndex: Long): Long
fun indexOf(b: Byte, fromIndex: Long, toIndex: Long): Long
// Search for byte strings
fun indexOf(bytes: ByteString): Long
fun indexOf(bytes: ByteString, fromIndex: Long): Long
fun indexOf(bytes: ByteString, fromIndex: Long, toIndex: Long): Long
// Search for any of multiple bytes
fun indexOfElement(targetBytes: ByteString): Long
fun indexOfElement(targetBytes: ByteString, fromIndex: Long): Long
// Range comparison
fun rangeEquals(offset: Long, bytes: ByteString): Boolean
fun rangeEquals(offset: Long, bytes: ByteString, bytesOffset: Int, byteCount: Int): Boolean
// Peek source (non-consuming reads)
fun peek(): BufferedSource
}// Reading different data types
val source = Buffer()
.writeByte(42)
.writeInt(1234)
.writeUtf8("Hello")
val byte = source.readByte() // 42
val int = source.readInt() // 1234
val text = source.readUtf8() // "Hello"
// Line-based reading
val lines = Buffer().writeUtf8("Line 1\nLine 2\nLine 3\n")
while (!lines.exhausted()) {
val line = lines.readUtf8Line()
println("Read: $line")
}
// Selection-based reading
val options = Options.of("GET".encodeUtf8(), "POST".encodeUtf8(), "PUT".encodeUtf8())
val request = Buffer().writeUtf8("POST /api/users")
val method = request.select(options) // Returns 1 (index of "POST")BufferedSink provides an efficient interface for writing data with internal buffering.
expect sealed interface BufferedSink : Sink {
// Buffer access
val buffer: Buffer
// ByteString and ByteArray writing
fun write(byteString: ByteString): BufferedSink
fun write(byteString: ByteString, offset: Int, byteCount: Int): BufferedSink
fun write(source: ByteArray): BufferedSink
fun write(source: ByteArray, offset: Int, byteCount: Int): BufferedSink
// Source writing
fun writeAll(source: Source): Long
fun write(source: Source, byteCount: Long): BufferedSink
// String writing
fun writeUtf8(string: String): BufferedSink
fun writeUtf8(string: String, beginIndex: Int, endIndex: Int): BufferedSink
fun writeUtf8CodePoint(codePoint: Int): BufferedSink
// Primitive writing
fun writeByte(b: Int): BufferedSink
fun writeShort(s: Int): BufferedSink
fun writeShortLe(s: Int): BufferedSink
fun writeInt(i: Int): BufferedSink
fun writeIntLe(i: Int): BufferedSink
fun writeLong(v: Long): BufferedSink
fun writeLongLe(v: Long): BufferedSink
// Number formatting
fun writeDecimalLong(v: Long): BufferedSink
fun writeHexadecimalUnsignedLong(v: Long): BufferedSink
// Buffer management
override fun flush()
fun emit(): BufferedSink
fun emitCompleteSegments(): BufferedSink
}// Basic writing operations
val sink = Buffer()
sink.writeUtf8("Hello, ")
.writeUtf8("Okio!")
.writeByte(10) // newline
.writeInt(42)
.flush()
// Writing different data types
val data = Buffer()
data.writeByte(0xFF)
.writeShort(1234)
.writeInt(0x12345678)
.writeLong(System.currentTimeMillis())
.writeUtf8("Timestamp")
// Chaining operations
val output = Buffer()
.writeUtf8("HTTP/1.1 200 OK\r\n")
.writeUtf8("Content-Type: application/json\r\n")
.writeUtf8("Content-Length: 13\r\n")
.writeUtf8("\r\n")
.writeUtf8("{\"ok\": true}")Okio uses internal segment pools to minimize memory allocations and garbage collection:
// Efficient data movement - segments are moved, not copied
val source = Buffer().writeUtf8("Large data chunk")
val destination = Buffer()
source.copyTo(destination) // Segments are transferred, not duplicated// Reading large files without loading everything into memory
val largeFile: Source = // ... source for large file
val processedData = Buffer()
largeFile.use { source ->
val buffered = source.buffer()
while (!buffered.exhausted()) {
val chunk = buffered.readByteString(8192) // Read 8KB chunks
val processed = processData(chunk)
processedData.write(processed)
}
}Common exceptions when working with core I/O operations:
expect open class IOException : Exception
expect class EOFException : IOException
expect class ArrayIndexOutOfBoundsException : ExceptionInstall with Tessl CLI
npx tessl i tessl/maven-com-squareup-okio--okio