Chuỗi nén / giải nén với C #


144

Tôi là người mới trong .net. Tôi đang thực hiện chuỗi nén và giải nén trong C #. Có một XML và tôi đang chuyển đổi theo chuỗi và sau đó tôi đang thực hiện nén và giải nén. Không có lỗi biên dịch trong mã của tôi ngoại trừ khi tôi giải nén mã của mình và trả về chuỗi của mình, nó chỉ trả về một nửa XML.

Dưới đây là mã của tôi, xin vui lòng sửa cho tôi nơi tôi sai.

Mã số:

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

Kích thước XML của tôi là 63KB.


1
Tôi nghi ngờ vấn đề sẽ "tự khắc phục" nếu sử dụng UTF8Encoding (hoặc UTF16 hoặc whatnot) và GetBytes / GetString. Nó cũng sẽ đơn giản hóa rất nhiều mã. Cũng khuyên bạn nên sử dụng using.

Bạn không thể chuyển đổi char thành byte và ngược lại như bạn làm (sử dụng cách truyền đơn giản). Bạn cần sử dụng một mã hóa và mã hóa tương tự để nén / giải nén. Xem câu trả lời xanatos dưới đây.
Simon Mourier

@pst không nó sẽ không; bạn sẽ sử dụng Encodingsai cách xung quanh. Bạn cần cơ sở 64 ở đây, theo câu trả lời của xanatos
Marc Gravell

@Marc Gravell Đúng, đã bỏ lỡ phần đó của chữ ký / ý định. Chắc chắn không phải là lựa chọn đầu tiên của tôi về chữ ký.

Câu trả lời:


257

Mã để nén / giải nén một chuỗi

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

Hãy nhớ rằng Ziptrả về a byte[], trong khi Unziptrả về a string. Nếu bạn muốn một chuỗi từ Zipbạn có thể mã hóa Base64 (ví dụ bằng cách sử dụng Convert.ToBase64String(r1)) (kết quả Ziplà RẤT nhị phân! Đây không phải là thứ bạn có thể in ra màn hình hoặc viết trực tiếp bằng XML)

Phiên bản được đề xuất là dành cho .NET 2.0, đối với .NET 4.0, hãy sử dụng MemoryStream.CopyTo.

QUAN TRỌNG: Nội dung nén không thể được ghi vào luồng đầu ra cho đến khi GZipStreambiết rằng nó có tất cả đầu vào (nghĩa là để nén hiệu quả, nó cần tất cả dữ liệu). Bạn cần phải chắc chắn rằng bạn Dispose()của GZipStreamtrước khi kiểm tra dòng đầu ra (ví dụ mso.ToArray()). Điều này được thực hiện với các using() { }khối ở trên. Lưu ý rằng đó GZipStreamlà khối trong cùng và nội dung được truy cập bên ngoài nó. Cũng vậy với giải nén: Dispose()của GZipStreamtrước khi cố gắng truy cập dữ liệu.


Cảm ơn bạn đã trả lời. Khi tôi sử dụng mã của mình, nó sẽ gây ra lỗi biên dịch cho tôi. "CopyTo () không có không gian tên hoặc tham chiếu lắp ráp.". Sau đó, tôi đã tìm kiếm trên Google và tìm thấy nó là một phần của CopyTo () của .NET 4 Framework. Nhưng tôi đang làm việc trên khung .net 2.0 và 3.5. Xin hãy gợi ý cho tôi. :)
Mohit Kumar

Tôi chỉ muốn nhấn mạnh rằng GZipStream phải được xử lý trước khi gọi ToArray () trên luồng đầu ra. Tôi bỏ qua bit đó, nhưng nó làm cho một sự khác biệt!
Mì ướt

1
Đây có phải là cách hiệu quả nhất để nén ở .net 4.5 không?
MonsterMMORPG

1
Lưu ý rằng điều này không thành công (unzipped-string! = Original) trong trường hợp chuỗi chứa các cặp thay thế, vd string s = "X\uD800Y". Tôi nhận thấy rằng nó hoạt động nếu chúng ta thay đổi Mã hóa thành UTF7 ... nhưng với UTF7, chúng ta có chắc chắn tất cả các ký tự có thể được thể hiện không?
digEmAll

@digEmAll Tôi sẽ nói rằng nó không hoạt động nếu có các cặp thay thế INVALID (như trong trường hợp của bạn). Chuyển đổi UTF8 GetByes âm thầm thay thế cặp thay thế không hợp lệ bằng 0xFFFD.
xanatos

103

