قراءة الملفات
قراءة الملفات
تتيح Java عدة طرق لقراءة الملف، واختيار الطريقة المناسبة أمر جوهري. الاختيار الخاطئ قد يُحمّل جيجابايتات في الذاكرة الكومية، أو يُخلّف تدفقات مفتوحة مبعثرة في الكود. يتناول هذا الدرس المقاربات الثلاث الرئيسية — Files.readString وFiles.readAllLines وBufferedReader — ويشرح متى تلجأ إلى كل منها.
طريقتا الراحة: readString وreadAllLines
منذ Java 11، يوفّر java.nio.file.Files دالتين ساكنتين مساعدتين تُختصران قراءة الملف في سطر واحد. كلتاهما مبنيتان على NIO.2 وتتولّيان إغلاق الموارد تلقائيًا.
Files.readString تقرأ الملف بأكمله إلى سلسلة نصية واحدة من نوع String:
الوسيطة الثانية — وهي Charset — اختيارية؛ إن أغفلتها استخدمت Java الترميز الافتراضي للمنصة. مرّر دائمًا StandardCharsets.UTF_8 صراحةً كي يتصرف كودك بشكل متطابق على Windows وLinux وmacOS.
Files.readAllLines تقرأ كل سطر إلى قائمة List<String>، محذوفةً منها فواصل الأسطر (\n و\r\n و\r):
readAllLines قائمة ArrayList عادية. تُحذف فواصل الأسطر، وتصبح الأسطر الفارغة سلاسل نصية خالية، ويُدرج السطر الأخير حتى لو لم ينتهِ الملف بسطر جديد. تكون القائمة كاملةً في الذاكرة قبل معالجة أي سطر.
المقايضة: الراحة مقابل الذاكرة
كلتا الطريقتين مريحتان، لكنهما تشتركان في خاصية جوهرية: يُحمَّل الملف بأكمله في الذاكرة قبل أن تتمكن من لمس بايت واحد. بالنسبة لملف إعداد بحجم 5 كيلوبايت أو ملف CSV بحجم 200 كيلوبايت فهذا مقبول تمامًا، أما بالنسبة لملف سجلات بحجم 2 جيجابايت فذلك انفجار للذاكرة ينتظر الحدوث.
Files.readString— الأنسب للنصوص البنيوية الصغيرة (الإعدادات، JSON، XML، القوالب) التي تريدها كتلةً واحدة.Files.readAllLines— الأنسب للملفات السطرية المتوسطة الحجم عند الحاجة للوصول العشوائي إلى أي سطر أو تمرير القائمة الكاملة لدالة أخرى.- لا تُناسب أيٌّ منهما الملفات الكبيرة. استخدم
BufferedReaderعوضًا عنها.
BufferedReader للملفات الكبيرة
يقرأ BufferedReader جزءًا قابلًا للضبط (8 كيلوبايت افتراضيًا) من القرص في المرة الواحدة ويُسلّمك الأسطر سطرًا بسطر. يظل استخدام ذاكرة تطبيقك ثابتًا تقريبًا بغض النظر عن حجم الملف.
ملاحظات جديرة بالانتباه:
Files.newBufferedReader(path, charset)هو المصنع المفضل علىnew BufferedReader(new FileReader(path))— فالأخير يستخدم الترميز الافتراضي للمنصة ويتطلب مزيدًا من الشفرة المعيارية.- تُعيد
readLine()القيمةnullعند نهاية الملف لا استثناءً. نمطwhile (... != null)هو الأسلوب المعهود في Java. - يُغلق كتلة
try-with-resources القارئَ حتى لو أُطلق استثناء في منتصف الملف — وهذا أمر جوهري للصحة عند التعامل مع الملفات الكبيرة.
بث الأسطر باستخدام Files.lines
إن كنت مرتاحًا للـ Stream API، فإن Files.lines (Java 8+) يمنحك Stream<String> كسولًا يتناسق جيدًا مع filter وmap وcollect:
readAllLines، يحتفظ التدفق بمعالج ملف مفتوح. إن أغفلت كتلة try-with-resources، يتسرّب المعالج حتى يُنهي مجمّع البيانات المهملة (GC) التدفقَ — وهو ما قد لا يحدث أبدًا في تطبيق طويل الأمد، مما قد يُنضب حد واصف ملفات نظام التشغيل.
اختيار الأداة المناسبة
- ملف صغير، تحتاج النص كاملًا →
Files.readString - ملف صغير، تحتاج كل سطر عنصرًا في قائمة →
Files.readAllLines - ملف كبير، معالجة سطر بسطر بأسلوب إجرائي →
BufferedReader+readLine() - ملف كبير، معالجة بخط أنابيب Stream →
Files.linesداخل try-with-resources
BufferedReader أو Files.lines هو الاختيار الصحيح.
الخلاصة
تُقدّم Files.readString وFiles.readAllLines شفرةً مختصرة ونظيفة للملفات الصغيرة على حساب تحميل كل شيء في الذاكرة. يمنحك BufferedReader عبر Files.newBufferedReader معالجةً سطر بسطر بذاكرة ثابتة لملفات ضخمة تعسفيًا. يردم Files.lines الفجوة حين تريد البث الكسول مع كامل قدرة Stream API. حدّد دائمًا مجموعة المحارف صراحةً، وأغلق دائمًا موارد الإدخال/الإخراج باستخدام try-with-resources.