Fortsetzung des Artikels Wie das Zip-Archiv aussieht und was wir damit machen können .
Vorwort
Guten Tag.
Und wieder auf Sendung haben wir unkonventionelle Programmierung in PHP.
In einem früheren Artikel waren angesehene Leser an Zip-Komprimierung und Zip-Streaming interessiert. Heute werden wir versuchen, dieses Thema ein wenig zu eröffnen.
Werfen wir einen Blick darauf
Code aus dem letzten Artikel Was ist sein Problem? Nun, fairerweise ist es erwähnenswert, dass sein einziger Vorteil darin besteht, dass es funktioniert und es dort Probleme gibt, aber immer noch.
Meiner Meinung nach besteht das Hauptproblem darin, dass wir zuerst den lokalen Dateikopf (Local File Header, LFH) mit crc32 und der Dateilänge und dann den Inhalt der Datei selbst schreiben müssen.
Was bedroht das? Oder wir laden die gesamte Datei in den Speicher, ziehen crc32 in Betracht, schreiben LFH , und dann ist der Inhalt der Datei aus Sicht der E / A wirtschaftlich, bei großen Dateien jedoch nicht akzeptabel. Oder wir lesen die Datei zweimal - zuerst, um den Hash zu berechnen, und dann, um den Inhalt zu lesen und in das Archiv zu schreiben - wirtschaftlich aus Sicht des RAM, aber zum Beispiel verdoppelt sie zum einen die Belastung des Laufwerks, das nicht unbedingt eine SSD ist.
Und wenn sich die Datei remote befindet und ihr Volumen beispielsweise 1,5 GB beträgt? Nun, Sie müssen entweder alle 1,5 GB in den Speicher laden oder warten, bis alle diese 1,5 GB heruntergeladen sind, und wir werden den Hash berechnen und sie dann erneut herunterladen, um den Inhalt zu erhalten. Wenn wir beispielsweise im laufenden Betrieb eine Dump-Datenbank bereitstellen möchten, die wir beispielsweise aus stdout lesen, ist dies im Allgemeinen nicht akzeptabel. Die Daten in der Datenbank haben sich geändert, die Dump-Daten ändern sich, es gibt einen völlig anderen Hash und wir erhalten ein ungültiges Archiv. Ja, die Dinge sind natürlich schlecht.
Datenbeschreibungsstruktur für das Streaming von Archivdatensätzen
Aber lassen Sie sich nicht entmutigen, die ZIP-Spezifikation ermöglicht es uns, zuerst Daten zu schreiben und dann die Data Descriptor (DD) -Struktur nach den Daten zu kleben, die bereits crc32, die Länge der gepackten Daten und die Länge der Daten ohne Komprimierung enthalten. Dazu müssen wir in LFH nur dreimal täglich auf nüchternen Magen generalPurposeBitFlag gleich 0x0008 angeben , und crc32 , compressSize und unkomprimierte Größe geben 0 an . Dann schreiben wir nach den Daten die DD- Struktur, die ungefähr so aussieht:
pack('LLLL', ...array_values([ 'signature' => 0x08074b50,
Und im Central Directory File Header (CDFH) ändert sich nur der generalPurposeBitFlag , der Rest der Daten muss real sein. Dies ist jedoch kein Problem, da wir nach allen Daten CDFH schreiben und Hashes mit Datenlängen in jedem Fall bekannt sind.
Das ist natürlich alles gut. Es bleibt nur in PHP zu implementieren.
Und die Standard- Hash- Bibliothek wird uns sehr helfen. Wir können einen Hash-Kontext erstellen, in dem es ausreicht, Chunks mit Daten zu füllen und am Ende den Hash-Wert zu erhalten. Natürlich ist diese Lösung etwas umständlicher als Hash ('crc32b', $ content) , aber sie spart uns nur eine unvorstellbare Menge an Ressourcen und Zeit.
Es sieht ungefähr so aus:
$hashCtx = hash_init('crc32b'); $handle = fopen($source, 'r'); while (!feof($handle)) { $chunk = fread($handle, 8 * 1024); hash_update($hashCtx, $chunk); $chunk = null; } $hash = hash_final($hashCtx);
Wenn alles richtig gemacht wurde, unterscheidet sich der Wert überhaupt nicht von
hash_file ('crc32b', $ source) oder
hash ('crc32b', file_get_content ($ source)) .
Lassen Sie uns versuchen, dies alles irgendwie in einer Funktion zusammenzufassen, damit wir die Datei auf bequeme Weise für uns lesen und am Ende ihren Hash und ihre Länge erhalten können. Und die Generatoren helfen uns dabei:
function read(string $path): \Generator { $length = 0; $handle = fopen($path, 'r'); $hashCtx = hash_init('crc32b'); while (!feof($handle)) { $chunk = fread($handle, 8 * 1024); $length += strlen($chunk); hash_update($hashCtx, $chunk); yield $chunk; $chunk = null; } fclose($handle); return ['length' => $length, 'crc32' => hexdec(hash_final($hashCtx))]; }
und jetzt können wir einfach
$reader = read('https://speed.hetzner.de/1GB.bin'); foreach ($reader as $chunk) {
Meiner Meinung nach ist es ganz einfach und bequem. Bei einer 1-GB-Datei betrug mein maximaler Speicherverbrauch 2 MB.
Versuchen wir nun, den Code aus dem vorherigen Artikel so zu ändern, dass wir diese Funktion verwenden können.
Endgültiges Skript <?php function read(string $path): \Generator { $length = 0; $handle = fopen($path, 'r'); $hashCtx = hash_init('crc32b'); while (!feof($handle)) { $chunk = fread($handle, 8 * 1024); $length += strlen($chunk); hash_update($hashCtx, $chunk); yield $chunk; $chunk = null; } fclose($handle); return ['length' => $length, 'crc32' => hexdec(hash_final($hashCtx))]; } $entries = ['https://speed.hetzner.de/100MB.bin', __FILE__]; $destination = 'test.zip'; $handle = fopen($destination, 'w'); $written = 0; $dictionary = []; foreach ($entries as $entry) { $filename = basename($entry); $fileInfo = [ 'versionToExtract' => 10,
Bei der Ausgabe sollten wir ein Zip-Archiv mit dem Namen test.zip erhalten, in dem sich eine Datei mit dem obigen Skript und 100 MB.Bin befindet, die ungefähr 100 MB groß ist.
Komprimierung in Zip-Archiven
Jetzt haben wir praktisch alles, um die Daten zu komprimieren und dies auch im laufenden Betrieb zu tun.
So wie wir einen Hash erhalten, indem wir Funktionen kleine Blöcke zuweisen , können wir dank der wunderbaren Zlib- Bibliothek und ihrer Funktionen deflate_init und deflate_add auch Daten komprimieren.
Es sieht ungefähr so aus:
$deflateCtx = deflate_init(ZLIB_ENCODING_RAW, ['level' => 6]); $handle = fopen($source, 'r'); while (!feof($handle)) { $chunk = fread($handle, 8 * 1024); yield deflate_add($deflateCtx, $chunk, feof($handle) ? ZLIB_FINISH : ZLIB_SYNC_FLUSH); $chunk = null; }
Ich habe eine Option wie diese getroffen, die im Vergleich zur vorherigen am Ende einige Nullen hinzufügt.Spoiler Überschrift while (!feof($handle)) { yield deflate_add($deflateCtx, $chunk, ZLIB_SYNC_FLUSH); } yield deflate_add($deflateCtx, '', ZLIB_FINISH);
Aber entpacken schwor, also musste ich solche Vereinfachung loswerden.Korrigieren wir unseren Reader so, dass er unsere Daten sofort komprimiert und am Ende einen Hash zurückgibt, die Länge der Daten ohne Komprimierung und die Länge der Daten mit Komprimierung:
function read(string $path): \Generator { $uncompressedSize = 0; $compressedSize = 0; $hashCtx = hash_init('crc32b'); $deflateCtx = deflate_init(ZLIB_ENCODING_RAW, ['level' => 6]); $handle = fopen($path, 'r'); while (!feof($handle)) { $chunk = fread($handle, 8 * 1024); hash_update($hashCtx, $chunk); $compressedChunk = deflate_add($deflateCtx, $chunk, feof($handle) ? ZLIB_FINISH : ZLIB_SYNC_FLUSH); $uncompressedSize += strlen($chunk); $compressedSize += strlen($compressedChunk); yield $compressedChunk; $chunk = null; $compressedChunk = null; } fclose($handle); return [ 'uncompressedSize' => $uncompressedSize, 'compressedSize' => $compressedSize, 'crc32' => hexdec(hash_final($hashCtx)) ]; }
und probieren Sie eine 100-MB-Datei an:
$reader = read('https://speed.hetzner.de/100MB.bin'); foreach ($reader as $chunk) {
Der Speicherverbrauch zeigt immer noch, dass wir nicht die gesamte Datei in den Speicher geladen haben.
Lassen Sie uns alles zusammenfügen und endlich einen wirklich echten Skriptarchivierer bekommen.
Im Gegensatz zur vorherigen Version ändert sich unser generalPurposeBitFlag - jetzt ist sein Wert 0x0018 sowie die Komprimierungsmethode - 8 (was " Deflate" bedeutet).
Endgültiges Skript <?php function read(string $path): \Generator { $uncompressedSize = 0; $compressedSize = 0; $hashCtx = hash_init('crc32b'); $deflateCtx = deflate_init(ZLIB_ENCODING_RAW, ['level' => 6]); $handle = fopen($path, 'r'); while (!feof($handle)) { $chunk = fread($handle, 8 * 1024); hash_update($hashCtx, $chunk); $compressedChunk = deflate_add($deflateCtx, $chunk, feof($handle) ? ZLIB_FINISH : ZLIB_SYNC_FLUSH); $uncompressedSize += strlen($chunk); $compressedSize += strlen($compressedChunk); yield $compressedChunk; $chunk = null; $compressedChunk = null; } fclose($handle); return [ 'uncompressedSize' => $uncompressedSize, 'compressedSize' => $compressedSize, 'crc32' => hexdec(hash_final($hashCtx)) ]; } $entries = ['https://speed.hetzner.de/100MB.bin', __FILE__]; $destination = 'test.zip'; $handle = fopen($destination, 'w'); $written = 0; $dictionary = []; foreach ($entries as $entry) { $filename = basename($entry); $fileInfo = [ 'versionToExtract' => 10,
Als Ergebnis erhielt ich ein Archiv mit einer Größe von 360183 Bytes (unsere 100-MB-Datei wurde sehr gut komprimiert, was höchstwahrscheinlich nur ein Satz identischer Bytes ist), und das
Entpacken zeigte, dass im Archiv keine Fehler gefunden wurden.
Fazit
Wenn ich genug Energie und Zeit für einen anderen Artikel habe, werde ich versuchen zu zeigen, wie und vor allem warum all dies genutzt werden kann.
Wenn Sie an etwas anderem zu diesem Thema interessiert sind - schlagen Sie in den Kommentaren vor, ich werde versuchen, eine Antwort auf Ihre Frage zu geben. Höchstwahrscheinlich werden wir uns nicht mit Verschlüsselung befassen, da das Skript bereits gewachsen ist und solche Archive im wirklichen Leben meines Erachtens nicht sehr oft verwendet werden.
Vielen Dank für Ihre Aufmerksamkeit und Ihre Kommentare.