🚀 فهم I/O Streams في جافا: بوابتك للتعامل مع البيانات! 📂

مرحباً بك في عالم إدخال وإخراج البيانات في جافا! تخيل أن برنامجك مثل جزيرة معزولة. I/O Streams هي الجسور التي تربط هذه الجزيرة بالعالم الخارجي، مما يسمح لك بقراءة البيانات من مصادر خارجية (مثل الملفات) وكتابة البيانات إليها. في هذا الدرس، سنتعلم أساسيات هذه الجسور الحيوية.


📚 ما المقصود بـ Stream (الدّفق)؟

في جافا، الدّفق (Stream) هو تسلسل متدفق من البيانات. فكر فيه كـنهر من المعلومات يتدفق من مصدر إلى وجهة.

  • المصدر (Source): المكان الذي تأتي منه البيانات (مثل ملف، لوحة المفاتيح، شبكة).
  • الوجهة (Destination): المكان الذي تذهب إليه البيانات (مثل ملف، الشاشة، شبكة).

يتم التعامل مع هذه البيانات بالبايت (Byte) أو بالحرف (Character). في هذا الدرس، سنركز على مستوى البايتات الأساسي.


🔍 أنواع Streams الرئيسية: الإدخال vs الإخراج

تنقسم الـ Streams في جافا إلى نوعين رئيسيين، وكلاهما كلاسات مجردة (Abstract Classes) تمثل الفكرة العامة:

  1. InputStream 📥 (دَفق الإدخال):
    • وظيفته: قراءة البيانات من مصدر ما (مثل ملف).
    • تخيله: كـخرطوم ماء يضخ البيانات إلى داخل برنامجك.
    • الطريقة الأساسية فيه هي read() التي تقرأ بايتاً واحداً من الدفق.
  2. OutputStream 📤 (دَفق الإخراج):
    • وظيفته: كتابة البيانات إلى وجهة ما (مثل ملف).
    • تخيله: كـخرطوم ماء يضخ البيانات من داخل برنامجك إلى الخارج.
    • الطريقة الأساسية فيه هي write() التي تكتب بايتاً واحداً إلى الدفق.
// هذه مجرد فكرة عن الشكل، لا يمكن إنشاء كائنات منها مباشرة لأنها مجردة
InputStream inputStream; // لقراءة البيانات
OutputStream outputStream; // لكتابة البيانات

🛠️ أول خطوة عملية: FileInputStream و FileOutputStream

لنبدأ بأبسط مثال: القراءة من ملف والكتابة إلى ملف. نستخدم فئتين فرعيتين من InputStream وOutputStream مخصصتين للملفات.

📖 قراءة ملف باستخدام FileInputStream

لقراءة محتويات ملف بايتاً بايتاً، نتبع هذه الخطوات:

  1. إنشاء كائن FileInputStream وربطه بمسار الملف.
  2. قراءة البيانات باستخدام طريقة read().
  3. إغلاق الدفق باستخدام طريقة close() بعد الانتهاء (وهذه خطوة مهمة جداً).
import java.io.FileInputStream;
import java.io.IOException;

public class ReadFileExample {
    public static void main(String[] args) {
        // 1. إنشاء وإعداد دفق الإدخال
        FileInputStream fileIn = null;
        
        try {
            // ربط الدفق بملف اسمه "myfile.txt" الموجود في نفس مجلد المشروع
            fileIn = new FileInputStream("myfile.txt");
            
            int data; // سيخزن قيمة البايت الذي تم قراءته
            System.out.println("📄 محتويات الملف:");
            
            // 2. قراءة البيانات
            // طريقة read() ترجع -1 عند نهاية الملف (End Of File)
            while ((data = fileIn.read()) != -1) {
                // تحويل البايت إلى حرف وطباعته
                System.out.print((char) data);
            }
            
        } catch (IOException e) {
            System.out.println("حدث خطأ أثناء قراءة الملف: " + e.getMessage());
        } finally {
            // 3. إغلاق الدفق (في قسم finally لضمان التنفيذ)
            try {
                if (fileIn != null) {
                    fileIn.close();
                    System.out.println("\n✅ تم إغلاق دفق القراءة بنجاح.");
                }
            } catch (IOException e) {
                System.out.println("خطأ في إغلاق الملف: " + e.getMessage());
            }
        }
    }
}

