Как использовать GZipStream с System.IO.MemoryStream?

У меня проблема с этой тестовой функцией, когда я беру строку в памяти, сжимаю ее и распаковываю. Сжатие работает отлично, но я не могу заставить работать декомпрессию.

//Compress
System.IO.MemoryStream outStream = new System.IO.MemoryStream();                
GZipStream tinyStream = new GZipStream(outStream, CompressionMode.Compress);
mStream.Position = 0;
mStream.CopyTo(tinyStream);

//Decompress    
outStream.Position = 0;
GZipStream bigStream = new GZipStream(outStream, CompressionMode.Decompress);
System.IO.MemoryStream bigStreamOut = new System.IO.MemoryStream();
bigStream.CopyTo(bigStreamOut);

//Results:
//bigStreamOut.Length == 0
//outStream.Position == the end of the stream.

Я считаю, что bigStream out должен как минимум содержать данные, особенно если мой исходный поток (outStream) читается. это ошибка MSFT или моя?


person halfbit    schedule 15.09.2010    source источник


Ответы (8)


В вашем коде происходит то, что вы продолжаете открывать потоки, но никогда их не закрываете.

  • В строке 2 вы создаете GZipStream. Этот поток ничего не будет записывать в основной поток, пока не почувствует, что это подходящее время. Вы можете сказать это, закрыв его.

  • Однако, если вы закроете его, он также закроет базовый поток (outStream). Следовательно, вы не можете использовать на нем mStream.Position = 0.

Вы всегда должны использовать using, чтобы гарантировать, что все ваши потоки будут закрыты. Вот вариант вашего кода, который работает.

var inputString = "“ ... ”";
byte[] compressed;
string output;

using (var outStream = new MemoryStream())
{
    using (var tinyStream = new GZipStream(outStream, CompressionMode.Compress))
    using (var mStream = new MemoryStream(Encoding.UTF8.GetBytes(inputString)))
        mStream.CopyTo(tinyStream);

    compressed = outStream.ToArray();
}

// “compressed” now contains the compressed string.
// Also, all the streams are closed and the above is a self-contained operation.

using (var inStream = new MemoryStream(compressed))
using (var bigStream = new GZipStream(inStream, CompressionMode.Decompress))
using (var bigStreamOut = new MemoryStream())
{
    bigStream.CopyTo(bigStreamOut);
    output = Encoding.UTF8.GetString(bigStreamOut.ToArray());
}

// “output” now contains the uncompressed string.
Console.WriteLine(output);
person Timwi    schedule 15.09.2010
comment
+1 хороший ответ Тимви. Чтобы добавить к этому, GZip имеет некоторую внутреннюю буферизацию данных, которая необходима для сжатия. Он не может знать, что он получил данные, пока вы его не закроете, и поэтому он не выплевывает последние несколько байтов, и декомпрессия частичного потока не выполняется. - person MerickOWA; 16.09.2010
comment
Я думаю, что мы на .NET 3.5 (работаем с Unity), поэтому .CopyTo еще не существует. Ищем в другом месте на SO, как копировать из одного потока в другой: stackoverflow.com/questions/230128/ - person Almo; 22.05.2014
comment
Спасибо за это, у меня возникли проблемы с тем, чтобы точно определить, как организовать потоки, чтобы получить правильный результат в обоих направлениях. - person MikeT; 15.04.2016

Это известная проблема: http://blogs.msdn.com/b/bclteam/archive/2006/05/10/592551.aspx.

Я немного изменил ваш код, чтобы он работал:

var mStream = new MemoryStream(new byte[100]);
var outStream = new System.IO.MemoryStream();

using (var tinyStream = new GZipStream(outStream, CompressionMode.Compress))
{
    mStream.CopyTo(tinyStream);           
}

byte[] bb = outStream.ToArray();

//Decompress                
var bigStream = new GZipStream(new MemoryStream(bb), CompressionMode.Decompress);
var bigStreamOut = new System.IO.MemoryStream();
bigStream.CopyTo(bigStreamOut);
person Aliostad    schedule 15.09.2010
comment
Не следует ли использовать new GZipStream(outStream, CompressionMode.Compress, true), чтобы оставить поток открытым, чтобы оператор using мог закрыть его, как это предлагается в ответе @briantyler? - person bounav; 25.07.2019

Способ сжатия и распаковки в MemoryStream и обратно:

public static Stream Compress(
    Stream decompressed, 
    CompressionLevel compressionLevel = CompressionLevel.Fastest)
{
    var compressed = new MemoryStream();
    using (var zip = new GZipStream(compressed, compressionLevel, true))
    {
        decompressed.CopyTo(zip);
    }

    compressed.Seek(0, SeekOrigin.Begin);
    return compressed;
}

public static Stream Decompress(Stream compressed)
{
    var decompressed = new MemoryStream();
    using (var zip = new GZipStream(compressed, CompressionMode.Decompress, true))
    {
        zip.CopyTo(decompressed);
    }

    decompressed.Seek(0, SeekOrigin.Begin);
    return decompressed;
}

При этом сжатый / распакованный поток остается открытым и может использоваться после его создания.

person satnhak    schedule 26.08.2016

Другая реализация в VB.NET:

Imports System.Runtime.CompilerServices
Imports System.IO
Imports System.IO.Compression

