web-dev-qa-db-fra.com

Comment puis-je compresser et décompresser une chaîne à l'aide de GZIPOutputStream compatible avec .Net?

J'ai besoin d'un exemple pour compresser une chaîne à l'aide de GZip sous Android. Je veux envoyer une chaîne comme "hello" à la méthode et obtenir la chaîne zippée suivante:

BQAAA + LQAQAAAQABAQABA

Ensuite, j'ai besoin de le décompresser. Quelqu'un peut-il me donner un exemple et compléter les méthodes suivantes?

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

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

Merci,


mettre à jour

Selon réponse de scessor , maintenant j'ai les 4 méthodes suivantes. Les méthodes de compression et de décompression Android et .net. Ces méthodes sont compatibles les unes avec les autres sauf dans un cas. Je veux dire qu'ils sont compatibles dans les 3 premiers états mais incompatibles dans le 4ème état:

  • état 1) Android.compress <-> Android.decompress: (OK)
  • état 2) Net.compress <-> Net.decompress: (OK)
  • state 3) Net.compress -> Android.decompress: (OK)
  • état 4) Android.compress -> .Net.decompress: (NON OK)

quelqu'un peut-il le résoudre?

Méthodes 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 "";
    }
}

Méthodes .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);
    }
}
54
Bobs

Les méthodes 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();
}

Et un test:

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();
}

=== Mise à jour ===

Si vous avez besoin de la compatibilité .Net, mon code doit être légèrement modifié:

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();
}

Vous pouvez utiliser le même script de test.

83
scessor

Quoi que ce soit que ce "Bonjour" compressé à BQAAAB + LC ... est une implémentation particulièrement médiocre d'un gzipper. Il a étendu "Hello", bien plus que nécessaire, en utilisant un bloc dynamique au lieu d’un bloc statique au format deflate. Après avoir supprimé le préfixe de quatre octets du flux gzip (qui commence toujours par hex 1f 8b), "Hello" a été étendu à 123 octets. Dans le monde de la compression, cela est considéré comme un crime.

La méthode Compress dont vous vous plaignez fonctionne correctement. Il génère un bloc statique et une sortie totale de 25 octets. Le format gzip a un en-tête de dix octets et une surcharge de huit octets, l’entrée de cinq octets ayant été codée sur sept octets. C'est plus comme ça.

Les flux non compressibles seront étendus, mais cela ne devrait pas être beaucoup. Le format de déflation utilisé par gzip ajoutera cinq octets à chaque 16 Ko à 64 Ko pour les données incompressibles.

Pour obtenir une compression réelle, vous devez en général donner au compresseur beaucoup plus de ressources pour travailler avec ces cinq octets, de sorte qu'il puisse trouver des chaînes répétées et des statistiques biaisées dans des données compressibles. Je comprends que vous venez de faire des tests avec une courte chaîne. Mais dans une application réelle, vous n'utiliseriez jamais de compresseur à usage général avec des chaînes aussi courtes, car il serait toujours préférable d'envoyer simplement la chaîne.

14
Mark Adler

J'ai essayé votre code dans mon projet et trouvé un bogue d'encodage dans la méthode de compression sur Android:

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());

sur le code ci-dessus, vous devez utiliser le codage corrigé et renseigner la longueur en octets et non la longueur de la chaîne:

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

Dans votre méthode Decompress(), les 4 premiers octets de l'entrée décodée en Base64 sont ignorés avant de passer à GZipInputStream. Ces octets se trouvent être 05 00 00 00 dans ce cas particulier. Ainsi, dans la méthode Compress(), ces octets doivent être replacés juste avant l'encodage Base64.

Si je le fais, Compress () renvoie ce qui suit:

BQAAAB+LCAAAAAAAAADLSM3JyQcAhqYQNgUAAAA=

Je sais que ce n'est pas exactement la même chose que ce que vous attendiez, à savoir:

BQAAAB+LCAAAAAAABADtvQdgHEmWJSYvbcp7f0r1StfgdKEIgGATJNiQQBDswYjN5pLsHWlHIymrKoHKZVZlXWYWQMztnbz33nvvvffee++997o7nU4n99//P1xmZAFs9s5K2smeIYCqyB8/fnwfPyLmeVlW/w+GphA2BQAAAA==

Mais, si mon résultat est rebranché à Decompress(), je pense que vous aurez toujours "Hello". Essayez-le La différence peut être due au niveau de compression différent avec lequel vous avez obtenu la chaîne d'origine.

Alors, quels sont les mystérieux octets préfixés 05 00 00 00? Selon cette réponse il peut s'agir de la longueur de la chaîne compressée pour que le programme sache combien de temps le tampon d'octet décompressé devrait être. Cela ne correspond toutefois pas dans ce cas.

Voici le code modifié pour 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());
}

Mettre à jour:

La raison pour laquelle les chaînes de sortie dans Android et votre code .NET ne correspondent pas est que l'implémentation .NET GZip effectue une compression plus rapide (et donc une sortie plus grande). Cela peut être vérifié avec certitude en consultant les valeurs brutes d'octets décodées en 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 
 .9D4E 27F7 DFFF 3F5C 6664 016C F6CE 4ADA 
 C99E 2180AAC8 1F3F 7E7C 1F3F 22E6 7959 
 56FF 0F86 A610 3605 0000 00 

Ma version Android:

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

Maintenant, si nous vérifions le format de fichier GZip , nous constatons que les versions .NET et Android sont pratiquement identiques dans l’en-tête initial et dans les champs CRC32 et Taille de fin. Les seules différences sont dans les champs ci-dessous:

  • XFL = 04 (algorithme le plus rapide utilisé par le compresseur) dans le cas de .NET, alors que c’est 00 dans Android
  • Les blocs compressés réels

Il est donc clair dans le champ XFL que l'algorithme de compression .NET produit une sortie plus longue.

En effet, lorsque je crée un fichier binaire avec ces valeurs de données brutes et que je les décompresse ensuite à l'aide de gunzip, les versions .NET et Android donnent exactement le même résultat que "hello".

Donc, vous n'avez pas à vous soucier des résultats différents.

4
Dheeraj V.S.

Je suis devenu fou avec ce problème. À la fin, dans mon cas (.Net 4), il n’était pas nécessaire d’ajouter 4 octets supplémentaires au début pour la compatibilité .Net. 

Cela fonctionne simplement comme ceci:

Compresse Android:

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

OK, je déteste intervenir quand il y a des tonnes de réponses existantes, mais malheureusement, la plupart d’entre elles sont tout simplement fausses pour diverses raisons:

  • Il existe des différences entre les algorithmes GZIP dans .NET Framework. Si vous utilisez .NET 4.5, la plupart des plaintes que vous voyez dans des réponses différentes ne vous concernent tout simplement pas (plutôt à ceux qui utilisent 2.0 ou 3.5). Si vous utilisez des versions "corrigées" du code, vous risquez de gâcher compression/décompression.
  • Java utilise des octets non signés [], .NET utilise des octets signés []. Cela peut causer des problèmes pendant le transport en fonction de la manière dont vous transportez cet octet [].
  • J'ai utilisé Base64 pour transporter byte [], ce qui peut poser encore plus de problèmes. Il existe diverses autres raisons, mais passons maintenant au cri du code ...

Si vous utilisez .NET Framework 4.5, voici la classe C # dont vous avez besoin (Base64 en bonus):

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;
    }
}

Et voici la classe Android/Java dont vous avez besoin:

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;
    }
}

Vous y êtes donc, libre de toute dépendance, classes de compression Android/Java/C #/.NET fonctionnant à 100%. Si vous trouvez que la chaîne utilisée est ne fonctionne pas avec .NET 4.5 _ (j'ai tout essayé, de «Hello world» à la nouvelle en 1 000 mots), faites-le moi savoir.

1
kape123

La méthode Android ne décompresse pas correctement

Compresse Android -> 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 -> Pas 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

Voici un exemple simple pour vous aider à démarrer.

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