InputStreamがgzip圧縮されているかどうかを確認する方法はありますか?コードは次のとおりです。
public static InputStream decompressStream(InputStream input) {
try {
GZIPInputStream gs = new GZIPInputStream(input);
return gs;
} catch (IOException e) {
logger.info("Input stream not in the GZIP format, using standard format");
return input;
}
}
私はこの方法を試しましたが、期待どおりに動作しません-ストリームから読み取られた値は無効です。編集:データを圧縮するために使用する方法を追加しました:
public static byte[] compress(byte[] content) {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
try {
GZIPOutputStream gs = new GZIPOutputStream(baos);
gs.write(content);
gs.close();
} catch (IOException e) {
logger.error("Fatal error occured while compressing data");
throw new RuntimeException(e);
}
double ratio = (1.0f * content.length / baos.size());
if (ratio > 1) {
logger.info("Compression ratio equals " + ratio);
return baos.toByteArray();
}
logger.info("Compression not needed");
return content;
}
それは絶対確実ではありませんが、おそらく最も簡単であり、外部データに依存しません。すべての適切な形式と同様に、GZipもマジックナンバーから始まります。マジックナンバーは、ストリーム全体を読み取らずにすばやく確認できます。
public static InputStream decompressStream(InputStream input) {
PushbackInputStream pb = new PushbackInputStream( input, 2 ); //we need a pushbackstream to look ahead
byte [] signature = new byte[2];
int len = pb.read( signature ); //read the signature
pb.unread( signature, 0, len ); //Push back the signature to the stream
if( signature[ 0 ] == (byte) 0x1f && signature[ 1 ] == (byte) 0x8b ) //check if matches standard gzip magic number
return new GZIPInputStream( pb );
else
return pb;
}
(マジックナンバーのソース: GZipファイル形式仕様 )
更新:GZipInputStream
にGZIP_MAGIC
という定数があり、この値が含まれていることを発見しました。 reallyしたい場合は、その下位2バイトを使用できます。
InputStreamはHttpURLConnection#getInputStream()から取得されます
その場合、HTTP Content-Encoding
応答ヘッダーはgzip
と等しくなります。
URLConnection connection = url.openConnection();
InputStream input = connection.getInputStream();
if ("gzip".equals(connection.getContentEncoding())) {
input = new GZIPInputStream(input);
}
// ...
これはすべて HTTP仕様 で明確に指定されています。
Update:ストリームのソースをどのように圧縮したかによって、この比率チェックはかなり...狂気です。それを取り除きます。同じ長さは、必ずしもバイトが同じであることを意味しません。 alwaysgzipされたストリームを返すようにして、alwaysgzipされたストリームを期待し、GZIPInputStream
を適用せずに厄介なチェック。
私はこれを見つけました 便利な例isCompressed()
のクリーンな実装を提供します:
/*
* Determines if a byte array is compressed. The Java.util.Zip GZip
* implementaiton does not expose the GZip header so it is difficult to determine
* if a string is compressed.
*
* @param bytes an array of bytes
* @return true if the array is compressed or false otherwise
* @throws Java.io.IOException if the byte array couldn't be read
*/
public boolean isCompressed(byte[] bytes) throws IOException
{
if ((bytes == null) || (bytes.length < 2))
{
return false;
}
else
{
return ((bytes[0] == (byte) (GZIPInputStream.GZIP_MAGIC)) && (bytes[1] == (byte) (GZIPInputStream.GZIP_MAGIC >> 8)));
}
}
私は成功してそれをテストしました:
@Test
public void testIsCompressed() {
assertFalse(util.isCompressed(originalBytes));
assertTrue(util.isCompressed(compressed));
}
これは、バイト配列がgzip形式であるかどうかを確認する最も簡単な方法であり、HTTPエンティティまたはMIMEタイプのサポートに依存しないと考えています
public static boolean isGzipStream(byte[] bytes) {
int head = ((int) bytes[0] & 0xff) | ((bytes[1] << 8) & 0xff00);
return (GZIPInputStream.GZIP_MAGIC == head);
}
@biziclopの回答に基づいて構築-このバージョンはGZIP_MAGICヘッダーを使用し、さらに空またはシングルバイトのデータストリームに対して安全です。
public static InputStream maybeDecompress(InputStream input) {
final PushbackInputStream pb = new PushbackInputStream(input, 2);
int header = pb.read();
if(header == -1) {
return pb;
}
int b = pb.read();
if(b == -1) {
pb.unread(header);
return pb;
}
pb.unread(new byte[]{(byte)header, (byte)b});
header = (b << 8) | header;
if(header == GZIPInputStream.GZIP_MAGIC) {
return new GZIPInputStream(pb);
} else {
return pb;
}
}
この関数はJavaで完璧に機能します:
public static boolean isGZipped(File f) {
val raf = new RandomAccessFile(file, "r")
return GZIPInputStream.GZIP_MAGIC == (raf.read() & 0xff | ((raf.read() << 8) & 0xff00))
}
scala:
def isGZip(file:File): Boolean = {
int gzip = 0
RandomAccessFile raf = new RandomAccessFile(f, "r")
gzip = raf.read() & 0xff | ((raf.read() << 8) & 0xff00)
raf.close()
return gzip == GZIPInputStream.GZIP_MAGIC
}
元のストリームをBufferedInputStreamにラップしてから、GZipInputStreamにラップします。次に、ZipEntryの抽出を試みます。これが機能する場合、Zipファイルです。その後、BufferedInputStreamで「マーク」と「リセット」を使用して、チェック後にストリームの初期位置に戻ることができます。
まさにあなたが求めているものではありませんが、HttpClientを使用している場合は代替アプローチになる可能性があります。
private static InputStream getInputStream(HttpEntity entity) throws IOException {
Header encoding = entity.getContentEncoding();
if (encoding != null) {
if (encoding.getValue().equals("gzip") || encoding.getValue().equals("Zip") || encoding.getValue().equals("application/x-gzip-compressed")) {
return new GZIPInputStream(entity.getContent());
}
}
return entity.getContent();
}
SimpleMagic はJavaコンテンツタイプを解決するためのライブラリ:
<!-- pom.xml -->
<dependency>
<groupId>com.j256.simplemagic</groupId>
<artifactId>simplemagic</artifactId>
<version>1.8</version>
</dependency>
import com.j256.simplemagic.ContentInfo;
import com.j256.simplemagic.ContentInfoUtil;
import com.j256.simplemagic.ContentType;
// ...
public class SimpleMagicSmokeTest {
private final static Logger log = LoggerFactory.getLogger(SimpleMagicSmokeTest.class);
@Test
public void smokeTestSimpleMagic() throws IOException {
ContentInfoUtil util = new ContentInfoUtil();
InputStream possibleGzipInputStream = getGzipInputStream();
ContentInfo info = util.findMatch(possibleGzipInputStream);
log.info( info.toString() );
assertEquals( ContentType.GZIP, info.getContentType() );
}
これは、gzipで圧縮できるファイルを読み取る方法です。
private void read(final File file)
throws IOException {
InputStream stream = null;
try (final InputStream inputStream = new FileInputStream(file);
final BufferedInputStream bInputStream = new BufferedInputStream(inputStream);) {
bInputStream.mark(1024);
try {
stream = new GZIPInputStream(bInputStream);
} catch (final ZipException e) {
// not gzipped OR not supported Zip format
bInputStream.reset();
stream = bInputStream;
}
// USE STREAM HERE
} finally {
if (stream != null) {
stream.close();
}
}
}