ستتعلم في هذا المقال ثلاث طرائق الأكثر شيوعًا لنسخ ملف عبر لغة البرمجة Go.
هذا المقال جزء من سلسلة Go التي كتبها Mihalis Tsoukalos. اقرأ الجزء 1: إنشاء كلمات مرور عشوائية وآمنة في Go، والجزء 2: إنشاء خادم TCP متزامن في Go.
بالرغم من وجود أكثر من ثلاث طرق لنسخ ملف في Go، يُقدم هذا المقال الطرق الثلاثة الأكثر شيوعًا:
- استخدام استدعاء دالة ()io.copy من مكتبة Go.
- قراءة ملف الإدخال مرة واحدة وكتابته إلى ملف آخر.
- نسخ الملف في قطع صغيرة باستخدام مخزن مؤقت.
الطريقة الأولى: استخدام ()io.copy
الإصدار الأول من هذه الطريقة يستخدم الدالة ()io.copy من مكتبة Go. يمكنك العثور على منطق هذه الطريقة من خلال الإطّلاع على كود الدالة ()copy، وهي كما يلي:
func copy(src, dst string) (int64, error) { sourceFileStat, err := os.Stat(src) if err != nil { return 0, err } if !sourceFileStat.Mode().IsRegular() { return 0, fmt.Errorf("%s is not a regular file", src) } source, err := os.Open(src) if err != nil { return 0, err } defer source.Close() destination, err := os.Create(dst) if err != nil { return 0, err } defer destination.Close() nBytes, err := io.Copy(destination, source) return nBytes, err }
بصرف النظر عن اختبار ما إذا كان الملف الذي سيتم نسخه موجودًا (عبر (os.Stat (src
)، وهو ملف عادي (يمكن التأكد عبر sourceFileStat.Mode().IsRegular()
)، حتى تتمكن من فتحه للقراءة، يتم تنفيذ كل العمل بواسطة الجملة التعريفية (io.copy(destination, Source
السابقة.
تُرجِع الدالة ()io.copy
عدد البايتات المنسوخة ورسالة الخطأ الأولى التي حدثت أثناء عملية النسخ. في Go، إذا لم تكن هناك رسالة خطأ، فستكون قيمة متغير الخطأ صفر.
يُمكنك معرفة المزيد عن ()io.copy
بالإطّلاع على صفحة التوثيق الخاصة بـالـ io Package.
سيؤدي تنفيذ cp1.go إلى إنشاء النوع التالي من النواتج:
$ go run cp1.go Please provide two command line arguments! $ go run cp1.go fileCP.txt /tmp/fileCPCOPY Copied 3826 bytes! $ diff fileCP.txt /tmp/fileCPCOPY
هذه التقنية بسيطة بقدر الإمكان ولكنها لا تعطي أي مرونة للمطور، وهذا ليس شيئًا سيئًا دائمًا. ومع ذلك، هناك أوقات يحتاج فيها المطور أو يريد أن يقرر كيف يريد قراءة الملف.
الطريقة الثانية: استخدام ()ioutil.WriteFile و ()ioutil.ReadFile
الطريقة الثانية لنسخ ملف هي استخدام الدوال ()ioutil.ReadFile
و ()ioutil.WriteFile
. تقرأ الدالة الأولى محتويات ملف بأكمله في شريحة بايت، بينما تقوم الدالة الثانية بكتابة محتويات شريحة بايت في ملف.
يمكنك العثور على منطق هذه الطريقة في كود Go التالي:
input, err := ioutil.ReadFile(sourceFile) if err != nil { fmt.Println(err) return } err = ioutil.WriteFile(destinationFile, input, 0644) if err != nil { fmt.Println("Error creating", destinationFile) fmt.Println(err) return }
بصرف النظر عن كتلتي if، التي تُعد جزءًا من طريقة عمل Go، تستطيع أن ترى أنّ الأداء الوظيفي للبرنامج موجود في كلٍ من ()ioutil.ReadFile
و ()ioutil.WriteFile
.
سيؤدي تنفيذ cp2.go إلى إنشاء نوع الناتج التالي:
$ go run cp2.go Please provide two command line arguments! $ go run cp2.go fileCP.txt /tmp/copyFileCP $ diff fileCP.txt /tmp/copyFileCP
يُرجى ملاحظة أنه على الرغم من أن هذه التقنية ستنسخ ملفًا، فقد لا تكون فعّالة عندما تريد نسخ ملفات ضخمة لأن شريحة البايت التي يتم إرجاعها بواسطة ()ioutil.ReadFile
ستكون كبيرة هي الأخرى.
الطريقة الثالثة: استخدام ()os.read و ()os.write
الطريقة الثالثة لنسخ ملفات في Go، هي استخدام وسيلة cp3.go المُطوّرة في هذا القسم.
تأخذ ثلاث معاملات:
- اسم ملف الإدخال.
- اسم ملف الإخراج.
- حجم المخزن المؤقت.
يتواجد الجزء الأكثر أهمية من cp3.go في الحلقة for التالية، والذي يمكن العثور عليه في الدالة ()copy
:
buf := make([]byte, BUFFERSIZE) for { n, err := source.Read(buf) if err != nil && err != io.EOF { return err } if n == 0 { break } if _, err := destination.Write(buf[:n]); err != nil { return err } }
تستخدم هذه التقنية الدالة ()os.read
لقراءة أجزاء صغيرة من ملف الإدخال في المخزن المؤقت المُسمى buf. وتستخدم الدالة ()os.write
لكتابة محتويات هذا المخزن المؤقت إلى ملف.
تتوقف عملية النسخ عندما يكون هناك خطأ في القراءة أو عند الوصول إلى نهاية الملف (io.EOF
).
سيؤدي تنفيذ cp3.go إلى إنشاء نوع الناتج التالي:
$ go run cp3.go usage: cp3 source destination BUFFERSIZE $ go run cp3.go fileCP.txt /tmp/buf10 10 Copying fileCP.txt to /tmp/buf10 $ go run cp3.go fileCP.txt /tmp/buf20 20 Copying fileCP.txt to /tmp/buf20
كما سترى، يؤثر حجم المخزن المؤقت بشكل كبير على أداء cp3.go.
بعض من التقييم بهدف الموازنة
سيحاول الجزء الأخير من هذ المقال موازنة البرامج الثلاثة بالإضافة إلى أداء cp3.go لمختلف أحجام المخزن المؤقت باستخدام الأداة المساعدة لسطر الأوامر (time (1.
يُظهر الناتج التالي أداء cp1.go و cp2.go و cp3.go عند نسخ ملف بحجم 500 ميجابايت:
$ ls -l INPUT -rw-r--r-- 1 mtsouk staff 512000000 Jun 5 09:39 INPUT $ time go run cp1.go INPUT /tmp/cp1 Copied 512000000 bytes! real 0m0.980s user 0m0.219s sys 0m0.719s $ time go run cp2.go INPUT /tmp/cp2 real 0m1.139s user 0m0.196s sys 0m0.654s $ time go run cp3.go INPUT /tmp/cp3 1000000 Copying INPUT to /tmp/cp3 real 0m1.025s user 0m0.195s sys 0m0.486s
يُظهر الناتج أنّ أداء الأدوات المساعدة الثلاثة متشابه إلى حدٍ ما، مما يعني أن دوال مكتبة Go القياسية ذكية ومُحسَّنة تمامًا.
الآن، دعنا نختبر كيف يؤثر حجم المخزن المؤقت على أداء cp3.go. سيؤدي تنفيذ cp3.go بحجم المخزن المؤقت 10 و 20 و 1000 بايت لنسخ ملف 500 ميجابايت على جهاز سريع إلى حد كبير لاستخراج النتائج التالية:
$ ls -l INPUT -rw-r--r-- 1 mtsouk staff 512000000 Jun 5 09:39 INPUT $ time go run cp3.go INPUT /tmp/buf10 10 Copying INPUT to /tmp/buf10 real 6m39.721s user 1m18.457s sys 5m19.186s $ time go run cp3.go INPUT /tmp/buf20 20 Copying INPUT to /tmp/buf20 real 3m20.819s user 0m39.444s sys 2m40.380s $ time go run cp3.go INPUT /tmp/buf1000 1000 Copying INPUT to /tmp/buf1000 real 0m4.916s user 0m1.001s sys 0m3.986s
يُظهر الناتج الذي تم إنشاؤه أنه كلما زاد حجم المخزن المؤقت، كلما كان أداء cp3.go أسرع، وهو أمر مُتوَقع إلى حدٍ ما. علاوةً على ذلك، يُعد استخدام أحجام المخازن المؤقتة أصغر من 20 بايت لنسخ الملفات الكبيرة عملية بطيئة للغاية ويجب تجنُّبها.
يمكنك العثور على كود Go الخاص بـ cp1.go و cp2.go و cp3.go على Github.
ترجمة وبتصرّف للمقال 3 ways to copy files in Go، لصاحبه Mihalis Tsoukalos.
أفضل التعليقات
لا توجد أية تعليقات بعد
انضم إلى النقاش
يمكنك أن تنشر الآن وتسجل لاحقًا. إذا كان لديك حساب، فسجل الدخول الآن لتنشر باسم حسابك.