web-dev-qa-db-ja.com

.Netと互換性のあるGZIPOutputStreamを使用して文字列を圧縮および圧縮解除するにはどうすればよいですか?

AndroidでGZipを使用して文字列を圧縮する例が必要です。 「hello」などの文字列をメソッドに送信して、次の圧縮された文字列を取得します。

A

次に、解凍​​する必要があります。誰かが例を挙げて、次の方法を完了できますか?

private String compressString(String input) {
    //...
}

private String decompressString(String input) {
    //...
}

おかげで、


update

scessor's answer によると、次の4つの方法があります。 Android and .net compressおよびdecompressメソッド。これらのメソッドは、1つの場合を除いて互いに互換性があります。最初の3つの状態では互換性がありますが、4番目の状態では互換性がありません:

  • 状態1)Android.compress <-> Android.decompress:([〜#〜] ok [〜#〜]
  • 状態2)Net.compress <-> Net.decompress:([〜#〜] ok [〜#〜]
  • 状態3)Net.compress-> Android.decompress:([〜#〜] ok [〜#〜]
  • 状態4)Android.compress-> .Net.decompress:(NOT OK

誰でも解決できますか?

Androidのメソッド:

public static String compress(String str) throws IOException {

    byte[] blockcopy = ByteBuffer
            .allocate(4)
            .order(Java.nio.ByteOrder.LITTLE_ENDIAN)
            .putInt(str.length())
            .array();
    ByteArrayOutputStream os = new ByteArrayOutputStream(str.length());
    GZIPOutputStream gos = new GZIPOutputStream(os);
    gos.write(str.getBytes());
    gos.close();
    os.close();
    byte[] compressed = new byte[4 + os.toByteArray().length];
    System.arraycopy(blockcopy, 0, compressed, 0, 4);
    System.arraycopy(os.toByteArray(), 0, compressed, 4,
            os.toByteArray().length);
    return Base64.encode(compressed);

}

public static String decompress(String zipText) throws IOException {
    byte[] compressed = Base64.decode(zipText);
    if (compressed.length > 4)
    {
        GZIPInputStream gzipInputStream = new GZIPInputStream(
                new ByteArrayInputStream(compressed, 4,
                        compressed.length - 4));

        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        for (int value = 0; value != -1;) {
            value = gzipInputStream.read();
            if (value != -1) {
                baos.write(value);
            }
        }
        gzipInputStream.close();
        baos.close();
        String sReturn = new String(baos.toByteArray(), "UTF-8");
        return sReturn;
    }
    else
    {
        return "";
    }
}

。Netメソッド:

public static string compress(string text)
{
    byte[] buffer = Encoding.UTF8.GetBytes(text);
    MemoryStream ms = new MemoryStream();
    using (GZipStream Zip = new GZipStream(ms, CompressionMode.Compress, true))
    {
        Zip.Write(buffer, 0, buffer.Length);
    }

    ms.Position = 0;
    MemoryStream outStream = new MemoryStream();

    byte[] compressed = new byte[ms.Length];
    ms.Read(compressed, 0, compressed.Length);

    byte[] gzBuffer = new byte[compressed.Length + 4];
    System.Buffer.BlockCopy(compressed, 0, gzBuffer, 4, compressed.Length);
    System.Buffer.BlockCopy(BitConverter.GetBytes(buffer.Length), 0, gzBuffer, 0, 4);
    return Convert.ToBase64String(gzBuffer);
}

public static string decompress(string compressedText)
{
    byte[] gzBuffer = Convert.FromBase64String(compressedText);
    using (MemoryStream ms = new MemoryStream())
    {
        int msgLength = BitConverter.ToInt32(gzBuffer, 0);
        ms.Write(gzBuffer, 4, gzBuffer.Length - 4);

        byte[] buffer = new byte[msgLength];

        ms.Position = 0;
        using (GZipStream Zip = new GZipStream(ms, CompressionMode.Decompress))
        {
            Zip.Read(buffer, 0, buffer.Length);
        }

        return Encoding.UTF8.GetString(buffer);
    }
}
56
Bobs

GZIPメソッド:

public static byte[] compress(String string) throws IOException {
    ByteArrayOutputStream os = new ByteArrayOutputStream(string.length());
    GZIPOutputStream gos = new GZIPOutputStream(os);
    gos.write(string.getBytes());
    gos.close();
    byte[] compressed = os.toByteArray();
    os.close();
    return compressed;
}

public static String decompress(byte[] compressed) throws IOException {
    final int BUFFER_SIZE = 32;
    ByteArrayInputStream is = new ByteArrayInputStream(compressed);
    GZIPInputStream gis = new GZIPInputStream(is, BUFFER_SIZE);
    StringBuilder string = new StringBuilder();
    byte[] data = new byte[BUFFER_SIZE];
    int bytesRead;
    while ((bytesRead = gis.read(data)) != -1) {
        string.append(new String(data, 0, bytesRead));
    }
    gis.close();
    is.close();
    return string.toString();
}

そしてテスト:

final String text = "hello";
try {
    byte[] compressed = compress(text);
    for (byte character : compressed) {
        Log.d("test", String.valueOf(character));
    }
    String decompressed = decompress(compressed);
    Log.d("test", decompressed);
} catch (IOException e) {
    e.printStackTrace();
}

===更新===

.Net互換性が必要な場合、私のコードを少し変更する必要があります。

public static byte[] compress(String string) throws IOException {
    byte[] blockcopy = ByteBuffer
        .allocate(4)
        .order(Java.nio.ByteOrder.LITTLE_ENDIAN)
        .putInt(string.length())
        .array();
    ByteArrayOutputStream os = new ByteArrayOutputStream(string.length());
    GZIPOutputStream gos = new GZIPOutputStream(os);
    gos.write(string.getBytes());
    gos.close();
    os.close();
    byte[] compressed = new byte[4 + os.toByteArray().length];
    System.arraycopy(blockcopy, 0, compressed, 0, 4);
    System.arraycopy(os.toByteArray(), 0, compressed, 4, os.toByteArray().length);
    return compressed;
}

public static String decompress(byte[] compressed) throws IOException {
    final int BUFFER_SIZE = 32;
    ByteArrayInputStream is = new ByteArrayInputStream(compressed, 4, compressed.length - 4);
    GZIPInputStream gis = new GZIPInputStream(is, BUFFER_SIZE);
    StringBuilder string = new StringBuilder();
    byte[] data = new byte[BUFFER_SIZE];
    int bytesRead;
    while ((bytesRead = gis.read(data)) != -1) {
        string.append(new String(data, 0, bytesRead));
    }
    gis.close();
    is.close();
    return string.toString();
}

同じテストスクリプトを使用できます。

84
scessor

「Hello」をBQAAAB + LCに圧縮したことはどうであれ、gzipperの実装は特に貧弱です。 deflate形式の静的ブロックではなく動的ブロックを使用して、必要以上に「Hello」を拡張しました。 gzipストリーム(常に16進数1f 8bで始まる)から4バイトのプレフィックスを削除した後、「Hello」は123バイトに拡張されました。圧縮の世界では、それは犯罪と見なされます。

苦情を言っているCompressメソッドは、正しく正しく機能しています。静的ブロックと25バイトの合計出力を生成しています。 gzip形式には、10バイトのヘッダーと8バイトのトレーラオーバーヘッドがあり、5バイトの入力は7バイトでコーディングされています。それはもっと似ています。

圧縮可能でないストリームは拡張されますが、あまり圧縮されるべきではありません。 gzipで使用されるdeflate形式は、非圧縮データの場合、16K〜64Kごとに5バイトを追加します。

実際の圧縮を取得するには、一般的に、圧縮可能なデータで繰り返し文字列と偏りのある統計を見つけることができるように、その5バイトを処理するためにコンプレッサーをより多く与える必要があります。短い文字列でテストを行っているだけだと理解しています。しかし、実際のアプリケーションでは、このような短い文字列で汎用コンプレッサーを使用することは決してありません。文字列を送信するだけの方が常に良いからです。

14
Mark Adler

私のプロジェクトであなたのコードを試してみましたが、Androidのcompressメソッドでエンコードのバグが見つかりました。

byte[] blockcopy = ByteBuffer
        .allocate(4)
        .order(Java.nio.ByteOrder.LITTLE_ENDIAN)
        .putInt(str.length())
        .array();
ByteArrayOutputStream os = new ByteArrayOutputStream(str.length());
GZIPOutputStream gos = new GZIPOutputStream(os);
gos.write(str.getBytes());

上記のコードでは、uは修正されたエンコードを使用し、文字列の長さではなくバイトの長さを埋める必要があります。

byte[] data = str.getBytes("UTF-8");

byte[] blockcopy = ByteBuffer
        .allocate(4)
        .order(Java.nio.ByteOrder.LITTLE_ENDIAN)
        .putInt(data.length)
            .array();

ByteArrayOutputStream os = new ByteArrayOutputStream( data.length );    
GZIPOutputStream gos = new GZIPOutputStream(os);
gos.write( data );
4
Halowb

Decompress()メソッドでは、GZipInputStreamに渡す前に、Base64でデコードされた入力の最初の4バイトがスキップされます。この特定の場合、これらのバイトは_05 00 00 00_であることがわかります。そのため、Compress()メソッドでは、これらのバイトをBase64エンコードの直前に戻す必要があります。

これを行うと、Compress()は次を返します。

_BQAAAB+LCAAAAAAAAADLSM3JyQcAhqYQNgUAAAA=
_

私はこれがあなたの期待と正確に同じではないことを知っています:

_BQAAAB+LCAAAAAAABADtvQdgHEmWJSYvbcp7f0r1StfgdKEIgGATJNiQQBDswYjN5pLsHWlHIymrKoHKZVZlXWYWQMztnbz33nvvvffee++997o7nU4n99//P1xmZAFs9s5K2smeIYCqyB8/fnwfPyLmeVlW/w+GphA2BQAAAA==
_

しかし、結果がDecompress()にプラグインされた場合、_"Hello"_が得られると思います。それを試してみてください。違いは、元の文字列を取得した圧縮レベルが異なるためである可能性があります。

では、神秘的な接頭辞付きバイト_05 00 00 00_は何ですか? この答え によれば、圧縮された文字列の長さであるため、プログラムは圧縮解除されたバイトバッファーの長さを知ることができます。それでも、この場合は集計されません。

これはcompress()の修正されたコードです:

_public static String Compress(String text) throws IOException {
    ByteArrayOutputStream baos = new ByteArrayOutputStream();

    // TODO: Should be computed instead of being hard-coded
    baos.write(new byte[]{0x05, 0, 0, 0}, 0, 4);

    GZIPOutputStream gzos = new GZIPOutputStream(baos);
    gzos.write(text.getBytes());
    gzos.close();

    return Base64.encode(baos.toByteArray());
}
_

更新:

Androidの出力文字列と.NETコードが一致しない理由は、.NET GZip実装がより高速な圧縮を実行するためです(したがって、より大きな出力)。これは確実に確認できます。 Base64でデコードされた未加工のバイト値を見ることにより:

。NET:

 1F8B 0800 0000 0000 0400 EDBD 0760 1C49 
 9625 262F 6DCA 7B7F 4AF5 4AD7 E074 A108 
 8060 1324 D890 4010 ECC1 88CD E692 EC1D 
 6947 2329 AB2A 81CA 6556 655D 6616 40CC 
 ED9D BCF DE7B EFBD F7DE 7BEF BDF7 BA3B 
 9D4E 27F7 DFFF 3F5C 6664 016C F6CE 4ADA 
 C99E 2180 AAC8 1F3F 7E7C 1F3F 22E6 7959 
 56FF 0F86 A610 3605_0000.0000_0000.0000_0000.0000

My Android version:

 1F8B 0800 0000 0000 0000 CB48 CDC9 C907 
 0086 A610 3605 0000 00 

GZip File Format を確認すると、.NETとAndroidの両方のバージョンは、最初のヘッダーと後続のCRC32&Sizeフィールドでほとんど同一であることがわかります。以下のフィールドには違いのみがあります。

  • XFL = 04(コンプレッサーは最速のアルゴリズムを使用).NETの場合、Androidでは00
  • 実際の圧縮ブロック

したがって、XFLフィールドから、.NET圧縮アルゴリズムがより長い出力を生成することは明らかです。

実際、これらの生データ値を使用してバイナリファイルを作成し、gunzipを使用してそれらを解凍すると、.NETとAndroidバージョンはまったく同じ出力を与えました「hello」として。

そのため、異なる結果について気にする必要はありません。

4
Dheeraj V.S.

この問題に夢中になりました。最後に、私の場合(.Net 4)、. Net互換性のために、最初にこの4バイトを追加する必要はありませんでした。

単純に次のように機能します。

Android Compress:

public static byte[] compress(String string) throws IOException {
    ByteArrayOutputStream os = new ByteArrayOutputStream(string.length());
    GZIPOutputStream gos = new GZIPOutputStream(os);
    gos.write(string.getBytes());
    gos.close();
    byte[] compressed = os.toByteArray();
    os.close();
    return compressed;
}

。Net Decompress

public static byte[] DecompressViD(byte[] gzip)
    {
        // Create a GZIP stream with decompression mode.
        // ... Then create a buffer and write into while reading from the GZIP stream.
        using (GZipStream stream = new GZipStream(new MemoryStream(gzip), CompressionMode.Decompress))
        {
            const int size = 4096;
            byte[] buffer = new byte[size];
            using (MemoryStream memory = new MemoryStream())
            {
                int count = 0;
                do
                {
                    count = stream.Read(buffer, 0, size);
                    if (count > 0)
                    {
                        memory.Write(buffer, 0, count);
                    }
                }
                while (count > 0);
                return memory.ToArray();
            }
        }
    }
2
Ivan BASART

わかりました。既存の回答がたくさんあるときは、チャイムは嫌いですが、残念ながら、それらのほとんどはさまざまな理由で単純に間違っています。

  • .NET Framework内のGZIPアルゴリズムには違いがあります。 .NET 4.5を使用している場合、さまざまな回答に表示される苦情のほとんどは(2.0または3.5を使用している人には)当てはまりません。 「修正された」バージョンのコードを使用すると、実際には圧縮/解凍が台無しになります。
  • Javaは符号なしbyte []を使用し、.NETは符号付きbyte []を使用します。これは、そのbyte []を正確に転送する方法によっては、転送中に問題を引き起こす可能性があります。
  • さらに多くの問題を引き起こす可能性のあるbyte []を転送するためにBase64を使用しました。他にもさまざまな理由がありますが、さらに泣き言を飛ばしてコードを見てみましょう...

.NET Framework 4.5を使用している場合、ここに必要なC#クラスがあります(ボーナスとしてBase64):

public class CompressString
{
    private static void CopyTo(Stream src, Stream dest)
    {
        byte[] bytes = new byte[4096];
        int cnt;

        while ((cnt = src.Read(bytes, 0, bytes.Length)) != 0)
        {
            dest.Write(bytes, 0, cnt);
        }
    }

    public static byte[] Zip(string str)
    {
        var bytes = Encoding.UTF8.GetBytes(str);

        using (var msi = new MemoryStream(bytes))
        using (var mso = new MemoryStream())
        {
            using (var gs = new GZipStream(mso, CompressionMode.Compress))
            {
                //msi.CopyTo(gs);
                CopyTo(msi, gs);
            }

            return mso.ToArray();
        }
    }

    public static string Unzip(byte[] bytes)
    {
        using (var msi = new MemoryStream(bytes))
        using (var mso = new MemoryStream())
        {
            using (var gs = new GZipStream(msi, CompressionMode.Decompress))
            {
                //gs.CopyTo(mso);
                CopyTo(gs, mso);
            }

            return Encoding.UTF8.GetString(mso.ToArray());
        }
    }

    // Base64
    public static string ZipBase64(string compress)
    {
        var bytes = Zip(compress);
        var encoded = Convert.ToBase64String(bytes, Base64FormattingOptions.None);
        return encoded;
    }

    public static string UnzipBase64(string compressRequest)
    {
        var bytes = Convert.FromBase64String(compressRequest);
        var unziped = Unzip(bytes);
        return unziped;
    }

    // Testing
    public static bool TestZip(String stringToTest)
    {
        byte[] compressed = Zip(stringToTest);
        Debug.WriteLine("Compressed to " + compressed.Length + " bytes");
        String decompressed = Unzip(compressed);
        Debug.WriteLine("Decompressed to: " + decompressed);

        return stringToTest == decompressed;
    }
}

必要なAndroid/Javaクラスは次のとおりです。

public class CompressString {
    public static byte[] compress(String string) {
        try {
            ByteArrayOutputStream os = new ByteArrayOutputStream(string.length());
            GZIPOutputStream gos = new GZIPOutputStream(os);
            gos.write(string.getBytes());
            gos.close();
            byte[] compressed = os.toByteArray();
            os.close();
            return compressed;
        } catch (IOException ex) {
            return null;
        }
    }

    public static String decompress(byte[] compressed) {
        try {
            final int BUFFER_SIZE = 32;
            ByteArrayInputStream is = new ByteArrayInputStream(compressed);
            GZIPInputStream gis = new GZIPInputStream(is, BUFFER_SIZE);
            StringBuilder string = new StringBuilder();
            byte[] data = new byte[BUFFER_SIZE];
            int bytesRead;
            while ((bytesRead = gis.read(data)) != -1) {
                string.append(new String(data, 0, bytesRead));
            }
            gis.close();
            is.close();
            return string.toString();
        } catch (IOException ex) {
            return null;
        }
    }    

    // Base64
    public static String compressBase64(String strToCompress) {
        byte[] compressed = compress(strToCompress);
        String encoded = Android.util.Base64.encodeToString(compressed, Android.util.Base64.NO_WRAP);
        return encoded;
    }

    public static String decompressBase64(String strEncoded) {
        byte[] decoded = Android.util.Base64.decode(strEncoded, Android.util.Base64.NO_WRAP);
        String decompressed = decompress(decoded);
        return decompressed;
    }


    // test
    public static boolean testCompression(String stringToTest) {
        byte[] compressed = compress(stringToTest);
        Log.d("compress-test", "Compressed to " + compressed.length + " bytes");
        String decompressed = decompress(compressed);
        Log.d("compress-test", "Decompressed to " + decompressed);

        return stringToTest == decompressed;
    }
}

だから、依存関係のない、100%動作する圧縮Android/Java/C#/。NETクラス。 。NET 4.5で動作しない(「Hello world」から1000 Wordの短編まですべて試してみました)の文字列を見つけた場合はお知らせください。

1
nikib3ro

Androidのメソッドは解凍できません

Android Compress-> OK:

public static byte[] compress(String string) throws IOException {
    ByteArrayOutputStream os = new ByteArrayOutputStream(string.length());
    GZIPOutputStream gos = new GZIPOutputStream(os);
    gos.write(string.getBytes());
    gos.close();
    byte[] compressed = os.toByteArray();
    os.close();
    return compressed;
}

。Net Decompress-> OK:

public static byte[] DecompressViD(byte[] gzip)
{
    // Create a GZIP stream with decompression mode.
    // ... Then create a buffer and write into while reading from the GZIP stream.
    using (GZipStream stream = new GZipStream(new MemoryStream(gzip), CompressionMode.Decompress))
    {
        const int size = 4096;
        byte[] buffer = new byte[size];
        using (MemoryStream memory = new MemoryStream())
        {
            int count = 0;
            do
            {
                count = stream.Read(buffer, 0, size);
                if (count > 0)
                {
                    memory.Write(buffer, 0, count);
                }
            }
            while (count > 0);
            return memory.ToArray();
        }
    }
}

。Net Compress-> OK:

    public static string compress(string text)
    {
        byte[] buffer = Encoding.UTF8.GetBytes(text);
        MemoryStream ms = new MemoryStream();
        using (GZipStream Zip = new GZipStream(ms, CompressionMode.Compress, true))
        {
            Zip.Write(buffer, 0, buffer.Length);
        }

        ms.Position = 0;
        MemoryStream outStream = new MemoryStream();

        byte[] compressed = new byte[ms.Length];
        ms.Read(compressed, 0, compressed.Length);

        return Convert.ToBase64String(compressed);
    }

Android Decompress-> Not OK:

public static String decompress(String zipText) throws IOException {
    byte[] compressed = Base64.decode(zipText);

    GZIPInputStream os = new GZIPInputStream(new ByteArrayInputStream(compressed));

    GZIPInputStream gzipInputStream = new GZIPInputStream(os);

    ByteArrayOutputStream baos = new ByteArrayOutputStream();
    for (int value = 0; value != -1;) {
        value = gzipInputStream.read();
        if (value != -1) {
            baos.write(value);
        }
    }
    gzipInputStream.close();
    baos.close();

    return new String(baos.toByteArray(), "UTF-8");
}
0

以下に、簡単な例を示します。

public static void main(String[] args) throws IOException 
{
    byte[] buffer = new byte[4096];
    StringBuilder sb = new StringBuilder();

    //read file to compress

    String read = readFile( "spanish.xml", Charset.defaultCharset());

    if( read != null )
    {
        //compress file to output

        FileOutputStream fos = new FileOutputStream("spanish-new.xml");
        GZIPOutputStream gzos = new GZIPOutputStream(fos);
        gzos.write( read.getBytes());
        gzos.close();

        //uncompress and read back

        FileInputStream fis = new FileInputStream("spanish-new.xml");
        GZIPInputStream gzis = new GZIPInputStream(fis);

        int bytes = 0;

        while ((bytes = gzis.read(buffer)) != -1) {
            sb.append( new String( buffer ) );
        }
    }
}

static String readFile(String path, Charset encoding) throws IOException {
    byte[] encoded = Files.readAllBytes(Paths.get(path));
    return new String(encoded, encoding);
}
0
woahguy