59 votes

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

J'ai besoin d'un exemple pour compresser une chaîne de caractères en utilisant GZip dans Android. Je veux envoyer une chaîne comme "hello" à la méthode et obtenir la chaîne compressée suivante :

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

Alors je dois 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,


mise à jour

Selon réponse du sceptre J'ai maintenant les 4 méthodes suivantes. Les méthodes de compression et de décompression Android et .net. Ces méthodes sont compatibles entre elles sauf dans un cas. Je veux dire qu'elles 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 )
  • état 3) Net.compress -> Android.decompress : ( OK )
  • état 4) Android.compress -> .Net.decompress : ( PAS 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);
    }
}

0 votes

Résultat de compressString ne peut être String il doit être bytes[] . De même, l'entrée pour decompressString ne peut être String il doit être bytes[] aussi.

0 votes

Que voulez-vous dire ? inversement ' ? Compresser un GZIPOutputStream en utilisant un String ? ;-)

0 votes

Ma méthode .Net crée la grande chaîne mentionnée qui n'est pas la même que le résultat de la méthode de compression dans Android. J'ai mis à jour ma question et inséré les méthodes .Net Compress et Decompress. Quelqu'un peut-il modifier ces méthodes pour créer les mêmes chaînes compressées ?

96voto

scessor Points 11699

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é avec .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 test script.

3 votes

Mec, je te tire mon chapeau ! Très peu de personnes sur ce site ont une idée des tests unitaires et du pourquoi de leur réalisation ! +1 bien mérité !

0 votes

Merci pour votre réponse. J'ai testé votre solution mais je n'ai pas obtenu la chaîne : "BQAAAB+LCAAAAAAABADtvQdgHEmWJSYvbcp7f0r1StfgdKEIgGATJNiQBDswYjN5pLsHWlHIymrKoHKZVZlXWYWQMztnbz33nvvvffee++997o7nU4n99//P1xmZAFs9s5K2smeIYCqyB8/fnwfPyLmeVlW/w+GphA2BQAAAA==" Le résultat de votre méthode n'était pas le même. La chaîne mentionnée est générée par l'algorithme gzip dans .Net pour le mot "hello". J'ai besoin de générer cette chaîne. Comment puis-je le faire ?

0 votes

J'ai trouvé la méthode Decompress et elle fonctionne correctement. Mais la méthode Compress ne fonctionne pas correctement.

15voto

Mark Adler Points 15178

Ce qui a compressé "Hello" en BQAAAB+LC... est une implémentation particulièrement pauvre d'un gzipper. Il a étendu "Hello" beaucoup, beaucoup plus que nécessaire, en utilisant un bloc dynamique au lieu d'un bloc statique dans le 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, c'est considéré comme un crime.

La méthode Compress dont vous vous plaignez fonctionne correctement et convenablement. Elle génère un bloc statique et une sortie totale de 25 octets. Le format gzip comporte un en-tête de dix octets et une fin de ligne de huit octets, de sorte que l'entrée de cinq octets a été codée en sept octets. C'est mieux comme ça.

Les flux qui ne sont pas compressibles seront étendus, mais cela ne devrait pas être de beaucoup. Le format deflate utilisé par gzip ajoutera cinq octets à tous les 16K à 64K pour les données incompressibles.

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

4voto

Dheeraj V.S. Points 6384

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

Si je fais cela, Compress() renvoie le résultat suivant :

BQAAAB+LCAAAAAAAAADLSM3JyQcAhqYQNgUAAAA=

Je sais que ce n'est pas exactement la même chose que votre attente, qui est :

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

Mais, si mon résultat est réinjecté dans Decompress() je pense que tu auras quand même "Hello" . Essayez-le. La différence peut être due au niveau de compression différent avec lequel vous avez obtenu la chaîne originale.

Que sont donc ces 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 afin que le programme sache quelle doit être la longueur du tampon d'octets décompressé. Mais cela ne correspond pas à 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());
}

Mise à jour :

La raison pour laquelle les chaînes de sortie dans Android et votre code .NET ne correspondent pas est que l'implémentation GZip de .NET effectue une compression plus rapide (et donc une sortie plus grande). Cela peut être vérifié en regardant les valeurs brutes des octets décodés en Base64 :

.NET :

1F8B 0800 0000 0000 **04**00 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 BCF7 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 00

Ma version Android :

1F8B 0800 0000 0000 **00**00 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 en ce qui concerne l'en-tête initial et les champs CRC32 et Size qui suivent. Les seules différences se situent dans les champs ci-dessous :

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

Il est donc clair, d'après le champ XFL, que l'algorithme de compression .NET produit une sortie plus longue.

En fait, lorsque j'ai créé un fichier binaire avec ces valeurs de données brutes et que je les ai ensuite décompressées à l'aide de gunzip, les versions .NET et Android ont toutes deux donné exactement le même résultat comme "hello".

Vous n'avez donc pas à vous soucier de la différence de résultats.

1 votes

Ce n'est pas plus rapide de générer plus de rendement. C'est plus lent. Il faut le même temps pour trouver les chaînes de caractères correspondantes avant de prendre la décision statique ou dynamique. Il ne faut presque pas de temps pour prédire la taille de la sortie pour statique ou dynamique, puis prendre la bonne décision dans ce cas (statique). Il faut ensuite beaucoup moins de temps pour générer une sortie beaucoup plus courte pour la statique dans ce cas particulier. Le compromis ici n'était pas un compresseur plus rapide mais plutôt un temps de développement plus court (ou un développeur paresseux) pour ne pas écrire un déflateur complet.

0 votes

@MarkAdler Vous avez peut-être raison, mais ce que je voulais dire, c'est que les deux méthodes donnent des résultats différents parce que leurs schémas de compression sont différents. Le PO s'inquiétait de savoir pourquoi le résultat différait.

4voto

Halowb Points 17

J'ai essayé votre code dans mon projet, et j'ai trouvé un bug 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());

dans le code ci-dessus, vous devriez utiliser l'encodage corrigé, et remplir la longueur des octets, pas 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 );

2voto

Ivan BASART Points 92

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

Cela fonctionne simplement comme ceci :

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

Décompression .Net

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

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X