theo đoạn mã này tôi sử dụng mã này và nó hoạt động tốt:

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
Tôi chỉ muốn cảm ơn bạn đã đăng mã này. Tôi đã bỏ nó vào dự án của mình và nó hoạt động ngay lập tức mà không gặp vấn đề gì.
BoltBait

3
Yup làm việc ra khỏi hộp! Tôi cũng thích ý tưởng thêm độ dài là bốn byte đầu tiên
JustADev

2
Đây là câu trả lời tốt nhất. Điều này nên được đánh dấu là câu trả lời!
Eriawan Kusumawardhono

1
@Matt giống như nén tệp .zip - .png đã là một nội dung được nén
fubo

2
Câu trả lời được đánh dấu là câu trả lời không ổn định. Đây là một câu trả lời tốt nhất.
Sari

38

Với sự ra đời của .NET 4.0 (và cao hơn) với các phương thức Stream.CopyTo (), tôi nghĩ rằng tôi sẽ đăng một cách tiếp cận cập nhật.

Tôi cũng nghĩ rằng phiên bản dưới đây hữu ích như một ví dụ rõ ràng về một lớp khép kín để nén các chuỗi thông thường thành các chuỗi được mã hóa Base64 và ngược lại:

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

Đây là một cách tiếp cận khác sử dụng kỹ thuật phương thức mở rộng để mở rộng lớp String để thêm nén và giải nén chuỗi. Bạn có thể thả lớp bên dưới vào một dự án hiện có và sau đó sử dụng:

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

var decompressedString = compressedString.Decompress();

Để dí dỏm:

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
Jace: Tôi nghĩ rằng bạn đang thiếu các usingtuyên bố cho các phiên bản MemoryStream. Và với các nhà phát triển F # ngoài đó: không sử dụng từ khóa usecho phiên bản ToArray()
nénStream

1
Nó sẽ tốt hơn để sử dụng GZipStream vì nó thêm một số xác nhận bổ sung? Lớp GZipStream hay DeflateStream?
Michael Freidgeim

2
@Michael Freidgeim Tôi sẽ không nghĩ như vậy để nén và giải nén các luồng bộ nhớ. Đối với các tập tin, hoặc vận chuyển không đáng tin cậy nó có ý nghĩa. Tôi sẽ nói rằng trong trường hợp sử dụng cụ thể của tôi, tốc độ cao là rất mong muốn vì vậy bất kỳ chi phí nào tôi có thể tránh đều tốt hơn.
Jace

Chất rắn. Lấy chuỗi JSON 20 MB của tôi xuống còn 4,5 MB. 🎉
James Esh

1
Hoạt động rất tốt, nhưng bạn nên loại bỏ dòng bộ nhớ sau khi sử dụng hoặc đưa mọi luồng vào sử dụng theo đề xuất của @knocte
Sebastian

8

Đây là phiên bản cập nhật cho .NET 4.5 và mới hơn bằng cách sử dụng async / await và IEnumerables:

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

Với điều này, bạn có thể tuần tự hóa mọi thứ BinaryFormatterhỗ trợ, thay vì chỉ các chuỗi.

Biên tập:

Trong trường hợp, bạn cần quan tâm Encoding, bạn chỉ có thể sử dụng Convert.ToBase64String (byte []) ...

Hãy xem câu trả lời này nếu bạn cần một ví dụ!


Bạn phải đặt lại vị trí Luồng trước khi DeSerializing, chỉnh sửa mẫu của bạn. Ngoài ra, các bình luận XML của bạn không liên quan.
Magnus Johansson

Đáng chú ý điều này hoạt động nhưng chỉ cho những thứ dựa trên UTF8. Nếu bạn thêm vào, giả sử, các ký tự Thụy Điển như åäö vào giá trị chuỗi bạn sắp xếp theo thứ tự / giải tuần tự hóa, nó sẽ thất bại trong bài kiểm tra khứ hồi: /
bc3tech

Trong trường hợp này bạn có thể sử dụng Convert.ToBase64String(byte[]). Xin vui lòng, xem câu trả lời này ( stackoverflow.com/a/23908465/3286975 ). Hy vọng nó giúp!
z3nth10n

6

Đối với những người vẫn nhận được Số ma thuật trong tiêu đề GZip là không chính xác. Hãy chắc chắn rằng bạn đang truyền trong luồng GZip. LRI và nếu chuỗi của bạn được nén bằng php, bạn sẽ cần phải làm một cái gì đó như:

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

Tôi nhận được ngoại lệ này: Ngoại lệ bị ném: 'System.IO.InvalidDataException' trong System.dll Thông tin bổ sung: CRC trong chân trang GZip không khớp với CRC được tính từ dữ liệu được giải nén.
Dainius Kreivys
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.