Public Module Compressor

    <Extension()> _
    Function CompressASCII(str As String) As Byte()

        Dim bytes As Byte() = Encoding.ASCII.GetBytes(str)

        Using ms As New MemoryStream

            Using gzStream As New GZipStream(ms, CompressionMode.Compress)

                gzStream.Write(bytes, 0, bytes.Length)

            End Using

            Return ms.ToArray

        End Using

    End Function

    <Extension()> _
    Function DecompressASCII(compressedString As Byte()) As String

        Using ms As New MemoryStream(compressedString)

            Using gzStream As New GZipStream(ms, CompressionMode.Decompress)

                Using sr As New StreamReader(gzStream, Encoding.ASCII)

                    Return sr.ReadToEnd

                End Using

            End Using

        End Using

    End Function

    Sub TestCompression()

        Dim input As String = "fh3o047gh"

        Dim compressed As Byte() = input.CompressASCII()

        Dim decompressed As String = compressed.DecompressASCII()

        If input <> decompressed Then
            Throw New ApplicationException("failure!")
        End If

    End Sub

End Module
person Nikolai Koudelia    schedule 25.04.2013
comment
Даже если вашему посту 6 лет и я не отвечаю за вопрос - сегодня он мне помог. Спасибо! - person muffi; 31.01.2019

Если вы пытаетесь использовать MemoryStream (например, передаете его в другую функцию), но получаете исключение «Невозможно получить доступ к закрытому потоку». тогда есть еще один конструктор GZipStream, который может вам помочь.

Передав значение true параметру leaveOpen, вы можете указать GZipStream оставить поток открытым после удаления самого себя, по умолчанию он закрывает целевой поток (чего я не ожидал). https://msdn.microsoft.com/en-us/library/27ck2z1y(v=vs.110).aspx

using (FileStream fs = File.OpenRead(f))
using (var compressed = new MemoryStream())
{
    //Instruct GZipStream to leave the stream open after performing the compression.
    using (var gzipstream = new GZipStream(compressed, CompressionLevel.Optimal, true))
        fs.CopyTo(gzipstream);

    //Do something with the memorystream
    compressed.Seek(0, SeekOrigin.Begin);
    MyFunction(compressed);
}
person Snives    schedule 03.07.2016

Если он вам все еще нужен, вы можете использовать конструктор GZipStream с логическим аргументом (таких конструкторов два) и передать туда истинное значение:

tinyStream = new GZipStream(outStream, CompressionMode.Compress, true);

В этом случае, когда вы закроете свой tynyStream, ваш исходящий поток все равно будет открыт. Не забудьте скопировать данные:

mStream.CopyTo(tinyStream);
tinyStream.Close();

Теперь у вас есть поток памяти outStream с заархивированными данными.

Жуки и поцелуи для U

Удачи

person Олег Рубан    schedule 28.08.2018

См. Ссылку ниже: Избегайте использования двойного MemoryStream. https://stackoverflow.com/a/53644256/1979406

person Haryono    schedule 06.12.2018

У меня возникла проблема, когда *.CopyTo(stream)* закончился бы byte[0] результатом. Решением было добавить .Position=0 перед вызовом .CopyTo(stream) Ответили здесь

Я также использую BinaryFormatter, который генерирует исключение «Конец потока, обнаруженный до завершения синтаксического анализа», если позиция не была установлена ​​в 0 перед десериализацией. Ответили здесь

Это код, который у меня сработал.

 public static byte[] SerializeAndCompressStateInformation(this IPluginWithStateInfo plugin, Dictionary<string, object> stateInfo)
    {
        byte[] retArr = new byte[] { byte.MinValue };
        try
        {
            using (MemoryStream msCompressed = new MemoryStream())//what gzip writes to
            {
                using (GZipStream gZipStream = new GZipStream(msCompressed, CompressionMode.Compress))//setting up gzip
                using (MemoryStream msToCompress = new MemoryStream())//what the settings will serialize to
                {
                    BinaryFormatter formatter = new BinaryFormatter();
                    //serialize the info into bytes
                    formatter.Serialize(msToCompress, stateInfo);
                    //reset to 0 to read from beginning byte[0] fix.
                    msToCompress.Position = 0;
                    //this then does the compression
                    msToCompress.CopyTo(gZipStream);
                }
                //the compressed data as an array of bytes
                retArr = msCompressed.ToArray();
            }
        }
        catch (Exception ex)
        {
            Logger.Error(ex.Message, ex);
            throw ex;
        }
        return retArr;
    }


    public static Dictionary<string, object> DeserializeAndDecompressStateInformation(this IPluginWithStateInfo plugin, byte[] stateInfo)
    {
        Dictionary<string, object> settings = new Dictionary<string, object>();
        try
        {

            using (MemoryStream msDecompressed = new MemoryStream()) //the stream that will hold the decompressed data
            {
                using (MemoryStream msCompressed = new MemoryStream(stateInfo))//the compressed data
                using (GZipStream gzDecomp = new GZipStream(msCompressed, CompressionMode.Decompress))//the gzip that will decompress
                {
                    msCompressed.Position = 0;//fix for byte[0]
                    gzDecomp.CopyTo(msDecompressed);//decompress the data
                }
                BinaryFormatter formatter = new BinaryFormatter();
                //prevents 'End of stream encountered' error
                msDecompressed.Position = 0;
                //change the decompressed data to the object
                settings = formatter.Deserialize(msDecompressed) as Dictionary<string, object>;
            }
        }
        catch (Exception ex)
        {
            Logger.Error(ex.Message, ex);
            throw ex;
        }
        return settings;
    }
person CobyC    schedule 04.12.2019