Kelanjutan artikel Bagaimana tampilan arsip zip dan apa yang dapat kita lakukan dengannya .
Kata Pengantar
Hari yang baik
Dan sekali lagi siaran kami memiliki pemrograman tidak konvensional dalam PHP.
Dalam artikel sebelumnya, pembaca yang terhormat tertarik pada kompresi zip dan streaming zip. Hari ini kami akan mencoba sedikit membuka topik ini.
Mari kita lihat
Kode dari artikel terakhir Apa masalahnya? Ya, dalam keadilan, perlu dicatat bahwa satu-satunya keuntungannya adalah ia bekerja, dan ada masalah di sana, tapi tetap saja.
Menurut pendapat saya, masalah utama adalah bahwa pertama-tama kita harus menulis Header File Lokal (LFH) dengan crc32 dan panjang file, dan kemudian isi file itu sendiri.
Apa yang mengancam ini? Atau kita memuat seluruh file ke dalam memori, pertimbangkan crc32 untuk itu, tulis LFH , dan kemudian isi file ekonomis dari sudut pandang I / O, tetapi tidak diizinkan dengan file besar. Atau kita membaca file 2 kali - pertama untuk menghitung hash, dan kemudian membaca konten dan menulis ke arsip - secara ekonomis dari sudut pandang RAM, tetapi, misalnya, pertama kali menggandakan beban pada drive, yang belum tentu merupakan SSD.
Dan jika file tersebut terletak jarak jauh dan volumenya, misalnya, 1,5GB? Nah, Anda harus memuat semua 1.5GB ke dalam memori, atau menunggu sampai semua 1.5GB ini diunduh dan kami akan menghitung hash, dan kemudian mengunduhnya lagi untuk memberikan konten. Jika kita ingin memberikan dengan cepat, misalnya, database dump, yang kita, misalnya, baca dari stdout, ini umumnya tidak dapat diterima - data dalam database telah berubah, data dump akan berubah, akan ada hash yang sama sekali berbeda dan kita akan mendapatkan arsip yang tidak valid. Ya, semuanya buruk, tentu saja.
Struktur Deskriptor Data untuk Streaming Arsip Arsip
Tapi jangan berkecil hati, spesifikasi ZIP memungkinkan kita untuk menulis data terlebih dahulu, dan kemudian menempelkan struktur Data Descriptor (DD) setelah data, yang sudah berisi crc32, panjang data yang dikemas dan panjang data tanpa kompresi. Untuk melakukan ini, kita hanya perlu 3 kali sehari dengan perut kosong di LFH tentukan generalPurBitFlag sama dengan 0x0008 , dan crc32 , compressedSize dan uncompressedSize tentukan 0 . Kemudian, setelah data, kita menulis struktur DD , yang akan terlihat seperti ini:
pack('LLLL', ...array_values([ 'signature' => 0x08074b50,
Dan di Header Direktori File Pusat (CDFH) hanya perubahan GeneralPurposeBitFlag , sisa data harus nyata. Tapi ini bukan masalah, karena kita menulis CDFH setelah semua data, dan hash dengan panjang data diketahui dalam hal apa pun.
Ini semua tentu saja bagus. Tetap hanya menerapkan di PHP.
Dan perpustakaan Hash standar akan banyak membantu kami. Kita dapat membuat konteks hash di mana itu akan cukup untuk mengisi potongan data, dan pada akhirnya mendapatkan nilai hash. Tentu saja, solusi ini akan sedikit lebih rumit daripada hash ('crc32b', $ content) , tetapi ini akan menghemat banyak sumber daya dan waktu yang tidak terbayangkan.
Itu terlihat seperti ini:
$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);
Jika semuanya dilakukan dengan benar, maka nilainya tidak akan berbeda sama sekali dari
hash_file ('crc32b', $ source) atau
hash ('crc32b', file_get_content ($ source)) .
Mari kita coba untuk membungkus semuanya dalam satu fungsi, sehingga kita dapat membaca file dengan cara yang mudah bagi kita, dan pada akhirnya mendapatkan hash dan panjangnya. Dan generator akan membantu kita dengan ini:
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))]; }
dan sekarang kita bisa adil
$reader = read('https://speed.hetzner.de/1GB.bin'); foreach ($reader as $chunk) {
Menurut saya itu cukup sederhana dan nyaman. Dengan file 1GB, konsumsi memori puncak saya adalah 2MB.
Sekarang mari kita coba memodifikasi kode dari artikel sebelumnya sehingga kita dapat menggunakan fungsi ini.
Naskah akhir <?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,
Pada output, kita harus mendapatkan arsip Zip dengan nama test.zip, di mana akan ada file dengan skrip di atas dan 100MB.bin, berukuran sekitar 100 MB.
Kompresi dalam arsip zip
Sekarang kita memiliki hampir segalanya untuk mengompresi data dan melakukannya juga dengan cepat.
Sama seperti kita mendapatkan hash dengan memberikan potongan kecil untuk fungsi, kita juga dapat memampatkan data berkat pustaka Zlib yang luar biasa dan fungsi deflate_init dan deflate_add .
Itu terlihat seperti ini:
$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; }
Saya bertemu opsi seperti ini bahwa, dibandingkan dengan yang sebelumnya, itu akan menambahkan beberapa nol di akhir.Judul spoiler while (!feof($handle)) { yield deflate_add($deflateCtx, $chunk, ZLIB_SYNC_FLUSH); } yield deflate_add($deflateCtx, '', ZLIB_FINISH);
Tapi unzip bersumpah, jadi aku harus menyingkirkan penyederhanaan seperti itu.Mari kita perbaiki pembaca kita sehingga segera memampatkan data kita, dan pada akhirnya mengembalikan kita hash, panjang data tanpa kompresi dan panjang data dengan kompresi:
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)) ]; }
dan coba pada file 100 mb:
$reader = read('https://speed.hetzner.de/100MB.bin'); foreach ($reader as $chunk) {
Konsumsi memori masih menunjukkan bahwa kami tidak memuat seluruh file ke dalam memori.
Mari kita kumpulkan semuanya dan akhirnya dapatkan pengarsipan script yang benar-benar nyata.
Berbeda dengan versi sebelumnya, generalPurposeBitFlag kami akan berubah - sekarang nilainya 0x0018 , serta compressionMethod - 8 (yang berarti Deflate ).
Naskah akhir <?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,
Akibatnya, saya mendapatkan arsip berukuran 360183 byte (file 100MB kami dikompresi dengan sangat baik, di mana kemungkinan besar hanya satu set byte yang identik), dan
unzip menunjukkan bahwa tidak ada kesalahan yang ditemukan dalam arsip.
Kesimpulan
Jika saya memiliki energi dan waktu yang cukup untuk artikel lain, maka saya akan mencoba menunjukkan bagaimana dan, yang paling penting, mengapa semua ini dapat digunakan.
Jika Anda tertarik pada hal lain tentang topik ini - sarankan di komentar, saya akan mencoba memberikan jawaban untuk pertanyaan Anda. Kemungkinan besar kita tidak akan berurusan dengan enkripsi, karena skrip sudah tumbuh, dan dalam kehidupan nyata arsip seperti itu, menurut saya, jarang digunakan.
Terima kasih atas perhatian dan komentar Anda.