JavaRush /בלוג Java /Random-HE /קבצי Java, נתיב

קבצי Java, נתיב

פורסם בקבוצה
שלום! היום נדבר על עבודה עם קבצים וספריות. אתה כבר יודע איך לנהל את התוכן של קבצים: היו לנו הרבה שיעורים שהוקדשו לזה :) אני חושב שאתה יכול בקלות לזכור כמה שיעורים הדרושים למטרות אלה. בהרצאה של היום נדבר ספציפית על ניהול קבצים – יצירה, שינוי שם וכו'. לפני Java 7, כל הפעולות הללו בוצעו באמצעות File. על עבודתו תוכלו לקרוא כאן . אבל ב-Java 7, יוצרי השפה החליטו לשנות את אופן העבודה שלהם עם קבצים וספריות. זה נבע מהעובדה שלכיתה Fileהיו מספר חסרונות. לדוגמה, לא הייתה לו שיטה copy()שתאפשר לך להעתיק קובץ ממיקום אחד לאחר (תכונה נחוצה לכאורה). בנוסף, לכיתה Fileהיו די הרבה שיטות שהחזירו boolean-ערכים. אם מתרחשת שגיאה, שיטה כזו מחזירה false במקום לזרוק חריג, מה שמקשה מאוד על אבחון שגיאות וקביעת הסיבות שלהן. במקום מחלקה בודדת, 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(), ולא רק קבוצה של תווים: אחרת התוצאה תמיד תהיה false , גם אם הנתיב הנוכחי מסתיים למעשה ברצף כזה של תווים (כמו במקרה של "estFile.txt " בדוגמה למעלה).

    בנוסף, ישנה Pathקבוצת שיטות המפשטת את העבודה עם נתיבים מוחלטים (מלאים) ויחסיים .

בואו נסתכל על השיטות האלה:
  • boolean isAbsolute()- מחזירה true אם הנתיב הנוכחי הוא מוחלט:

    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:\Users\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));
       }
    }

    פלט מסוף:

    שם משתמש\Desktop\testFile.txt

רשימת השיטות המלאה Pathהיא די גדולה. אתה יכול למצוא את כולם בתיעוד של Oracle . נעבור לסקירה 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()). פלט מסוף: האם הקובץ נוצר בהצלחה? true האם הספרייה נוצרה בהצלחה? true האם הקובץ שלנו עדיין על שולחן העבודה? false האם הקובץ שלנו הועבר ל-testDirectory? true האם הקובץ עדיין קיים? שֶׁקֶר שים לב:בדיוק כמו שיטות ממשק 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")));
   }
}
פלט מסוף: האם הקובץ נוצר בהצלחה? true האם הספרייה נוצרה בהצלחה? true האם הקובץ שלנו עדיין על שולחן העבודה? true האם הקובץ שלנו הועתק ל-testDirectory? true עכשיו אתה יכול להעתיק קבצים באופן תכנותי! :) אבל הכיתה 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 הוא רצף של אלמנטים שעליהם אתה יכול לבצע פונקציות שונות. אנו מקבלים את אובייקט ה-Stream מהשיטה Files.lines()ולאחר מכן מיישמים עליו 3 פונקציות:
  1. באמצעות השיטה, filter()אנו בוחרים רק את השורות מהקובץ שמתחילות ב"איך".

  2. אנו עוברים על כל השורות הנבחרות בשיטה map()ומביאים כל אחד מהם ל- UPPER CASE.

  3. אנו משלבים את כל השורות המתקבלות תוך Listשימוש ב- collect().

ביציאה נקבל את אותה תוצאה: LIKE A FASTING VISION, LIKE A GENIUS OF PURE BEAUTY. אם אתה מעוניין ללמוד עוד על היכולות של ספרייה זו, אנו ממליצים לקרוא מאמר זה . נחזור לכבשים שלנו, כלומר לתיקים :) האפשרות האחרונה שנבחן היום היא הליכה בעץ התיקים . מבנה הקבצים במערכות הפעלה מודרניות לרוב מקבל צורה של עץ: יש לו שורש ויש ענפים מהם ניתן להפריד ענפים אחרים וכו'. ספריות ממלאות את התפקיד של שורש וענפים. לדוגמה, הספרייה " C:// " יכולה לשמש כשורש . שני סניפים מסתעפים ממנו: " C://Downloads " ו-" C://Users ". מכל אחד מהסניפים הללו ישנם 2 סניפים נוספים: " C://Downloads/Pictures ", " C://Downloads/Video ", " C://Users/JohnSmith ", " C://Users/Pudge2005 " . סניפים אחרים מסתעפים מסניפים אלו וכו'. - כך יוצא עץ. בלינוקס זה נראה בערך אותו הדבר, רק שם התיקיה פועלת בתור השורש / קבצים, נתיב - 2 עכשיו דמיינו שיש לנו משימה: הכרת ספריית השורש, עלינו לעבור עליה, להסתכל בתיקיות בכל הרמות ולמצוא בהן קבצים עם התוכן אנחנו צריכים. נחפש קבצים המכילים את השורה "זה הקובץ שאנחנו צריכים!" ספריית השורש שלנו תהיה תיקיית "testFolder", הממוקמת על שולחן העבודה. בתוכו יש את התכנים הבאים: קבצים, נתיב - 3בתוך התיקיות level1-a ו- level1-b יש גם תיקיות: קבצים, נתיב - 4קבצים, נתיב - 5בתוך "תיקיות ברמה שנייה" אלו אין יותר תיקיות, אלא רק קבצים בודדים: קבצים, נתיב - 6קבצים, נתיב - 7אנו נייעד במיוחד 3 קבצים עם התוכן שאנו צריך עם שמות ברורים - FileWeNeed1.txt , FileWeNeed2.txt, FileWeNeed3.txt אלה הם אלה שעלינו למצוא לפי תוכן באמצעות Java. איך אנחנו יכולים לעשות את זה? שיטה חזקה מאוד לחציית עץ קבצים באה לעזרה - 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, יישם את כל 4 השיטות והמצא מטרה לתוכנית זו. לדוגמה, אתה יכול לכתוב תוכנית שתתעד את כל הפעולות שלך: הצג את שם הקובץ או התיקיה בקונסולה לפני/אחרי הכניסה אליהן. זה הכל - נתראה מאוחר יותר! :)
הערות
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION