استمرار المقال كيف يبدو أرشيف الرمز البريدي وماذا يمكننا أن نفعل به .
مقدمة
يوم جيد.
ومرة أخرى على الهواء لدينا برامج غير تقليدية في PHP.
في مقال سابق ، كان القراء المحترمون مهتمين بالضغط والرمز البريدي. اليوم سنحاول فتح هذا الموضوع قليلاً.
دعنا نلقي نظرة على
ما هي مشكلته؟ حسنًا ، في الإنصاف ، تجدر الإشارة إلى أن ميزته الوحيدة هي أنه يعمل ، وهناك مشاكل هناك ، ولكن لا يزال.
في رأيي ، المشكلة الرئيسية هي أنه يجب علينا أولاً كتابة "رأس الملف المحلي" (LFH) مع crc32 وطول الملف ، ثم محتويات الملف نفسه.
ماذا يهدد هذا؟ أو نقوم بتحميل الملف بأكمله في الذاكرة ، والنظر في crc32 لذلك ، وكتابة LFH ، ثم تكون محتويات الملف اقتصادية من وجهة نظر I / O ، لكنها غير مقبولة مع الملفات الكبيرة. أو نقرأ الملف مرتين - أولاً لحساب البعثرة ، ثم لقراءة المحتويات والكتابة إلى الأرشيف - اقتصاديًا من وجهة نظر RAM ، ولكن ، على سبيل المثال ، يضاعف الحمل على محرك الأقراص ، وهو ليس بالضرورة SSD.
وإذا كان الملف يقع عن بعد وحجمه ، على سبيل المثال ، 1.5GB؟ حسنًا ، يجب عليك إما تحميل كل 1.5 جيجا بايت في الذاكرة ، أو الانتظار حتى يتم تنزيل كل هذه السعة 1.5 جيجا بايت وسنقوم بحساب التجزئة ، ثم ننزعها مرة أخرى لإعطاء المحتويات. إذا كنا نريد أن نقدم على الطاير ، على سبيل المثال ، قاعدة بيانات تفريغ ، والتي ، على سبيل المثال ، نقرأها من stdout ، فإن هذا غير مقبول عمومًا - لقد تغيرت البيانات في قاعدة البيانات ، وستتغير بيانات التفريغ ، وسيكون هناك تجزئة مختلفة تمامًا وسنحصل على أرشيف غير صالح. نعم ، الأمور سيئة ، بالطبع.
هيكل واصف البيانات لتدفق سجلات الأرشيف
لكن لا يتم إحباطك ، فإن مواصفات ZIP تسمح لنا بكتابة البيانات أولاً ، ثم التمسك ببنية واصف البيانات (DD) بعد البيانات ، التي تحتوي بالفعل على crc32 وطول البيانات المحزومة وطول البيانات دون ضغط. للقيام بذلك ، نحتاج فقط 3 مرات في اليوم على معدة فارغة في LFH حدد generalPurposeBitFlag تساوي 0x0008 ، وحدد crc32 و compressedSize و uncompressedSize 0 . بعد ذلك ، بعد البيانات ، نكتب بنية DD ، والتي ستبدو كما يلي:
pack('LLLL', ...array_values([ 'signature' => 0x08074b50,
وفي رأس ملف الدليل المركزي (CDFH) فقط generalPurposeBitFlag ، يجب أن تكون بقية البيانات حقيقية. لكن هذه ليست مشكلة ، حيث نكتب CDFH بعد جميع البيانات ، والتجزئة مع أطوال البيانات معروفة في أي حال.
هذا كل شيء ، بالطبع ، جيد. يبقى فقط للتنفيذ في PHP.
وستساعدنا مكتبة هاش القياسية. يمكننا إنشاء سياق تجزئة سيكون فيه كافياً لتعبئة القطع بالبيانات ، وفي النهاية الحصول على قيمة التجزئة. بالطبع ، سيكون هذا الحل أكثر تعقيدًا إلى حد ما من التجزئة ('crc32b' ، المحتوى $) ، ولكنه سيوفر لنا مجموعة من الموارد والوقت لا يمكن تخيلهما .
يبدو شيء مثل هذا:
$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);
إذا تم كل شيء بشكل صحيح ، فلن تختلف القيمة على الإطلاق عن
hash_file ('crc32b' ، أو
$ source) أو
hash ('crc32b' ، file_get_content ($ source)) .
دعونا نحاول أن نلف كل هذا بطريقة أو بأخرى في وظيفة واحدة ، حتى نتمكن من قراءة الملف بطريقة مناسبة لنا ، وفي النهاية نحصل على التجزئة والطول. وسوف تساعدنا المولدات في هذا:
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))]; }
والآن يمكننا فقط
$reader = read('https://speed.hetzner.de/1GB.bin'); foreach ($reader as $chunk) {
في رأيي ، الأمر بسيط ومريح للغاية. مع ملف 1 غيغابايت ، كان الحد الأقصى لاستهلاك الذاكرة 2 ميغابايت.
الآن دعونا نحاول تعديل الشفرة من المقالة السابقة حتى نتمكن من استخدام هذه الوظيفة.
النصي النهائي <?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,
في الخرج ، يجب أن نحصل على أرشيف مضغوط باسم test.zip ، سيكون فيه ملف به البرنامج النصي أعلاه و 100 MB.bin ، بحجم حوالي 100 ميغابايت.
ضغط في الرمز البريدي المحفوظات
الآن لدينا كل شيء تقريبًا لضغط البيانات والقيام بذلك أيضًا أثناء الطيران.
تمامًا كما حصلنا على علامة تجزئة عن طريق إعطاء مجموعات صغيرة للوظائف ، يمكننا أيضًا ضغط البيانات بفضل مكتبة Zlib الرائعة ووظائف deflate_init و deflate_add الخاصة بها .
يبدو شيء مثل هذا:
$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; }
لقد قابلت خيارًا كهذا ، مقارنةً بالخيار السابق ، سيضيف بعض الأصفار في النهاية.المفسد العنوان while (!feof($handle)) { yield deflate_add($deflateCtx, $chunk, ZLIB_SYNC_FLUSH); } yield deflate_add($deflateCtx, '', ZLIB_FINISH);
ولكن بفك أقسم ، لذلك اضطررت للتخلص من هذا التبسيط.دعنا نصلح القارئ الخاص بنا حتى يقوم بضغط بياناتنا على الفور ، وفي النهاية يعيدنا التجزئة ، وطول البيانات دون ضغط وطول البيانات مع الضغط:
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)) ]; }
وحاول على ملف 100 ميغابايت:
$reader = read('https://speed.hetzner.de/100MB.bin'); foreach ($reader as $chunk) {
لا يزال استهلاك الذاكرة يدل على أننا لم نقم بتحميل الملف بأكمله في الذاكرة.
دعنا نجمع كل شيء معًا وأخيراً احصل على أرشيف نص حقيقي بالفعل.
على عكس الإصدار السابق ، سيتغير generalPurposeBitFlag - الآن تبلغ قيمته 0x0018 ، وكذلك compressionMethod - 8 (مما يعني Deflate ).
النصي النهائي <?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,
نتيجةً لذلك ، حصلت على أرشيف بحجم 360183 بايت (تم ضغط ملفنا البالغ 100 ميجابايت بشكل جيد للغاية ، والذي على الأرجح مجرد مجموعة من وحدات البايت المتطابقة) ، وأظهر
unzip عدم وجود أخطاء في الأرشيف.
استنتاج
إذا كان لدي ما يكفي من الطاقة والوقت لمقال آخر ، فسأحاول توضيح كيف ، والأهم من ذلك ، لماذا يمكن استخدام كل هذا.
إذا كنت مهتمًا بأي شيء آخر حول هذا الموضوع - اقترح في التعليقات ، سأحاول إعطاء إجابة لسؤالك. على الأرجح لن نتعامل مع التشفير ، لأن البرنامج النصي قد نما بالفعل ، وفي الواقع ، لا تستخدم هذه المحفوظات في كثير من الأحيان.
شكرا لاهتمامكم وتعليقاتكم.