تحويل البايتات إلى تدفق: io.Reader من []byte في Go

مدرب بايثون أونلاين للمبتدئين

تعلم Python بسهولة دون إرهاق نظري. حل مهام عملية مع التحقق التلقائي واكتب الكود مباشرة في المتصفح.

ابدأ الدورة

مقدمة: لماذا قد تحتاج إلى تحويل البايتات إلى تدفق؟

\n\n

تخيل موقفًا: لقد كتبت خدمة رائعة بلغة Go تستقبل بيانات من واجهة برمجة تطبيقات خارجية. يأتي الرد على شكل مصفوفة بايتات ([]byte). كل شيء على ما يرام، ولكن المشكلة أن الدالة التالية في خط أنابيبك تتطلب إدخالًا ليس شريحة بايتات، بل واجهة io.Reader. ماذا تفعل؟ هل تشعر بالذعر؟ هل تكتب حلاً من الصفر؟ لا! يكفي أن تتذكر bytes.NewReader.

\n\n

هذا السؤال هو أحد أكثر الأسئلة شيوعًا على Stack Overflow (آلاف المشاهدات!). وليس من دون سبب. في Go، مفهوم io.Reader هو الأساس للعمل مع تدفقات البيانات: الملفات، الاتصالات الشبكية، استجابات HTTP، وبالطبع، مجرد بايتات في الذاكرة. إذا كنت مطور Go مبتدئًا، فستصطدم بهذا الجدار عاجلاً أم آجلاً. دعنا نفهم كيف نتجاوزه بشكل أنيق وفعال.

\n\n

في هذه المقالة، لن نقدم مجرد أمر جاف "استخدم bytes.NewReader". سنشرح لماذا يعمل هذا، وما هي البدائل، وسنعرض أمثلة حقيقية من الحياة الواقعية. هيا بنا!

\n\n

نظرية: ما هو io.Reader وماذا يؤكل معه؟

\n\n

قبل التحويل، دعنا نتذكر ما هو io.Reader. إنها واجهة محددة في المكتبة القياسية للغة Go:

\n\n
type Reader interface {\n    Read(p []byte) (n int, err error)\n}
\n\n

كل شيء عبقري بسيط. أي هيكل لديه طريقة Read(p []byte) (int, error) يفي تلقائيًا بواجهة io.Reader. هذا يسمح بكتابة كود عالمي لقراءة البيانات من أي مصدر: ملف، مقبس، سلسلة نصية، شريحة بايتات.

\n\n

لماذا هذا رائع؟ لأنه يمكنك كتابة دالة واحدة تقوم بتحليل JSON أو XML أو مجرد نسخ البيانات، دون القلق بشأن مصدرها. كل ما تحتاجه هو io.Reader. هذا هو تجريد التدفق.

\n\n

ولكن ماذا تفعل إذا كانت لديك بالفعل بيانات في الذاكرة (على سبيل المثال، []byte)؟ أنت بحاجة إلى كائن "يتظاهر" بأنه تدفق، ولكنه في نفس الوقت يقرأ من شريحتك. هنا يأتي دور bytes.NewReader.

\n\n

الطريقة الأساسية: bytes.NewReader

\n\n

هذه هي الطريقة الأبسط والأصح. تأخذ الدالة bytes.NewReader []byte وتعيد مؤشرًا إلى هيكل bytes.Reader، الذي ينفذ io.Reader (بالإضافة إلى io.ReadSeeker و io.WriterTo وواجهات مفيدة أخرى).

\n\n

مثال 1: الاستخدام الأساسي

\n\n
package main\n\nimport (\n    "bytes"\n    "fmt"\n    "io"\n    "log"\n)\n\nfunc main() {\n    // شريحة البايتات الأصلية لدينا\n    data := []byte("مرحبًا بالعالم! هذه بايتات.")\n\n    // إنشاء قارئ من شريحة البايتات\n    reader := bytes.NewReader(data)\n\n    // قراءة البيانات من القارئ إلى مخزن مؤقت\n    buf := make([]byte, 10) // اقرأ 10 بايتات في كل مرة\n    for {\n        n, err := reader.Read(buf)\n        if err != nil {\n            if err == io.EOF {\n                break // انتهت القراءة\n            }\n            log.Fatal(err)\n        }\n        fmt.Printf("تمت قراءة %d بايت: %s\\n", n, buf[:n])\n    }\n    // Output:\n    // تمت قراءة 10 بايت: مرحبًا بال\n    // تمت قراءة 10 بايت: عالم! هذه\n    // تمت قراءة 6 بايت: بايتات.\n}
\n\n

لاحظ: bytes.Reader يتصرف مثل تدفق حقيقي. عندما تنتهي البيانات، يعيد io.EOF. كل شيء عادل.

\n\n

مثال 2: الاستخدام مع io.Copy

\n\n

السيناريو الأكثر شيوعًا هو نسخ البايتات إلى مكان ما (إلى ملف، إلى استجابة HTTP). لهذا، io.Copy مثالي:

\n\n
package main\n\nimport (\n    "bytes"\n    "fmt"\n    "io"\n    "os"\n)\n\nfunc main() {\n    data := []byte("نحفظ هذه البيانات في ملف.")\n    reader := bytes.NewReader(data)\n\n    // إنشاء ملف\n    file, err := os.Create("output.txt")\n    if err != nil {\n        panic(err)\n    }\n    defer file.Close()\n\n    // نسخ من القارئ إلى الملف\n                

المدونات

توصيات الكتب