3 طرائق لنسخ الملفات في Go


أمل عبدالله محمد الجنايني

ستتعلم في هذا المقال ثلاث طرائق الأكثر شيوعًا لنسخ ملف عبر لغة البرمجة Go.

هذا المقال جزء من سلسلة Go التي كتبها Mihalis Tsoukalos. اقرأ الجزء 1: إنشاء كلمات مرور عشوائية وآمنة في Go، والجزء 2: إنشاء خادم TCP متزامن في Go.

بالرغم من وجود أكثر من ثلاث طرق لنسخ ملف في Go، يُقدم هذا المقال الطرق الثلاثة الأكثر شيوعًا:

  1. استخدام استدعاء دالة ()io.copy من مكتبة Go.
  2. قراءة ملف الإدخال مرة واحدة وكتابته إلى ملف آخر.
  3. نسخ الملف في قطع صغيرة باستخدام مخزن مؤقت.

الطريقة الأولى: استخدام ()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 المُطوّرة في هذا القسم.

تأخذ ثلاث معاملات:

  1. اسم ملف الإدخال.
  2. اسم ملف الإخراج.
  3. حجم المخزن المؤقت.

يتواجد الجزء الأكثر أهمية من 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.





تفاعل الأعضاء


لا توجد أيّة تعليقات بعد



يجب أن تكون عضوًا لدينا لتتمكّن من التعليق

انشاء حساب جديد

يستغرق التسجيل بضع ثوان فقط


سجّل حسابًا جديدًا

تسجيل الدخول

تملك حسابا مسجّلا بالفعل؟


سجّل دخولك الآن