קבצים

מבוא

עבודה עם קבצים היא חלק בסיסי מכל שפת תכנות. ב-Java יש כמה דרכים לעבוד עם קבצים, מהישנות והמסורבלות עד החדשות והפשוטות.

ייבואים נפוצים

import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.List;

המחלקה File

File file = new File("data.txt");

file.exists();        // האם קיים?
file.length();        // גודל בבייטים
file.isFile();        // האם קובץ?
file.isDirectory();   // האם תיקייה?
file.getName();       // שם הקובץ
file.getAbsolutePath(); // נתיב מלא
file.delete();        // מחיקה
file.createNewFile(); // יצירה

שימו לב: File לא מייצגת את תוכן הקובץ - רק את הנתיב והמטא-דאטה.

Path ו-Files (מודרני)

מ-Java 7, החבילה java.nio.file הציעה API חדש ונוח יותר:

Path path = Paths.get("data.txt");

או דרך Path.of מ-Java 11:

Path path = Path.of("data.txt");

קריאה מקובץ

הדרך המודרנית - קריאה כמחרוזת

import java.nio.file.Files;
import java.nio.file.Path;

String content = Files.readString(Path.of("data.txt"));
System.out.println(content);

קריאה לפי שורות

List<String> lines = Files.readAllLines(Path.of("data.txt"));
for (String line : lines) {
    System.out.println(line);
}

הדרך הקלאסית - עם Scanner

import java.io.File;
import java.util.Scanner;

Scanner scanner = new Scanner(new File("data.txt"));
while (scanner.hasNextLine()) {
    String line = scanner.nextLine();
    System.out.println(line);
}
scanner.close();

הדרך הקלאסית - BufferedReader

לקבצים גדולים, יעיל יותר:

import java.io.BufferedReader;
import java.io.FileReader;

try (BufferedReader br = new BufferedReader(new FileReader("data.txt"))) {
    String line;
    while ((line = br.readLine()) != null) {
        System.out.println(line);
    }
}

כתיבה לקובץ

כמחרוזת

Files.writeString(Path.of("output.txt"), "שלום עולם");

כרשימת שורות

List<String> lines = List.of("שורה 1", "שורה 2", "שורה 3");
Files.write(Path.of("output.txt"), lines);

עם PrintWriter

import java.io.PrintWriter;

try (PrintWriter writer = new PrintWriter("output.txt")) {
    writer.println("שורה ראשונה");
    writer.println("שורה שנייה");
    writer.printf("מספר: %d%n", 42);
}

הוספה (append) במקום דריסה

import java.io.FileWriter;

try (FileWriter fw = new FileWriter("output.txt", true)) {
    fw.write("שורה חדשה\n");
}

הפרמטר השני true אומר "append".

try-with-resources

בכל עבודה עם קבצים - חובה לסגור את הזרם. try-with-resources עושה את זה אוטומטית:

try (BufferedReader br = new BufferedReader(new FileReader("data.txt"))) {
    // שימוש ב-br
}
// br סגור אוטומטית, גם אם היתה חריגה

זה הרבה יותר בטוח מ-try-finally ידני.

עבודה עם תיקיות

Path dir = Path.of("myFolder");

// יצירת תיקייה
Files.createDirectory(dir);

// יצירת תיקייה כולל אב-תיקיות
Files.createDirectories(Path.of("a/b/c"));

// רשימת קבצים בתיקייה
try (var stream = Files.list(dir)) {
    stream.forEach(System.out::println);
}

// האם קיים?
Files.exists(dir);

// מחיקה
Files.delete(dir);

חריגות

רוב פעולות I/O זורקות IOException. או שתתפוס אותה ב-try/catch, או שתכריז שהפונקציה זורקת אותה:

public static void readFile() throws IOException {
    String content = Files.readString(Path.of("data.txt"));
    System.out.println(content);
}

או:

public static void readFile() {
    try {
        String content = Files.readString(Path.of("data.txt"));
        System.out.println(content);
    } catch (IOException e) {
        System.err.println("שגיאה: " + e.getMessage());
    }
}

נתיבים יחסיים מול מוחלטים

Path relative = Path.of("data.txt");       // יחסי לתיקיית העבודה
Path absolute = Path.of("C:/Users/nafta/data.txt");  // מוחלט

תיקיית העבודה היא בדרך כלל המקום שממנו הרצת את התוכנית. אפשר לבדוק:

System.out.println(System.getProperty("user.dir"));

דוגמה מלאה: קורא וכותב

import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.List;
import java.util.ArrayList;

public class FileDemo {
    public static void main(String[] args) throws IOException {
        Path input = Path.of("names.txt");
        Path output = Path.of("uppercased.txt");

        // קריאת קובץ
        List<String> names = Files.readAllLines(input);

        // עיבוד
        List<String> upper = new ArrayList<>();
        for (String name : names) {
            upper.add(name.toUpperCase());
        }

        // כתיבה
        Files.write(output, upper);

        System.out.println("הומר " + names.size() + " שמות");
    }
}

דוגמה: ספירת שורות בקובץ

Path path = Path.of("data.txt");
long lineCount = Files.lines(path).count();
System.out.println("שורות: " + lineCount);

בדקו את עצמכם

נסו לענות לבד לפני שאתם פותחים את התשובה.

  1. מה מייצג אובייקט File?

    1. את הנתיב והמידע על הקובץ, לא את התוכן
    2. תיקייה בלבד
    3. חיבור לאינטרנט
    4. את תוכן הקובץ
    הצגת התשובה

    תשובה א. File מתאר נתיב. כדי לקרוא תוכן משתמשים ב-Files או ב-Reader.

  2. למה משתמשים ב-try-with-resources בקריאת קבצים?

    1. כדי שהקובץ ייסגר אוטומטית גם אם נזרקה חריגה
    2. כי אחרת אי אפשר לקרוא
    3. כדי להצפין את הקובץ
    4. כדי שהקריאה תהיה מהירה
    הצגת התשובה

    תשובה א. try(...) סוגר את המשאב בסוף הבלוק בכל מקרה.