steveloughran commented on code in PR #3555:
URL: https://github.com/apache/parquet-java/pull/3555#discussion_r3240973528
##########
parquet-hadoop/src/main/java/org/apache/parquet/hadoop/CodecFactory.java:
##########
@@ -367,4 +418,447 @@ public abstract void decompress(ByteBuffer input, int
compressedSize, ByteBuffer
public abstract void release();
}
+
+ // ---- Optimized Snappy compressor/decompressor using direct JNI calls ----
+
+ /**
+ * Compresses using Snappy's byte-array JNI API directly, bypassing the
Hadoop
+ * stream abstraction. This avoids intermediate direct ByteBuffer copies and
+ * reduces the compression to a single native call per page.
+ */
+ static class SnappyBytesCompressor extends BytesCompressor {
+ private byte[] outputBuffer;
+
+ @Override
+ public BytesInput compress(BytesInput bytes) throws IOException {
+ byte[] input = bytes.toByteArray();
+ int maxLen = Snappy.maxCompressedLength(input.length);
+ if (outputBuffer == null || outputBuffer.length < maxLen) {
+ outputBuffer = new byte[maxLen];
+ }
+ int compressed = Snappy.compress(input, 0, input.length, outputBuffer,
0);
+ return BytesInput.from(outputBuffer, 0, compressed);
+ }
+
+ @Override
+ public CompressionCodecName getCodecName() {
+ return CompressionCodecName.SNAPPY;
+ }
+
+ @Override
+ public void release() {
+ outputBuffer = null;
+ }
+ }
+
+ /**
+ * Decompresses using Snappy's byte-array JNI API directly.
+ */
+ static class SnappyBytesDecompressor extends BytesDecompressor {
+ @Override
+ public BytesInput decompress(BytesInput bytes, int decompressedSize)
throws IOException {
+ byte[] input = bytes.toByteArray();
+ byte[] output = new byte[decompressedSize];
+ Snappy.uncompress(input, 0, input.length, output, 0);
+ return BytesInput.from(output);
+ }
+
+ @Override
+ public void decompress(ByteBuffer input, int compressedSize, ByteBuffer
output, int decompressedSize)
+ throws IOException {
+ byte[] inputBytes = new byte[compressedSize];
+ input.get(inputBytes);
+ byte[] outputBytes = new byte[decompressedSize];
+ Snappy.uncompress(inputBytes, 0, compressedSize, outputBytes, 0);
+ output.put(outputBytes);
+ }
+
+ @Override
+ public void release() {}
+ }
+
+ // ---- Optimized ZSTD compressor/decompressor using zstd-jni streaming API
directly ----
+
+ /**
+ * Compresses using zstd-jni's {@link ZstdOutputStreamNoFinalizer} directly,
+ * bypassing the Hadoop codec framework ({@code ZstandardCodec}, {@code
CodecPool},
+ * {@code CompressionOutputStream} wrapper). Uses a configurable {@link
BufferPool}
+ * (defaulting to {@link RecyclingBufferPool}) for the internal 128KB output
buffer,
+ * matching the streaming API's natural buffer size. The buffer pool
strategy is
+ * controlled by the {@code
parquet.compression.codec.zstd.bufferPool.enabled} config.
+ * This avoids the overhead of Hadoop codec instantiation and compressor
pool management
+ * while using the same underlying ZSTD streaming path, which is
well-optimized for all
+ * input sizes including large pages (256KB+).
+ */
+ static class ZstdBytesCompressor extends BytesCompressor {
+ private final int level;
+ private final int workers;
+ private final BufferPool bufferPool;
+ private final ByteArrayOutputStream compressedOutBuffer;
+
+ ZstdBytesCompressor(int level, int workers, int pageSize, BufferPool
bufferPool) {
+ this.level = level;
+ this.workers = workers;
+ this.bufferPool = bufferPool;
+ this.compressedOutBuffer = new ByteArrayOutputStream(pageSize);
+ }
+
+ @Override
+ public BytesInput compress(BytesInput bytes) throws IOException {
+ compressedOutBuffer.reset();
+ try (ZstdOutputStreamNoFinalizer zos =
+ new ZstdOutputStreamNoFinalizer(compressedOutBuffer, bufferPool,
level)) {
+ if (workers > 0) {
+ zos.setWorkers(workers);
+ }
+ bytes.writeAllTo(zos);
+ }
+ return BytesInput.from(compressedOutBuffer);
+ }
+
+ @Override
+ public CompressionCodecName getCodecName() {
+ return CompressionCodecName.ZSTD;
+ }
+
+ @Override
+ public void release() {
+ // ByteArrayOutputStream does not hold native resources
+ }
+ }
+
+ /**
+ * Decompresses using zstd-jni's {@link ZstdInputStreamNoFinalizer} directly,
+ * bypassing the Hadoop codec framework. Uses a configurable {@link
BufferPool}
+ * for internal buffers, matching the streaming decompression path. The
buffer pool
+ * strategy is controlled by the {@code
parquet.compression.codec.zstd.bufferPool.enabled}
+ * config. Reads the full decompressed output in a single pass via
+ * {@link InputStream#readNBytes(int)}.
+ */
+ static class ZstdBytesDecompressor extends BytesDecompressor {
+ private final BufferPool bufferPool;
+
+ ZstdBytesDecompressor(BufferPool bufferPool) {
+ this.bufferPool = bufferPool;
+ }
+
+ @Override
+ public BytesInput decompress(BytesInput bytes, int decompressedSize)
throws IOException {
+ try (ZstdInputStreamNoFinalizer zis = new
ZstdInputStreamNoFinalizer(bytes.toInputStream(), bufferPool)) {
+ byte[] output = new byte[decompressedSize];
+ int offset = 0;
+ while (offset < decompressedSize) {
+ int read = zis.read(output, offset, decompressedSize - offset);
+ if (read < 0) {
+ throw new IOException(
Review Comment:
EOFException would be stricter here
##########
parquet-hadoop/src/main/java/org/apache/parquet/hadoop/CodecFactory.java:
##########
@@ -367,4 +418,447 @@ public abstract void decompress(ByteBuffer input, int
compressedSize, ByteBuffer
public abstract void release();
}
+
+ // ---- Optimized Snappy compressor/decompressor using direct JNI calls ----
+
+ /**
+ * Compresses using Snappy's byte-array JNI API directly, bypassing the
Hadoop
+ * stream abstraction. This avoids intermediate direct ByteBuffer copies and
+ * reduces the compression to a single native call per page.
+ */
+ static class SnappyBytesCompressor extends BytesCompressor {
+ private byte[] outputBuffer;
+
+ @Override
+ public BytesInput compress(BytesInput bytes) throws IOException {
+ byte[] input = bytes.toByteArray();
+ int maxLen = Snappy.maxCompressedLength(input.length);
+ if (outputBuffer == null || outputBuffer.length < maxLen) {
+ outputBuffer = new byte[maxLen];
+ }
+ int compressed = Snappy.compress(input, 0, input.length, outputBuffer,
0);
+ return BytesInput.from(outputBuffer, 0, compressed);
+ }
+
+ @Override
+ public CompressionCodecName getCodecName() {
+ return CompressionCodecName.SNAPPY;
+ }
+
+ @Override
+ public void release() {
+ outputBuffer = null;
+ }
+ }
+
+ /**
+ * Decompresses using Snappy's byte-array JNI API directly.
+ */
+ static class SnappyBytesDecompressor extends BytesDecompressor {
+ @Override
+ public BytesInput decompress(BytesInput bytes, int decompressedSize)
throws IOException {
+ byte[] input = bytes.toByteArray();
+ byte[] output = new byte[decompressedSize];
+ Snappy.uncompress(input, 0, input.length, output, 0);
+ return BytesInput.from(output);
+ }
+
+ @Override
+ public void decompress(ByteBuffer input, int compressedSize, ByteBuffer
output, int decompressedSize)
+ throws IOException {
+ byte[] inputBytes = new byte[compressedSize];
+ input.get(inputBytes);
+ byte[] outputBytes = new byte[decompressedSize];
+ Snappy.uncompress(inputBytes, 0, compressedSize, outputBytes, 0);
+ output.put(outputBytes);
+ }
+
+ @Override
+ public void release() {}
+ }
+
+ // ---- Optimized ZSTD compressor/decompressor using zstd-jni streaming API
directly ----
+
+ /**
+ * Compresses using zstd-jni's {@link ZstdOutputStreamNoFinalizer} directly,
+ * bypassing the Hadoop codec framework ({@code ZstandardCodec}, {@code
CodecPool},
+ * {@code CompressionOutputStream} wrapper). Uses a configurable {@link
BufferPool}
+ * (defaulting to {@link RecyclingBufferPool}) for the internal 128KB output
buffer,
+ * matching the streaming API's natural buffer size. The buffer pool
strategy is
+ * controlled by the {@code
parquet.compression.codec.zstd.bufferPool.enabled} config.
+ * This avoids the overhead of Hadoop codec instantiation and compressor
pool management
+ * while using the same underlying ZSTD streaming path, which is
well-optimized for all
+ * input sizes including large pages (256KB+).
+ */
+ static class ZstdBytesCompressor extends BytesCompressor {
+ private final int level;
+ private final int workers;
+ private final BufferPool bufferPool;
+ private final ByteArrayOutputStream compressedOutBuffer;
+
+ ZstdBytesCompressor(int level, int workers, int pageSize, BufferPool
bufferPool) {
+ this.level = level;
+ this.workers = workers;
+ this.bufferPool = bufferPool;
+ this.compressedOutBuffer = new ByteArrayOutputStream(pageSize);
+ }
+
+ @Override
+ public BytesInput compress(BytesInput bytes) throws IOException {
+ compressedOutBuffer.reset();
+ try (ZstdOutputStreamNoFinalizer zos =
+ new ZstdOutputStreamNoFinalizer(compressedOutBuffer, bufferPool,
level)) {
+ if (workers > 0) {
+ zos.setWorkers(workers);
+ }
+ bytes.writeAllTo(zos);
+ }
+ return BytesInput.from(compressedOutBuffer);
+ }
+
+ @Override
+ public CompressionCodecName getCodecName() {
+ return CompressionCodecName.ZSTD;
+ }
+
+ @Override
+ public void release() {
+ // ByteArrayOutputStream does not hold native resources
+ }
+ }
+
+ /**
+ * Decompresses using zstd-jni's {@link ZstdInputStreamNoFinalizer} directly,
+ * bypassing the Hadoop codec framework. Uses a configurable {@link
BufferPool}
+ * for internal buffers, matching the streaming decompression path. The
buffer pool
+ * strategy is controlled by the {@code
parquet.compression.codec.zstd.bufferPool.enabled}
+ * config. Reads the full decompressed output in a single pass via
+ * {@link InputStream#readNBytes(int)}.
+ */
+ static class ZstdBytesDecompressor extends BytesDecompressor {
+ private final BufferPool bufferPool;
+
+ ZstdBytesDecompressor(BufferPool bufferPool) {
+ this.bufferPool = bufferPool;
+ }
+
+ @Override
+ public BytesInput decompress(BytesInput bytes, int decompressedSize)
throws IOException {
+ try (ZstdInputStreamNoFinalizer zis = new
ZstdInputStreamNoFinalizer(bytes.toInputStream(), bufferPool)) {
+ byte[] output = new byte[decompressedSize];
+ int offset = 0;
+ while (offset < decompressedSize) {
+ int read = zis.read(output, offset, decompressedSize - offset);
+ if (read < 0) {
+ throw new IOException(
+ "Unexpected end of ZSTD stream at offset " + offset + " of " +
decompressedSize);
+ }
+ offset += read;
+ }
+ return BytesInput.from(output);
+ }
+ }
+
+ @Override
+ public void decompress(ByteBuffer input, int compressedSize, ByteBuffer
output, int decompressedSize)
+ throws IOException {
+ byte[] inputBytes = new byte[compressedSize];
+ input.get(inputBytes);
+ ByteArrayInputStream bais = new ByteArrayInputStream(inputBytes);
+ try (ZstdInputStreamNoFinalizer zis = new
ZstdInputStreamNoFinalizer(bais, bufferPool)) {
+ byte[] outputBytes = new byte[decompressedSize];
+ int offset = 0;
+ while (offset < decompressedSize) {
+ int read = zis.read(outputBytes, offset, decompressedSize - offset);
+ if (read < 0) {
+ throw new IOException(
+ "Unexpected end of ZSTD stream at offset " + offset + " of " +
decompressedSize);
+ }
+ offset += read;
+ }
+ output.put(outputBytes);
+ }
+ }
+
+ @Override
+ public void release() {
+ // No persistent resources - streams are closed per call
+ }
+ }
+
+ // ---- Optimized LZ4_RAW compressor/decompressor using airlift LZ4 directly
----
+
+ /**
+ * Compresses using airlift's LZ4 compressor directly with heap ByteBuffers,
+ * bypassing the Hadoop stream abstraction and NonBlockedCompressor's direct
+ * buffer copies.
+ */
+ static class Lz4RawBytesCompressor extends BytesCompressor {
+ private final Lz4Compressor compressor = new Lz4Compressor();
+ private byte[] outputBuffer;
+
+ @Override
+ public BytesInput compress(BytesInput bytes) throws IOException {
+ byte[] input = bytes.toByteArray();
+ int maxLen = compressor.maxCompressedLength(input.length);
+ if (outputBuffer == null || outputBuffer.length < maxLen) {
+ outputBuffer = new byte[maxLen];
+ }
+ ByteBuffer inputBuf = ByteBuffer.wrap(input);
+ ByteBuffer outputBuf = ByteBuffer.wrap(outputBuffer);
+ compressor.compress(inputBuf, outputBuf);
+ int compressedSize = outputBuf.position();
+ return BytesInput.from(outputBuffer, 0, compressedSize);
+ }
+
+ @Override
+ public CompressionCodecName getCodecName() {
+ return CompressionCodecName.LZ4_RAW;
+ }
+
+ @Override
+ public void release() {
+ outputBuffer = null;
+ }
+ }
+
+ /**
+ * Decompresses using airlift's LZ4 decompressor directly with heap
ByteBuffers.
+ */
+ static class Lz4RawBytesDecompressor extends BytesDecompressor {
+ private final Lz4Decompressor decompressor = new Lz4Decompressor();
+
+ @Override
+ public BytesInput decompress(BytesInput bytes, int decompressedSize)
throws IOException {
+ byte[] input = bytes.toByteArray();
+ byte[] output = new byte[decompressedSize];
+ ByteBuffer inputBuf = ByteBuffer.wrap(input);
+ ByteBuffer outputBuf = ByteBuffer.wrap(output);
+ decompressor.decompress(inputBuf, outputBuf);
+ return BytesInput.from(output);
+ }
+
+ @Override
+ public void decompress(ByteBuffer input, int compressedSize, ByteBuffer
output, int decompressedSize)
+ throws IOException {
+ byte[] inputBytes = new byte[compressedSize];
+ input.get(inputBytes);
+ byte[] outputBytes = new byte[decompressedSize];
+ ByteBuffer inputBuf = ByteBuffer.wrap(inputBytes);
+ ByteBuffer outputBuf = ByteBuffer.wrap(outputBytes);
+ decompressor.decompress(inputBuf, outputBuf);
+ output.put(outputBytes);
+ }
+
+ @Override
+ public void release() {}
+ }
+
+ // ---- Optimized GZIP compressor/decompressor using Deflater/Inflater
directly ----
+
+ /** GZIP magic number: 0x1f 0x8b. */
+ private static final int GZIP_MAGIC = 0x8b1f;
+
+ /** Minimal 10-byte GZIP header: magic, method=8 (deflate), flags=0,
mtime=0, xfl=0, os=0. */
+ private static final byte[] GZIP_HEADER = {
+ 0x1f,
+ (byte) 0x8b, // magic
+ 0x08, // method: deflate
+ 0x00, // flags: none
+ 0x00,
+ 0x00,
+ 0x00,
+ 0x00, // mtime: not set
+ 0x00, // extra flags
+ 0x00 // OS: FAT (matches Java's GZIPOutputStream default)
+ };
+
+ /**
+ * Compresses using {@link Deflater} directly with a reusable instance,
+ * bypassing Hadoop's GzipCodec and the stream overhead of
+ * {@link java.util.zip.GZIPOutputStream}. The Deflater is kept across
+ * calls and reset via {@link Deflater#reset()}, avoiding native zlib
+ * state allocation per page. Writes a minimal GZIP header and trailer
+ * (CRC32 + original size) manually.
+ *
+ * <p>Note: this implementation always uses Java's built-in {@link Deflater}
+ * (java.util.zip / JDK zlib). It does <em>not</em> use Hadoop native
libraries,
+ * so hardware-accelerated compression via Intel ISA-L will not be used even
if
+ * the native libraries are installed. The overhead reduction from bypassing
the
+ * Hadoop codec framework typically outweighs the ISA-L advantage for the
page
+ * sizes used by Parquet.
+ */
+ static class GzipBytesCompressor extends BytesCompressor {
Review Comment:
it'd be good to verify that the hadoop gzip can decompress this and vice
versa, as the regression test on this implementation
##########
parquet-hadoop/src/main/java/org/apache/parquet/hadoop/DirectCodecFactory.java:
##########
@@ -76,6 +85,46 @@ class DirectCodecFactory extends CodecFactory implements
AutoCloseable {
DIRECT_DECOMPRESSION_CODEC_CLASS = tempClass;
CREATE_DIRECT_DECOMPRESSOR_METHOD = tempCreateMethod;
DECOMPRESS_METHOD = tempDecompressMethod;
+
+ // Initialize Brotli JNI bypass via reflection
+ boolean brotliLoaded = false;
+ Method brotliDecompress = null;
+ Method brotliCompress = null;
+ Constructor<?> brotliDecompressorCtor = null;
+ Constructor<?> brotliCompressorCtor = null;
+ Object brotliParam = null;
+ try {
+ // Load native library
+ Class<?> loaderClass =
Class.forName("org.meteogroup.jbrotli.libloader.BrotliLibraryLoader");
+ loaderClass.getMethod("loadBrotli").invoke(null);
+
+ // BrotliDeCompressor: no-arg ctor + deCompress(ByteBuffer, ByteBuffer)
-> int
+ Class<?> decompClass =
Class.forName("org.meteogroup.jbrotli.BrotliDeCompressor");
+ brotliDecompressorCtor = decompClass.getConstructor();
+ brotliDecompress = decompClass.getMethod("deCompress", ByteBuffer.class,
ByteBuffer.class);
Review Comment:
I'd recommend using org.apache.parquet.util.DynMethods for this stuff
* versions of it in different asf projects (iceberg, hadoop...)
* uses context classloader, which may matter in some deployments
* easier!
##########
parquet-hadoop/src/main/java/org/apache/parquet/hadoop/CodecFactory.java:
##########
@@ -367,4 +418,447 @@ public abstract void decompress(ByteBuffer input, int
compressedSize, ByteBuffer
public abstract void release();
}
+
+ // ---- Optimized Snappy compressor/decompressor using direct JNI calls ----
+
+ /**
+ * Compresses using Snappy's byte-array JNI API directly, bypassing the
Hadoop
+ * stream abstraction. This avoids intermediate direct ByteBuffer copies and
+ * reduces the compression to a single native call per page.
+ */
+ static class SnappyBytesCompressor extends BytesCompressor {
+ private byte[] outputBuffer;
+
+ @Override
+ public BytesInput compress(BytesInput bytes) throws IOException {
+ byte[] input = bytes.toByteArray();
+ int maxLen = Snappy.maxCompressedLength(input.length);
+ if (outputBuffer == null || outputBuffer.length < maxLen) {
+ outputBuffer = new byte[maxLen];
+ }
+ int compressed = Snappy.compress(input, 0, input.length, outputBuffer,
0);
+ return BytesInput.from(outputBuffer, 0, compressed);
+ }
+
+ @Override
+ public CompressionCodecName getCodecName() {
+ return CompressionCodecName.SNAPPY;
+ }
+
+ @Override
+ public void release() {
+ outputBuffer = null;
+ }
+ }
+
+ /**
+ * Decompresses using Snappy's byte-array JNI API directly.
+ */
+ static class SnappyBytesDecompressor extends BytesDecompressor {
+ @Override
+ public BytesInput decompress(BytesInput bytes, int decompressedSize)
throws IOException {
+ byte[] input = bytes.toByteArray();
+ byte[] output = new byte[decompressedSize];
+ Snappy.uncompress(input, 0, input.length, output, 0);
+ return BytesInput.from(output);
+ }
+
+ @Override
+ public void decompress(ByteBuffer input, int compressedSize, ByteBuffer
output, int decompressedSize)
+ throws IOException {
+ byte[] inputBytes = new byte[compressedSize];
+ input.get(inputBytes);
+ byte[] outputBytes = new byte[decompressedSize];
+ Snappy.uncompress(inputBytes, 0, compressedSize, outputBytes, 0);
+ output.put(outputBytes);
+ }
+
+ @Override
+ public void release() {}
+ }
+
+ // ---- Optimized ZSTD compressor/decompressor using zstd-jni streaming API
directly ----
+
+ /**
+ * Compresses using zstd-jni's {@link ZstdOutputStreamNoFinalizer} directly,
+ * bypassing the Hadoop codec framework ({@code ZstandardCodec}, {@code
CodecPool},
+ * {@code CompressionOutputStream} wrapper). Uses a configurable {@link
BufferPool}
+ * (defaulting to {@link RecyclingBufferPool}) for the internal 128KB output
buffer,
+ * matching the streaming API's natural buffer size. The buffer pool
strategy is
+ * controlled by the {@code
parquet.compression.codec.zstd.bufferPool.enabled} config.
+ * This avoids the overhead of Hadoop codec instantiation and compressor
pool management
+ * while using the same underlying ZSTD streaming path, which is
well-optimized for all
+ * input sizes including large pages (256KB+).
+ */
+ static class ZstdBytesCompressor extends BytesCompressor {
+ private final int level;
+ private final int workers;
+ private final BufferPool bufferPool;
+ private final ByteArrayOutputStream compressedOutBuffer;
+
+ ZstdBytesCompressor(int level, int workers, int pageSize, BufferPool
bufferPool) {
+ this.level = level;
+ this.workers = workers;
+ this.bufferPool = bufferPool;
+ this.compressedOutBuffer = new ByteArrayOutputStream(pageSize);
+ }
+
+ @Override
+ public BytesInput compress(BytesInput bytes) throws IOException {
+ compressedOutBuffer.reset();
+ try (ZstdOutputStreamNoFinalizer zos =
+ new ZstdOutputStreamNoFinalizer(compressedOutBuffer, bufferPool,
level)) {
+ if (workers > 0) {
+ zos.setWorkers(workers);
+ }
+ bytes.writeAllTo(zos);
+ }
+ return BytesInput.from(compressedOutBuffer);
+ }
+
+ @Override
+ public CompressionCodecName getCodecName() {
+ return CompressionCodecName.ZSTD;
+ }
+
+ @Override
+ public void release() {
+ // ByteArrayOutputStream does not hold native resources
+ }
+ }
+
+ /**
+ * Decompresses using zstd-jni's {@link ZstdInputStreamNoFinalizer} directly,
+ * bypassing the Hadoop codec framework. Uses a configurable {@link
BufferPool}
+ * for internal buffers, matching the streaming decompression path. The
buffer pool
+ * strategy is controlled by the {@code
parquet.compression.codec.zstd.bufferPool.enabled}
+ * config. Reads the full decompressed output in a single pass via
+ * {@link InputStream#readNBytes(int)}.
+ */
+ static class ZstdBytesDecompressor extends BytesDecompressor {
+ private final BufferPool bufferPool;
+
+ ZstdBytesDecompressor(BufferPool bufferPool) {
+ this.bufferPool = bufferPool;
+ }
+
+ @Override
+ public BytesInput decompress(BytesInput bytes, int decompressedSize)
throws IOException {
+ try (ZstdInputStreamNoFinalizer zis = new
ZstdInputStreamNoFinalizer(bytes.toInputStream(), bufferPool)) {
+ byte[] output = new byte[decompressedSize];
+ int offset = 0;
+ while (offset < decompressedSize) {
+ int read = zis.read(output, offset, decompressedSize - offset);
+ if (read < 0) {
+ throw new IOException(
+ "Unexpected end of ZSTD stream at offset " + offset + " of " +
decompressedSize);
+ }
+ offset += read;
+ }
+ return BytesInput.from(output);
+ }
+ }
+
+ @Override
+ public void decompress(ByteBuffer input, int compressedSize, ByteBuffer
output, int decompressedSize)
+ throws IOException {
+ byte[] inputBytes = new byte[compressedSize];
+ input.get(inputBytes);
+ ByteArrayInputStream bais = new ByteArrayInputStream(inputBytes);
+ try (ZstdInputStreamNoFinalizer zis = new
ZstdInputStreamNoFinalizer(bais, bufferPool)) {
+ byte[] outputBytes = new byte[decompressedSize];
+ int offset = 0;
+ while (offset < decompressedSize) {
+ int read = zis.read(outputBytes, offset, decompressedSize - offset);
+ if (read < 0) {
+ throw new IOException(
Review Comment:
EOFException
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: [email protected]
For queries about this service, please contact Infrastructure at:
[email protected]
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]