Вопрос по compression, c#, .net-2.0, decompression, string – Вы должны сбросить позицию потока до того, как DeSerializing отредактировал ваш образец. Кроме того, ваши комментарии XML не связаны.

124

ичок в .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 составляет 63 КБ.

@ нет, не будет; вы бы использовалиEncoding неправильный путь. Вам нужен base-64 здесь, согласно ответу xanatos Marc Gravell♦
Я подозреваю, что проблема будет "исправлена", если использоватьUTF8Encoding (или UTF16 или еще много чего) и GetBytes / GetString. Это также значительно упростит код. Также рекомендуем использоватьusing. user166390
@Marc Gravell Правда, пропустил ту часть подписи / намерения. Определенно не мой первый выбор подписей. user166390
Вы не можете конвертировать char в байты и наоборот, как вы делаете (используя простое приведение). Вам нужно использовать кодировку и ту же кодировку для сжатия / распаковки. Смотрите ответ ксанатоса ниже. Simon Mourier

Ваш Ответ

5   ответов
226

Код для сжатия / распаковки строки

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() изGZipStream перед проверкой выходного потока (например,mso.ToArray()). Это сделано сusing() { } блок выше. Обратите внимание, чтоGZipStream является самым внутренним блоком и доступ к содержимому за его пределами. То же самое касается декомпрессии:Dispose() изGZipStream прежде чем пытаться получить доступ к данным.

Спасибо за ответ. Когда я использую ваш код, он выдает ошибку компиляции. «CopyTo () не имеет пространства имен или ссылки на сборку». После этого я искал в Google и обнаружил, что CopyTo () является частью .NET 4 Framework. Но я работаю над .net 2.0 и 3.5 framework. Пожалуйста, предложите мне. :) Mohit Kumar
это самый эффективный способ архивирования в .net 4.5? MonsterMMORPG
Я просто хочу подчеркнуть, что GZipStream должен быть удален перед вызовом ToArray () в выходном потоке. Я проигнорировал этот бит, но это имеет значение! Wet Noodles
Обратите внимание, что это не работает (unzipped-string! = Original) в случае строки, содержащей суррогатные пары, напримерstring s = "X\uD800Y", Я заметил, что это работает, если мы изменим кодировку на UTF7 ... но с UTF7 мы уверены, что все символы могут быть представлены? digEmAll
@ DigEmAll Я скажу, что это не будет работать, если есть НЕПРАВИЛЬНЫЕ суррогатные пары (как в вашем случае). Преобразование GetByes в UTF8 автоматически заменяет недопустимую суррогатную пару на 0xFFFD. xanatos
76

согласно сэтот фрагмент Я использую этот код, и он работает нормально:

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);
            }
        }
    }
}
Это лучший ответ. Этот должен быть отмечен как ответ! Eriawan Kusumawardhono
Ответ, помеченный как ответ, не является стабильным. Это лучший ответ. Sari
Я просто хотел поблагодарить вас за размещение этого кода. Я бросил его в свой проект, и он работал прямо из коробки без каких-либо проблем. BoltBait
@Matt - это как архивирование файла .zip - .png уже сжатый контент fubo
Да работает из коробки! Мне также понравилась идея добавить длину как первые четыре байта OmarBizreh
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
25

бликовать обновленный подход.

Я также думаю, что приведенная ниже версия полезна в качестве наглядного примера автономного класса для сжатия обычных строк в строки, закодированные в 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)))
        {
            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, this approach avoids relying 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)))
        {
            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, this approach avoids relying 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);
    }
@ knocte, я верю, что ты прав. Jace
@Michael Freidgeim Я бы так не думал о сжатии и распаковке потоков памяти. Для файлов или ненадежных транспортов это имеет смысл. Я скажу, что в моем конкретном случае высокая скорость очень желательна, поэтому любые издержки, которых я могу избежать, тем лучше. Jace
Джейс: Я думаю, что ты пропалusing операторы для экземпляров MemoryStream. И разработчикам F #: воздерживаться от использования ключевого словаuse для экземпляра compressorStream / depressorStream, поскольку их необходимо удалить доToArray() вызывается knocte
Будет ли лучше использовать GZipStream, поскольку он добавляет дополнительную проверку?GZipStream или класс DeflateStream? Michael Freidgeim
3

использующая 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))
            {
                //gs.CopyTo(mso);
                await gs.CopyToAsync(mso);
            }

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

public static class SerializerExtensions
{
    /// <summary>
    /// Writes the given object instance to a binary file.
    /// <para>Object type (and all child types) must be decorated with the [Serializable] attribute.</para>
    /// <para>To prevent a variable from being serialized, decorate it with the [NonSerialized] attribute; cannot be applied to properties.</para>
    /// </summary>
    /// <typeparam name="T">The type of object being written to the XML file.</typeparam>
    /// <param name="filePath">The file path to write the object instance to.</param>
    /// <param name="objectToWrite">The object instance to write to the XML file.</param>
    /// <param name="append">If false the file will be overwritten if it already exists. If true the contents will be appended to the file.</param>
    public static byte[] Serialize<T>(this T objectToWrite)
    {
        using (MemoryStream stream = new MemoryStream())
        {
            BinaryFormatter binaryFormatter = new BinaryFormatter();
            binaryFormatter.Serialize(stream, objectToWrite);

            return stream.GetBuffer();
        }
    }

    /// <summary>
    /// Reads an object instance from a binary file.
    /// </summary>
    /// <typeparam name="T">The type of object to read from the XML.</typeparam>
    /// <param name="filePath">The file path to read the object instance from.</param>
    /// <returns>Returns a new instance of the object read from the binary file.</returns>
    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, а не только строки.

Вы должны сбросить позицию потока до того, как DeSerializing отредактировал ваш образец. Кроме того, ваши комментарии XML не связаны. Magnus Johansson

Похожие вопросы