C#による圧縮/解凍文字列


144

私は.netの初心者です。C#で圧縮および圧縮解除の文字列を実行しています。XMLがあり、文字列に変換しています。その後、圧縮と解凍を行っています。コードを解凍して文字列を返し、XMLの半分しか返さない場合を除いて、コードにコンパイルエラーはありません。

以下は私のコードですが、間違っている箇所を修正してください。

コード:

class Program
{
    public static string Zip(string value)
    {
        //Transform string into byte[]  
        byte[] byteArray = new byte[value.Length];
        int indexBA = 0;
        foreach (char item in value.ToCharArray())
        {
            byteArray[indexBA++] = (byte)item;
        }

        //Prepare for compress
        System.IO.MemoryStream ms = new System.IO.MemoryStream();
        System.IO.Compression.GZipStream sw = new System.IO.Compression.GZipStream(ms, System.IO.Compression.CompressionMode.Compress);

        //Compress
        sw.Write(byteArray, 0, byteArray.Length);
        //Close, DO NOT FLUSH cause bytes will go missing...
        sw.Close();

        //Transform byte[] zip data to string
        byteArray = ms.ToArray();
        System.Text.StringBuilder sB = new System.Text.StringBuilder(byteArray.Length);
        foreach (byte item in byteArray)
        {
            sB.Append((char)item);
        }
        ms.Close();
        sw.Dispose();
        ms.Dispose();
        return sB.ToString();
    }

    public static string UnZip(string value)
    {
        //Transform string into byte[]
        byte[] byteArray = new byte[value.Length];
        int indexBA = 0;
        foreach (char item in value.ToCharArray())
        {
            byteArray[indexBA++] = (byte)item;
        }

        //Prepare for decompress
        System.IO.MemoryStream ms = new System.IO.MemoryStream(byteArray);
        System.IO.Compression.GZipStream sr = new System.IO.Compression.GZipStream(ms,
            System.IO.Compression.CompressionMode.Decompress);

        //Reset variable to collect uncompressed result
        byteArray = new byte[byteArray.Length];

        //Decompress
        int rByte = sr.Read(byteArray, 0, byteArray.Length);

        //Transform byte[] unzip data to string
        System.Text.StringBuilder sB = new System.Text.StringBuilder(rByte);
        //Read the number of bytes GZipStream red and do not a for each bytes in
        //resultByteArray;
        for (int i = 0; i < rByte; i++)
        {
            sB.Append((char)byteArray[i]);
        }
        sr.Close();
        ms.Close();
        sr.Dispose();
        ms.Dispose();
        return sB.ToString();
    }

    static void Main(string[] args)
    {
        XDocument doc = XDocument.Load(@"D:\RSP.xml");
        string val = doc.ToString(SaveOptions.DisableFormatting);
        val = Zip(val);
        val = UnZip(val);
    }
} 

私のXMLサイズは63KBです。


1
UTF8Encoding(またはUTF16など)とGetBytes / GetString を使用している場合、問題は「自分自身を修正する」と思います。また、コードを大幅に簡略化します。の使用もお勧めしusingます。

charをバイトに変換したり、その逆を(単純なキャストを使用して)行うことはできません。エンコーディング、および圧縮/解凍には同じエンコーディングを使用する必要があります。以下のxanatosの回答を参照してください。
Simon Mourier、2011

@pstいいえ、そうではありません。Encoding間違った方法で使用することになります。xanatosの回答に従って、ここにbase-64が必要です
マークグラベル

@Marc Gravell True、署名/インテントのその部分を逃しました。間違いなく私の最初の署名の選択ではありません。

回答:


257

文字列を圧縮/解凍するコード

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

static void Main(string[] args) {
    byte[] r1 = Zip("StringStringStringStringStringStringStringStringStringStringStringStringStringString");
    string r2 = Unzip(r1);
}

はをZip返し、byte[]はをUnzip返しますstring。文字列がZip必要な場合は、Base64でエンコードできます(たとえばを使用してConvert.ToBase64String(r1))(結果Zipは非常にバイナリです!画面に出力したり、XMLに直接書き込んだりすることはできません)

推奨されるバージョンは.NET 2.0用であり、.NET 4.0用にはを使用しMemoryStream.CopyToます。

