Java
Completed
-
Section 1: Getting Started
-
Section 2: Core Syntax
-
Section 3: Object-Oriented Java
-
Section 4: Collections Framework
-
Section 5: Exception Handling
-
Section 6: Generics
-
Section 7: Functional Java
-
Section 8: Concurrency
-
Section 9: I/O and NIO
-
Section 10: JVM Internals
-
Section 11: Modern Java Features
-
Section 12: Build Tools and Project Structure
-
Section 13: Testing
-
Section 14: Databases and Persistence
-
Section 15: Networking
-
Section 16: Design and Best Practices
-
Section 17: Reflection and Annotations
-
Section 18: Logging and Diagnostics
-
Section 19: Date, Time, and Internationalization
-
Section 20: Java Platform Module System
-
Section 21: Security in Java
-
Section 22: Advanced Collections and Data Structures
-
Section 23: More Concurrency Patterns
-
Section 24: Compression, Files, and System Integration
-
Section 25: GUI Programming
-
Section 26: Practical Projects
-
Section 27: Data Structures and Algorithms
-
Section 28: Interview and Algorithm Practice
-
Section 29: JSON and Data Interchange
-
Section 30: More Concurrency Utilities
-
Section 31: More Collections and Streams Practice
-
Section 32: More File and System Programming
-
Section 33: Standard Library Deep Dive
-
Section 34: More Practice and Drills
-
Section 35: More Testing and Quality
-
Section 36: More Design Patterns and Architecture
-
Section 37: Career and Ecosystem
-
Section 38: More OOP and Architecture Practice
-
Section 39: More Enterprise Concepts
-
Section 40: Advanced JavaFX
-
Section 41: More Interview Practice
206: Working with ZIP and GZIP Compression
One of the most common mistakes I see when developers first touch compression in Java is treating ZIP and GZIP as if they are the same thing. They aren't. In the simplest terms: GZIP is for compressing a single stream of data (think of a single 500MB log file), while ZIP is an archive format used to bundle multiple files together into one compressed package. If you try to use a GZIPOutputStream to "zip" a folder, you're going to have a bad time.
The Memory Trap: Why Byte Arrays Fail Us
When I first started writing archival tools, my instinct was to read the entire source file into a byte array, then pass that array into the compression stream. It feels intuitive: get the data, compress the data, write the data. But imagine you're building a system to compress daily server logs that can grow to several gigabytes. If you try to load a 4GB log file into a byte[], your JVM is going to throw an OutOfMemoryError before the first byte even gets compressed.
The naive approach looks like this: reading the whole file into memory, then wrapping a GZIPOutputStream around a file stream. The better way is to treat the data as a continuous flow. You want to pipe the input stream directly into the output stream using a small buffer. This keeps your memory footprint constant, regardless of whether the log file is ten kilobytes or ten terabytes.
// The professional way to GZIP a large file
try (InputStream is = Files.newInputStream(Paths.get("server.log"));
OutputStream os = Files.newOutputStream(Paths.get("server.log.gz"));
GZIPOutputStream gzipOS = new GZIPOutputStream(os)) {
byte[] buffer = new byte[8192];
int len;
while ((len = is.read(buffer)) > 0) {
gzipOS.write(buffer, 0, len);
}
}
By using a 8KB buffer, we're only ever holding a tiny slice of the file in RAM. I usually recommend 8KB or 16KB; going much larger rarely provides a noticeable performance boost but starts to eat into your heap.
The ZipEntry Dance: Orchestrating Multiple Files
Moving over to ZIP files, the complexity shifts. Since a ZIP is an archive, you can't just stream data into it blindly. You have to tell the ZipOutputStream exactly when a new file starts and what it's called using a ZipEntry. A common mistake I see is forgetting to call closeEntry() or, worse, trying to write data before creating the entry.
If you're bundling a set of configuration reports into a package, you might be tempted to manually manage the loop and the stream. However, the real trick to doing this cleanly is ensuring that each entry is isolated. If you forget to close an entry, you might end up with a corrupted archive where the files bleed into one another.
try (ZipOutputStream zos = new ZipOutputStream(new FileOutputStream("reports.zip"))) {
File[] filesToZip = // ... logic to get your report files
for (File file : filesToZip) {
// We must create a new entry for every single file in the archive
ZipEntry entry = new ZipEntry(file.getName());
zos.putNextEntry(entry);
Files.copy(file.toPath(), zos);
zos.closeEntry(); // Don't skip this, or the archive may be invalid
}
}
I used Files.copy(Path, OutputStream) here because it's a concise way to handle the buffering for you. It's cleaner than writing your own while loop with a buffer, and under the hood, it's doing exactly what we did with the GZIP example.
Choosing Between the Two: Stream vs. Archive
So, when do you actually choose one over the other? If you're building a feature to allow users to download a "Project Export" containing 50 different JSON files, use ZIP. It preserves the file structure and the filenames. If you're building a backend process to compress old database dumps for long-term storage, use GZIP. GZIP is generally faster and more efficient for single-file compression because it doesn't have the overhead of maintaining a central directory of filenames (which is what a ZIP file is, essentially).
One last thing: always remember that compression is CPU-intensive. If you're compressing files in a high-throughput web request, you'll spike your CPU usage. In those cases, I usually move the compression logic to a background worker thread or an asynchronous queue so the user isn't staring at a loading spinner while your server struggles to crunch bytes.
📋 Practical Task
Build a Log Rotation and Compression Utility
Your task is to create a utility class called LogArchiver. This utility should be able to handle two different scenarios based on the input provided:
- Scenario A: If provided with a single file path, the utility should compress that file into a
.gzarchive usingGZIPOutputStream. - Scenario B: If provided with a directory path, the utility should find all files ending in
.logwithin that directory and bundle them into a single.ziparchive usingZipOutputStream.
Requirements:
- Ensure you use try-with-resources to prevent memory leaks.
- For the GZIP process, implement a manual buffer (e.g., 8KB) to demonstrate streaming rather than loading the whole file into memory.
- For the ZIP process, ensure every
ZipEntryis explicitly closed. - The utility should handle
IOExceptionsgracefully by logging the error rather than crashing the entire application.
There are no comments for now.