【问题标题】:Compression/Decompression string with C#使用 C# 压缩/解压缩字符串
【发布时间】:2011-11-12 16:41:19
【问题描述】:

我是 .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。

【问题讨论】:

  • 我怀疑如果使用 UTF8Encoding(或 UTF16 或诸如此类)和 GetBytes/GetString,问题会“自行解决”。它还将大大简化代码。也推荐使用using
  • 你不能像你一样将 char 转换为 byte 和反向(使用简单的转换)。您需要使用一种编码,并使用相同的编码进行压缩/解压缩。请参阅下面的 xanatos 答案。
  • @pst 不会的;你会以错误的方式使用Encoding。根据 xanatos 的回答,您在这里需要 base-64
  • @Marc Gravell True,错过了签名/意图的那部分。绝对不是我的首选签名。

标签: c# string .net-2.0 compression


【解决方案1】:

压缩/解压字符串的代码

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 知道它拥有所有输入之前(即,要有效压缩它需要所有数据),压缩内容才能写入输出流。在检查输出流(例如,mso.ToArray())之前,您需要确保您是 GZipStreamDispose()。这是通过上面的using() { } 块完成的。请注意,GZipStream 是最里面的块,并且可以在其外部访问内容。在尝试访问数据之前解压缩:GZipStream 中的Dispose() 也是如此。

【讨论】:

  • 感谢您的回复。当我使用您的代码时,它给了我编译错误。“CopyTo() 没有命名空间或程序集引用。”。之后,我在 Google 上搜索并发现 CopyTo() 是 .NET 4 Framework 的一部分。但我正在研究 .net 2.0 和 3.5 框架。请给我建议。:)
  • 这是 .net 4.5 最有效的压缩方式吗?
  • 请注意,如果字符串包含代理对,则此操作会失败(解压缩字符串!= 原始),例如string s = "X\uD800Y"。我注意到如果我们将编码更改为 UTF7,它会起作用...但是使用 UTF7,我们确定所有字符都可以表示吗?
  • @Pan.student 我已经检查过了,它似乎适用于我制作的 gz 文件。该文件有可能不是真正的 gz 文件。请注意,gz 文件不是 rar 文件,也不是 zip 文件,也不是 bz2 文件。它们都是不兼容的格式。如果您能够从 Windows 中打开它,那么我建议您在发布您正在使用的代码时发布一个问题。
  • 答案中的代码将导致“GZip 标头中的幻数不正确。确保您传入 GZip 流。”的异常,正如@rluks 所说。
【解决方案2】:

根据 this snippet 我使用此代码,它工作正常:

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

【讨论】:

  • 我只是想感谢您发布此代码。我把它放到我的项目中,它开箱即用,完全没有问题。
  • 是的,开箱即用!我也喜欢将长度添加为前四个字节的想法
  • 这是最好的答案。这个应该被标记为答案!
  • @Matt 这就像压缩 .zip 文件 - .png 已经是压缩内容
  • 被标记为答案的答案不稳定。这是最好的答案。
【解决方案3】:

随着带有 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);
    }

【讨论】:

  • Jace:我认为您缺少用于 MemoryStream 实例的 using 语句。对于 F# 开发人员:不要对 compressorStream/decompressorStream 实例使用关键字 use,因为它们需要在调用 ToArray() 之前手动处理
  • 使用 GZipStream 会不会更好,因为它会增加一些额外的验证? GZipStream or DeflateStream class?
  • @Michael Freidgeim 对于压缩和解压缩内存流,我不这么认为。对于文件或不可靠的传输,这是有道理的。我会说,在我的特定用例中,高速是非常可取的,因此我可以避免的任何开销都会更好。
  • 固体。将我的 20MB JSON 字符串减少到 4.5MB。 ?
  • 效果很好,但是您应该在使用后处理内存流,或者按照@knocte 的建议将每个流都投入使用
【解决方案4】:

我最喜欢@fubo 的回答,但我认为这更优雅。

这种方法更兼容,因为它不会预先手动存储长度。

