Сжатие и распаковка файлов через GZipStream


#1

Здравствуйте! Возникла проблема при использовании System.IO.Compression.GZipStream: Сжатие потока происходит корректно, а вот распаковка никак не удаётся. Вот код для сжатия:

begin
Var Compressed:=System.IO.File.Create('Compressed.gz');
Var C:=new GZipStream(Compressed, System.IO.Compression.CompressionLevel.Fastest);
(System.IO.File.OpenRead('Data.dat')).CopyTo(C);
end.

Как распаковать такой файл? При этом для распаковки не желательно использовать внешний файл(для распакованных данных).


#2

А у меня и сжатие не проходит.

begin
  var Compressed := System.IO.File.Create('Compressed.gz');
  var C := new System.IO.Compression.GZipStream(Compressed, System.IO.Compression.CompressionLevel.Fastest);
  (System.IO.File.OpenRead('json.txt')).CopyTo(C);
  Compressed.Flush;
end.

Создаёт Не читающийся архив. image

Причём файла который я запаковывал, как видите, в нём нет.


#3

Он и не должен читаться обычным архиватором. Это просто данные, сжатые по определённому алгоритму, не содержащие данных о исходном файле. То есть сжать так несколько файлов невозможно. Для распаковки используется GZipStream.


#4

Надо использовать C.Flush, похоже, вместо Compressed.Flush. Теперь хотя бы запаковалось. И кстати архиватор всё ещё читает этот файл без проблем. Ну разве что имя и расширение отсутствуют)). Ну то есть, я так понял, и все данные типа жанра .mp3 файлов тоже сотрёт.


#5

Вот так работает:

function ReadGZip(C: System.IO.Compression.GZipStream): sequence of byte;
begin
  while true do
  begin
    var i := C.ReadByte;
    if i = -1 then break;
    yield i;
  end;
end;

begin
  
  var Compressed := System.IO.File.Create('Compressed.gz');
  var C := new System.IO.Compression.GZipStream(Compressed, System.IO.Compression.CompressionLevel.Fastest);
  (System.IO.File.OpenRead('json.txt')).CopyTo(C);
  C.Close;
  Compressed.Close;
  
  Compressed := System.IO.File.OpenRead('Compressed.gz');
  C := new System.IO.Compression.GZipStream(Compressed, System.IO.Compression.CompressionMode.Decompress);
  var a := ReadGZip(C).ToArray;
  System.IO.File.WriteAllBytes('json copy.txt', a);
  
end.

#6

Вроде всё понял, но что такое yield? А правильность файла после распаковки проверяли? То есть, совпадает ли содержимое распакованного файла и оригинала.


#7

Фунции с yield это особые функции для создания последовательностей - алгоритмов. К примеру:

function f1:sequence of byte;
begin
  loop 10 do yield Random(256);
end;

begin
  var seq := f1();
  writeln(seq);
  writeln(seq);
end.

Выпишет 2 разных массива, потому что в seq хранится только алгоритм. Ну а в данном случае я использовал это только потому что заранее не известно количество байт. Я не уверен быстрее ли это чем список, но хотя бы удобнее. Почитайте справку про yield и yield sequence.

И да, я проверил - распаковка дала точную копию исходного файла.


#8

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


#9

Нет, я декомпилировал - ToArray в данном случае просто перебирает всю последовательность до конца, с помощью foreach и добавляет каждый элемент в списк-о-подобный класс (алгоритм добавления элементов тот же что и в List). Так только много лишних объектов создаёт и уничтожает (функция с yield это на самом деле тоже класс, она превращается в function f1:sequence of byte := new clyield#1, а потом ещё много классов - обёрток передают друг другу своё содержимое, при вызове ToArray).


#10

Объём данных у меня небольшой, порядка 40-45 КБ, поэтому скорость выполнения этой части кода будет составлять единицы миллисекунд. А вообще, благодаря Вашему примеру я нашёл у себя в коде ошибку, из-за которой распаковка происходила некорректно. Проблема проста: нужно всего лишь закрывать потоки. :slight_smile: