CodeGym /בלוג Java /Random-HE /קבצי Java, נתיב
John Squirrels
רָמָה
San Francisco

קבצי Java, נתיב

פורסם בקבוצה
היי! היום נדבר על עבודה עם קבצים וספריות. אתה כבר יודע איך לנהל את תוכן הקבצים: הקדשנו לזה הרבה שיעורים :) אני חושב שקל לך לזכור כמה שיעורים המשמשים למטרות אלו. בשיעור של היום, נדבר ספציפית על ניהול קבצים: יצירה, שינוי שם וכו'. לפני Java 7, כל הפעולות הללו בוצעו באמצעות מחלקה File . אתה יכול לקרוא על זה כאן . אבל ב-Java 7, יוצרי השפה החליטו לשנות את אופן העבודה עם קבצים וספריות. זה קרה מכיוון שלמחלקת File היו כמה חסרונות. לדוגמה, לא הייתה לו שיטת copy() שתאפשר לך להעתיק קובץ ממיקום אחד לאחר (יכולת חיונית לכאורה). בנוסף, למחלקה File היו לא מעט שיטות שהחזירו ערכים בוליאניים . כשיש שגיאה, שיטה כזו מחזירה false. זה לא זורק חריג, מה שמקשה מאוד לזהות שגיאות ולאבחן את הסיבות שלהן. במקום המחלקה הבודדת File , הופיעו 3 מחלקות: Paths , Path ו- Files . ובכן, אם לדייק, Path הוא ממשק, לא מחלקה. בואו להבין איך הם שונים זה מזה ולמה אנחנו צריכים כל אחד מהם. נתחיל מהפשוט ביותר: נתיבים .

שבילים

Paths היא מחלקה פשוטה מאוד עם שיטה סטטית אחת: get() . הוא נוצר אך ורק כדי לקבל אובייקט Path מהמחרוזת או ה-URI שעברו. אין לו פונקציונליות אחרת. הנה דוגמה לכך בעבודה:
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 , בגדול, הוא אנלוגי שעוצב מחדש למחלקה File . הרבה יותר קל לעבוד איתו מאשר עם File . ראשית , שיטות שימוש רבות (סטטיות) הוצאו והועברו למחלקה Files . שנית , הוטל סדר על ערכי ההחזרה של השיטות של ממשק הנתיב . במחלקה File , מתודות החזירו מחרוזת , או בוליאנית , או קובץ . זה לא היה קל להבין את זה. לדוגמה, הייתה שיטה getParent() שהחזירה מחרוזת המייצגת את נתיב האב של הקובץ הנוכחי. אבל הייתה גם שיטה getParentFile() שהחזירה את אותו הדבר אבל בצורה של אובייקט File ! ברור שזה מיותר. בהתאם לכך, בממשק Path , שיטת getParent() ושיטות אחרות לעבודה עם קבצים פשוט מחזירות אובייקט Path . אין ערימה של אפשרויות - הכל קל ופשוט. מהן כמה מהשיטות השימושיות שיש ל-Path ? הנה כמה מהם ודוגמאות לאופן שבו הם פועלים:
  • getFileName() : מחזירה את שם הקובץ מהנתיב;

  • getParent() : מחזירה את ספריית "האב" של הנתיב הנוכחי (במילים אחרות, הספרייה הממוקמת מיד מעל בעץ הספריות);

  • getRoot() : מחזירה את ספריית ה-"root", כלומר את הספרייה בראש עץ הספריות;

  • 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"));
       }
    }

    פלט מסוף:

    
    false
    true

    יש להעביר את המתודה endsWith() נתיב אמיתי, לא רק קבוצה של תווים: אחרת, התוצאה תמיד תהיה שקר, גם אם הנתיב הנוכחי באמת מסתיים ברצף התווים הזה (כמו במקרה של " 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());
       }
    }

    פלט מסוף:

    
    true
  • 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));
       }
    }

    פלט מסוף:

    
    Username\Desktop\testFile.txt

הרשימה המלאה של שיטות הנתיב ארוכה למדי. אתה יכול למצוא את כולם בתיעוד של Oracle . כעת נעבור לשקול קבצים .

קבצים

Files היא מחלקת שירות שמחזיקה את השיטות הסטטיות שהוצאו ממחלקת File . ניתן להשוות קבצים למערכים או לאוספים . ההבדל הוא שהוא עובד עם קבצים, לא מערכים או אוספים :) הוא מתמקד בניהול קבצים וספריות. באמצעות השיטות הסטטיות של המחלקה 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 {

       // Create a file
       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 a directory
       Path testDirectory = Files.createDirectory(Paths.get("C:\\Users\\Username\\Desktop\\testDirectory"));
       System.out.println("Was the directory created successfully?");
       System.out.println(Files.exists(Paths.get("C:\\Users\\Username\\Desktop\\testDirectory")));

       // Move the file from the desktop to the testDirectory directory. When you move a folder, you need to indicate its name in the folder!
       testFile1 = Files.move(testFile1, Paths.get("C:\\Users\\Username\\Desktop\\testDirectory\\testFile111.txt"), REPLACE_EXISTING);

       System.out.println("Did our file remain 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")));

       // Delete a 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() ). פלט מסוף:

