CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/maven-com-squareup-okio--okio

A modern I/O library for Android, Java, and Kotlin Multiplatform

Pending
Overview
Eval results
Files

compression.mddocs/

Compression and Encoding

This document covers Okio's data compression capabilities, including GZIP and deflate algorithms with native zlib integration, as well as ZIP file system access.

GZIP Compression

GZIP is a widely-used compression format that combines deflate compression with headers and checksums.

GzipSink

class GzipSink(sink: Sink) : Sink {
    val deflater: Deflater
    
    override fun write(source: Buffer, byteCount: Long)
    override fun flush()
    override fun timeout(): Timeout
    override fun close()
}

// Extension function for easy creation
fun Sink.gzip(): GzipSink

GzipSource

class GzipSource(source: Source) : Source {
    override fun read(sink: Buffer, byteCount: Long): Long
    override fun timeout(): Timeout
    override fun close()
}

// Extension function for easy creation  
fun Source.gzip(): GzipSource

GZIP Usage Examples

// Compressing data with GZIP
val originalData = "This is some text that will be compressed using GZIP compression algorithm."
val buffer = Buffer()

// Create GZIP compressed data
buffer.gzip().use { gzipSink ->
    gzipSink.writeUtf8(originalData)
}

val compressedData = buffer.readByteString()
println("Original size: ${originalData.length}")
println("Compressed size: ${compressedData.size}")
println("Compression ratio: ${compressedData.size.toFloat() / originalData.length}")

// Decompressing GZIP data
val decompressedBuffer = Buffer()
Buffer().write(compressedData).gzip().use { gzipSource ->
    decompressedBuffer.writeAll(gzipSource)
}

val decompressedText = decompressedBuffer.readUtf8()
println("Decompressed: '$decompressedText'")
println("Match: ${originalData == decompressedText}")

File Compression Example

val fs = FileSystem.SYSTEM
val sourceFile = "/tmp/large-file.txt".toPath()
val compressedFile = "/tmp/large-file.txt.gz".toPath()

// Create a large text file
fs.write(sourceFile) {
    repeat(1000) { i ->
        writeUtf8("Line $i: This is some repeated text to demonstrate compression.\n")
    }
}

// Compress file using GZIP
fs.write(compressedFile) {
    gzip().use { gzipSink ->
        fs.read(sourceFile) {
            writeAll(this)
        }
    }
}

// Compare file sizes
val originalSize = fs.metadata(sourceFile).size ?: 0
val compressedSize = fs.metadata(compressedFile).size ?: 0
println("Original: $originalSize bytes")
println("Compressed: $compressedSize bytes")
println("Saved: ${originalSize - compressedSize} bytes (${(1.0 - compressedSize.toDouble() / originalSize) * 100}%)")

// Decompress and verify
val decompressedContent = fs.read(compressedFile) {
    gzip().buffer().readUtf8()
}
val originalContent = fs.read(sourceFile) { readUtf8() }
println("Content matches: ${originalContent == decompressedContent}")

Deflate Compression

Deflate is the core compression algorithm used by GZIP and ZIP formats. Okio provides native deflate support using system zlib libraries.

Deflater (Native Implementation)

actual class Deflater {
    // Constructors
    constructor()                                    // Default compression
    constructor(level: Int, nowrap: Boolean)         // Custom level and format
    
    // Properties
    var flush: Int                                   // Flush mode constants
    
    // Methods
    fun getBytesRead(): Long                         // Total input bytes processed
    fun end()                                        // End deflation and release resources
    
    companion object {
        // Compression levels
        const val NO_COMPRESSION: Int = 0
        const val BEST_SPEED: Int = 1
        const val BEST_COMPRESSION: Int = 9
        const val DEFAULT_COMPRESSION: Int = -1
        
        // Flush modes
        const val NO_FLUSH: Int = 0
        const val SYNC_FLUSH: Int = 2
        const val FULL_FLUSH: Int = 3
        const val FINISH: Int = 4
    }
}

DeflaterSink

class DeflaterSink(sink: Sink, deflater: Deflater) : Sink {
    override fun write(source: Buffer, byteCount: Long)
    override fun flush()
    override fun timeout(): Timeout
    override fun close()
    
    fun finishDeflate()                              // Finish deflation without closing
}

Inflater and InflaterSource

actual class Inflater {
    constructor()
    constructor(nowrap: Boolean)
    
    fun getBytesRead(): Long
    fun getBytesWritten(): Long
    fun end()
}

class InflaterSource(source: Source, inflater: Inflater) : Source {
    override fun read(sink: Buffer, byteCount: Long): Long
    override fun timeout(): Timeout
    override fun close()
}

Deflate Usage Examples

// Custom deflate compression with different levels
val testData = "ABCDEFGHIJKLMNOPQRSTUVWXYZ".repeat(100)

fun compressWithLevel(data: String, level: Int): ByteString {
    val buffer = Buffer()
    val deflater = Deflater(level, nowrap = false)
    
    DeflaterSink(buffer, deflater).use { sink ->
        sink.writeUtf8(data)
    }
    
    return buffer.readByteString()
}

// Compare compression levels
val levels = listOf(
    Deflater.NO_COMPRESSION,
    Deflater.BEST_SPEED,  
    Deflater.DEFAULT_COMPRESSION,
    Deflater.BEST_COMPRESSION
)

levels.forEach { level ->
    val compressed = compressWithLevel(testData, level)
    val ratio = compressed.size.toFloat() / testData.length
    println("Level $level: ${compressed.size} bytes (${ratio * 100}%)")
}

// Manual deflate/inflate cycle
val deflater = Deflater(Deflater.DEFAULT_COMPRESSION, nowrap = true)
val inflater = Inflater(nowrap = true)

val originalBuffer = Buffer().writeUtf8("Data to compress")
val compressedBuffer = Buffer()
val decompressedBuffer = Buffer()

// Compress
DeflaterSink(compressedBuffer, deflater).use { sink ->
    sink.writeAll(originalBuffer)
}

// Decompress
InflaterSource(compressedBuffer, inflater).use { source ->
    decompressedBuffer.writeAll(source)
}

println("Original: ${originalBuffer.size} bytes")
println("Compressed: ${compressedBuffer.size} bytes") 
println("Decompressed: ${decompressedBuffer.readUtf8()}")

ZIP File System

Okio provides read-only access to ZIP files through a FileSystem implementation.

ZipFileSystem

class ZipFileSystem(
    zipPath: Path,
    fileSystem: FileSystem = FileSystem.SYSTEM,
    comment: String? = null
) : FileSystem {
    val comment: String?
    
    // Implements all FileSystem methods for ZIP file contents
    override fun canonicalize(path: Path): Path
    override fun metadataOrNull(path: Path): FileMetadata?
    override fun list(dir: Path): List<Path>
    override fun source(file: Path): Source
    
    // Note: ZIP FileSystem is read-only
    // write operations throw UnsupportedOperationException
}

ZIP File Usage Examples

val fs = FileSystem.SYSTEM
val zipPath = "/tmp/example.zip".toPath()

// Create a ZIP file (using standard Java ZIP APIs for writing)
java.util.zip.ZipOutputStream(fs.sink(zipPath).buffer().outputStream()).use { zipOut ->
    // Add first file
    zipOut.putNextEntry(java.util.zip.ZipEntry("hello.txt"))
    zipOut.write("Hello, ZIP world!".toByteArray())
    zipOut.closeEntry()
    
    // Add second file in subdirectory
    zipOut.putNextEntry(java.util.zip.ZipEntry("subdir/file2.txt"))
    zipOut.write("File in subdirectory".toByteArray())
    zipOut.closeEntry()
    
    // Add empty directory
    zipOut.putNextEntry(java.util.zip.ZipEntry("empty-dir/"))
    zipOut.closeEntry()
}

// Read ZIP file using Okio ZipFileSystem
val zipFs = ZipFileSystem(zipPath)

// List contents of ZIP file
println("ZIP file contents:")
zipFs.listRecursively("/".toPath()).forEach { path ->
    val metadata = zipFs.metadataOrNull(path)
    val type = when {
        metadata?.isDirectory == true -> "[DIR]"
        metadata?.isRegularFile == true -> "[FILE]"
        else -> "[OTHER]"
    }
    val size = metadata?.size?.let { " (${it} bytes)" } ?: ""
    println("$type $path$size")
}

// Read files from ZIP
val helloContent = zipFs.read("/hello.txt".toPath()) {
    readUtf8()
}
println("Content of hello.txt: '$helloContent'")

val subdirContent = zipFs.read("/subdir/file2.txt".toPath()) {
    readUtf8()
}
println("Content of subdir/file2.txt: '$subdirContent'")

// Check ZIP comment
println("ZIP comment: ${zipFs.comment}")

// Cleanup
fs.delete(zipPath)

ZIP File Metadata

val zipFs = ZipFileSystem(zipPath)

// Get detailed metadata for ZIP entries
zipFs.list("/".toPath()).forEach { path ->
    val metadata = zipFs.metadata(path)
    
    println("Path: $path")
    println("  Type: ${if (metadata.isDirectory) "Directory" else "File"}")
    println("  Size: ${metadata.size} bytes")
    
    metadata.lastModifiedAtMillis?.let { timestamp ->
        val date = java.util.Date(timestamp)
        println("  Modified: $date")
    }
    
    // ZIP-specific metadata in extras
    metadata.extras.forEach { (type, value) ->
        println("  Extra ${type.simpleName}: $value")
    }
}

Compression Utilities

Choosing Compression Methods

// Utility function to compare compression methods
fun compareCompressionMethods(data: String) {
    val originalSize = data.length
    
    // GZIP compression
    val gzipBuffer = Buffer()
    gzipBuffer.gzip().use { sink ->
        sink.writeUtf8(data)
    }
    val gzipSize = gzipBuffer.size
    
    // Raw deflate compression
    val deflateBuffer = Buffer()
    val deflater = Deflater(Deflater.DEFAULT_COMPRESSION, nowrap = true)
    DeflaterSink(deflateBuffer, deflater).use { sink ->
        sink.writeUtf8(data)
    }
    val deflateSize = deflateBuffer.size
    
    println("Original: $originalSize bytes")
    println("GZIP: $gzipSize bytes (${gzipSize.toFloat() / originalSize * 100}%)")
    println("Deflate: $deflateSize bytes (${deflateSize.toFloat() / originalSize * 100}%)")
    println("GZIP overhead: ${gzipSize - deflateSize} bytes")
}

// Test with different types of data
compareCompressionMethods("A".repeat(1000))                    // Highly repetitive
compareCompressionMethods("The quick brown fox jumps over the lazy dog. ".repeat(50))  // Natural text
compareCompressionMethods((0..255).map { it.toChar() }.joinToString(""))  // Random-like data

Streaming Compression

// Compress large amounts of data without loading everything into memory
fun compressLargeFile(inputPath: Path, outputPath: Path) {
    val fs = FileSystem.SYSTEM
    
    fs.sink(outputPath).buffer().gzip().use { compressedSink ->
        fs.source(inputPath).buffer().use { source ->
            // Process in chunks to avoid memory issues
            while (!source.exhausted()) {
                val chunk = source.readByteString(minOf(8192L, source.buffer.size))
                compressedSink.write(chunk)
            }
        }
    }
}

