我的Java程序实现了一个服务器,它应该通过websockets从客户端获取一个非常大的文件,使用gzip压缩,并且应该检查文件内容中的某些字节模式。
客户端发送嵌入在专有协议中的文件块,因此我收到来自客户端的消息后的消息,解析消息并提取gzip压缩文件内容。
我无法将整个文件保存在程序存储器中,因此我尝试解压缩每个块,处理数据并继续下一个块。
我正在使用以下代码:
public static String gzipDecompress(byte[] compressed) throws IOException {
String uncompressed;
try (
ByteArrayInputStream bis = new ByteArrayInputStream(compressed);
GZIPInputStream gis = new GZIPInputStream(bis);
Reader reader = new InputStreamReader(gis);
Writer writer = new StringWriter()
) {
char[] buffer = new char[10240];
for (int length = 0; (length = reader.read(buffer)) > 0; ) {
writer.write(buffer, 0, length);
}
uncompressed = writer.toString();
}
return uncompressed;
}
但是在使用第一个压缩块调用函数时,我得到以下异常:
java.io.EOFException: Unexpected end of ZLIB input stream
at java.util.zip.InflaterInputStream.fill(InflaterInputStream.java:240)
at java.util.zip.InflaterInputStream.read(InflaterInputStream.java:158)
at java.util.zip.GZIPInputStream.read(GZIPInputStream.java:117)
at sun.nio.cs.StreamDecoder.readBytes(StreamDecoder.java:284)
at sun.nio.cs.StreamDecoder.implRead(StreamDecoder.java:326)
at sun.nio.cs.StreamDecoder.read(StreamDecoder.java:178)
at java.io.InputStreamReader.read(InputStreamReader.java:184)
at java.io.Reader.read(Reader.java:140)
重要的是要提到我没有跳过任何块并尝试按顺序解压缩块。
我错过了什么?
答案 0 :(得分:2)
问题是您手动使用这些块。
正确的方法是获取一些InputStream
,用GZIPInputStream
换行,然后读取数据。
InputStream is = // obtain the original gzip stream
GZIPInputStream gis = new GZIPInputStream(is);
Reader reader = new InputStreamReader(gis);
//... proceed reading and so on
GZIPInputStream
以流方式工作,因此如果您只从reader
一次询问10kb,则无论初始GZIP文件的大小如何,总内存占用量都会很低。
问题更新后更新
针对您的情况的一种可能的解决方案是编写一个InputStream
实现,该实现会流式传输由客户端协议处理程序以块的形式放入的字节。
这是一个原型:
public class ProtocolDataInputStream extends InputStream {
private BlockingQueue<byte[]> nextChunks = new ArrayBlockingQueue<byte[]>(100);
private byte[] currentChunk = null;
private int currentChunkOffset = 0;
private boolean noMoreChunks = false;
@Override
public synchronized int read() throws IOException {
boolean takeNextChunk = currentChunk == null || currentChunkOffset >= currentChunk.length;
if (takeNextChunk) {
if (noMoreChunks) {
// stream is exhausted
return -1;
} else {
currentChunk = nextChunks.take();
currentChunkOffset = 0;
}
}
return currentChunk[currentChunkOffset++];
}
@Override
public synchronized int available() throws IOException {
if (currentChunk == null) {
return 0;
} else {
return currentChunk.length - currentChunkOffset;
}
}
public synchronized void addChunk(byte[] chunk, boolean chunkIsLast) {
nextChunks.add(chunk);
if (chunkIsLast) {
noMoreChunks = true;
}
}
}
您的客户端协议处理程序使用addChunk()
添加字节块,而您的解压缩代码将数据从此流中提取出来(通过Reader
)。
请注意,此代码存在一些问题:
addChunk()
,则可能会填充队列,这将阻止addChunk()
。这可能是可取的或不是。read()
方法。为了提高性能,最好以相同的方式实现read(byte[])
。addChunk()
未在InterruptedException
上处理以避免过多细节。如果您的解压缩程序和take()
在同一个线程中执行(在同一个循环中),那么在使用addChunk()
或{{1}进行提取时,您可以尝试使用InputStream.available()
方法用InputStream
拉动时。
答案 1 :(得分:0)
来自gzip流的任意字节序列不是有效的独立gzip数据。无论如何,您必须连接所有字节块。
最简单的方法是使用简单的管道累计它们:
import java.io.PipedOutputStream;
import java.io.IOException;
import java.util.zip.GZIPInputStream;
public class ChunkInflater {
private final PipedOutputStream pipe;
private final InputStream stream;
public ChunkInflater()
throws IOException {
pipe = new PipedOutputStream();
stream = new GZIPInputStream(new PipedInputStream(pipe));
}
public InputStream getInputStream() {
return stream;
}
public void addChunk(byte[] compressedChunk)
throws IOException {
pipe.write(compressedChunk);
}
}
现在您有一个InputStream,您可以按照您想要的任何增量读取。例如:
ChunkInflater inflater = new ChunkInflater();
Callable<Void> chunkReader = new Callable<Void>() {
@Override
public Void call()
throws IOException {
byte[] chunk;
while ((chunk = readChunkFromSource()) != null) {
inflater.addChunk(chunk);
}
return null;
}
};
ExecutorService executor = Executors.newSingleThreadExecutor();
executor.submit(chunkReader);
executor.shutdown();
Reader reader = new InputStreamReader(inflater.getInputStream());
// read text here