Was the file created successfully? 
true 
Was the directory created successfully? 
true
Did our file remain on the desktop? 
false 
Has our file been moved to testDirectory? 
true 
Does the file still exist? 
false
הערה:כמו השיטות של 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 {

       // Create a file
       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 a directory
       Path testDirectory2 = Files.createDirectory(Paths.get("C:\\Users\\Username\\Desktop\\testDirectory2"));
       System.out.println("Was the directory created successfully?");
       System.out.println(Files.exists(Paths.get("C:\\Users\\Username\\Desktop\\testDirectory2")));

       // Copy the file from the desktop to the testDirectory2 directory.
       testFile1 = Files.copy(testFile1, Paths.get("C:\\Users\\Username\\Desktop\\testDirectory2\\testFile111.txt"), REPLACE_EXISTING);

       System.out.println("Did our file remain on the desktop?");
       System.out.println(Files.exists(Paths.get("C:\\Users\\Username\\Desktop\\testFile111.txt")));

       System.out.println("Was our file copied to testDirectory?");
       System.out.println(Files.exists(Paths.get("C:\\Users\\Username\\Desktop\\testDirectory2\\testFile111.txt")));
   }
}
פלט מסוף:

Was the file created successfully? 
true 
Was the directory created successfully? 
true 
Did our file remain on the desktop? 
true 
Was our file copied to 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);
       }
   }
}
פלט מסוף:

I still recall the wondrous moment: 
When you appeared before my sight, 
As though a brief and fleeting omen, 
Pure phantom in enchanting light.
סופר נוח! :) יכולת זו הופיעה ב-Java 7. ה- Stream API הופיע ב-Java 8. הוא מוסיף כמה אלמנטים של תכנות פונקציונלי ל-Java. כולל יכולות טיפול עשירות יותר בקבצים. תארו לעצמכם שיש לנו את המשימה הבאה: מצאו את כל השורות שמתחילות במילה "As", המירו אותן לאותיות רישיות והצגו אותן בקונסולה. איך ייראה פתרון באמצעות 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("As")) {
               String upper = s.toUpperCase();
               result.add(upper);
           }
       }

       for (String s: result) {
           System.out.println(s);
       }
   }
}
פלט מסוף:

AS THOUGH A BRIEF AND FLEETING OMEN, 
PURE PHANTOM IN ENCHANTING LIGHT.
המשימה הושלמה, אבל אתה לא חושב שלמשימה כל כך פשוטה התברר שהקוד שלנו היה קצת... מילולי? באמצעות ה- Stream API של Java 8, הפתרון נראה הרבה יותר אלגנטי:
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("As"))
               .map(String::toUpperCase)
               .collect(Collectors.toList());
       result.forEach(System.out::println);
   }
}
השגנו את אותה תוצאה, אבל עם הרבה פחות קוד! מה גם שאף אחד לא יכול להגיד שאיבדנו את ה"קריאה". אני חושב שאתה יכול להגיב בקלות על מה שהקוד הזה עושה, אפילו בלי להכיר את ה- Stream API. בקיצור, Stream הוא רצף של אלמנטים, עליהם ניתן לבצע פעולות שונות. אנו מקבלים אובייקט Stream מהשיטה Files.lines(), ולאחר מכן מיישמים עליו 3 פונקציות:
  1. אנו משתמשים filter()בשיטה כדי לבחור רק את השורות מהקובץ שמתחילות ב-"As".

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

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

אנחנו מקבלים את אותו פלט:

AS THOUGH A BRIEF AND FLEETING OMEN, 
PURE PHANTOM IN ENCHANTING LIGHT.
עכשיו נחזור ללחם והחמאה שלנו, כלומר לקבצים :) היכולת האחרונה שנבחן היום היא הליכה דרך עץ תיקים . במערכות הפעלה מודרניות, מבנה הקבצים נראה לרוב כמו עץ: יש לו שורש ויש ענפים, שיכולים להיות להם ענפים אחרים וכו'. השורש והענפים הם ספריות. לדוגמה, הספרייה " С:// " עשויה להיות השורש. הוא כולל שני סניפים: " C://Downloads " ו-" C://Users ". לכל אחד מהסניפים הללו יש שני סניפים: " C://Downloads/Pictures ", " C://Downloads/Video ", " C://Users/JohnSmith ", " C://Users/Pudge2005 ". ולענפים אלו בתורם יש ענפים אחרים וכו' ולכן אנו קוראים לזה עץ. ב-Linux, המבנה דומה, אבל הספרייה /קבצים, נתיב - 2 היא השורש. כעת דמיינו שעלינו להתחיל בספריית השורש, לעבור על כל התיקיות ותיקיות המשנה שלה, ולמצוא קבצים בעלי תוכן מסוים. נחפש קבצים המכילים את השורה "זה הקובץ שאנחנו צריכים!" ניקח את התיקיה "testFolder", שנמצאת על שולחן העבודה, בתור ספריית השורש. להלן התוכן שלו: קבצים, נתיב - 3התיקיות level1-a ו-level1-b מכילות גם תיקיות: קבצים, נתיב - 4קבצים, נתיב - 5אין תיקיות ב"תיקיות ברמה השנייה", רק קבצים בודדים: קבצים, נתיב - 6קבצים, נתיב - 73 הקבצים עם התוכן שאנו צריכים מקבלים בכוונה שמות מסבירים: 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("We found a file we need!");
               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());
   }
}
פלט מסוף:

We found a file we need! 
C:\Users\Username\Desktop\testFolder\FileWeNeed1.txt 
We found a file we need! 
C:\Users\Username\Desktop\testFolder\level1-a\level2-a-a\FileWeNeed2.txt 
We found a file we need! 
C:\Users\Username\Desktop\testFolder\level1-b\level2-b-b\FileWeNeed3.txt
מְעוּלֶה! זה עבד! :) אתה יכול גם לקבל את האתגר הקטן הזה: להחליף SimpleFileVisitorעם רגיל FileVisitor, לעקוף את כל 4 השיטות, ולהמציא מטרה משלך לתוכנית. לדוגמה, אתה יכול לכתוב תוכנית שמתעדת את כל הפעולות שלה: הצג את שם הקובץ או התיקיה לפני או אחרי הזנתן. זה הכל לעת עתה. נתראה בקרוב! :)
הערות
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION