File
. يمكنك أن تقرأ عن عمله هنا . لكن في Java 7، قرر منشئو اللغة تغيير طريقة عملهم مع الملفات والأدلة. كان هذا بسبب حقيقة أن الفصل File
كان به عدد من العيوب. على سبيل المثال، لم يكن لديه طريقة copy()
تسمح لك بنسخ ملف من موقع إلى آخر (وهي ميزة مطلوبة بشكل واضح). بالإضافة إلى ذلك، كان لدى الفصل File
عدد كبير جدًا من الأساليب التي تُرجع boolean
القيم. في حالة حدوث خطأ، تقوم هذه الطريقة بإرجاع خطأ بدلاً من طرح استثناء، مما يجعل تشخيص الأخطاء وتحديد أسبابها أمرًا صعبًا للغاية. بدلاً من فئة واحدة، File
ظهر ما يصل إلى 3 فئات Paths
: Path
و Files
. حسنًا، على وجه الدقة، Path
هذه واجهة وليست فئة. دعونا نتعرف على كيفية اختلافها عن بعضها البعض وسبب الحاجة إلى كل منها. لنبدأ بأسهل شيء - Paths
.
مسارات
Paths
هي فئة بسيطة جدًا ذات طريقة ثابتة واحدة get()
. تم إنشاؤه فقط للحصول على كائن من النوع من السلسلة أو URI التي تم تمريرها Path
. ليس لديه وظيفة أخرى. وهنا مثال على عمله:
import java.nio.file.Path;
import java.nio.file.Paths;
public class Main {
public static void main(String[] args) {
Path testFilePath = Paths.get("C:\\Users\\Username\\Desktop\\testFile.txt");
}
}
ليس الصف الأكثر صعوبة، أليس كذلك؟ :) حسنًا، بما أننا حصلنا على كائن من النوع Path
، فلنكتشف ما هو Path
ولماذا هو مطلوب :)
طريق
Path
، بشكل عام، هو نظير مُعاد تصميمه لـ File
. إن العمل معه أسهل بكثير من العمل معه File
. أولاً ، تمت إزالة العديد من الأساليب المساعدة (الثابتة) منه ونقلها إلى الفصل Files
. ثانيًا ، Path
تم ترتيب القيم المرجعة للطرق. في الفصل، File
أعادت الأساليب هذا String
وذاك boolean
وذاك File
- لم يكن من السهل معرفة ذلك. على سبيل المثال، كانت هناك طريقة getParent()
تقوم بإرجاع المسار الأصلي للملف الحالي كسلسلة. ولكن في نفس الوقت كانت هناك طريقة getParentFile()
تعيد نفس الشيء، ولكن في شكل كائن File
! ومن الواضح أن هذا زائدة عن الحاجة. لذلك، في الواجهة، تقوم Path
الطريقة getParent()
والطرق الأخرى للعمل مع الملفات ببساطة بإرجاع كائن Path
. لا يوجد الكثير من الخيارات - كل شيء سهل وبسيط. ما هي الأساليب المفيدة لديها Path
؟ وهذه بعض منهم وأمثلة على أعمالهم:
-
getFileName()
- إرجاع اسم الملف من المسار؛ -
getParent()
- إرجاع الدليل "الأصل" فيما يتعلق بالمسار الحالي (أي الدليل الأعلى في شجرة الدليل)؛ -
getRoot()
- إرجاع الدليل "الجذر"؛ أي الذي يوجد في أعلى شجرة الدليل؛ -
startsWith()
،endsWith()
- تحقق مما إذا كان المسار يبدأ/ينتهي بالمسار الذي تم تمريره:import java.nio.file.Path; import java.nio.file.Paths; public class Main { public static void main(String[] args) { Path testFilePath = Paths.get("C:\\Users\\Username\\Desktop\\testFile.txt"); Path fileName = testFilePath.getFileName(); System.out.println(fileName); Path parent = testFilePath.getParent(); System.out.println(parent); Path root = testFilePath.getRoot(); System.out.println(root); boolean endWithTxt = testFilePath.endsWith("Desktop\\testFile.txt"); System.out.println(endWithTxt); boolean startsWithLalala = testFilePath.startsWith("lalalala"); System.out.println(startsWithLalala); } }
إخراج وحدة التحكم:
testFile.txt
C:\Users\Username\Desktop
C:\
true
falseانتبه إلى كيفية عمل الطريقة
endsWith()
. يتحقق مما إذا كان المسار الحالي ينتهي بالمسار الذي تم تمريره . إنه على المسار ، وليس على مجموعة الشخصيات .قارن نتائج هاتين النداءتين:
import java.nio.file.Path; import java.nio.file.Paths; public class Main { public static void main(String[] args) { Path testFilePath = Paths.get("C:\\Users\\Username\\Desktop\\testFile.txt"); System.out.println(testFilePath.endsWith("estFile.txt")); System.out.println(testFilePath.endsWith("Desktop\\testFile.txt")); } }
إخراج وحدة التحكم:
صحيح كاذبتحتاج إلى تمرير المسار الكامل إلى الطريقة
endsWith()
، وليس مجرد مجموعة من الأحرف: وإلا فإن النتيجة ستكون دائمًا خاطئة ، حتى لو كان المسار الحالي ينتهي بالفعل بمثل هذا التسلسل من الأحرف (كما في حالة "estFile.txt" "" في المثال أعلاه).بالإضافة إلى ذلك، هناك
Path
مجموعة من الأساليب التي تبسط العمل بالمسارات المطلقة (الكاملة) والنسبية .
-
boolean isAbsolute()
- يُرجع صحيحًا إذا كان المسار الحالي مطلقًا:import java.nio.file.Path; import java.nio.file.Paths; public class Main { public static void main(String[] args) { Path testFilePath = Paths.get("C:\\Users\\Username\\Desktop\\testFile.txt"); System.out.println(testFilePath.isAbsolute()); } }
إخراج وحدة التحكم:
حقيقي
-
Path normalize()
- "تطبيع" المسار الحالي، وإزالة العناصر غير الضرورية منه. ربما تعلم أن أنظمة التشغيل الشائعة غالبًا ما تستخدم الأحرف "." عند الإشارة إلى المسارات. ("الدليل الحالي") و".." (الدليل الأصلي). على سبيل المثال: " ./Pictures/dog.jpg " تعني أنه في الدليل الذي نحن فيه الآن يوجد مجلد صور، وفيه يوجد ملف "dog.jpg"حتى هنا هو عليه. إذا كان لبرنامجك مسارًا يستخدم "." أو ".."، فسيقوم الأسلوب
normalize()
بإزالتها والحصول على مسار لا يحتوي عليها:import java.nio.file.Path; import java.nio.file.Paths; public class Main { public static void main(String[] args) { Path path5 = Paths.get("C:\\Users\\Java\\.\\examples"); System.out.println(path5.normalize()); Path path6 = Paths.get("C:\\Users\\Java\\..\\examples"); System.out.println(path6.normalize()); } }
إخراج وحدة التحكم:
C:\المستخدمين\Java\examples
C:\Users\examples -
Path relativize()
- يحسب المسار النسبي بين المسار الحالي والمسار الذي تم تمريره.على سبيل المثال:
import java.nio.file.Path; import java.nio.file.Paths; public class Main { public static void main(String[] args) { Path testFilePath1 = Paths.get("C:\\Users\\Users\\Users\\Users"); Path testFilePath2 = Paths.get("C:\\Users\\Users\\Users\\Users\\Username\\Desktop\\testFile.txt"); System.out.println(testFilePath1.relativize(testFilePath2)); } }
إخراج وحدة التحكم:
اسم المستخدم\سطح المكتب\testFile.txt
Path
كبيرة جدًا. يمكنك العثور عليها جميعًا في وثائق أوراكل . سننتقل للمراجعة Files
.
ملفات
Files
- هذه فئة فائدة حيث تم نقل الأساليب الثابتة من الفئة File
. Files
- هذا هو نفسه تقريبًا Arrays
أو Collections
، فهو يعمل فقط مع الملفات، وليس مع المصفوفات والمجموعات :) وهو يركز على إدارة الملفات والدلائل. باستخدام الأساليب الثابتة Files
، يمكننا إنشاء الملفات والأدلة وحذفها ونقلها. يتم استخدام الطرق لهذه العمليات createFile()
(للدلائل - createDirectory()
)، move()
و delete()
. وإليك كيفية استخدامها:
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import static java.nio.file.StandardCopyOption.REPLACE_EXISTING;
public class Main {
public static void main(String[] args) throws IOException {
//file creation
Path testFile1 = Files.createFile(Paths.get("C:\\Users\\Username\\Desktop\\testFile111.txt"));
System.out.println("Was the file created successfully?");
System.out.println(Files.exists(Paths.get("C:\\Users\\Username\\Desktop\\testFile111.txt")));
// create directory
Path testDirectory = Files.createDirectory(Paths.get("C:\\Users\\Username\\Desktop\\testDirectory"));
System.out.println("Was the directory successfully created?");
System.out.println(Files.exists(Paths.get("C:\\Users\\Username\\Desktop\\testDirectory")));
//move file from desktop to testDirectory. You need to move with the name of the file in the folder!
testFile1 = Files.move(testFile1, Paths.get("C:\\Users\\Username\\Desktop\\testDirectory\\testFile111.txt"), REPLACE_EXISTING);
System.out.println("Is our file left on the desktop?");
System.out.println(Files.exists(Paths.get("C:\\Users\\Username\\Desktop\\testFile111.txt")));
System.out.println("Has our file been moved to testDirectory?");
System.out.println(Files.exists(Paths.get("C:\\Users\\Username\\Desktop\\testDirectory\\testFile111.txt")));
//remove file
Files.delete(testFile1);
System.out.println("Does the file still exist?");
System.out.println(Files.exists(Paths.get("C:\\Users\\Username\\Desktop\\testDirectory\\testFile111.txt")));
}
}
هنا نقوم أولاً بإنشاء ملف (طريقة Files.createFile()
) على سطح المكتب، ثم نقوم بإنشاء مجلد هناك (طريقة Files.createDirectory()
). بعد ذلك نقوم بنقل الملف (الطريقة Files.move()
) من سطح المكتب إلى هذا المجلد الجديد، وفي النهاية نقوم بحذف الملف (الطريقة Files.delete()
). إخراج وحدة التحكم: هل تم إنشاء الملف بنجاح؟ صحيح هل تم إنشاء الدليل بنجاح؟ صحيح هل لا يزال ملفنا موجودًا على سطح المكتب؟ خطأ هل تم نقل ملفنا إلى دليل الاختبار؟ صحيح هل الملف لا يزال موجودا؟ خطأ شنيع انتبه:تمامًا مثل أساليب الواجهة Path
، تقوم العديد من الأساليب Files
بإرجاع كائنPath
. Files
تقبل معظم أساليب الفصل أيضًا Path
. هنا ستصبح الطريقة مساعدك المخلص Paths.get()
- استخدمها بنشاط. ما هو الشيء الآخر المثير للاهتمام Files
؟ ما افتقرت إليه الطبقة القديمة حقًا هو الطريقة ! File
. copy()
تحدثنا عنه في بداية المحاضرة، والآن هو الوقت المناسب لمقابلته!
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import static java.nio.file.StandardCopyOption.REPLACE_EXISTING;
public class Main {
public static void main(String[] args) throws IOException {
//file creation
Path testFile1 = Files.createFile(Paths.get("C:\\Users\\Username\\Desktop\\testFile111.txt"));
System.out.println("Was the file created successfully?");
System.out.println(Files.exists(Paths.get("C:\\Users\\Username\\Desktop\\testFile111.txt")));
// create directory
Path testDirectory2 = Files.createDirectory(Paths.get("C:\\Users\\Username\\Desktop\\testDirectory2"));
System.out.println("Was the directory successfully created?");
System.out.println(Files.exists(Paths.get("C:\\Users\\Username\\Desktop\\testDirectory2")));
//copy the file from the desktop to the directory testDirectory2.
testFile1 = Files.copy(testFile1, Paths.get("C:\\Users\\Username\\Desktop\\testDirectory2\\testFile111.txt"), REPLACE_EXISTING);
System.out.println("Is our file left on the desktop?");
System.out.println(Files.exists(Paths.get("C:\\Users\\Username\\Desktop\\testFile111.txt")));
System.out.println("Has our file been copied to testDirectory?");
System.out.println(Files.exists(Paths.get("C:\\Users\\Username\\Desktop\\testDirectory2\\testFile111.txt")));
}
}
إخراج وحدة التحكم: هل تم إنشاء الملف بنجاح؟ صحيح هل تم إنشاء الدليل بنجاح؟ صحيح هل لا يزال ملفنا موجودًا على سطح المكتب؟ صحيح هل تم نسخ ملفنا إلى testDirectory؟ صحيح الآن يمكنك نسخ الملفات برمجياً! :) لكن الفصل Files
يسمح لك ليس فقط بإدارة الملفات نفسها، ولكن أيضًا بالعمل مع محتوياتها. لكتابة البيانات في الملف، هناك طريقة write()
، وللقراءة - ما يصل إلى 3:، read()
وسوف readAllBytes()
نتناول readAllLines()
هذا الأخير بالتفصيل. لماذا عليه؟ لأنه يحتوي على نوع إرجاع مثير جدًا للاهتمام - List<String>
! أي أنه يعيد لنا قائمة الأسطر الموجودة في الملف. بالطبع، هذا يجعل العمل مع المحتوى مريحًا للغاية، لأن الملف بأكمله، سطرًا تلو الآخر، يمكن، على سبيل المثال، إخراجه إلى وحدة التحكم في حلقة عادية for
:
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.List;
import static java.nio.charset.StandardCharsets.UTF_8;
public class Main {
public static void main(String[] args) throws IOException {
List<String> lines = Files.readAllLines(Paths.get("C:\\Users\\Username\\Desktop\\pushkin.txt"), UTF_8);
for (String s: lines) {
System.out.println(s);
}
}
}
مخرجات وحدة التحكم: أتذكر لحظة رائعة: لقد ظهرت أمامي، مثل رؤية عابرة، مثل عبقري الجمال الخالص. مريح جدا! :) ظهرت هذه الميزة في Java 7. وفي Java 8، ظهر Stream API ، الذي أضاف بعض عناصر البرمجة الوظيفية إلى Java. بما في ذلك قدرات أكثر ثراءً للعمل مع الملفات. تخيل أن لدينا مهمة: العثور على جميع الأسطر في الملف الذي يبدأ بكلمة "كيف"، وتحويلها إلى أحرف كبيرة وإخراجها إلى وحدة التحكم. كيف سيبدو الحل باستخدام فئة Files
في Java 7؟ شيء من هذا القبيل:
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.List;
import static java.nio.charset.StandardCharsets.UTF_8;
public class Main {
public static void main(String[] args) throws IOException {
List<String> lines = Files.readAllLines(Paths.get("C:\\Users\\Username\\Desktop\\pushkin.txt"), UTF_8);
List<String> result = new ArrayList<>();
for (String s: lines) {
if (s.startsWith("How")) {
String upper = s.toUpperCase();
result.add(upper);
}
}
for (String s: result) {
System.out.println(s);
}
}
}
إخراج وحدة التحكم: مثل رؤية سريعة، مثل عبقري الجمال النقي. يبدو أننا قمنا بذلك، لكن ألا تعتقد أنه بالنسبة لمثل هذه المهمة البسيطة، تبين أن الكود الخاص بنا كان قليلاً... مطولاً؟ باستخدام Java 8 Stream API، يبدو الحل أكثر أناقة:
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;
public class Main {
public static void main(String[] args) throws IOException {
Stream<String> stream = Files.lines(Paths.get("C:\\Users\\Username\\Desktop\\pushkin.txt"));
List<String> result = stream
.filter(line -> line.startsWith("How"))
.map(String::toUpperCase)
.collect(Collectors.toList());
result.forEach(System.out::println);
}
}
لقد حققنا نفس النتيجة، ولكن مع كود أقل بكثير! علاوة على ذلك، لا يمكن القول أننا فقدنا "سهولة القراءة". أعتقد أنه يمكنك التعليق بسهولة على ما يفعله هذا الرمز، حتى لو لم تكن على دراية بـ Stream API. لكن باختصار، الدفق عبارة عن سلسلة من العناصر التي يمكنك أداء وظائف مختلفة عليها. نحصل على كائن Stream من الطريقة Files.lines()
ثم نطبق عليه 3 وظائف:
-
باستخدام هذه الطريقة،
filter()
نختار فقط تلك الأسطر من الملف التي تبدأ بـ "كيف". -
نمر عبر جميع الصفوف المحددة باستخدام الطريقة
map()
ونختصر كل منها إلى أحرف كبيرة. -
نحن نجمع كل الخطوط الناتجة في
List
استخدامcollect()
.
Files.walkFileTree()
. وهنا ما يتعين علينا القيام به. أولا نحتاج FileVisitor
. FileVisitor
هي واجهة خاصة تصف جميع طرق اجتياز شجرة الملفات. على وجه التحديد، سنضع المنطق هناك لقراءة محتويات الملف والتحقق مما إذا كان يحتوي على النص الذي نحتاجه. هذا ما سيبدو عليه حالنا FileVisitor
:
import java.io.IOException;
import java.nio.file.FileVisitResult;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.SimpleFileVisitor;
import java.nio.file.attribute.BasicFileAttributes;
import java.util.List;
public class MyFileVisitor extends SimpleFileVisitor<Path> {
@Override
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
List<String> lines = Files.readAllLines(file);
for (String s: lines) {
if (s.contains("This is the file we need")) {
System.out.println("Required file found!");
System.out.println(file.toAbsolutePath());
break;
}
}
return FileVisitResult.CONTINUE;
}
}
في هذه الحالة، يرث فصلنا من SimpleFileVisitor
. هذه فئة تنفذ FileVisitor
، حيث تحتاج إلى تجاوز طريقة واحدة فقط: visitFile()
. نحن هنا نصف ما يجب القيام به مع كل ملف في كل دليل. إذا كنت بحاجة إلى منطق اجتياز أكثر تعقيدًا، فيجب عليك كتابة التنفيذ الخاص بك FileVisitor
. هناك سوف تحتاج إلى تنفيذ 3 طرق أخرى:
-
preVisitDirectory()
— المنطق الذي يجب تنفيذه قبل الدخول إلى المجلد؛ -
visitFileFailed()
— ماذا تفعل إذا كان إدخال الملف مستحيلا (لا يمكن الوصول إليه، أو لأسباب أخرى)؛ -
postVisitDirectory()
— المنطق الذي يجب تنفيذه بعد الدخول إلى المجلد.
SimpleFileVisitor
. المنطق داخل الطريقة visitFile()
بسيط للغاية: اقرأ جميع الأسطر من الملف، وتحقق مما إذا كانت تحتوي على المحتوى الذي نحتاجه، وإذا كان الأمر كذلك، فاطبع المسار المطلق إلى وحدة التحكم. السطر الوحيد الذي قد يسبب لك المتاعب هو هذا السطر:
return FileVisitResult.CONTINUE;
في الواقع، كل شيء بسيط. نحن هنا نصف ببساطة ما يجب أن يفعله البرنامج بعد إدخال الملف وإتمام جميع العمليات الضرورية. في حالتنا، نحتاج إلى مواصلة اجتياز الشجرة، لذلك نختار الخيار CONTINUE
. لكن، على سبيل المثال، يمكن أن تكون لدينا مهمة أخرى: ليس العثور على جميع الملفات التي تحتوي على "هذا هو الملف الذي نحتاجه"، ولكن العثور على ملف واحد فقط . وبعد ذلك يجب إنهاء البرنامج. في هذه الحالة، سيبدو الكود الخاص بنا هو نفسه تمامًا، ولكن بدلاً من الفاصل؛ كان:
return FileVisitResult.TERMINATE;
حسنًا، فلنقم بتشغيل الكود الخاص بنا ونرى ما إذا كان يعمل أم لا.
import java.io.IOException;
import java.nio.file.*;
public class Main {
public static void main(String[] args) throws IOException {
Files.walkFileTree(Paths.get("C:\\Users\\Username\\Desktop\\testFolder"), new MyFileVisitor());
}
}
إخراج وحدة التحكم: تم العثور على الملف المطلوب! C:\Users\Username\Desktop\testFolder\FileWeNeed1.txt تم العثور على الملف المطلوب! C:\Users\Username\Desktop\testFolder\level1-a\level2-aa\FileWeNeed2.txt تم العثور على الملف المطلوب! C:\Users\Username\Desktop\testFolder\level1-b\level2-bb\FileWeNeed3.txt رائع، لقد فعلنا ذلك! :) إذا كنت تريد معرفة المزيد عنه walkFileTree()
، أوصيك بهذا المقال . يمكنك أيضًا إكمال مهمة صغيرة - استبدالها SimpleFileVisitor
بمهمة عادية FileVisitor
، وتنفيذ جميع الطرق الأربعة والتوصل إلى غرض لهذا البرنامج. على سبيل المثال، يمكنك كتابة برنامج يسجل جميع إجراءاتك: اعرض اسم الملف أو المجلد في وحدة التحكم قبل/بعد إدخالهما. هذا كل شيء - أراك لاحقًا! :)
GO TO FULL VERSION