// Decompress with progress monitoring
fun decompressWithProgress(inputPath: Path, outputPath: Path) {
    val fs = FileSystem.SYSTEM
    val totalSize = fs.metadata(inputPath).size ?: 0L
    var processedBytes = 0L
    
    fs.sink(outputPath).buffer().use { output ->
        fs.source(inputPath).buffer().gzip().use { compressedSource ->
            val buffer = Buffer()
            
            while (!compressedSource.exhausted()) {
                val bytesRead = compressedSource.read(buffer, 8192L)
                if (bytesRead > 0) {
                    output.write(buffer, bytesRead)
                    processedBytes += bytesRead
                    val progress = (processedBytes.toFloat() / totalSize * 100).toInt()
                    print("\rDecompressing: $progress%")
                }
            }
        }
    }
    println("\nDecompression complete!")
}

Error Handling

Compression operations can encounter various error conditions:

expect open class IOException : Exception
expect class DataFormatException : Exception      // Invalid compressed data format
expect class ZipException : IOException           // ZIP file format issues

Common Error Scenarios

// Handle corrupted compressed data
fun safeDecompress(compressedData: ByteString): String? {
    return try {
        Buffer().write(compressedData).gzip().buffer().readUtf8()
    } catch (e: DataFormatException) {
        println("Corrupted compressed data: ${e.message}")
        null
    } catch (e: IOException) {
        println("I/O error during decompression: ${e.message}")
        null
    }
}

// Handle ZIP file errors
fun safeReadZip(zipPath: Path): List<String> {
    return try {
        val zipFs = ZipFileSystem(zipPath)
        zipFs.listRecursively("/".toPath())
            .filter { path -> zipFs.metadata(path).isRegularFile }
            .map { path -> path.toString() }
            .toList()
    } catch (e: ZipException) {
        println("Invalid ZIP file: ${e.message}")
        emptyList()
    } catch (e: FileNotFoundException) {
        println("ZIP file not found: ${e.message}")
        emptyList()
    }
}

// Resource cleanup with error handling
fun compressWithCleanup(data: String): ByteString? {
    val deflater = Deflater()
    val buffer = Buffer()
    
    return try {
        DeflaterSink(buffer, deflater).use { sink ->
            sink.writeUtf8(data)
        }
        buffer.readByteString()
    } catch (e: Exception) {
        println("Compression failed: ${e.message}")
        null
    } finally {
        // Ensure native resources are released
        deflater.end()
    }
}

Performance Considerations

Memory Usage

// Efficient streaming for large data
fun efficientCompression(inputSource: Source, outputSink: Sink) {
    // Use buffered streams to optimize I/O
    val bufferedOutput = outputSink.buffer()
    val gzipSink = bufferedOutput.gzip()
    
    inputSource.buffer().use { bufferedInput ->
        gzipSink.use { compressor ->
            // Process in reasonable chunks
            val buffer = Buffer()
            while (!bufferedInput.exhausted()) {
                val bytesRead = bufferedInput.read(buffer, 16384L) // 16KB chunks
                if (bytesRead > 0) {
                    compressor.write(buffer, bytesRead)
                }
            }
        }
    }
}

Compression Level Trade-offs

// Benchmark different compression levels
fun benchmarkCompression(data: ByteString) {
    val levels = listOf(
        Deflater.BEST_SPEED to "Best Speed",
        Deflater.DEFAULT_COMPRESSION to "Default", 
        Deflater.BEST_COMPRESSION to "Best Compression"
    )
    
    levels.forEach { (level, name) ->
        val startTime = System.currentTimeMillis()
        
        val buffer = Buffer()
        val deflater = Deflater(level, nowrap = false)
        DeflaterSink(buffer, deflater).use { sink ->
            sink.write(data)
        }
        
        val endTime = System.currentTimeMillis()
        val compressedSize = buffer.size
        val ratio = compressedSize.toFloat() / data.size
        
        println("$name: ${compressedSize} bytes (${ratio * 100}%) in ${endTime - startTime}ms")
        
        deflater.end()
    }
}

Install with Tessl CLI

npx tessl i tessl/maven-com-squareup-okio--okio

docs

compression.md

core-io.md

filesystem.md

hashing.md

index.md

utilities.md

tile.json