C # ile Sıkıştırma / Açma dizesi


151

.Net'te acemiyim. C # 'da sıkıştırma ve açma dizesi yapıyorum. Bir XML var ve dizede dönüştürme yapıyorum ve bundan sonra sıkıştırma ve açma yapıyorum. Kodumda, kodumu açıp dizgemi döndürmem dışında hiçbir derleme hatası yok, XML'in yalnızca yarısını döndürüyor.

Kodum aşağıdadır, lütfen yanlış olduğum yerde beni düzeltin.

Kod:

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 boyutum 63 KB.


1
UTF8Encoding (veya UTF16 veya başka bir şey) ve GetBytes / GetString kullanılıyorsa sorunun "kendi kendine çözüleceğinden " şüpheleniyorum . Ayrıca kodu büyük ölçüde basitleştirecektir. Ayrıca kullanmanızı tavsiye ederiz using.

Char'ı bayta ve yaptığınız gibi tersine çeviremezsiniz (basit bir döküm kullanarak). Sıkıştırma / açma için bir kodlama ve aynı kodlamayı kullanmanız gerekir. Aşağıdaki xanatos cevabına bakın.
Simon Mourier

@pst hayır olmayacak; Encodingyanlış bir yol kullanıyor olursun . Burada xanatos'un cevabına göre 64 tabanına ihtiyacınız var
Marc Gravell

@Marc Gravell True, imzanın / niyetin o kısmını kaçırdı. Kesinlikle ilk imza tercihim değil.

Yanıtlar:


266

Bir dizeyi sıkıştırmak / açmak için kod

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

Unutmayın Zipbir döner byte[]iken Unzipgetiri bir string. Sizden bir dize Zipistiyorsanız, Base64'ü kodlayabilirsiniz (örneğin kullanarak Convert.ToBase64String(r1)) (sonucu ZipÇOK ikilidir! Ekrana yazdırabileceğiniz veya doğrudan XML olarak yazabileceğiniz bir şey değildir)

Önerilen sürüm .NET 2.0 içindir, .NET 4.0 için MemoryStream.CopyTo.

ÖNEMLİ: Sıkıştırılmış içerikler GZipStream, tüm girdilere sahip olduğunu bilene kadar (yani, etkili bir şekilde sıkıştırmak için tüm verilere ihtiyaç duyduğu) çıkış akışına yazılamaz . Emin sen misin yapmak gerekir Dispose()ait GZipStreamçıkış akışı kontrol etmeden önce (örneğin mso.ToArray()). Bu, using() { }yukarıdaki blok ile yapılır . Unutmayın ki en GZipStreamiçteki blok ve içeriğe onun dışından erişilir. Aynı açılırken için de geçerli: Dispose()bir GZipStreamerişim denemeden önce veri.


Cevabınız için teşekkürler. Kodunuzu kullandığımda bana derleme hatası veriyor. "CopyTo () ad alanı veya derleme referansı yok." Bundan sonra Google'da arama yaptım ve .NET 4 Framework'ün CopyTo () bölümünü çalıştırdım. Ama .net 2.0 ve 3.5 çerçevesi üzerinde çalışıyorum. Lütfen beni öner. :)
Mohit Kumar

Sadece GZipStream'in çıktı akışında ToArray () çağırmadan önce atılması gerektiğini vurgulamak istiyorum. O kısmı görmezden geldim ama bir fark yaratıyor!
Islak Erişte

1
.net 4.5'te sıkıştırmanın en etkili yolu bu mu?
MonsterMMORPG

1
Vekil çiftleri içeren dizge durumunda bunun başarısız olacağını unutmayın (unzipped-string! = Original) string s = "X\uD800Y". Kodlamayı UTF7 olarak değiştirirsek işe yaradığını fark ettim ... ama UTF7 ile tüm karakterlerin temsil edilebileceğinden emin miyiz?
digEmAll

1
@ Pan.student Kontrol ettim ve ürettiğim bir gz dosyasıyla çalışıyor gibi görünüyor. Dosyanın gerçekten bir gz dosyası olmaması ihtimali var. Bir gz dosyasının bir rar dosyası olmadığını, bir zip dosyası olmadığını ve bir bz2 dosyası olmadığını unutmayın. Bunların hepsi uyumsuz formatlardır. Windows'tan açabiliyorsanız, kullandığınız kodu SO'ya göndererek bir soru göndermenizi öneririm.
xanatos

106

bu kod parçacığına göre bu kodu kullanıyorum ve iyi çalışıyor:

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
Sadece bu kodu gönderdiğiniz için teşekkür etmek istedim. Onu projeme düşürdüm ve kutudan çıktığı gibi hiçbir sorun yaşamadan çalıştı.
BoltBait

4
Evet, kutunun dışında çalışıyor! Ayrıca uzunluğu ilk dört bayt olarak ekleme fikrini de beğendim
JustADev

2
Bu en iyi cevap. Bu cevap olarak işaretlenmelidir!
Eriawan Kusumawardhono

1
@Matt, bir .zip dosyasını sıkıştırmak gibidir - .png zaten sıkıştırılmış bir içeriktir
fubo

2
Cevap olarak işaretlenen cevap sabit değil. Bu en iyi cevap.
Sari

44

Stream.CopyTo () yöntemleriyle .NET 4.0 (ve üzeri) 'nin ortaya çıkmasıyla birlikte, güncellenmiş bir yaklaşım göndereceğimi düşündüm.

Ayrıca aşağıdaki sürümün, normal dizeleri Base64 kodlu dizelere sıkıştırmak için kendi kendine yeten bir sınıfın açık bir örneği olarak yararlı olduğunu düşünüyorum ve bunun tersi de geçerlidir:

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 sınıfını dize sıkıştırması ve sıkıştırmasını açmak üzere genişletmek için uzantı yöntemlerini kullanan başka bir yaklaşım. Aşağıdaki sınıfı mevcut bir projeye bırakabilir ve ardından şu şekilde kullanabilirsiniz:

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

ve

var decompressedString = compressedString.Decompress();

Zekaya:

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: usingMemoryStream örneklerine ilişkin ifadelerin eksik olduğunu düşünüyorum . Ve oradaki F # geliştiricilerine: useToArray()
comporStream

1
Ek doğrulama eklediği için GZipStream'i kullanmak daha iyi olacak mı? GZipStream veya DeflateStream sınıfı mı?
Michael Freidgeim

2
@Michael Freidgeim Bellek akışlarını sıkıştırmak ve açmak için böyle düşünmezdim. Dosyalar veya güvenilmez taşımalar için mantıklı. Benim özel kullanım durumumda yüksek hızın çok arzu edildiğini, dolayısıyla kaçınabileceğim herhangi bir ek yükün daha iyi olduğunu söyleyeceğim.
Jace

Katı. 20MB JSON dizimi 4.5MB’ye düşürdüm. 🎉
James Esh

1
Harika çalışıyor, ancak kullanımdan sonra hatıra akışını atmalı veya her akışı @knocte
Sebastian

9

Bu, async / await ve IEnumerables kullanan .NET 4.5 ve daha yenisi için güncellenmiş bir sürümdür:

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

Bununla BinaryFormatter, yalnızca dizeler yerine, desteklenen her şeyi seri hale getirebilirsiniz .

Düzenle:

Eğer ilgilenmeniz gerekiyorsa Encoding, sadece Convert.ToBase64String (bayt []) kullanabilirsiniz ...

Bir örneğe ihtiyacınız varsa bu cevaba bir göz atın!


Örneğinizi Serileştirmeden, düzenlemeden önce Akış konumunu sıfırlamalısınız. Ayrıca, XML yorumlarınız birbiriyle alakasızdır.
Magnus Johansson

Bunun işe yaradığını belirtmek gerekir, ancak yalnızca UTF8 tabanlı şeyler için. Serileştirdiğiniz / seriyi kaldırdığınız dize değerine åäö gibi İsveç karakterleri eklerseniz, bu bir gidiş-dönüş testinde başarısız olur: /
bc3tech

Bu durumda kullanabilirsiniz Convert.ToBase64String(byte[]). Lütfen bu yanıta bakın ( stackoverflow.com/a/23908465/3286975 ). Umarım yardımcı olur!
z3nth10n

6

Hala GZip başlığındaki sihirli numarayı alanlar için doğru değil. Bir GZip akışından geçtiğinizden emin olun. HATA ve dizeniz php kullanılarak sıkıştırılmışsa, aşağıdaki gibi bir şey yapmanız gerekir:

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

Şu istisnayı alıyorum: Özel durum: System.dll'de 'System.IO.InvalidDataException' Ek bilgi: GZip alt bilgisindeki CRC, sıkıştırılmış verilerden hesaplanan CRC ile eşleşmiyor.
Dainius Kreivys 02

1

@ Fubo'nun cevabını en çok seviyorum ama bunun çok daha zarif olduğunu düşünüyorum.

Bu yöntem daha uyumludur çünkü uzunluğu önceden manuel olarak kaydetmez.

Ayrıca dizeden dizeye, bayttan [] bayt'a [] ve Akıştan Akışa sıkıştırmayı destekleyen uzantıları gösterdim.

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);
        }
    }
}
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.