Чтение сжатого файла и запись в новый файл не позволит распаковать

У меня есть тестовая программа, которая демонстрирует конечный результат, на который я надеюсь (хотя в этой тестовой программе шаги могут показаться ненужными).

Программа сжимает данные в файл с помощью GZipStream. Полученный сжатый файл называется C:\mydata.dat.

Затем я читаю этот файл и записываю его в новый файл.

//Read original file
string compressedFile = String.Empty;
using (StreamReader reader = new StreamReader(@"C:\mydata.dat"))
{
    compressedFile = reader.ReadToEnd();
    reader.Close();
    reader.Dispose();
}

//Write to a new file
using (StreamWriter file = new StreamWriter(@"C:\mynewdata.dat"))
{
    file.WriteLine(compressedUserFile);
}

Когда я пытаюсь распаковать два файла, исходный распаковывается отлично, но новый файл выдает исключение InvalidDataException с сообщением Магическое число в заголовке GZip неверно. Убедитесь, что вы передаете поток GZip.

Почему эти файлы разные?


person jkh    schedule 13.07.2011    source источник
comment
Почему вы используете string вместо byte[]?   -  person leppie    schedule 13.07.2011
comment
Похоже на работу для File.Copy (нет?)   -  person musefan    schedule 13.07.2011
comment
reader.ReadToEnd() возвращает строку   -  person jkh    schedule 13.07.2011
comment
Кроме того, я не могу использовать File.Copy... Я читаю файл, загружаю его в другое место и создаю там новый файл... это просто упрощенная версия проблемы.   -  person jkh    schedule 13.07.2011
comment
@John: вы должны читать по куску байта за раз. Я опубликую краткий ответ   -  person musefan    schedule 13.07.2011
comment
@John: Кроме того, вы уверены, что не вызываете проблем с загрузкой части кода?   -  person musefan    schedule 13.07.2011
comment
Почему вы записываете данные в файл, а затем читаете их оттуда? Вы не можете работать с ним в памяти все время?   -  person svick    schedule 13.07.2011
comment
@John: Еще одна вещь - нужно вызвать reader.Close() и reader.Dispose(). Блок using позаботится об этом за вас.   -  person Bryan Crosby    schedule 13.07.2011


Ответы (2)


РЕДАКТИРОВАТЬ: по-видимому, мои предложения неверны/недействительны/независимо... пожалуйста, используйте один из других, которые, несомненно, были сильно реорганизованы до такой степени, что невозможно достичь дополнительной производительности (иначе это означало бы, что они такой же недействующий, как и мой)

using (System.IO.StreamReader sr = new System.IO.StreamReader(@"C:\mydata.dat"))
{
    using (System.IO.StreamWriter sw = new System.IO.StreamWriter(@"C:\mynewdata.dat"))
    {
        byte[] bytes = new byte[1024];
        int count = 0;
        while((count = sr.BaseStream.Read(bytes, 0, bytes.Length)) > 0){
            sw.BaseStream.Write(bytes, 0, count);
        }
    }
}

Читать все байты

byte[] bytes = null;
using (System.IO.StreamReader sr = new System.IO.StreamReader(@"C:\mydata.dat"))
{
    bytes = new byte[sr.BaseStream.Length];
    int index = 0;
    int count = 0;
    while((count = sr.BaseStream.Read(bytes, index, 1024)) > 0){
        index += count;
    }
}

Прочитать все байты/записать все байты (из ответа svick):

byte[] bytes = File.ReadAllBytes(@"C:\mydata.dat");
File.WriteAllBytes(@"C:\mynewdata.dat", bytes);

ТЕСТИРОВАНИЕ ПРОИЗВОДИТЕЛЬНОСТИ С ДРУГИМИ ОТВЕТАМИ:

Только что провел быстрый тест между моим ответом (StreamReader) (первая часть выше, копия файла) и ответом svick (FileStream/MemoryStream) (первый). Тест состоит из 1000 итераций кода, вот результаты 4 тестов (результаты указаны в целых секундах, все фактические результаты немного превышают эти значения):

My Code | svick code
--------------------
9       | 12
9       | 14
8       | 13
8       | 14

Как видите, по крайней мере в моем тесте мой код работал лучше. Одна вещь, которую, возможно, следует отметить в моем случае, это то, что я не читаю поток символов, я фактически обращаюсь к BaseStream, который предоставляет поток байтов. Возможно, ответ Свика медленный, потому что он использует два потока для чтения, а затем два для записи. Конечно, есть много оптимизаций, которые можно было бы сделать, чтобы ответить svick для повышения производительности (и он также предоставил альтернативу для простого копирования файлов)

Тестирование с третьим вариантом (ReadAllBytes/WriteAllBytes)

My Code | svick code | 3rd
----------------------------
8       | 14         | 7
9       | 18         | 9
9       | 17         | 8
9       | 17         | 9

Примечание: в миллисекундах 3-й вариант всегда был лучше

