与Handling HTTP ContentEncoding "deflate"相关,我想知道如何使用OutputStream
来扩充gzip
和deflate
个流。原因如下:
我有一个从Web服务器获取资源的类(想想wget
,但是在Java中)。我严格执行响应的内容长度,我想继续执行。所以,我想要做的是从响应中读取特定数量的字节(我已经在做了),但如果响应已被压缩,它会产生更多的字节。
我这样做的deflate
响应是这样的:
OutputStream out = System.out;
out = new InflateOutputStream(out);
// repeatedly:
out.write(compressedBytesFromResponse);
我希望能够使用gzip
响应做同样的事情,但是没有GunzipOutputStream,我不知道该怎么做,接下来。
我正在考虑建造类似this的东西,但它似乎完全是疯了。也许这是使用OutputStream
来扩充数据的唯一方法。
答案 0 :(得分:0)
对于deflate
,Java有InflaterOutputStream可以满足您的需求:提供压缩的缩减数据,并将未压缩的数据发送到其基础输出流。
对于gzip
...似乎无法找到等价物。 InflaterOutputStream
的伴侣InflaterInputStream
有一个GZipInputStream
子类来处理所有标头,但是没有等效的解压缩输出流类可能是InflaterOutputStream
的子类
为GZIP自己构建InflaterOutputStream
的子类看起来很毛茸茸,查看GZipInputStream
的来源(处理标题,预告片等)
使用管道流似乎是两个邪恶中较小的一个。
答案 1 :(得分:0)
回答我自己的问题:
这里有两种可能性:输出上的gunzip(例如,使用GunzipOutputStream
,不是由Java API提供的),或输入上的gunzip(例如,使用Java API提供的GZIPInputStream
)< em> plus 在读取过程中强制执行Content-Length。
我已经完成了两者,我认为我更喜欢后者,因为a)它不需要启动单独的线程来将PipedOutputStream
的字节泵送到PipedIOnputStream
和b)(一个必然结果) ,我猜)它没有种族条件和其他同步问题的威胁。
首先,这是我LimitedInputStream
的实现,它允许我包装输入流并对读取的数据量实施限制。请注意,我还有BigLimitedInputStream
使用BigInteger
计数来支持大于Long.MAX_LONG
的Content-Length值:
public class LimitedInputStream
extends InputStream
{
private long _limit;
private long _read;
private InputStream _in;
public LimitedInputStream(InputStream in, long limit)
{
_limit= limit;
_in = in;
_read = 0;
}
@Override
public int available()
throws IOException
{
return _in.available(); // sure?
}
@Override
public void close()
throws IOException
{
_in.close();
}
@Override
public boolean markSupported()
{
return false;
}
@Override
public int read()
throws IOException
{
int read = _in.read();
if(-1 == read)
return -1;
++_read;
if(_read > _limit)
return -1;
// throw new IOException("Read limit reached: " + _limit);
return read;
}
@Override
public int read(byte[] b)
throws IOException
{
return read(b, 0, b.length);
}
@Override
public int read(byte[] b, int off, int len)
throws IOException
{
// 'len' is an int, so 'max' is an int; narrowing cast is safe
int max = (int)Math.min((long)(_limit - _read), (long)len);
if(0 == max && len > 0)
return -1;
//throw new IOException("Read limit reached: " + _limit);
int read = _in.read(b, off, max);
_read += read;
// This should never happen
if(_read > _limit)
return -1;
//throw new IOException("Read limit reached: " + _limit);
return read;
}
@Override
public long skip(long n)
throws IOException
{
long max = Math.min((long)(_limit - _read), n);
if(0 == max)
return 0;
long read = _in.skip(max);
_read += read;
return read;
}
}
使用上面的类来包装从InputStream
获得的HttpURLConnection
允许我简化现有代码,我必须阅读Content-Length
标题中提到的精确字节数盲目地将输入复制到输出。然后我将输入流(已经包含在LimitedInputStream
中)包装在GZIPInputStream
中进行解压缩,然后将字节从(双重包装)输入泵送到输出。
不那么直截了当的路线是追求我的原始路线:使用(结果是)一个笨拙的类来包装OutputStream:GunzipOutputStream
。我写了一个GunzipOutputStream
,它使用内部线程通过一对管道流来抽取字节。这很难看,它基于OpenRDF's GunzipOutputStream
的代码。我认为我的方法有点简单:
public class GunzipOutputStream
extends OutputStream
{
final private Thread _pump;
// Streams
final private PipedOutputStream _zipped; // Compressed bytes are written here (by clients)
final private PipedInputStream _pipe; // Compressed bytes are read (internally) here
final private OutputStream _out; // Uncompressed data is written here (by the pump thread)
// Internal state
private IOException _e;
public GunzipOutputStream(OutputStream out)
throws IOException
{
_zipped = new PipedOutputStream();
_pipe = new PipedInputStream(_zipped);
_out = out;
_pump = new Thread(new Runnable() {
public void run() {
InputStream in = null;
try
{
in = new GZIPInputStream(_pipe);
pump(in, _out);
}
catch (IOException e)
{
_e = e;
System.err.println(e);
_e.printStackTrace();
}
finally
{
try { in.close(); } catch (IOException ioe)
{ ioe.printStackTrace(); }
}
}
private void pump(InputStream in, OutputStream out)
throws IOException
{
long count = 0;
byte[] buf = new byte[4096];
int read;
while ((read = in.read(buf)) >= 0) {
System.err.println("===> Pumping " + read + " bytes");
out.write(buf, 0, read);
count += read;
}
out.flush();
System.err.println("===> Pumped a total of " + count + " bytes");
}
}, "GunzipOutputStream stream pump " + GunzipOutputStream.this.hashCode());
_pump.start();
}
public void close() throws IOException {
throwIOException();
_zipped.close();
_pipe.close();
_out.close();
}
public void flush() throws IOException {
throwIOException();
_zipped.flush();
}
public void write(int b) throws IOException {
throwIOException();
_zipped.write(b);
}
public void write(byte[] b) throws IOException {
throwIOException();
_zipped.write(b);
}
public void write(byte[] b, int off, int len) throws IOException {
throwIOException();
_zipped.write(b, off, len);
}
public String toString() {
return _zipped.toString();
}
protected void finish()
throws IOException
{
try
{
_pump.join();
_pipe.close();
_zipped.close();
}
catch (InterruptedException ie)
{
// Ignore
}
}
private void throwIOException()
throws IOException
{
if(null != _e)
{
IOException e = _e;
_e = null; // Clear the existing error
throw e;
}
}
}
同样,这有效,但看起来相当......脆弱。
最后,我重新考虑了我的代码以使用LimitedInputStream
和GZIPInputStream
,并且没有使用GunzipOutputStream
。如果Java API提供了GunzipOutputStream
,那就太好了。但事实并非如此,如果没有编写“原生”的gunzip算法,实现自己的GunzipOutputStream
会延伸适当的限制。
答案 2 :(得分:-1)
如果您使用HttpURLConnection,所有这些都会自动发生。