لوحة المتصدرين
المحتوى الأكثر حصولًا على سمعة جيدة
المحتوى الأعلى تقييمًا في 07/11/23 in مقالات البرمجة
-
يمثل المخزن المؤقت buffer مساحة ما في الذاكرة RAM تحتوي على البيانات بالصيغة الثنائية binary، ويمكن لنود Node.js أن تتعامل مع هذه الذاكرة باستخدام الصنف Buffer، حيث يمثل البيانات كسلسلة من الأعداد بطريقة مشابهة لعمل المصفوفات في جافاسكربت، إلا أن الفرق أن هذه البيانات لا يمكن التعديل على حجمها بعد إنشاء المخزن، وكثيرًا ما نتعامل مع المخازن المؤقتة عند تطوير البرامج ضمن بيئة نود دون أن نشعر، فمثلًا عند قراءة ملف ما باستخدام التابع fs.readFile() فسيمرر كائن من نوع مخزن مؤقت يحوي بيانات الملف الذي نحاول قراءته إلى تابع رد النداء callback أو كنتيجة للوعد Promise، وحتى عند إنشاء طلبات HTTP فالنتيجة هي مجرى stream من البيانات المخزنة مؤقتًا في مخزن مؤقت داخلي يساعد المستخدم على معالجة بيانات جواب الطلب على دفعات بدلًا من دفعة واحدة. ونستفيد من المخازن المؤقتة أيضًا عند التعامل مع البيانات الثنائية عند كتابة البرامج منخفضة المستوى مثل التي تتعامل مع إرسال واستقبال البيانات عبر الشبكة، كما توفر القدرة على التعامل مع البيانات على أخفض مستوى ممكن والتعديل عليها في الحالات التي نحتاج بها لذلك. سنتعرف في هذا الفصل على المخازن المؤقتة وطريقة إنشائها والقراءة والنسخ منها والكتابة إليها، وحتى تحويل البيانات الثنائية ضمنها إلى صيغ ترميز أخرى. المستلزمات هذا الفصل جزء من سلسلة دليل تعلم Node.js لذا يجب قبل قراءته: تثبيت بيئة Node.js على الجهاز، حيث استخدمنا في هذا المقال الإصدار رقم 10.19.0. معرفة التعامل مع حلقة REPL في نود، يمكنك الاطلاع على المقال الثاني من هذه السلسلة للتعرف أكثر على طريقة استخدام هذا الوضع. معرفة بأساسيات جافاسكربت وأنواع البيانات المتوفرة ضمن اللغة. إنشاء المخزن المؤقت سنتعرف في هذه الفقرة على طريقتين لإنشاء كائن التخزين المؤقت في نود، حيث يجب يجب أن نسأل أنفسنا دومًا في ما إذا كنا نريد إنشاء مخزن مؤقت جديد، أو استخراج مخزن مؤقت من بيانات موجودة مسبقًا، وعلى أساس ذلك سنحدد الطريقة المستخدمة لإنشائه، ففي حال أردنا تخزين بيانات غير موجودة ونتوقع أن تصل لاحقًا ففي تلك الحالة يجب إنشاء مخزن مؤقت جديد باستدعاء بالتابع alloc() من الصنف Buffer، ولنوضح هذه الطريقة نبدأ بفتح جلسة جديدة من وضع حلقة REPL بتنفيذ الأمر node في سطر الأوامر كالتالي: $ node يظهر الرمز > في بداية السطر، ما يدل على استعداد هذا الوضع لتلقي التعليمات البرمجية وتنفيذها، حيث يقبل التابع alloc() تمرير عدد كمعامل أول إجباري يشير إلى حجم المخزن المؤقت الذي نود إنشاءه، أي يمثل هذا المعامل عدد البايتات التي ستُحجز في الذاكرة للمخزن المؤقت الجديد، فمثلًا لإنشاء مخزن مؤقت بسعة 1 كيلوبايت أي ما يعادل 1024 بايت يمكننا استخدام التابع السابق كالتالي: > const firstBuf = Buffer.alloc(1024); نلاحظ أن الصنف Buffer متاح بشكل عام في بيئة نود، ومنه يمكننا الوصول مباشرة إلى التابع alloc() لاستخدامه، ونلاحظ كيف مررنا القيمة 1024 كمعامل أول له لينتج لدينا مخزن مؤقت بسعة 1 كيلوبايت، حيث ستحوي المساحة المحجوزة للمخزن المؤقت الجديد مؤقتًا على أصفار افتراضيًا، وذلك ريثما نكتب البيانات ضمنه لاحقًا، وبإمكاننا تخصيص ذلك فإذا أردنا أن تحتوي تلك المساحة على واحدات بدلًا من الأصفار يمكننا تمرير هذه القيمة كمعامل ثاني للتابع alloc() كالتالي: > const filledBuf = Buffer.alloc(1024, 1); ينتج لدينا مخزنًا مؤقتًا بمساحة 1 كيلوبايت من الذاكرة المملوءة بالواحدات، ويجب التأكيد أن البيانات التي يمثلها المخزن المؤقت ستكون بيانات ثنائية binary مهما كانت القيمة التي نحددها له كقيمة أولية، حيث يمكن تمثيل العديد من صيغ البيانات بواسطة البيانات الثنائية، فمثلًا البيانات الثنائية التالية تمثل حجم 1 بايت: 01110110، ويمكن تفسيرها كنص بترميز ASCII باللغة الإنكليزية وبالتالي ستُعبّر عن الحرف v، ويمكن أيضًا تفسير هذه البيانات بسياق آخر وترميز مختلف على أنها لون لبكسل واحد من صورة ما، حيث يمكن للحاسوب التعامل مع هذه البيانات ومعالجتها بعد معرفة صيغة ترميزها. ويستخدم المخزن المؤقت في نود افتراضيًا ترميز UTF-8 في حال كانت القيمة الأولية المخزنة ضمنه عند إنشاءه هي سلسلة نصية، حيث يمكن للبايت الواحد في ترميز UTF-8 أن يمثل حرفًا من أي لغة أو عددًا أو رمزًا ما، ويعتبر هذا الترميز توسعة لمعيار الترميز الأمريكي لتبادل البيانات أو ASCII والذي يقتصر على ترميز الأحرف الإنكليزية الكبيرة والصغيرة والأعداد وبعض الرموز القليلة الأخرى فقط، كعلامة التعجب "!" وعلامة الضم "&"، ويمكننا تحديد الترميز المستخدم من قبل المخزن المؤقت عبر تمريره كمعامل ثالث للتابع alloc()، فمثلًا لو اقتصرت حاجة برنامج ما على التعامل مع محارف بترميز ASCII يمكننا تحديده كترميز للبيانات ضمن المخزن المؤقت كالتالي: > const asciiBuf = Buffer.alloc(5, 'a', 'ascii'); نلاحظ تمرير المحرف a كمعامل ثانِ وبذلك سيتم تخزينه ضمن المساحة الأولية التي ستُحجز للمخزن المؤقت الجديد، ويدعم نود افتراضيًا صيغ ترميز المحارف التالية: ترميز ASCII ويُمثّل بالسلسلة النصية ascii. ترميز UTF-8 ويُمثّل بالسلسلة النصية utf-8 أو utf8. ترميز UTF-16 ويُمثّل بالسلسلة النصية utf-16le أو utf16le. ترميز UCS-2 ويُمثّل بالسلسلة النصية ucs-2 أو ucs2. ترميز Base64 ويُمثّل بالسلسلة النصية base64. الترميز الست عشري Hexadecimal ويُمثّل بالسلسلة النصية hex. الترميز ISO/IEC 8859-1 ويُمثّل بالسلسلة النصية latin1 أو binary. حيث يمكن استخدام أي من أنواع الترميز السابقة مع أي تابع من الصنف Buffer يقبل ضمن معاملاته معاملًا بالاسم encoding لتحديد صيغة الترميز، ومن ضمنها التابع alloc() الذي تعرفنا عليه. قد نحتاج أحيانًا لإنشاء مخزن مؤقت يُعبر عن بيانات جاهزة موجودة مسبقًا، كقيمة متغير أو سلسلة نصية أو مصفوفة، حيث يمكننا ذلك باستخدام التابع from() الذي يدعم إنشاء مخزن مؤقت جديد من عدة أنواع من البيانات وهي: مصفوفة من الأعداد التي تتراوح قيمها بين 0 و 255،حيث يمثل كل عدد منها قيمة بايت واحد. كائن من نوع ArrayBuffer والذي يخزن داخله حجمًا ثابتًا من البايتات. سلسلة نصية. مخزن مؤقت آخر. أي كائن جافاسكربت يملك الخاصية Symbol.toPrimitive التي تُعبر عن طريقة تحويل هذا الكائن إلى بيانات أولية، مثل القيم المنطقية boolean أو null أو undefined أو الأعداد number أو السلاسل النصية string أو الرموز symbol. لنختبر الآن طريقة إنشاء مخزن مؤقت جديد من سلسلة نصية باستخدام التابع from كالتالي: > const stringBuf = Buffer.from('My name is Hassan'); ينتج بذلك لدينا كائن مخزن مؤقت جديد يحتوي على قيمة السلسلة النصية My name is Hassan، ويمكننا كما ذكرنا إنشاء مخزن مؤقت جديد من مخزن مؤقت آخر مثلًا كالتالي: > const asciiCopy = Buffer.from(asciiBuf); ينتج بذلك لدينا المخزن المؤقت asciiCopy والذي هو نسخة مطابقة من المخزن الأول asciiBuf، وبذلك نكون قد تعرفنا على طرق إنشاء المخازن المؤقتة، وفي الفقرة التالية سنتعلم طرق قراءة البيانات منها. القراءة من المخزن المؤقت يوجد عدة طرق تمكننا من قراءة بيانات المخزن المؤقت، حيث يمكنن قراءة بايت واحد محدد فقط منه إذا أردنا، أو قراءة كل البيانات دفعة واحدة، ولقراءة بايت واحد فقط يمكن الوصول إليه عبر رقم ترتيب مكان هذا البايت ضمن المخزن المؤقت، حيث تُخزِّن المخازن المؤقتة البيانات بترتيب متتابع تمامًا كالمصفوفات، ويبدأ ترتيب أول مكان للبيانات داخلها من الصفر 0 تمامًا كالمصفوفات، ويمكن استخدام نفس صيغة الوصول إلى عناصر المصفوفة لقراءة البايتات بشكل مفرد من المخزن مؤقت. لنختبر ذلك نبدأ بإنشاء مخزن مؤقت جديد من سلسلة نصية كالتالي: > const hiBuf = Buffer.from('Hi!'); ونحاول قراءة أول بايت من هذا المخزن كالتالي: > hiBuf[0]; بعد الضغط على زر الإدخال ENTER وتنفيذ التعليمة السابقة سيظهر لنا النتيجة التالية: 72 حيث يرمز العدد 72 ضمن ترميز UTF-8 للحرف H وهو أول حرف من السلسلة النصية المُخزنة، حيث تقع قيمة أي بايت ضمن المجال من صفر 0 إلى 255، وذلك لأن البايت يتألف من 8 بتات أو bits، وكل بت بدوره يمثل إما صفر 0 أو واحد 1، فأقصى قيمة يمكن تمثيلها بسلسلة من ثمانية بتات تساوي 2⁸ وهو الحجم الأقصى للبايت الواحد، أي يمكن للبايت تمثيل قيمة من 256 قيمة ممكنة، وبما أن أول قيمة هي الصفر فأكبر عدد يمكن تمثيله في البايت الواحد هو 255، والآن لنحاول قراءة قيمة البايت الثاني ضمن المخزن كالتالي: > hiBuf[1]; سنلاحظ ظهور القيمة 105 والتي ترمز للحرف الصغير i، والآن نحاول قراءة آخر بايت من هذا المخزن كالتالي: > hiBuf[2]; نلاحظ ظهور القيمة 33 والتي ترمز إلى إشارة التعجب !، ولكن ماذا سيحدث لو حاولنا قراءة بايت غير موجود بتمرير قيمة لمكان خاطئ ضمن المخزن كالتالي: > hiBuf[3]; سنلاحظ ظهور القيمة التالية: undefined وهو نفس ما سيحدث لو حاولنا الوصول إلى عنصر غير موجود ضمن مصفوفة ما. والآن بعد أن تعرفنا على طريقة قراءة بايت واحد من البيانات ضمن المخزن مؤقت، سنتعرف على طريقة لقراءة كل البيانات المخزنة ضمنه دفعة واحدة. يوفر كائن المخزن مؤقت التابعين toString() و toJSON() والذي يعيد كل منهما البيانات الموجودة ضمن المخزن دفعة واحدة كل منهما بصيغة مختلفة، ونبدأ بالتابع toString() والذي يحول البايتات ضمن المخزن المؤقت إلى قيمة سلسلة نصية ويعيدها، لنختبر ذلك باستدعائه على المخزن المؤقت السابق hiBuf كالتالي: > hiBuf.toString(); سنلاحظ ظهور القيمة التالية: 'Hi!' وهي قيمة السلسلة النصية التي خزناها ضمن المخزن المؤقت عند إنشاءه، ولكن ماذا سيحدث لو استدعينا التابع toString() على مخزن مؤقت تم إنشاءه من بيانات من نوع مختلف؟ لنختبر ذلك بإنشاء مخزن مؤقت جديد فارغ بحجم 10 بايت كالتالي: > const tenZeroes = Buffer.alloc(10); ونستدعي التابع toString() ونلاحظ النتيجة: > tenZeroes.toString(); سيظهر ما يلي: '\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000' حيث تقابل السلسلة النصية \u0000 المحرف في ترميز Unicode المقابل للقيمة NULL، وهو ما يقابل قيمة الصفر 0، حيث يعيد التابع toString() ترميز UTF-8 للبايتات المخزنة في حال كانت البيانات ضمن المخزن المؤقت ليست من نوع سلسلة نصية، ويقبل التابع toString() معامل اختياري بالاسم encoding لتحديد ترميز البيانات المطلوب، حيث يمكن باستخدامه تعديل ترميز قيمة السلسلة النصية التي يعيدها التابع، فيمكن مثلًا قراءة نفس البيانات للمخزن hiBuf السابق لكن بالترميز الست عشري كالتالي: > hiBuf.toString('hex'); سنلاحظ ظهور النتيجة التالية: '486921' حيث تُعبر تلك القيمة عن الترميز الست عشري للبايتات التي تتألف منها السلسلة النصية Hi!. ويُستفاد في نود من تلك الطريقة لتحويل ترميز بيانات ما من شكل لآخر، بإنشاء مخزن مؤقت جديد يحوي قيمة السلسلة النصية المراد تحويلها ثم استدعاء التابع toString() مع تمرير الترميز الجديد المرغوب به. أما وفي المقابل يعيد التابع toJSON() البيانات ضمن المخزن المؤقت كأعداد تمثل قيم البايتات المخزنة مهما كان نوعها، والآن لنختبر ذلك على كل من المخزنين السابقين hiBuf و tenZeroes ونبدأ بإدخال التعلمية التالية: > hiBuf.toJSON(); سنلاحظ ظهور القيمة التالية: { type: 'Buffer', data: [ 72, 105, 33 ] } يحوي الكائن الناتج من استدعاء التابع toJSON() على خاصية النوع type بالقيمة نفسها دومًا وهي Buffer، حيث يُستفاد من هذه القيمة لتمييز نوع كائن JSON هذا عن الكائنات الأخرى، ويحتوي على خاصية البيانات data وهي مصفوفة من الأعداد التي تمثل البايتات المخزنة، ونلاحظ أنها تحتوي على القيم 72 و 105 و 33 بالترتيب وهي نفس القيم التي ظهرت لنا سابقًا عند محاولة قراءة البايتات المخزنة بشكل مفرد. والآن لنختبر استدعاء التابع toJSON() على المخزن الفارغ tenZeroes: > tenZeroes.toJSON(); سنلاحظ ظهور النتيجة التالية: { type: 'Buffer', data: [ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ] } الخاصية type تحوي نفس القيمة السابقة، بينما البيانات في المصفوفة هي عشرة أصفار تمثل البايتات العشرة الفارغة التي يحويها المخزن المؤقت، وبذلك نكون قد تعلمنا طرق قراءة البيانات من المخازن المؤقتة، وفي الفقرة التالية سنتعلم طريقة التعديل على تلك البيانات ضمن المخزن المؤقت. التعديل على المخزن المؤقت يوجد عدة طرق للتعديل على البيانات ضمن المخزن المؤقت، وهي مشابهة لطريقة قراءة البيانات حيث يمكن إما تعديل قيمة بايت واحد مباشرة باستخدام نفس صيغة الوصول لعناصر المصفوفات، أو كتابة محتوى جديد وتبديل المحتوى المخزن مسبقًا. ولنبدأ بالتعرف على الطريقة الأولى لذلك سنستخدم المخزن السابق hiBuf الذي يحتوي على قيمة السلسلة النصية Hi! داخله، ولنحاول تعديل محتوى كل بايت منه على حدى إلى أن تصبح القيمة الجديدة هي Hey، حيث نبدأ بتعديل الحرف الثاني من المخزن hiBuf إلى الحرف e كالتالي: > hiBuf[1] = 'e'; نتأكد من صحة التعديل السابق بقراءة محتوى المخزن المؤقت الجديد باستدعاء التابع toString() كالتالي: > hiBuf.toString(); نلاحظ ظهور القيمة التالية: 'H\u0000!' القيمة الغريبة التي ظهرت تدل على أن المخزن مؤقت يقبل فقط القيم العددية عند تخزينها داخله، لذا لا يمكن تمرير الحرف e كسلسلة نصية مباشرةً، بل يجب تمرير القيمة الثنائية المقابلة له كالتالي: > hiBuf[1] = 101; الآن يمكننا معاينة القيمة الجديدة والتأكد: > hiBuf.toString(); نحصل على القيمة التالية: 'He!' نعدل الحرف الأخير من هذه القيمة وهو العنصر الثالث ونضع القيمة الثنائية المقابلة للحرف y كالتالي: > hiBuf[2] = 121; نتأكد من المحتوى بعد التعديل: > hiBuf.toString(); نحصل على القيمة: 'Hey' ماذا سيحدث لو حاولنا تعديل قيمة بايت يقع خارج مجال بيانات المخزن المؤقت؟ سنلاحظ تجاهل المخزن لتلك العملية وتبقى القيمة المخزنة ضمنه كما هي، لنختبر ذلك بكتابة الحرف o إلى المحرف الرابع الغير موجود ضمن المخزن السابق كالتالي: > hiBuf[3] = 111; نعاين قيمة المخزن بعد ذلك التعديل: > hiBuf.toString(); ونلاحظ أن القيمة بقيت كما هي دون تعديل: 'Hey' الطريقة الأخرى للتعديل على محتوى المخزن تكون بكتابة عدة بايتات معًا باستخدام التابع write() الذي يقبل سلسلة نصية كمعامل له تعبر عن المحتوى الجديد للبيانات، لنختبر ذلك عبر تعديل محتوى المخزن hiBuf إلى محتواه السابق Hi! كالتالي: > hiBuf.write('Hi!'); نلاحظ أن تنفيذ التعليمة السابقة يعيد القيمة 3 وهي عدد البايتات التي تم تعديلها ضمن المخزن في تلك العملية، حيث يعبر كل بايت عن محرف واحد لأننا نستخدم الترميز UTF-8، وفي حال كان المخزن يستخدم ترميز آخر مثل UTF-16 ففيه يُمثَّل كل محرف على 2 بايت، عندها سيعيد تنفيذ تابع الكتابة write() بنفس الطريقة القيمة 6 للدلالة على عدد البايتات التي تمثل المحارف الثلاث المكتوبة. والآن لنتأكد من المحتوى الجديد بعد التعديل نستدعيtoString() كالتالي: > hiBuf.toString(); نحصل على القيمة: 'Hi!' هذه الطريقة أسرع من طريقة تعديل كل بايت على حدى، ولكن ماذا سيحدث لو كتبنا بيانات بحجم أكبر من حجم المخزن الكلي؟ سيقبل المخزن البيانات المقابلة لحجمه فقط ويهمل البقية، لنختبر ذلك بإنشاء مخزن مؤقت بحجم 3 بايت كالتالي: > const petBuf = Buffer.alloc(3); ونحاول كتابة سلسلة نصية بأربعة محارف مثلًا Cats كالتالي: > petBuf.write('Cats'); نلاحظ أن ناتج التعليمة السابقة هي القيمة 3 أي تم تعديل قيمة ثلاث بايتات فقط وتجاهل باقي القيمة المُمررة، لنتأكد من القيمة الجديدة كالتالي: > petBuf.toString(); نلاحظ القيمة الجديدة: 'Cat' حيث يُعدل التابع write() البايتات بالترتيب فعدّل أول ثلاث بايتات فقط ضمن المخزن وتجاهل البقية. والآن لنختبر ماذا سيحدث لو كتبنا قيمة بحجم أقل من حجم المخزن الكلي، لهذا نُنشئ مخزن مؤقت جديد بحجم 4 بايت كالتالي: > const petBuf2 = Buffer.alloc(4); ونكتب القيمة الأولية داخله كالتالي: > petBuf2.write('Cats'); ثم نكتب قيمة جديدة حجمها أقل من حجم المخزن الكلي كالتالي: > petBuf2.write('Hi'); وبما أن البيانات ستكتب بالترتيب بدئًا من أول بايت سنلاحظ نتيجة ذلك عند معاينة القيمة الجديدة للمخزن: > petBuf2.toString(); ليظهر القيمة التالية: 'Hits' تم تعديل قيمة أول بايتين فقط، وبقيت البايتات الأخرى كما هي دون تعديل. تكون البيانات التي نود كتابتها موجودة أحيانًا ضمن مخزن مؤقت آخر، حيث يمكننا في تلك الحالة نسخ محتوى ذلك المخزن باستدعاء التابع copy()، لنختبر ذلك بداية بإنشاء مخزنين جديدين كالتالي: > const wordsBuf = Buffer.from('Banana Nananana'); > const catchphraseBuf = Buffer.from('Not sure Turtle!'); يحوي كل من المخزنين wordsBuf و catchphraseBuf على بيانات من نوع سلسلة نصية، فإذا أردنا تعديل قيمة المخزن catchphraseBuf ليحوي على القيمة Nananana Turtle! بدلًا من Not sure Turtle! يمكننا استدعاء تابع النسخ copy() لنسخ القيمة Nananana من المخزن wordsBuf إلى catchphraseBuf، حيث نستدعي التابع copy() على المخزن الحاوي على المعلومات المصدر لنسخها إلى مخزن آخر، ففي مثالنا النص الذي نريد نسخه موجود ضمن المخزن wordsBuf، لذا نستدعي تابع النسخ منه كالتالي: > wordsBuf.copy(catchphraseBuf); حيث يُعبّر معامل الوجهة target المُمرر له عن المخزن المؤقت الذي ستُنسخ البيانات إليه، ونلاحظ ظهور القيمة 15 كنتيجة لتنفيذ التعليمة السابقة وهي تعبر عن عدد البايتات التي تم كتابتها، ولكن بما أن النص Nananana مكوّن من ثمانية محارف فقط فهذا يدل على عمل مختلف نفذه تابع النسخ، لنحاول معرفة ماذا حدث ونعاين القيمة الجديدة باستخدام التابع toString() ونلاحظ النتيجة: > catchphraseBuf.toString(); نلاحظ القيمة الجديدة: 'Banana Nananana!' نلاحظ أن تابع النسخ copy() قد نسخ كامل المحتوى من المخزن wordsBuf وخزنه ضمن catchphraseBuf، ولكن ما نريده هو نسخ قسم من تلك البيانات فقط وهي القيمة Nananana، لنعيد القيمة السابقة للمخزن catchphraseBuf أولًا ثم نحاول تنفيذ المطلوب كالتالي: > catchphraseBuf.write('Not sure Turtle!'); يقبل التابع copy() عدة معاملات تمكننا من تحديد البيانات التي نرغب بنسخها إلى المخزن المؤقت الوجهة وهي: الوجهة target وهو المعامل الإجباري الوحيد، ويعبر عن المخزن المؤقت الوجهة لنسخ البيانات. targetStart وهو ترتيب أول بايت ستبدأ كتابة البيانات إليه ضمن المخزن الوجهة، وقيمته الافتراضية هي الصفر 0، أي بدء عملية الكتابة من أول بايت ضمن المخزن الوجهة. sourceStart وهو ترتيب أول بايت من البيانات التي نرغب بنسخها من المخزن المصدر. sourceEnd وهو ترتيب آخر بايت من البيانات الذي ستتوقف عملية النسخ عنده في المخزن المصدر، وقيمته الافتراضية هي الطول الكلي للبيانات ضمن المخزن المصدر. باستخدام تلك المعاملات يمكننا تحديد الجزء Nananana من المخزن wordsBuf ليُنسخ إلى المخزن catchphraseBuf، حيث نمرر المخزن catchphraseBuf كمعامل الوجهة target كما فعلنا سابقًا، ونمرر القيمة 0 للمعامل targetStart لكتابة القيمة Nananana في بداية المخزن catchphraseBuf، أما للقيمة sourceStart سنمرر 7 وهو ترتيب بداية أول محرف من القيمة Nananana ضمن المخزن wordsBuf، وللقيمة sourceEnd نمرر الحجم الكلي للمخزن المصدر، ليكون الشكل النهائي لاستدعاء تابع النسخ بعد تخصيص المعاملات السابقة كالتالي: > wordsBuf.copy(catchphraseBuf, 0, 7, wordsBuf.length); سيظهر هذه المرة القيمة 8 كنتيجة لتلك العملية ما يعني أن القيمة التي حددناها فقط هي ما تم نسخه، ونلاحظ كيف استخدمنا الخاصية wordsBuf.length لتمرير حجم المخزن كقيمة للمعامل sourceEnd، وهي نفس الخاصية length الموجودة ضمن المصفوفات، والآن لنعاين القيمة الجديدة للمخزن catchphraseBuf ونتأكد من النتيجة: > catchphraseBuf.toString(); نلاحظ القيمة الجديدة: 'Nananana Turtle!' بذلك نكون قد عدلنا البيانات ضمن المخزن catchphraseBuf عن طريق نسخ جزء محدد من بيانات المخزن wordsBuf إليه. والآن بعد أن انتهينا من تنفيذ الأمثلة في هذا الفصل يمكنك الخروج من جلسة REPL حيث ستُحذف كل المتغيرات السابقة التي عرفناها بعد عملية الخروج هذه، ولذلك ننفذ أمر الخروج كالتالي: > .exit ختامًا تعرفنا في هذا المقال على المخازن المؤقتة والتي تمثل مساحة محددة من الذاكرة محجوزة لتخزين البيانات بالصيغة الثنائية، وتعلمنا طرق إنشاء المخازن المؤقتة، سواء الجديدة أو التي تحتوي على بيانات موجودة مسبقًا، وتعرفنا بعدها على طرق قراءة تلك البيانات من المخزن سواء بقراءة كل بايت منه على حدى أو قراءة المحتوى كاملًا باستخدام التابعين toString() و toJSON()، ثم تعرفنا على طرق الكتابة إلى المخازن لتعديل البيانات المخزنة ضمنها، سواء بكتابة كل بايت على حدى أو باستخدام التابعين write() و copy(). يفتح التعامل مع المخازن المؤقتة في نود Node.js الباب للتعامل مع البيانات الثنائية مباشرة، فيمكن مثلًا دراسة تأثير صيغ الترميز المختلفة للمحارف على البيانات المخزنة، كمقارنة صيغ الترميز المختلفة مع الصيغتين UTF-8 و ASCII وملاحظة فرق الحجم بينها، كما يمكن مثلًا تحويل البيانات المخزنة من صيغة UTF-8 إلى صيغ الترميز الأخرى، ويمكنك الرجوع إلى التوثيق الرسمي العربي من نود للكائن Buffer للتعرف عليه أكثر. ترجمة -وبتصرف- للمقال Using Buffers in Node.js لصاحبه Stack Abuse. اقرأ أيضًا المقال السابق: إنشاء خادم ويب في Node.js باستخدام الوحدة HTTP مقدمة إلى Node.js أساسيات التخزين المؤقت للويب Web Caching: المصطلحات الأساسية أساسيات التخزين المؤقت للويب Web Caching: ترويسات HTTP واستراتيجيات التخزين المؤقت1 نقطة
-
بعد أن تعلّمنا في الدّرس السّابق كيفيّة التعامل مع كل من الصفوف، المجموعات والقواميس في لغة بايثون ، سنكمل مشوار تعلّم هذه اللغة الجميلة. سنتعلّم في هذا الدّرس كيفيّة التّعامل مع التّعابير الشّرطية، و كيفيّة استعمال الجمل الشّرطية في برامجنا وكيفّية القيّام بإزاحة مناسبة عند التّعامل مع أجزاء متعدّدة من الشّيفرة، وسنكمل هذا الدّرس بمثال تطبيقي يتجلى في برنامج تسجيل حساب والولوج إليه. مع التذكير بأنّ جميع الشّيفرات التّي تبدأ بعلامة <<< يجب أن تنفّذ على مفسر بايثون. التعابير الشرطية Conditional Expressions توفّر لنا لغة بايثون عدّة مُعاملات لمُقارنة القيّم، ونتيجة المُقارنة تكون قيمة منطقيّة Boolean إمّا True أو False. >>> 2 < 3 False >>> 2 > 3 True إليك قائمة بالمُعاملات المُتوفّرة في لغة بايثون. == يُساوي =! لا يُساوي > أصغر من < أكبر من => أصغر من أو تُساوي =< أكبر من أو تُساوي ويُمكن استعمال هذه المُعاملات أكثر من مرّة في السّطر الواحد: >>> x = 5 >>> 2 < x < 10 True >>> 2 < 3 < 4 < 5 < 6 True وتعمل المعاملات على السّلاسل النّصية كذلك (التّرتيب يكون بشكل معجمي، أي حسب ترتيب الكلمات في المُعجم). >>> "python" > "perl" True >>> "python" > "java" True يُمكن الجمع بين القيّم المنطقيّة بمعاملات منطقيّة: a and b: صحيح فقط إذا كان كل من a و b يحملان القيمة True. a or b: صحيح إذا كانت إحدى القيمتين صحيحة. not a: صحيح فقط إذا كان a يحمل القيمة False. وإليك مثالا على المُعاملات المنطقيّة: >>> True and True True >>> True and False False >>> 2 < 3 and 5 < 4 False >>> 2 < 3 or 5 < 4 True الجملة الشرطية If تُستعمل الجملة الشّرطيّة If (إذا كان) لتنفيذ جزء من الشّيفرة إذا كان الشّرط مُحقّقا: >>> if x % 2 == 0: ... print 'even' ... even >>> في المثال أعلاه طُبعَت الكلمة even لأنّ باقي قسمة 42 على 2 يُساوي صفرا (x % 2 == 0)، وبالتّالي فقد تحقّق الشّرط، فلو أنّ الشّرط لم يتحقّق لما أرجع المُفسّر أي نتيجة. يُمكن استخدام If كشرط لتنفيذ جزء منفصل من الشيفرة بالإزاحة (انظر فصل الإزاحة والمساحات أسفله)، ويكون هذا الأمر مُفيدا عندما يحتوي برنامجك على الكثير من الجمل. لاحظ بأنّ الجمل التّابعة للسّطر if x % 2 == 0 مُزاحةٌ بأربع مسافات بيضاء. الجملة else يُمكن أن تزيد على جملة If بند else وترجمته (وإلّا / إذا لم يكن ذلك صحيحًا)، والتي تنفّذ الشيفرة التّي من بعدها إذا كان الشّرط الأول خاطئا. >>> x = 3 >>> if x % 2 == 0: ... print 'even' ... else: ... print 'odd' ... odd >>> في المثال أعلاه، كان الشّرط الأول خاطئا لأن باقي قسمة 3 على 2 لا يُساوي صفرًا، ولذلك انتقل البرنامج لتنفيذ الشّيفرة بعد جملة else وقام بطباعة كلمة odd. الجملة elif يُمكن كذلك أن تزيد جملة elif وهي اختصار لـ else if والتّي تعني "إذا لم يتحقّق الشرط فانظر الشّرط التّالي": >>> x = 42 >>> if x < 10: ... print 'one digit number' ... elif x < 100: ... print 'two digit number' ... else: ... print 'big number' ... two digit number >>> في المثال أعلاه، هناك شرطان مختلفان، أولا عند تحقّق شرط كون قيمة المتغيّر x أصغر من العدد 10 فسيطبع البرنامج الجملة التي تقول بأنّ قيمة المتغير x عبارة عن رقم واحد، إذا لم يتحقّق الشّرط السّابق فسينتقل البرنامج إلى الشّرط الذي يليه وهو ما يتبع الجملة elif أي إذا كانت قيمة المتغيّر x أصغر من 100 فسيطبع البرنامج الجملة التي تفيد بأنّ قيمة المتغير x عبارة عن رقمين والجملة else تنفَّذُ إذا لم يتحقّق أي شرط وتفيد بأنّ قيمة المتغيّر x عبارة عن عدد كبير (لأنّه يتكون من 3 أرقام فأكثر). المساحة والمسافات والإزاحة المساحات التّي تسبق الشّيفرات في بايثون مُهمّة جدّا لترتيب شيفرة البرنامج ولتسهيل قراءته، كما أنّها تكون ضروريّة في بايثون، فإن لم تتّبع قوانين الإزاحة فلن يعمل البرنامج، وتُستعمل هذه الطّريقة لفصل الشّيفرات التّابعة لجزء معيّن من البرنامج، والإزاحة تكون بكتابة الشّيفرة بعد عدد معيّن من المسافات، ومن المُفضّل أن تكون أربع مسافات بيضاء، انظر المثال: هل الشّرط محقّق؟ .... نعم الشّرط محقّق .... لا الشرط غير محقق انتهى البرنامج في المثال أعلاه، الجملتان "نعم الشّرط محقّق" و "لا الشّرط غير محقّق" تابعتان للجملة "هل الشّرط محقّق؟" أمّا الجملة "انتهى البرنامج" فتابعة للبرنامج عامّة. وقد اعتمدت على النّقاط لتوضيح فكرة الإزاحة فقط وعليك أن تستبدل النّقاط بالمسافات في ملفّات بايثون. يُمكن كذلك أن تكون الشّيفرة تابعة لجملة ما، والتّي بدورها تابعة لجملة أخرى، لتتّضحك الأمور أكثر، فكّر في شجرة عائلة تتكوّن من الآباء والأولاد: جدّ ....أب 1 ........ابن 1.1 ........ابن 1.2 ....أب 2 ........ابن 2.1 ........ابن 2.2 للجد ابنان، ولكل ابن ابنان، نستنتج من هذا أنّ كلّ ابن تابع لأبيه بإزاحة أربع مسافات، أمّا الحفيد فتابع للجدّ بثماني مسافات. وإلى الآن تعرّفنا على موضع واحد تكون فيه الإزاحة إجباريّة وهي الجمل الشّرطيّة، فمثلا البرنامج التّالي: x = 42 if x % 2 == 0: print 'even' print 'Hello World' في المثال أعلاه جميع السّطور المزاحة بأربع مسافات (أي الجملة التي تطبع كلمة even) تُنفّذ عند تحقّق الشّرط فقط، أي أنّها جزء مرتبط بالشّرط في البرنامج، أمّا الشيفرة التي لم تُسبق بمسافات فتنفّذ بشكل طبيعي وتعتبر ضمن البرنامج عامّة. أمّا إذا أردنا أن نطبع جملة بعد التّحقّق من شرطين فسيكون البرنامج كالتّالي: الشّرط الأول محقّق، إذن انتقل إلى الشّرط الثّاني. الشّرط الأول غير محقّق، إذن لا تنتقل إلى الشّرط الثّاني. إليك تطبيقا للأمر في لغة بايثون: # البرنامج x = 42 if x % 2 == 0: print 'even' if x / 2 == 21: print '42/2 = 21 Is true' print 'Hello World' # المُخرج even 42/2 = 21 Is true Hello World في المثال أعلاه، قمنا بالتّحقّق من أنّ باقي قسمة العدد 42/2 يساوي 0، وبما أنّ الشّرط مُحقّق فقد طبعت الكلمة even وانتقل المُفسّر إلى الشّرط التّالي وهو التّحقّق من أن 42/2 يُساوي 21 وبما أنّه بدوره شرط مُحقّق أيضا فقد قام البرنامج طباعة الجملة 42/2 = 21 Is true وبعدها قام بطباعة الجملة Hello World لأنّها غير مُرتبطة بأي شرط. وبطبيعة الحال إذا لم يكن الشّرط الأول محقّقا، فلن ينتقل المُفسّر للشّرط التّالي ولن يقوم بأي شيء حتّى ولو كان الشّرط مُحقّقا. الحصول على قيم من مستخدم البرنامج يُمكن أن تطلب من المُستخدم أن يُدخل قيمة معيّنة، ثمّ تسند هذه القيمة إلى مُتغيّر في البرنامج، فمثلا لنقل بأنّك ترغب بالتّرحيب بالمُستخدم، أولا يجب أن تطلب منه أن يُدخل اسمه، بعدها تقوم بطباعة الجملة "Hello, Person"، بحيث تستبدل Person بالقيمة التّي أدخلها المُستخدم، انظر المثال: >>> person = raw_input('Enter your name: ') >>> 'Hello ', person إذا نفّذت البرنامج فسيكون المُخرج كالتّالي: Enter your name: وبعد إدخال اسمي والضّغط على مفتاح ENTER، فإنّ المُخرج سيكون كالتّالي: Hello Abdelhadi ما يحدث هو أن البرنامج يأخذ القيمة المُدخلة ويُسندها للمُتغيّر person وبعدها تستطيع أنت أن تطبع المُتغيّر للمُستخدم. تطبيق لنطبّق ما تعلّمناه ولننشئ برنامجا بسيطًا لجدولة مُقابلات العمل: سنطلب من المُستخدم توفير المعلومات التّاليّة: اسم المُتقدّم اسم المسؤول عن المُقابلة وقتُ المُقابلة وبعدها سنطبع جملة تحتوي على جميع المعلومات التّي قدّمها المُستخدم، افتح ملفّا باسم interview.py واكتب فيه التّالي: applicant = raw_input('Enter the applicant name: ') interviewer = raw_input('Enter the interviewer name: ') time = raw_input('Enter the appointment time: ') print interviewer, "will interview", applicant, "at", time في المثال أعلاه نطلب أولا من مُستخدم البرنامج أن يُدخل اسم المُتقدّم ثمّ تُسندُ القيمة إلى المتغيّر applicant، بعد ذلك يُطلب اسم المسؤول عن المُقابلة وتُسند قيمته إلى المُتغيّر interviewer وبعد ذلك نحصل على وقتُ المُقابلة من المُستخدم ونُسند القيمة إلى المُتغيّر time، وفي النّهاية نطبع الجملة الجامعة لكل المعلومات وتقديمها بشكل أسهل. جرّب الأمر بنفسك، وأسند القيّم التّي تريد، وانظر إلى النّتيجة، العب بالشّيفرة، أضف مميّزات أخرى كالتّحقّق من أن عُمر المُتقدّم أكبر من 18 عاما مثلا، وتذكّر بأنّ التعلّم بالتّطبيق أفضل من أي طريقة أخرى. تطبيق عملي: إنشاء برنامج تسجيل دخول بسيط الآن حان الوقت لنطبّق ما تعلّمناه، ولنقم بكتابة برنامج تسجيل دخول بسيط، ومبدأه كالتّالي: يُرحب البرنامج بالمُستخدم. يطلبُ منه معلومات التّسجيل. يفيد البرنامج المُستخدم بأنّ التّسجيل قد تم بنجاح أو بعكس ذلك. يطلب البرنامج معلومات الدّخول. يتحقّق البرنامج من أنّ المعلومات التّي أدخلها صحيحة. إذا كانت صحيحة، يطبع البرنامج رسالة نجاح. إذا كانت خاطئة، يطبع البرنامج رسالة فشل. المُتغيّرات التّي سنعتمد عليها: username: اسم المُستخدم password: كلمة المرور password_verification: تأكيد كلمة المرور (فقط للتّأكد من أنّ المُستخدم أدخل نفس كلمة المرور وأنّه يتذكّرها دون مشاكل). أولا جملة التّرحيب: print 'Hello User, this is a basic sign up/login Program' ثانيّا لنطلب من المُستخدم توفير قيّم المتغيرّات (username: اسم المُستخدم، password: كلمة المرور، password_verification: تأكيد كلمة المرور): username = raw_input('Enter your username please: ') password = raw_input('Enter the your password please: ') password_verification = raw_input('Verify password: ') الآن لنتحقق من أنّ كلمة المرور التّي أدخلها المستخدم في البداية هي نفسها التّي أدخلها عند تأكيد كلمة المرور وذلك بمقارنة المتغيّرين password و password_verification فإذا كانا يحملان نفس القيمة فهذا يعني بأنّ التّسجيل ناجح وسنطبع للمُستخدم جملة تفيد بأنّ عملية التّسجيل قد نجحت. أما إذا لم تكن القيم متساوية سنطبع جملة تفيد المستخدم بأنّ كلمة المرور وتأكيدها لا يتوافقان، ويمكن القيام بالأمر بالأسطر الآتية: if password == password_verification: print 'You have been successfully signed up!' else: print 'The password and the password verification don't match! Please try again' لقد انتهينا الآن من برمجة نظام التّسجيل، ويجب علينا الانتقال إلى الخطوة التّالية وهي مرحلة تسجيل الدّخول. بعد طباعة الجملة التّي تفيد بأنّ المستخدم قام بالتّسجيل بنجاح (انظر الشّيفرة أعلاه) سنطلب منه معلومات الولوج وذلك بإدخال اسم مُستخدمه وكلمة المرور، سنعيّن هذه القيم المدخلة حديثا إلى متغيّرين جديدين، وذلك لمقارنتهما مع قيم المتغيّرين القديمين، إذا كانت القيم توافق ما أدخله قبل قليل فسنطبع جملة تفيد بأنّ عمليّة الولوج قد نجحت، إذا لم يكن الأمر كذلك سنطبع جملة تفيد المستخدم بأنّ القيم التّي أدخلها خاطئة ثمّ يتوقّف البرنامج. الآن، لنطوّر الشّيفرة أعلاه وندخل عليها التّعديلات المطلوبة لإجراء عمليّة الولوج: if password == password_verification: print 'You have Successfully Signed up! \n' username_sign_in = raw_input('Enter your username please: ') password_sign_in = raw_input('Enter your password please: ') if username_sign_in == username and password_sign_in == password: print 'You have Successfully Signed in!' else: print 'username or password do not match! Please try again!' else: print 'The password and the password verification do not match! Please try again' لاحظ استخدام المعامل المنطقي and عند التّحقّق من أنّ اسم المستخدم وكلمة المرور المدخلتان (المخزّنة في المتغيّرين username_sign_in و password_sign_in) توافقان ما تم إدخاله من قبل. العامل and يحرص على أنّ كلا الشّرطين محققان. وبهذا نكون قد انتهينا من البرمجية الصغيرة والبسيطة، وهذه هي الشّيفرة الكاملة: print 'Hello User, this is a basic sign up/login Program' username = raw_input('Enter your username please: ') password = raw_input('Enter the your password please: ') password_verification = raw_input('Verify password: ') if password == password_verification: print 'You have Successfully Signed up! \n' username_sign_in = raw_input('Enter your username please: ') password_sign_in = raw_input('Enter your password please: ') if username_sign_in == username and password_sign_in == password: print 'You have Successfully Signed in!' else: print 'username or password do not match! Please try again!' else: print 'The password and the password verification do not match! Please try again' تمارين تمرين 1 ما مُخرجات البرنامج التّالي: print 2 < 3 and 3 > 1 print 2 < 3 or 3 > 1 print 2 < 3 or not 3 > 1 print 2 < 3 and not 3 > 1 تمرين 2 ما مُخرجات البرنامج التّالي: x = 4 y = 5 p = x < y or x < z print p تمرين 3 ما مُخرجات البرنامج التّالي: x = 4 y = 5 p = x < y or x < z print p تمرين 4 ماذا سيحدث بعد تنفيذ الشّيفرة التّالية، هل ستحدث أي أخطاء؟ علّل جوابك. x = 2 if x == 2: print x else: print y تمرين 5 ماذا سيحدث بعد تنفيذ الشّيفرة التّالية، هل ستحدث أي أخطاء؟ علّل جوابك. x = 2 if x == 2: print x else: x + تمرين 6 هل الإزاحة في البرنامج التّالي صحيحة؟ إذا لم يكن الأمر كذلك، فأصلح الخطأ. x = 2 if x == 2: print x if x+1 == 3: print 'x+1 = 3' else: print x + 2 مصادر درس Input and Output للكاتب Dr. Andrew N. Harrington. كتاب Python Practice Book لكاتبه Anand Chitipothu.1 نقطة