person musefan    schedule 13.07.2011
comment
Спасибо за ответ... но что, если у меня не было доступа к обоим файлам одновременно, и мне нужно какое-то временное хранилище... могу ли я просто прочитать его в byte[]? - person jkh; 13.07.2011
comment
Read() не требуется возвращать все байты во входном потоке, даже если они помещаются в массив. Например, с NetworkStream это происходит регулярно. Но всем остальным Stream разрешено делать то же самое. Если вы хотите использовать Read() таким образом, вы должны убедиться, что больше нет байтов для чтения. - person svick; 13.07.2011
comment
Почему вы вообще создаете StreamReader при чтении байтов? Просто используйте FileStream напрямую. - person svick; 13.07.2011
comment
Я пошел с тем, что использовал ОП, и, честно говоря, я не мог вспомнить все тонкости памяти/потока файлов. В любом случае, я повысил ваш ответ .... но я думаю, что голоса против моего голоса несправедливы, поскольку я предоставил действительное (хотя и альтернативное) решение - person musefan; 13.07.2011
comment
Другим допустимым (хотя и альтернативным) решением может быть C#-эквивалент system("copy mydata.dat mynewdata.dat"). Это не означает, что за него нельзя проголосовать: P Если код ОП неверен, как обстоят дела с тем, что ОП использовал полезный ответ? - person anton.burger; 13.07.2011
comment
@Anton: Что делает код неправильным? Можете ли вы показать, что один лучше (производительность), чем другой? - person musefan; 13.07.2011
comment
svick уже упоминал, что вы должны просто использовать Stream (байты), а не StreamReader (символы). Производительность абсолютно не имеет отношения к обсуждению; дело в том, что вы признаете, что опубликовали вводящий в заблуждение пример кода без каких-либо объяснений, не потратив время на поиск деталей. - person anton.burger; 13.07.2011
comment
Нет, я сказал, что знаю о FileStream и MemoryStream. Я никоим образом не утверждаю, что мой код плохой или неправильный. Рабочий код является допустимым кодом. И производительность - это ТОЧНО причина, чтобы определить, следует ли вам использовать тот или иной метод. - person musefan; 13.07.2011
comment
То, что вы знаете о них, никак не помогает ОП, если только это не находится в вашем ответе. Ваш ответ вводит в заблуждение, потому что вы ничего не сделали для устранения неправильных представлений OP о чтении файлов с Stream по сравнению с StreamReader. Если вы это сделаете, я удалю мой минус. - person anton.burger; 13.07.2011
comment
@musefan, производительность - одна из причин, по которой лучше выбрать один способ написания кода, а не другой. Но уж точно не единственный и, в большинстве случаев, не самый главный. Как сказал Антон, использование потоков символов для чтения байтов, даже правильным способом, вводит в заблуждение и может сбить с толку тех, кто знает разницу. (Он использует StreamReader вместо FilesStream? Почему? Должна быть какая-то скрытая причина, верно?) - person svick; 13.07.2011
comment
@svick: я не знаю его причин, и, возможно, у ОП они есть, но я могу работать только с тем, что у меня есть в вопросе. Я думаю, здесь дело в том, что альтернативное решение неверно, и на каждый заданный вопрос должен быть только один ответ? (если, конечно, люди просто не копируют и не вставляют ответы других людей) Я принял к сведению то, что было сказано, и с точки зрения производительности я не просто соглашусь с вами, не увидев для себя, что ваш путь лучше (что я не нет времени проверить) - иногда, казалось бы, один из способов имеет лучшую производительность, но это не всегда так. - person musefan; 14.07.2011
comment
@Anton: Вот, пожалуйста, я сообщил в своем ответе, что это может быть не лучший подход, основанный на других ответах. И мы собираемся сообщить ОП о плохих практиках, я не вижу никого, кто говорил бы, что вам не нужно закрывать/удалять в использовании - person musefan; 14.07.2011
comment
Ух ты, милость и ни намека на сарказм :P Прости, что мы не так обидели друг друга, musefan; Я должен был просто предложить правки к вашему ответу, а не вступать с вами в спор. - person anton.burger; 14.07.2011
comment
@Anton: Я не держу зла. Я также провел тест производительности, см. мой отредактированный ответ - person musefan; 14.07.2011

StreamReader предназначен для чтения последовательности символов, а не байтов. То же самое относится и к StremWriter. Поскольку рассматривать сжатые файлы как поток символов бессмысленно, вам следует использовать некоторую реализацию Stream. Если вы хотите получить поток в виде массива байтов, вы можете использовать MemoryStream.

Точная причина, по которой использование потоков символов не работает, заключается в том, что они по умолчанию предполагают кодировку UTF-8. Если какой-то байт не является допустимым UTF-8 (например, второй байт заголовка, 0x8B), он представляется как «символ замены» Unicode (U+FFFD). Когда строка записывается обратно, этот символ кодируется с использованием UTF-8 во что-то совершенно отличное от того, что было в исходном коде.

Например, чтобы прочитать файл из потока, получить его как массив байтов, а затем записать его в другие файлы как поток:

byte[] bytes;
using (var fileStream = new FileStream(@"C:\mydata.dat", FileMode.Open))
using (var memoryStream = new MemoryStream())
{
    fileStream.CopyTo(memoryStream);
    bytes = memoryStream.ToArray();
}

using (var memoryStream = new MemoryStream(bytes))
using (var fileStream = new FileStream(@"C:\mynewdata.dat", FileMode.Create))
{
    memoryStream.CopyTo(fileStream);
}

Метод CopyTo() доступен только в .Net 4, но вы можете написать свой собственный, если используете старые версии.

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

byte[] bytes = File.ReadAllBytes(@"C:\mydata.dat");
File.WriteAllBytes(@"C:\mynewdata.dat", bytes);
person svick    schedule 13.07.2011