สตริงการบีบอัด / บีบอัดด้วย C #


144

ฉันเป็นมือใหม่ใน. เน็ต ฉันกำลังบีบอัดและคลายสตริงใน 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 หรือ whatnot) และ GetBytes / GetString มันจะทำให้รหัสง่ายขึ้นอย่างมาก usingนอกจากนี้ยังแนะนำให้ใช้

คุณไม่สามารถแปลงถ่านให้เป็นไบต์และย้อนกลับเหมือนที่คุณทำ คุณต้องใช้การเข้ารหัสและการเข้ารหัสแบบเดียวกันสำหรับการบีบอัด / คลายการบีบอัด ดูคำตอบ xanatos ด้านล่าง
Simon Mourier

@pst ไม่มันจะไม่; คุณจะใช้Encodingวิธีที่ผิด ๆ คุณต้องการ base-64 ที่นี่ตามคำตอบของ xanatos
Marc Gravell

@ 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คือ VERY binary! ไม่ใช่สิ่งที่คุณสามารถพิมพ์ไปที่หน้าจอหรือเขียนโดยตรงใน XML)

รุ่นที่แนะนำคือสำหรับ NET 2.0 สำหรับ NET 4.0 MemoryStream.CopyToการใช้งาน

สำคัญ:เนื้อหาที่บีบอัดไม่สามารถเขียนไปยังกระแสข้อมูลออกจนกว่าจะGZipStreamรู้ว่ามันมีอินพุตทั้งหมด (เช่นเพื่อบีบอัดได้อย่างมีประสิทธิภาพมันต้องการข้อมูลทั้งหมด) คุณต้องให้แน่ใจว่าคุณDispose()ของGZipStreamก่อนที่จะตรวจสอบกระแสออก (เช่นmso.ToArray()) สิ่งนี้ทำกับusing() { }บล็อกด้านบน โปรดทราบว่านั่นGZipStreamคือบล็อกที่อยู่ด้านในสุดและมีการเข้าถึงเนื้อหาภายนอก เช่นเดียวกันสำหรับการคลายการบีบอัด: Dispose()ของGZipStreamก่อนที่จะพยายามเข้าถึงข้อมูล


ขอบคุณสำหรับการตอบกลับเมื่อฉันใช้รหัสของคุณมันทำให้ฉันมีข้อผิดพลาดในการรวบรวม "CopyTo () ไม่มีเนมสเปซหรือการอ้างอิงแอสเซมบลี" หลังจากนั้นฉันได้ทำการค้นหาบน Google และหาว่า CopyTo () ส่วนหนึ่งของ. NET 4 Framework แต่ฉันทำงานบน. net 2.0 และ 3.5 framework กรุณาแนะนำฉัน :)
Mohit Kumar

ฉันแค่ต้องการเน้นว่าต้องกำจัด GZipStream ก่อนที่จะเรียก ToArray () ในกระแสข้อมูลขาออก ฉันไม่สนใจบิตนั้น แต่มันสร้างความแตกต่าง!
ก๋วยเตี๋ยวเปียก

1
นี้เป็นวิธีที่มีประสิทธิภาพที่สุดของการซิปที่. สุทธิ 4.5?
MonsterMMORPG

1
โปรดทราบว่านี้ล้มเหลว (ซิปสตริง! = เดิม) string s = "X\uD800Y"ในกรณีของสตริงที่มีตัวแทนคู่เช่น ฉันสังเกตเห็นว่ามันใช้งานได้ถ้าเราเปลี่ยนการเข้ารหัสเป็น UTF7 ... แต่ด้วย UTF7 เราแน่ใจหรือว่าตัวละครทุกตัวสามารถแสดงได้?
digEmAll

@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

3
Yup ออกกำลังกายนอกกรอบ! ฉันชอบความคิดของการเพิ่มความยาวเป็นสี่ไบต์แรก
JustADev

2
นี่คือคำตอบที่ดีที่สุด อันนี้ควรถูกทำเครื่องหมายเป็นคำตอบ!
Eriawan Kusumawardhono

1
@Matt ที่เหมือนกับการซิปไฟล์. zip - .png เป็นเนื้อหาที่ถูกบีบอัดอยู่แล้ว
fubo

2
คำตอบที่ถูกทำเครื่องหมายว่าเป็นคำตอบนั้นไม่มั่นคง อันนี้เป็นคำตอบที่ดีที่สุด
ส่าหรี

38

ด้วยการถือกำเนิดของ. NET 4.0 (และสูงกว่า) ด้วย Stream.CopyTo () วิธีฉันคิดว่าฉันจะโพสต์วิธีการปรับปรุง

ฉันยังคิดว่ารุ่นด้านล่างมีประโยชน์เป็นตัวอย่างที่ชัดเจนของคลาสที่มีในตัวเองสำหรับการบีบอัดสตริงปกติไปยังสตริงที่เข้ารหัส 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
Jace: ฉันคิดว่าคุณไม่มีusingคำสั่งสำหรับอินสแตนซ์ MemoryStream และสำหรับนักพัฒนา F #: งดเว้นจากการใช้คำสำคัญuseสำหรับอินสแตนซ์ของ compressorStream / decompressorStream เพราะพวกเขาจำเป็นต้องกำจัดด้วยตนเองก่อนที่จะToArray()ถูกเรียก
knocte

1
การใช้ GZipStream จะเป็นการดีกว่าหรือไม่เมื่อเพิ่มการตรวจสอบพิเศษ คลาส GZipStream หรือ DeflateStream?
Michael Freidgeim

2
@Michael Freidgeim ฉันไม่คิดอย่างนั้นสำหรับการบีบอัดและคลายการบีบอัดหน่วยความจำ สำหรับไฟล์หรือการขนส่งที่ไม่น่าเชื่อถือ ฉันจะบอกว่าในกรณีที่ฉันใช้ความเร็วสูงเป็นที่พึงปรารถนามากดังนั้นค่าใช้จ่ายใด ๆ ที่ฉันสามารถหลีกเลี่ยงได้นั้นเป็นสิ่งที่ดีกว่า
Jace

ของแข็ง ใช้สตริง JSON ขนาด 20 MB ของฉันลดลงเหลือ 4.5MB 🎉
James Esh

1
ใช้งานได้ดี แต่คุณควรกำจัดเมมโมรี่สตรีมหลังการใช้งานหรือให้ทุกสตรีมใช้ตามที่แนะนำโดย @knocte
เซบาสเตียน

8

นี่เป็นรุ่นปรับปรุงสำหรับ. NET 4.5 และใหม่กว่าโดยใช้ async / await และ 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;
    }
}

ด้วยสิ่งนี้คุณสามารถทำให้การBinaryFormatterสนับสนุนทุกอย่างเป็นลำดับแทนที่จะเป็นสตริงเท่านั้น

แก้ไข:

ในกรณีที่คุณต้องการดูแลEncodingคุณสามารถใช้Convert.ToBase64String (byte []) ...

ดูคำตอบนี้ถ้าคุณต้องการตัวอย่าง!


คุณต้องรีเซ็ตตำแหน่งสตรีมก่อนที่จะยกเลิกการจัดวางแก้ไขตัวอย่างของคุณ นอกจากนี้ความคิดเห็น 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.IO.InvalidDataException' ใน System.dll ข้อมูลเพิ่มเติม: CRC ในส่วนท้ายของ GZip ไม่ตรงกับ CRC ที่คำนวณจากข้อมูลที่ถูกบีบอัด
Dainius Kreivys
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.