✍️ كتابة ملف باستخدام FileOutputStream

لإنشاء ملف جديد وكتابة بيانات إليه، نتبع خطوات مشابهة:

  1. إنشاء كائن FileOutputStream وربطه بمسار الملف (سيُنشئ الملف إذا لم يكن موجوداً).
  2. كتابة البيانات باستخدام طريقة write().
  3. إغلاق الدفق باستخدام طريقة close().
import java.io.FileOutputStream;
import java.io.IOException;

public class WriteFileExample {
    public static void main(String[] args) {
        FileOutputStream fileOut = null;
        
        try {
            // ربط الدفق بملف اسمه "output.txt"
            // إذا كان الملف موجوداً، سيتم كتابة البيانات فوقه
            fileOut = new FileOutputStream("output.txt");
            
            String text = "مرحباً بالعالم من كودكس أكاديمي! 🎉";
            byte[] textBytes = text.getBytes(); // تحويل النص إلى مصفوفة بايتات
            
            // 2. كتابة البيانات (مصفوفة البايتات كاملة)
            fileOut.write(textBytes);
            
            System.out.println("✅ تم كتابة النص إلى الملف 'output.txt' بنجاح!");
            
        } catch (IOException e) {
            System.out.println("حدث خطأ أثناء كتابة الملف: " + e.getMessage());
        } finally {
            // 3. إغلاق الدفق
            try {
                if (fileOut != null) {
                    fileOut.close();
                }
            } catch (IOException e) {
                System.out.println("خطأ في إغلاق الملف: " + e.getMessage());
            }
        }
    }
}

💡 ملاحظات مهمة للمبتدئين

  • معالجة الاستثناءات (try-catch): عمليات الإدخال والإخراج معرضة للأخطاء (مثل عدم وجود الملف). يجب دائماً وضع كود الـ I/O داخل كتلة try-catch للتعامل مع استثناءات IOException.
  • إغلاق الموارد (close()): لا تنسَ أبداً إغلاق الـ Stream بعد الانتهاء منه في كتلة finally. هذا يحرر الموارد التي يستخدمها النظام.
  • القراءة والكتابة بالبايت: FileInputStream و FileOutputStream يعملان على مستوى البايتات، وهو مناسب للبيانات الثنائية (مثل الصور). لمعالجة النصوص بشكل أسهل، سنتعلم في الدروس القادمة أدوات أكثر تخصصاً.

🔄 خلاصة الدرس

تعلمنا اليوم الأساسيات! 🎯

  • الـ Stream هو تدفق للبيانات من مصدر إلى وجهة.
  • InputStream للقراءة، و OutputStream للكتابة.
  • FileInputStream يقرأ بايتات من ملف.
  • FileOutputStream يكتب بايتات إلى ملف.
  • تذكر دائماً استخدام try-catch و close().

ماذا سنتعلم في الدرس القادم؟ 🔮

القراءة والكتابة بايتاً بايتاً قد تكون بطيئة وغير عملية للنصوص. في الدرس القادم، سنرفع مستوى الكفاءة! سنتعرف على Buffered Streams 🚀، مثل BufferedInputStream و BufferedOutputStream، التي تعمل كـ"ذاكرة وسيطة" لتسريع عمليات القراءة والكتابة بشكل كبير، وسنرى كيف يمكنها تحسين أداء برامجنا بشكل ملحوظ. استعد لكتابة أكواد أسرع وأنظف!