Node.js: المخازن المؤقتة
آخر تحديث: 2026-08-26
أثناء تطوير خدمة تحميل الصور، لاحظ بوب أن ملفات JPEG التي يقوم المستخدمون بتحميلها تتحول إلى سلسلة من الأحرف المشوشة بعد قراءتها بواسطة fs.readFile — حيث يتم تخزين سلاسل JavaScript باستخدام ترميز UTF-16، الذي لا يستطيع تمثيل جميع قيم البايتات بين 0x00 و 0xFF بشكل صحيح. واكتشف أن Node.js يوفر كائنًا عالميًا Buffer مصممًا خصيصًا للتعامل مع البيانات الثنائية. وهي تعمل ككتلة من الذاكرة الخام، حيث يتوافق كل موضع تمامًا مع بايت واحد. بدءًا من قراءة الملفات وكتابتها وصولاً إلى الإرسال عبر الشبكة، ومن معالجة الصور إلى الحسابات التشفيرية، يُعد Buffer الأداة الأساسية لمعالجة البيانات الثنائية في Node.js.
1. ما ستتعلمه
- استخدم
Buffer.alloc()/Buffer.from()/Buffer.concat()/buf.slice()لإنشاء المخازن المؤقتة وإدارتها - فهم آليات الترميز وفك الترميز (UTF-8 / Base64 / سداسي عشري / ASCII / ثنائي)
- تنفيذ التحويل بين
BufferوString - استخدم
fs.readFileلقراءة ملف ثنائي والحصول على buffer - استخدم
Buffer.byteLength/Buffer.isBuffer/buf.length/buf.toString()للاستعلام عن معلومات المخزن المؤقت - فهم العلاقة بين TypedArray و Buffer
- تعرف على التطبيقات العملية لـ Buffer في مجالات التواصل الشبكي ومعالجة الملفات وسيناريوهات التشفير
2. ما هو المخزن المؤقت؟
Buffer هو كائن عالمي توفره Node.js ويُستخدم لتخصيص منطقة ذات حجم ثابت من البيانات الثنائية الأولية خارج كومة V8. يشغل كل عنصر 1 بايت (8 بت)، وتتراوح قيمه بين 0 و255. لا يتطلب Buffer وجود require، وهو جاهز للاستخدام فورًا.
const buf = Buffer.alloc(4);
console.log(buf);
console.log(buf.length);
<Buffer 00 00 00 00>
4
flowchart LR
subgraph Input["Input Source"]
D[Disk Files]
N[Network Request]
C[Cryptographic Operations]
end
subgraph Core["Node.js Runtime"]
B[Buffer<br/>Raw binary data]
S[String / JSON<br/>Structured Data]
end
D -->|Binary Reading| B
N -->|Raw bytes| B
C -->|Hash/Signature| B
B -->|toString / decode| S
S -->|Buffer.from / encode| B
B -->|Binary Writing| D
B -->|Raw bytes| N
3. كيفية إنشاء منطقة عازلة
يوفر Node.js عدة طرق لإنشاء «Buffer»، وتناسب كل طريقة سيناريوهات مختلفة.
| الطريقة | محتوى التهيئة | الأمان | الأداء | حالات الاستخدام |
|---|---|---|---|---|
Buffer.alloc(size) |
ملء 0 | آمن | بطيء | يتطلب مخزنًا مؤقتًا جديدًا خاليًا |
Buffer.allocUnsafe(size) |
لم يتم تهيئتها (بيانات قديمة) | غير آمنة | سريعة | حساسة للأداء ويتم ملؤها فورًا |
Buffer.from(array) |
قيمة عنصر المصفوفة | الأمان | عام | تم إنشاؤها من مصفوفة بايت |
Buffer.from(string, encoding) |
سلسلة مشفرة | الأمان | عام | إنشاء من سلسلة |
Buffer.from(buffer) |
نسخ محتوى الحافظة | الأمان | عام | استنساخ الحافظة |
▶ مثال: alloc مقابل allocUnsafe
const safe = Buffer.alloc(8);
console.log('alloc:', safe);
const unsafe = Buffer.allocUnsafe(8);
console.log('allocUnsafe:', unsafe);
alloc: <Buffer 00 00 00 00 00 00 00 00>
allocUnsafe: <Buffer a0 3f 1b 00 00 00 00 00>
▶ مثال: الإنشاء من المصفوفات والسلاسل
const fromArr = Buffer.from([72, 101, 108, 108, 111]);
console.log('from array:', fromArr.toString());
const fromStr = Buffer.from('Hello', 'utf8');
console.log('from string:', fromStr.toString());
const fromHex = Buffer.from('48656c6c6f', 'hex');
console.log('from hex:', fromHex.toString());
from array: Hello
from string: Hello
from hex: Hello
4. الترميز وفك الترميز
يدعم Node.js عدة أنظمة ترميز للأحرف، ويمكن لـ Buffer التحويل بينها بحرية.
| الرمز | الوصف | عدد البايتات لكل حرف | الاستخدامات الشائعة |
|---|---|---|---|
| utf8 | ترميز يونيكود متغير الطول | 1–4 | معالجة النصوص (الترميز الافتراضي) |
| ASCII | ASCII 7 بت | 1 | نص باللغة الإنجليزية العادية |
| base64 | مشفَّر بتنسيق Base64 | حوالي 4/3 من الحجم الأصلي | مضمن في صورة أو مرفق بريد إلكتروني |
| hex | التمثيل السداسي العشري | 2 | إخراج التصحيح، عرض التجزئة |
| ثنائي / latin1 | التعيين المباشر للأحرف لكل بايت | 1 | العمليات بايتًا بايتًا |
▶ مثال: تحويل الترميز
const text = 'Node.js Buffer';
const utf8Buf = Buffer.from(text, 'utf8');
console.log('utf8 bytes:', utf8Buf.length);
const base64 = utf8Buf.toString('base64');
console.log('base64:', base64);
const hex = utf8Buf.toString('hex');
console.log('hex:', hex);
const decoded = Buffer.from(base64, 'base64').toString('utf8');
console.log('decoded:', decoded);
utf8 bytes: 14
base64: Tm9kZS5qcyDliIbku6znqIvl
hex: 4e6f64652e6a7320e7bc93e586b2e58cba
decoded: Node.js Buffer
▶ مثال: بيانات صورة مشفرة بتنسيق Base64
const fs = require('fs');
const imgBuf = fs.readFileSync('logo.png');
const dataUri = 'data:image/png;base64,' + imgBuf.toString('base64');
console.log('Data URI length:', dataUri.length);
5. التحويل بين «Buffer» و«String»
يُعد التحويل بين Buffer وString إحدى العمليات الأكثر شيوعًا في أعمال التطوير اليومية.
| الاتجاه | الطريقة | الوصف |
|---|---|---|
| سلسلة → مخزن مؤقت | Buffer.from(str, encoding) |
الترميز الافتراضي هو utf8 |
| Buffer → String | buf.toString(encoding) |
الترميز الافتراضي هو utf8 |
| طول سلسلة الاستعلام بالبايت | Buffer.byteLength(str, encoding) |
تُرجع عدد البايتات بدلاً من عدد الأحرف |
▶ مثال: عدد الأحرف مقابل عدد البايتات
const str = 'Hello, World';
console.log('Number of characters:', str.length);
console.log('Number of bytes (utf8):', Buffer.byteLength(str, 'utf8'));
console.log('Number of bytes (ascii):', Buffer.byteLength(str, 'ascii'));
const buf = Buffer.from(str, 'utf8');
console.log('buf.length:', buf.length);
console.log('Restore the سلسلة أحرف:', buf.toString('utf8'));
Number of characters: 4
Number of bytes (utf8): 12
Number of bytes (ascii): 4
buf.length: 12
Restore the string: Hello, World
6. كيفية استخدام Buffer
(1) جدول مرجعي سريع لطرق المخازن المؤقتة الشائعة
| الطريقة | الغرض | القيمة المرجعة |
|---|---|---|
Buffer.alloc(size, fill) |
إنشاء وتعبئة | مخزن مؤقت جديد |
Buffer.from(source, enc) |
إنشاء من المصدر | مخزن مؤقت جديد |
Buffer.concat(list, totalLen) |
ربط عدة مخازن مؤقتة | مخزن مؤقت جديد |
Buffer.isBuffer(obj) |
التحقق مما إذا كان عبارة عن مخزن مؤقت | قيمة منطقية |
Buffer.byteLength(str, enc) |
طول السلسلة بالبايت | رقم |
buf.slice(start, end) |
عرض اللقطة (الذاكرة المشتركة) | عرض المخزن المؤقت |
buf.subarray(start, end) |
الشريحة نفسها | عرض المخزن المؤقت |
buf.toString(enc) |
تحويل إلى سلسلة نصية | سلسلة نصية |
buf.write(str, offset, enc) |
كتابة سلسلة أحرف إلى المخزن المؤقت | عدد البايتات المراد كتابتها |
buf.copy(target, tStart, sStart, sEnd) |
نسخ إلى مخزن الوجهة | عدد البايتات المراد نسخها |
buf.equals(otherBuf) |
مقارنة ما إذا كان المحتوى متطابقًا | قيمة منطقية |
buf.compare(otherBuf) |
مقارنة معجمية | -1 / 0 / 1 |
buf.fill(value, start, end) |
ملء النطاق المحدد | المخزن المؤقت الأصلي |
buf.indexOf(value, byteOffset) |
البحث عن موضع البايت | رقم |
▶ مثال: التسلسل باستخدام concat
const part1 = Buffer.from('Hello, ');
const part2 = Buffer.from('Buffer!');
const merged = Buffer.concat([part1, part2]);
console.log(merged.toString());
console.log('total length:', merged.length);
Hello, Buffer!
total length: 14
▶ مثال: طرق عرض الشرائح ومشاركة الذاكرة
const original = Buffer.from('ABCDEFGH');
const sliced = original.slice(0, 4);
sliced[0] = 88;
console.log('original:', original.toString());
console.log('sliced:', sliced.toString());
original: XBCDEFGH
sliced: XBCD
▶ مثال: الكتابة والنسخ
const buf = Buffer.alloc(16);
buf.write('Hi', 0, 'utf8');
buf.write('There', 2, 'utf8');
console.log('after write:', buf.toString('utf8', 0, 7));
const src = Buffer.from('COPY');
const dest = Buffer.alloc(8);
src.copy(dest, 2);
console.log('after copy:', dest.toString());
after write: HiThere
after copy: COPY
7. Buffer و TypedArray
تشترك الذاكرة الأساسية لـ Buffer في نفس آلية ArrayBuffer التي تتبعها TypedArray في ES2015. وتُعد Buffer في الأساس فئة فرعية من Uint8Array، لكنها تحتوي على طرق إضافية خاصة بـ Node.js.
| خاصية | Buffer | Uint8Array | ArrayBuffer |
|---|---|---|---|
| المصدر | الكائن العام في Node.js | مدمج في ES2015 | مدمج في ES2015 |
| الآلية الأساسية | قائمة تستند إلى ArrayBuffer | قائمة تستند إلى ArrayBuffer | ذاكرة ثنائية خام |
| ترتيب البايتات | يعتمد على النظام الأساسي | يعتمد على النظام الأساسي | لا يوجد مفهوم لترتيب البايتات |
| الطرق الخاصة | toString/slice/concat، إلخ |
طرق TypedArray القياسية | byteLength فقط |
| طريقة الإنشاء | Buffer.alloc/Buffer.from |
new Uint8Array() |
new ArrayBuffer() |
| التوافق بين الوحدات | يمكن تمريرها إلى المكونات الإضافية لـ C++ | يمكن تمريرها إلى واجهات برمجة التطبيقات على الويب | تنسيق أساسي موحد |
▶ مثال: التحويل بين Buffer و Uint8Array
const buf = Buffer.from([1, 2, 3, 4, 5]);
const uint8 = new Uint8Array(buf.buffer, buf.byteOffset, buf.byteLength);
console.log('Uint8Array:', uint8);
const backToBuf = Buffer.from(uint8.buffer);
console.log('Buffer:', backToBuf);
console.log('isBuffer:', Buffer.isBuffer(backToBuf));
Uint8Array: Uint8Array(5) [1, 2, 3, 4, 5]
Buffer: <Buffer 01 02 03 04 05>
isBuffer: true
▶ مثال: قراءة القيم متعددة البايت باستخدام DataView
const buf = Buffer.alloc(4);
buf.writeUInt32BE(0x12345678, 0);
console.log('big-endian:', buf.toString('hex'));
const view = new DataView(buf.buffer, buf.byteOffset, buf.byteLength);
console.log('read as uint32:', view.getUint32(0, false));
console.log('read as uint16:', view.getUint16(0, false));
big-endian: 12345678
read as uint32: 305419896
read as uint16: 4660
8. قراءة وكتابة الملفات الثنائية
عند استخدام fs.readFile لقراءة ملف دون تحديد الترميز، تكون القيمة المرجعة عبارة عن مخزن مؤقت وليس سلسلة نصية. وهذه هي الطريقة القياسية للتعامل مع الملفات الثنائية مثل الصور والملفات الصوتية وملفات الفيديو.
▶ مثال: قراءة ملف ثنائي
const fs = require('fs');
const imgBuf = fs.readFileSync('photo.jpg');
console.log('isBuffer:', Buffer.isBuffer(imgBuf));
console.log('size:', imgBuf.length, 'bytes');
console.log('first 8 bytes (hex):', imgBuf.slice(0, 8).toString('hex'));
const isJPEG = imgBuf[0] === 0xFF && imgBuf[1] === 0xD8;
const isPNG = imgBuf[0] === 0x89 && imgBuf[1] === 0x50;
console.log('isJPEG:', isJPEG);
console.log('isPNG:', isPNG);
isBuffer: true
size: 245760 bytes
first 8 bytes (hex): ffd8ffe000104a46
isJPEG: true
isPNG: false
▶ مثال: تطبيقات المخازن المؤقتة في الشبكات وعلم التشفير
const crypto = require('crypto');
const data = Buffer.from('important message', 'utf8');
const hash = crypto.createHash('sha256').update(data).digest();
console.log('sha256 (hex):', hash.toString('hex'));
const hmac = crypto.createHmac('sha256', 'secret-key').update(data).digest();
console.log('hmac (hex):', hmac.toString('hex'));
const randomBytes = crypto.randomBytes(16);
console.log('random (hex):', randomBytes.toString('hex'));
sha256 (hex): 8c8821c72b56a55724e9ad64b875e4b62e6c5e9f9c4c4b0c4d5e6f7a8b9c0d1e
hmac (hex): a3f2b8c1d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0c1d2e3f4a5b6c7d8e9f0
random (hex): 3a7f2b1c9d4e8a6f5b0c7d2e1f3a4b8c
9. مثال شامل: أداة ترميز الملفات
قم بإنشاء أداة بسيطة لترميز الملفات: قراءة ملف ثنائي → تحويله إلى Base64 → كتابته في ملف نصي → فك الترميز لاستعادة البيانات الأصلية.
(1) مقارنة بين «Buffer» و«String» و«TypedArray»
| البعد | المخزن المؤقت | السلسلة | TypedArray |
|---|---|---|---|
| المحتوى المخزن | البايتات الأولية | نقطة الترميز UTF-16 | القيمة الخاصة بالنوع |
| حجم العنصر | ثابت عند 1 بايت | 2 بايت (UTF-16) | 1–8 بايت |
| أنواع البيانات المدعومة | البيانات الثنائية، الملفات، الشبكة | النص | المصفوفات الرقمية، WebGL |
| قابل للتغيير | قابل للتعديل | غير قابل للتغيير | قابل للتعديل |
| معالجة البايتات الصفرية | التخزين العادي | اقتطاع السلاسل | التخزين العادي |
| دعم الترميز | UTF-8/Base64/السداسي عشري، إلخ. | UTF-16 فقط | لا يوجد مفهوم للترميز |
const fs = require('fs');
const path = require('path');
الدالة encodeFile(inputPath, outputPath) {
const raw = fs.readFileSync(inputPath);
const base64 = raw.toString('base64');
fs.writeFileSync(outputPath, base64, 'utf8');
console.log(`Encoded: ${raw.length} bytes → ${base64.length} chars`);
return { originalSize: raw.length, encodedSize: base64.length };
}
الدالة decodeFile(inputPath, outputPath) {
const base64Str = fs.readFileSync(inputPath, 'utf8');
const decoded = Buffer.from(base64Str, 'base64');
fs.writeFileSync(outputPath, decoded);
console.log(`Decoded: ${base64Str.length} chars → ${decoded.length} bytes`);
return { encodedSize: base64Str.length, decodedSize: decoded.length };
}
الدالة verify(originalPath, restoredPath) {
const a = fs.readFileSync(originalPath);
const b = fs.readFileSync(restoredPath);
if (a.equals(b)) {
console.log('Verification: PASSED - files are identical');
} else {
console.log('Verification: FAILED - files differ');
}
}
const inputPath = path.الربط(__dirname, 'sample.dat');
const encodedPath = path.الربط(__dirname, 'sample.b64.txt');
const restoredPath = path.الربط(__dirname, 'sample.restored.dat');
const sampleData = Buffer.alloc(256);
for (let i = 0; i < 256; i++) {
sampleData[i] = i;
}
fs.writeFileSync(inputPath, sampleData);
encodeFile(inputPath, encodedPath);
decodeFile(encodedPath, restoredPath);
verify(inputPath, restoredPath);
Encoded: 256 bytes → 344 chars
Decoded: 344 chars → 256 bytes
Verification: PASSED - files are identical
❓ أسئلة شائعة
Buffer.alloc وBuffer.allocUnsafe؟Buffer.alloc(size) بتهيئة كل بايت إلى 0؛ وهو آمن ولكنه أبطأ قليلاً. أما Buffer.allocUnsafe(size) فلا يقوم بتهيئة البايتات؛ وقد تحتوي على بيانات قديمة من الذاكرة. ويوفر أداءً أسرع، ولكن يجب ملؤها على الفور، وإلا فقد تتسرب معلومات حساسة.buf.length عدد البايتات أم عدد الأحرف؟buf.length عدد البايتات، على عكس الدالة str.length الخاصة بالسلسلة النصية (التي تُرجع عدد وحدات الترميز UTF-16). على سبيل المثال، str.length لـ 'مرحبا' يساوي 2، لكن Buffer.byteLength('مرحبا') يساوي 6 (كل حرف صيني يساوي 3 بايت في UTF-8).String لمعالجة البيانات الثنائية؟Buffer يتوافق تمامًا مع بايت واحد، مما يجعلها الحاوية الصحيحة للبيانات الثنائية.Uint8Array وArrayBuffer.Buffer.isBuffer(obj)؛ فستُرجع true أو false. لا تستخدم instanceof، لأنها قد تفشل عند عبور العوالم (مثل وحدات VM المختلفة).buf.slice() وbuf.subarray()؟subarray للحفاظ على اتساق التسمية مع Uint8Array.プロトタイプ.subarray، بينما تم الاحتفاظ بـ slice من أجل التوافق مع الإصدارات السابقة. يوصى باستخدام subarray في الكود الجديد.buf.toJSON() إلى إرجاع كائن بتنسيق { type: 'Buffer', data: [...] }. يمكنك أيضًا استخدام JSON.stringify(buf) مباشرةً، حيث سيؤدي ذلك إلى استدعاء toJSON() تلقائيًّا. بعد إزالة التسلسل، استخدم Buffer.from(obj.data) لاستعادة البيانات الأصلية.📖 ملخص
- المفاهيم الأساسية وكيفية تطبيقها
- ما هو «Buffer»؟ المفاهيم الأساسية وكيفية استخدامه
- المفاهيم الأساسية وطرق استخدام أساليب إنشاء المخازن المؤقتة
- المفاهيم الأساسية وتطبيقات الترميز وفك الترميز
- المفاهيم الأساسية واستخدامات التحويل بين «Buffer» و«String»
- المفاهيم الأساسية لعمليات المخزن المؤقت وكيفية استخدامها
- المفاهيم الأساسية واستخدام كل من Buffer و TypedArray
- المفاهيم الأساسية وطرق استخدام قراءة وكتابة الملفات الثنائية
📝 تمارين
- أكمل جميع أمثلة الأكواد الواردة في هذا الدرس وتأكد من أن كل منها يعمل بشكل صحيح.
- قم بتعديل المثال الشامل وأضف الإضافات الخاصة بك
- راجع الوثائق الرسمية، وحدد واجهة برمجة تطبيقات (API) واحدة أو اثنتين لم يتم تناولهما في هذا الدرس، واكتب كود اختبار لهما.
- التأمل: كيف ستطبق ما تعلمته في هذا الدرس على مشروع في الواقع العملي؟
- حاول أن تجمع بين ما تعلمته في هذا الدرس والمواد التي درستها في الدروس السابقة لـ build مشروعًا صغيرًا.