我还公开了扩展以支持字符串到字符串、byte[] 到 byte[] 以及 Stream 到 Stream 的压缩。

public static class ZipExtensions
{
    public static string CompressToBase64(this string data)
    {
        return Convert.ToBase64String(Encoding.UTF8.GetBytes(data).Compress());
    }

    public static string DecompressFromBase64(this string data)
    {
        return Encoding.UTF8.GetString(Convert.FromBase64String(data).Decompress());
    }
    
    public static byte[] Compress(this byte[] data)
    {
        using (var sourceStream = new MemoryStream(data))
        using (var destinationStream = new MemoryStream())
        {
            sourceStream.CompressTo(destinationStream);
            return destinationStream.ToArray();
        }
    }

    public static byte[] Decompress(this byte[] data)
    {
        using (var sourceStream = new MemoryStream(data))
        using (var destinationStream = new MemoryStream())
        {
            sourceStream.DecompressTo(destinationStream);
            return destinationStream.ToArray();
        }
    }
    
    public static void CompressTo(this Stream stream, Stream outputStream)
    {
        using (var gZipStream = new GZipStream(outputStream, CompressionMode.Compress))
        {
            stream.CopyTo(gZipStream);
            gZipStream.Flush();
        }
    }

    public static void DecompressTo(this Stream stream, Stream outputStream)
    {
        using (var gZipStream = new GZipStream(stream, CompressionMode.Decompress))
        {
            gZipStream.CopyTo(outputStream);
        }
    }
}

【讨论】:

    【解决方案5】:

    这是使用 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 支持的东西,而不是只序列化字符串。

    编辑:

    如果你需要照顾Encoding,你可以使用Convert.ToBase64String(byte[])...

    Take a look at this answer if you need an example!

    【讨论】:

    • 您必须在反序列化之前重置 Stream 位置,编辑您的样本。此外,您的 XML cmets 是不相关的。
    • 值得注意的是,它只适用于基于 UTF8 的东西。如果您将瑞典字符(例如 åäö)添加到您正在序列化/反序列化的字符串值中,它将无法通过往返测试:/
    • 在这种情况下,您可以使用Convert.ToBase64String(byte[])。请看这个答案(stackoverflow.com/a/23908465/3286975)。希望对您有所帮助!
    【解决方案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 不匹配。
    • 我相信有人会面临同样的问题。要在压缩字符串中包含那个神奇的标题,您需要使用正确的 php 函数:“gzencode”而不是“gzcompress”。 PHP 中还有另一种压缩算法:“gzinflate”,但我个人从未使用过。附言您的代码有一个问题:您编写了一个标头,然后通过将偏移量 0 赋予第二个 Write() 方法用实际字节覆盖它,因此您在流中具有相同的字节。
    【解决方案7】:

    我们可以通过使用 StreamReader 和 StreamWriter 来降低代码复杂度,而不是手动将字符串转换为字节数组。您只需要三个流:

        public static byte[] Zip(string uncompressed)
        {
            byte[] ret;
            using (var outputMemory = new MemoryStream())
            {
                using (var gz = new GZipStream(outputMemory, CompressionLevel.Optimal))
                {
                    using (var sw = new StreamWriter(gz, Encoding.UTF8))
                    {
                        sw.Write(uncompressed);
                    }
                }
                ret = outputMemory.ToArray();
            }
            return ret;
        }
    
        public static string Unzip(byte[] compressed)
        {
            string ret = null;
            using (var inputMemory = new MemoryStream(compressed))
            {
                using (var gz = new GZipStream(inputMemory, CompressionMode.Decompress))
                {
                    using (var sr = new StreamReader(gz, Encoding.UTF8))
                    {
                        ret = sr.ReadToEnd();
                    }
                }
            }
            return ret;
        }
    

    【讨论】:

    • 我试过了,但在某些情况下会导致问题。我什至尝试过 Convert.UTF8,但在某些情况下它也有问题。唯一 100% 可行的解决方案是简单地 for 循环并手动构建字符串以及将字符串转换为字节。
    猜你喜欢
    • 1970-01-01
    • 2013-05-12
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多