Java 讀取檔案

Java 提供多種方式讀取檔案,適用於不同的使用場景。

使用 Files(推薦)

Java 7+ 的 Files 類別提供簡潔的 API:

讀取全部內容

import java.nio.file.Files;
import java.nio.file.Paths;

// 讀取為字串(Java 11+)
String content = Files.readString(Paths.get("file.txt"));

// 讀取為位元組陣列
byte[] bytes = Files.readAllBytes(Paths.get("file.txt"));
String content = new String(bytes, StandardCharsets.UTF_8);

// 讀取所有行
List<String> lines = Files.readAllLines(Paths.get("file.txt"));
List<String> lines = Files.readAllLines(Paths.get("file.txt"), StandardCharsets.UTF_8);

使用 Stream 逐行讀取

// 適合大檔案
try (Stream<String> lines = Files.lines(Paths.get("file.txt"))) {
    lines.forEach(System.out::println);
}

// 處理每一行
try (Stream<String> lines = Files.lines(Paths.get("file.txt"))) {
    long count = lines.filter(line -> line.contains("error"))
                      .count();
}

使用 BufferedReader

適合逐行讀取大型文字檔案:

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

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

// 指定編碼
try (BufferedReader reader = new BufferedReader(
        new InputStreamReader(new FileInputStream("file.txt"), StandardCharsets.UTF_8))) {
    // ...
}

// 使用 Files.newBufferedReader(推薦)
try (BufferedReader reader = Files.newBufferedReader(Paths.get("file.txt"))) {
    String line;
    while ((line = reader.readLine()) != null) {
        System.out.println(line);
    }
}

使用 FileInputStream

讀取二進位檔案:

import java.io.FileInputStream;

// 逐個位元組讀取
try (FileInputStream fis = new FileInputStream("image.png")) {
    int data;
    while ((data = fis.read()) != -1) {
        // 處理每個位元組
    }
}

// 使用緩衝區
try (FileInputStream fis = new FileInputStream("image.png")) {
    byte[] buffer = new byte[1024];
    int bytesRead;
    while ((bytesRead = fis.read(buffer)) != -1) {
        // 處理 buffer 中的 bytesRead 個位元組
    }
}

使用 Scanner

方便解析文字:

import java.util.Scanner;

// 讀取檔案
try (Scanner scanner = new Scanner(new File("file.txt"))) {
    while (scanner.hasNextLine()) {
        String line = scanner.nextLine();
        System.out.println(line);
    }
}

// 讀取數值
try (Scanner scanner = new Scanner(new File("numbers.txt"))) {
    while (scanner.hasNextInt()) {
        int number = scanner.nextInt();
        System.out.println(number);
    }
}

// 使用分隔符
try (Scanner scanner = new Scanner(new File("data.csv"))) {
    scanner.useDelimiter(",");
    while (scanner.hasNext()) {
        String token = scanner.next();
    }
}

使用 FileReader

簡單的字元讀取:

import java.io.FileReader;

try (FileReader reader = new FileReader("file.txt")) {
    int ch;
    while ((ch = reader.read()) != -1) {
        System.out.print((char) ch);
    }
}

// 使用緩衝區
try (FileReader reader = new FileReader("file.txt")) {
    char[] buffer = new char[1024];
    int charsRead;
    while ((charsRead = reader.read(buffer)) != -1) {
        String content = new String(buffer, 0, charsRead);
    }
}

讀取資源檔案

讀取 classpath 中的資源:

// 使用 ClassLoader
InputStream is = getClass().getClassLoader().getResourceAsStream("config.properties");
try (BufferedReader reader = new BufferedReader(new InputStreamReader(is))) {
    // ...
}

// 使用 Class
InputStream is = MyClass.class.getResourceAsStream("/config.properties");

// 讀取為字串
String content = new String(
    getClass().getClassLoader().getResourceAsStream("file.txt").readAllBytes(),
    StandardCharsets.UTF_8
);

實際範例

讀取設定檔

public Properties loadProperties(String filename) throws IOException {
    Properties props = new Properties();
    try (InputStream is = new FileInputStream(filename)) {
        props.load(is);
    }
    return props;
}

// 使用
Properties config = loadProperties("config.properties");
String host = config.getProperty("database.host", "localhost");

讀取 CSV

public List<String[]> readCSV(String filename) throws IOException {
    List<String[]> records = new ArrayList<>();
    try (BufferedReader reader = Files.newBufferedReader(Paths.get(filename))) {
        String line;
        while ((line = reader.readLine()) != null) {
            String[] values = line.split(",");
            records.add(values);
        }
    }
    return records;
}

讀取 JSON(使用 Jackson)

public <T> T readJson(String filename, Class<T> clazz) throws IOException {
    ObjectMapper mapper = new ObjectMapper();
    return mapper.readValue(new File(filename), clazz);
}

// 使用
User user = readJson("user.json", User.class);

計算檔案行數

public long countLines(String filename) throws IOException {
    try (Stream<String> lines = Files.lines(Paths.get(filename))) {
        return lines.count();
    }
}

搜尋檔案內容

public List<String> searchInFile(String filename, String keyword) throws IOException {
    try (Stream<String> lines = Files.lines(Paths.get(filename))) {
        return lines.filter(line -> line.contains(keyword))
                    .collect(Collectors.toList());
    }
}

讀取大檔案的特定行

public String readLine(String filename, int lineNumber) throws IOException {
    try (Stream<String> lines = Files.lines(Paths.get(filename))) {
        return lines.skip(lineNumber - 1)
                    .findFirst()
                    .orElse(null);
    }
}

方法比較

方法適用場景特點
Files.readString()小型文字檔最簡潔
Files.readAllLines()小型文字檔回傳 List
Files.lines()大型文字檔Stream,延遲讀取
BufferedReader大型文字檔逐行讀取
FileInputStream二進位檔案位元組讀取
Scanner解析資料方便解析

注意事項

處理編碼

// 明確指定編碼
BufferedReader reader = new BufferedReader(
    new InputStreamReader(new FileInputStream("file.txt"), StandardCharsets.UTF_8)
);

// Files 預設使用 UTF-8
List<String> lines = Files.readAllLines(Paths.get("file.txt"));

處理大檔案

// ✗ 不要一次讀取全部
String content = Files.readString(Paths.get("huge.txt"));  // 可能 OutOfMemory

// ✓ 使用 Stream 或 BufferedReader
try (Stream<String> lines = Files.lines(Paths.get("huge.txt"))) {
    lines.forEach(this::processLine);
}

關閉資源

// ✓ 使用 try-with-resources
try (BufferedReader reader = Files.newBufferedReader(path)) {
    // ...
}

// ✓ Stream 也需要關閉
try (Stream<String> lines = Files.lines(path)) {
    // ...
}

重點整理

  • 小檔案:使用 Files.readString()Files.readAllLines()
  • 大檔案:使用 Files.lines()BufferedReader
  • 二進位:使用 FileInputStreamFiles.readAllBytes()
  • 一定要使用 try-with-resources 關閉資源
  • 明確指定編碼避免亂碼