Конференция "Прочее" » Zlib - TZDecompressionStream.Seek написан криво
 
  • Кто б сомневался © (22.05.16 06:34) [0]
    Или не используйте метод  TZipFile.Read(out Stream: TStream;), который с потоком.

    Те кто использует Zlib - TZDecompressionStream и TZipFile, а он использует TZDecompressionStream - у тех файлы могут распаковываться в 2 раза медленнее.
    Просто потому что они распаковываются по 2 раза (XE5).
    Возможно у кого то есть XE 8, можете выложить куда нибудь System.ZLib.pas? Возможно там исправили баг...

    Просьба прокомментировать.. Пол ночи протрассировал :)
    Вобщем в чем суть:

    var
       Zip:TZipFile;
       AbstractStream: TStream;
       LocalHeader: TZipHeader;
       OutputStream: TStream;
    begin
     OutputStream := TFileStream.Create('d:\123.123', fmCreate);

     Zip:=TZipFile.Create;

     zip.Open('d:\321\321.ZIP',zmRead);
    // вытаскиваем из зипа файл section9.xhtml
     zip.Read('section9.xhtml', AbstractStream, LocalHeader);

    Read возвращает только абстрактный TStream, который создает у себя.
    Честно говоря такой вариант мне тоже не очень понятен, т.к. придется все равно копировать данные оттуда. С какой целью это сделано непонятно? ..
    По факту там создается Result := TZDecompressionStream.Create(InStream, -15); из модуля Zlib.

    Дальше копируем с него данные. Ведь по другому его никак не забрать (мне файл нужен только в памяти)..

     OutputStream.CopyFrom(AbstractStream, AbstractStream.Size);


    И вот теперь самое интересное:
    Разберем строчку:
    OutputStream.CopyFrom(AbstractStream, AbstractStream.Size);

    Сначала запрашивается AbstractStream.Size .

    Т.е. получаем:
    function TStream.GetSize: Int64;
    var
     Pos: Int64;
    begin
     Pos := Seek(0, soCurrent);
     Result := Seek(0, soEnd);  <<<<<
     Seek(Pos, soBeginning);
    end;


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

    function TZDecompressionStream.Seek(const Offset: Int64; Origin: TSeekOrigin): Int64;
    const
     BufSize = 8192;
    var
     buf: TBytes; <<<<<<<<<<<<<<<<<<<<
     i: Integer;
     localOffset: Int64;
    begin
     if (Offset = 0) and (Origin = soBeginning) then
     begin
       ZDecompressCheck(inflateReset(FZStream));

       FZStream.next_in := @FBuffer;
       FZStream.avail_in := 0;

       FStream.Position := FStreamStartPos;
       FStreamPos := FStreamStartPos;
     end
     else if ((Offset >= 0) and (Origin = soCurrent)) or
       (((NativeUInt(offset) - FZStream.total_out) > 0) and (Origin = soBeginning)) then
     begin
       localOffset := Offset;
       if (Origin = soBeginning) then Dec(localOffset, FZStream.total_out);

       if localOffset > 0 then
       begin
         SetLength(buf, BufSize);
         for i := 1 to localOffset div BufSize do ReadBuffer(buf, BufSize);
         ReadBuffer(buf, localOffset mod BufSize);
       end;
     end
     else if (Offset = 0) and (Origin = soEnd) then
     begin
       SetLength(buf, BufSize);
       while Read(buf, 0, BufSize) > 0 do ;
     end
     else raise EZDecompressionError.Create(SZInvalid);

     result := FZStream.total_out;
    end;


    Ну дальше вы уже догадались:
    OutputStream.CopyFrom(AbstractStream, AbstractStream.Size);
    Затем после GetSize идет обычный вызов ReadBuffer.

    Итого, файл опять распаковается повторно.

    Более того, если вы читаете с CopyFrom порциями (допустим вместо AbstractStream.Size - поставите свой размер буфера) - то тогда файл будет распаковываться полностью за один запрос КАЖДЫЙ РАЗ.

    Если в XE8 не исправили такой жирный баг, тогда было бы неплохо запостить его в QC (я не смогу)..

    Если нужно прочитать как следует (распаковав один раз файл), то лучше использовать :

    TZipFile.Read(const FileName: string; out Bytes: TBytes);
  • Кто б сомневался © (22.05.16 06:40) [1]
    Да, а вы спросите, как же задается  размер буфера в этом методе
    TZipFile.Read(const FileName: string; out Bytes: TBytes);

    он достается из заранее сохраненного поля UncompressedSize - у зипа есть такое поле в структуре каждого запакованного файла в TZipHeader.
  • Кто б сомневался © (22.05.16 07:22) [2]
    А вот например метод

    TZipFile.Extract(const FileName: string; const Path: string; CreateSubDirs: Boolean);

    Тут ребята исхитрились, и сделали так:

    LOutStream.CopyFrom(LInStream, FFiles[Index].UncompressedSize);

    Тот самое поле из структуры.. При этом FFiles находится в привате.
  • Rouse_ © (22.05.16 15:23) [3]
    Я вообще свой использую: http://rouse.drkb.ru/components.php#fwzip
    Оригинальный который в Delphi - не фонтан.
  • K-1000 © (22.05.16 15:53) [4]

    > Rouse_ ©   (22.05.16 15:23) [3]
    > Я вообще свой использую: http://rouse.drkb.ru/components.
    > php#fwzip
    > Оригинальный который в Delphi - не фонтан.


    Почему?
  • Кто б сомневался © (22.05.16 17:11) [5]

    > K-1000 ©   (22.05.16 15:53) [4]


    Тут имеется в виду TZipFile из zip.
    Не фонтан он потому, что целиком выделяет буфер для всего распакованного файла.
    Если файл будет 20 мб, - выделит 20 мб и распакует его. Я незнаю что будет если файл будет размером в 500 мб, по идее тоже самое.

    Мне вот например нужно, прочитать всего лишь кусочек в 10 кб из запакованного файла (одного из) - заголовок короче говоря.. Нет смысла распаковывать весь файл из архива.

    Вот я и думаю толи свой написать, то ли не надо...
  • Rouse_ © (22.05.16 18:05) [6]

    > K-1000 ©   (22.05.16 15:53) [4]
    > Почему?

    Ну те которые я смотрел не поддерживали ZIP64 спецификацию, шифрование, к памяти и скорости работы были претензии. Я свой писал чтобы учесть все эти недостатки и использую уже шестой год (да и не только я, уже достаточно много софта в том числе и коммерческого, написано с использованием моих классов).
  • sniknik © (23.05.16 10:41) [7]
    > у зипа есть такое поле в структуре каждого запакованного файла в TZipHeader.
    так Zlib вроде не zip, не полноценный zip, а как бы "внутренняя кухня" отвечает только за данные, типа архивирование "на лету" мимо проходящих потоков (потому и используется в http).
    т.е.
    function TStream.GetSize: Int64;
    var
      Pos: Int64;
    begin
      Pos := Seek(0, soCurrent);
      Result := Seek(0, soEnd);
      Seek(Pos, soBeginning);
    end;


    написан правильно, единственно возможным способом... а если "обертка сверху" для zip-а заимела заголовки с размером, то и метод нужно было бы перекрыть.

    в общем проблема не тут
    > Zlib - TZDecompressionStream.Seek написан криво
    использую его в "чистом" виде, и вроде все правильно.
 
Конференция "Прочее" » Zlib - TZDecompressionStream.Seek написан криво
Есть новые Нет новых   [134433   +22][b:0][p:0.001]