重要:GZipStreamすべての入力があることをが認識するまで、圧縮されたコンテンツを出力ストリームに書き込むことはできません(つまり、効果的に圧縮するにはすべてのデータが必要です)。出力ストリーム(例:)を検査する前に、自分Dispose()のことを確認する必要があります。これは上のブロックで行われます。注意してくださいGZipStreammso.ToArray()using() { }GZipStreamは最も内側のブロックであり、コンテンツはその外側でアクセスされる。:同じことが解凍のために行くDispose()GZipStreamデータにアクセスしようとする前に。


返信ありがとうございます。コードを使用すると、コンパイルエラーが発生します。「CopyTo()には名前空間またはアセンブリ参照がありません。」その後、Googleで検索し、.NET 4 FrameworkのCopyTo()部分を見つけました。しかし、私は.net 2.0および3.5フレームワークに取り組んでいます。私を提案してください。:)
Mohit Kumar 2011

出力ストリームでToArray()を呼び出す前にGZipStreamを破棄する必要があることを強調したいだけです。私はそのビットを無視しました、しかしそれは違いを生みます!
ウェットヌードル2014年

1
.net 4.5でこれを圧縮する最も効果的な方法は何ですか?
MonsterMMORPG

1
これは、サロゲートペアを含む文字列の場合は失敗することに注意してください(unzipped-string!= original)string s = "X\uD800Y"。エンコーディングをUTF7に変更すれば機能することに気づきましたが、UTF7ではすべての文字を確実に表現できますか?
digEmAll 2015年

@digEmAll(あなたの場合のように)無効なサロゲートペアがある場合は機能しません。UTF8 GetByes変換は、無効なサロゲートペアを暗黙的に0xFFFDに置き換えます。
xanatos

103

このスニペットによると 私はこのコードを使用し、それはうまく機能しています

using System;
using System.IO;
using System.IO.Compression;
using System.Text;

namespace CompressString
{
    internal static class StringCompressor
    {
        /// <summary>
        /// Compresses the string.
        /// </summary>
        /// <param name="text">The text.</param>
        /// <returns></returns>
        public static string CompressString(string text)
        {
            byte[] buffer = Encoding.UTF8.GetBytes(text);
            var memoryStream = new MemoryStream();
            using (var gZipStream = new GZipStream(memoryStream, CompressionMode.Compress, true))
            {
                gZipStream.Write(buffer, 0, buffer.Length);
            }

            memoryStream.Position = 0;

            var compressedData = new byte[memoryStream.Length];
            memoryStream.Read(compressedData, 0, compressedData.Length);

            var gZipBuffer = new byte[compressedData.Length + 4];
            Buffer.BlockCopy(compressedData, 0, gZipBuffer, 4, compressedData.Length);
            Buffer.BlockCopy(BitConverter.GetBytes(buffer.Length), 0, gZipBuffer, 0, 4);
            return Convert.ToBase64String(gZipBuffer);
        }

        /// <summary>
        /// Decompresses the string.
        /// </summary>
        /// <param name="compressedText">The compressed text.</param>
        /// <returns></returns>
        public static string DecompressString(string compressedText)
        {
            byte[] gZipBuffer = Convert.FromBase64String(compressedText);
            using (var memoryStream = new MemoryStream())
            {
                int dataLength = BitConverter.ToInt32(gZipBuffer, 0);
                memoryStream.Write(gZipBuffer, 4, gZipBuffer.Length - 4);

                var buffer = new byte[dataLength];

                memoryStream.Position = 0;
                using (var gZipStream = new GZipStream(memoryStream, CompressionMode.Decompress))
                {
                    gZipStream.Read(buffer, 0, buffer.Length);
                }

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

2
このコードを投稿してくれてありがとう。私はそれを自分のプロジェクトにドロップしましたが、箱から出してすぐに問題なく動作しました。
BoltBait 2015

3
はい、そのままで動作します!最初の4バイトとして長さを追加するアイデアも気に入りました
JustADev

2
これが最良の答えです。これは答えとしてマークする必要があります!
エリアワクスマワルドホノ2017

1
.zipファイルを圧縮するような@マット-.pngは既に圧縮されたコンテンツです
fubo

2
回答としてマークされている回答は安定していません。これが一番の答えです。
サリ

38

Stream.CopyTo()メソッドを使用した.NET 4.0(およびそれ以降)の登場により、更新されたアプローチを投稿するつもりでした。

また、以下のバージョンは、通常の文字列をBase64エンコードされた文字列に、またはその逆に圧縮するための自己完結型クラスの明確な例として役立つと思います。

public static class StringCompression
{
    /// <summary>
    /// Compresses a string and returns a deflate compressed, Base64 encoded string.
    /// </summary>
    /// <param name="uncompressedString">String to compress</param>
    public static string Compress(string uncompressedString)
    {
        byte[] compressedBytes;

        using (var uncompressedStream = new MemoryStream(Encoding.UTF8.GetBytes(uncompressedString)))
        {
            using (var compressedStream = new MemoryStream())
            { 
                // setting the leaveOpen parameter to true to ensure that compressedStream will not be closed when compressorStream is disposed
                // this allows compressorStream to close and flush its buffers to compressedStream and guarantees that compressedStream.ToArray() can be called afterward
                // although MSDN documentation states that ToArray() can be called on a closed MemoryStream, I don't want to rely on that very odd behavior should it ever change
                using (var compressorStream = new DeflateStream(compressedStream, CompressionLevel.Fastest, true))
                {
                    uncompressedStream.CopyTo(compressorStream);
                }

                // call compressedStream.ToArray() after the enclosing DeflateStream has closed and flushed its buffer to compressedStream
                compressedBytes = compressedStream.ToArray();
            }
        }

        return Convert.ToBase64String(compressedBytes);
    }

    /// <summary>
    /// Decompresses a deflate compressed, Base64 encoded string and returns an uncompressed string.
    /// </summary>
    /// <param name="compressedString">String to decompress.</param>
    public static string Decompress(string compressedString)
    {
        byte[] decompressedBytes;

        var compressedStream = new MemoryStream(Convert.FromBase64String(compressedString));

        using (var decompressorStream = new DeflateStream(compressedStream, CompressionMode.Decompress))
        {
            using (var decompressedStream = new MemoryStream())
            {
                decompressorStream.CopyTo(decompressedStream);

                decompressedBytes = decompressedStream.ToArray();
            }
        }

        return Encoding.UTF8.GetString(decompressedBytes);
    }

次に、拡張メソッドを使用してStringクラスを拡張し、文字列の圧縮と解凍を追加する別の方法を示します。以下のクラスを既存のプロジェクトにドロップして、次のように使用できます。

var uncompressedString = "Hello World!";
var compressedString = uncompressedString.Compress();

そして

var decompressedString = compressedString.Decompress();

ウィットするには:

public static class Extensions
{
    /// <summary>
    /// Compresses a string and returns a deflate compressed, Base64 encoded string.
    /// </summary>
    /// <param name="uncompressedString">String to compress</param>
    public static string Compress(this string uncompressedString)
    {
        byte[] compressedBytes;

        using (var uncompressedStream = new MemoryStream(Encoding.UTF8.GetBytes(uncompressedString)))
        {
            using (var compressedStream = new MemoryStream())
            { 
                // setting the leaveOpen parameter to true to ensure that compressedStream will not be closed when compressorStream is disposed
                // this allows compressorStream to close and flush its buffers to compressedStream and guarantees that compressedStream.ToArray() can be called afterward
                // although MSDN documentation states that ToArray() can be called on a closed MemoryStream, I don't want to rely on that very odd behavior should it ever change
                using (var compressorStream = new DeflateStream(compressedStream, CompressionLevel.Fastest, true))
                {
                    uncompressedStream.CopyTo(compressorStream);
                }

                // call compressedStream.ToArray() after the enclosing DeflateStream has closed and flushed its buffer to compressedStream
                compressedBytes = compressedStream.ToArray();
            }
        }

        return Convert.ToBase64String(compressedBytes);
    }

    /// <summary>
    /// Decompresses a deflate compressed, Base64 encoded string and returns an uncompressed string.
    /// </summary>
    /// <param name="compressedString">String to decompress.</param>
    public static string Decompress(this string compressedString)
    {
        byte[] decompressedBytes;

        var compressedStream = new MemoryStream(Convert.FromBase64String(compressedString));

        using (var decompressorStream = new DeflateStream(compressedStream, CompressionMode.Decompress))
        {
            using (var decompressedStream = new MemoryStream())
            {
                decompressorStream.CopyTo(decompressedStream);

                decompressedBytes = decompressedStream.ToArray();
            }
        }

        return Encoding.UTF8.GetString(decompressedBytes);
    }

2
ジェイス:usingMemoryStreamインスタンスのステートメントがないと思います。そして、F#開発者の方へ:呼び出されるuseToArray()に手動で
破棄する

1
検証が追加されるため、GZipStreamを使用する方が良いでしょうか?GZipStreamまたはDeflateStreamクラス?
Michael Freidgeim、2018年

2
@Michael Freidgeimメモリストリームの圧縮と解凍についてはそうは思いません。ファイル、または信頼できないトランスポートの場合、それは理にかなっています。私の特定のユースケースでは高速が非常に望ましいので、回避できるオーバーヘッドはすべて優れています。
ジェイス、

固体。20MBのJSON文字列を4.5MBまで取りました。🎉
ジェームズ・ESH

1
うまく機能しますが、使用後にメモリストリームを破棄するか、@ knocteの提案に従ってすべてのストリームを使用状態にしてください
Sebastian

8

これは、async / awaitとIEnumerablesを使用した.NET 4.5以降の更新バージョンです。

public static class CompressionExtensions
{
    public static async Task<IEnumerable<byte>> Zip(this object obj)
    {
        byte[] bytes = obj.Serialize();

        using (MemoryStream msi = new MemoryStream(bytes))
        using (MemoryStream mso = new MemoryStream())
        {
            using (var gs = new GZipStream(mso, CompressionMode.Compress))
                await msi.CopyToAsync(gs);

            return mso.ToArray().AsEnumerable();
        }
    }

    public static async Task<object> Unzip(this byte[] bytes)
    {
        using (MemoryStream msi = new MemoryStream(bytes))
        using (MemoryStream mso = new MemoryStream())
        {
            using (var gs = new GZipStream(msi, CompressionMode.Decompress))
            {
                // Sync example:
                //gs.CopyTo(mso);

                // Async way (take care of using async keyword on the method definition)
                await gs.CopyToAsync(mso);
            }

            return mso.ToArray().Deserialize();
        }
    }
}

public static class SerializerExtensions
{
    public static byte[] Serialize<T>(this T objectToWrite)
    {
        using (MemoryStream stream = new MemoryStream())
        {
            BinaryFormatter binaryFormatter = new BinaryFormatter();
            binaryFormatter.Serialize(stream, objectToWrite);

            return stream.GetBuffer();
        }
    }

    public static async Task<T> _Deserialize<T>(this byte[] arr)
    {
        using (MemoryStream stream = new MemoryStream())
        {
            BinaryFormatter binaryFormatter = new BinaryFormatter();
            await stream.WriteAsync(arr, 0, arr.Length);
            stream.Position = 0;

            return (T)binaryFormatter.Deserialize(stream);
        }
    }

    public static async Task<object> Deserialize(this byte[] arr)
    {
        object obj = await arr._Deserialize<object>();
        return obj;
    }
}

これですべてをシリアル化できます BinaryFormatter、文字列だけでなく、サポートするます。

編集:

必要な場合は、Convert.ToBase64String(byte [])をEncoding使用できます。

例が必要な場合は、この回答をご覧ください。


サンプルをデシリアライズして編集する前に、ストリームの位置をリセットする必要があります。また、XMLコメントは無関係です。
マグナスヨハンソン

これは機能するが、UTF8ベースの場合にのみ機能することに注意してください。たとえば、シリアライズ/デシリアライズする文字列値にåäöのようなスウェーデン語の文字を追加すると、往復テストに失敗します:/
bc3tech

この場合、を使用できますConvert.ToBase64String(byte[])。この回答をご覧ください(stackoverflow.com/a/23908465/3286975)。それが役に立てば幸い!
z3nth10n

6

それでもGZipヘッダーのマジックナンバーが表示される場合は、正しくありません。GZipストリームを渡していることを確認してください。エラーが発生 し、文字列がphpを使用して圧縮された場合は、次のようにする必要があります。

       public static string decodeDecompress(string originalReceivedSrc) {
        byte[] bytes = Convert.FromBase64String(originalReceivedSrc);

        using (var mem = new MemoryStream()) {
            //the trick is here
            mem.Write(new byte[] { 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00 }, 0, 8);
            mem.Write(bytes, 0, bytes.Length);

            mem.Position = 0;

            using (var gzip = new GZipStream(mem, CompressionMode.Decompress))
            using (var reader = new StreamReader(gzip)) {
                return reader.ReadToEnd();
                }
            }
        }

この例外が発生しました:例外がスローされました:System.dllの 'System.IO.InvalidDataException'追加情報:GZipフッターのCRCは、解凍されたデータから計算されたCRCと一致しません。
Dainius Kreivys 2016
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.