Java 文件与 I/O
Java - 文件和 I/O
Section titled “Java - 文件和 I/O”输入/输出 (I/O) 操作对于任何需要从源(如文件、网络连接或键盘)读取数据或将数据写入目标(如文件、网络或控制台)的应用程序都是必不可少的。
Java 提供了丰富的 API 来处理 I/O,主要位于 java.io 和 java.nio(新 I/O)包中。
流 (Streams):核心概念
Section titled “流 (Streams):核心概念”在 Java I/O 中,**流(stream)**代表数据从源流向目标的序列。可以把它想象成一条传输数据的管道。
- 输入流: 从源读取数据(例如,
InputStream,Reader)。 - 输出流: 将数据写入目标(例如,
OutputStream,Writer)。
流根据它们处理的数据类型进行分类:
- 字节流: 处理原始二进制数据(8 位字节)。适用于图像、音频或任何非文本数据。核心类:
InputStream,OutputStream。 - 字符流: 处理字符数据(16 位 Unicode 字符)。自动处理字符编码转换。非常适合文本文件。核心类:
Reader,Writer。
现代文件 I/O:java.nio.file 包 (NIO.2)
Section titled “现代文件 I/O:java.nio.file 包 (NIO.2)”java.nio.file 包(通常称为 NIO.2)在 Java 7 中引入,与旧的 java.io.File 类相比,它提供了更现代、更灵活、更强大的文件系统交互 API。它通常是推荐的文件操作方法。
java.nio.file 中的关键类:
Section titled “java.nio.file 中的关键类:”Path:表示文件系统中的文件或目录路径。它是操作系统无关的。Paths:一个工厂类,包含静态方法(如get()),用于从字符串或 URI 创建Path对象。Files:一个工具类,包含大量静态方法用于常见的文件操作(读取、写入、复制、删除、检查存在性、管理属性等)。
使用 Paths
Section titled “使用 Paths”import java.nio.file.Path;import java.nio.file.Paths;
public class PathExample { public static void main(String[] args) { // 创建 Path 对象(操作系统无关的方式) Path filePath = Paths.get("data", "input.txt"); // 相对路径 Path configPath = Paths.get("/etc/app/config.properties"); // 绝对路径(示例) Path userHome = Paths.get(System.getProperty("user.home"), "documents", "report.pdf");
System.out.println("File Path: " + filePath); System.out.println("File Name: " + filePath.getFileName()); System.out.println("Parent Directory: " + filePath.getParent()); System.out.println("Absolute Path: " + filePath.toAbsolutePath()); }}使用 Files 读取文件
Section titled “使用 Files 读取文件”读取小文件(将所有行读入内存):
import java.nio.file.Files;import java.nio.file.Path;import java.nio.file.Paths;import java.io.IOException;import java.util.List;import java.nio.charset.StandardCharsets;
public class ReadAllLines { public static void main(String[] args) { Path filePath = Paths.get("input.txt"); // 假设 input.txt 存在
try { List<String> lines = Files.readAllLines(filePath, StandardCharsets.UTF_8); System.out.println("File Content:"); for (String line : lines) { System.out.println(line); } } catch (IOException e) { System.err.println("Error reading file: " + e.getMessage()); // e.printStackTrace(); // 用于详细的堆栈跟踪 } }}读取大文件(使用流和 try-with-resources): 对于大文件,逐行读取更节省内存。
import java.nio.file.Files;import java.nio.file.Path;import java.nio.file.Paths;import java.io.BufferedReader;import java.io.IOException;import java.nio.charset.StandardCharsets;
public class ReadLineByLine { public static void main(String[] args) { Path filePath = Paths.get("large_input.txt");
// try-with-resources 确保 reader 会自动关闭 try (BufferedReader reader = Files.newBufferedReader(filePath, StandardCharsets.UTF_8)) { String line; System.out.println("Reading line by line:"); while ((line = reader.readLine()) != null) { System.out.println(line); } } catch (IOException e) { System.err.println("Error reading file: " + e.getMessage()); } }}使用 Files 写入文件
Section titled “使用 Files 写入文件”写入字符串或字符串列表:
import java.nio.file.Files;import java.nio.file.Path;import java.nio.file.Paths;import java.io.IOException;import java.util.Arrays;import java.util.List;import java.nio.charset.StandardCharsets;import java.nio.file.StandardOpenOption;
public class WriteToFile { public static void main(String[] args) { Path outputPath = Paths.get("output.txt"); List<String> linesToWrite = Arrays.asList("First line", "Second line", "End of file.");
try { // 写入行(默认会覆盖现有文件) Files.write(outputPath, linesToWrite, StandardCharsets.UTF_8); System.out.println("Successfully wrote lines to " + outputPath);
// 向文件中追加行 Files.write(outputPath, Arrays.asList("Appended line."), StandardCharsets.UTF_8, StandardOpenOption.APPEND); System.out.println("Successfully appended to " + outputPath);
} catch (IOException e) { System.err.println("Error writing to file: " + e.getMessage()); } }}写入大量数据(使用流和 try-with-resources):
import java.nio.file.Files;import java.nio.file.Path;import java.nio.file.Paths;import java.io.BufferedWriter;import java.io.IOException;import java.nio.charset.StandardCharsets;
public class WriteWithStream { public static void main(String[] args) { Path filePath = Paths.get("stream_output.txt");
try (BufferedWriter writer = Files.newBufferedWriter(filePath, StandardCharsets.UTF_8)) { for (int i = 1; i <= 10; i++) { writer.write("Line number " + i); writer.newLine(); // 写入平台特定的换行符 } System.out.println("Successfully wrote using stream to " + filePath); } catch (IOException e) { System.err.println("Error writing file: " + e.getMessage()); } }}资源管理:try-with-resources
Section titled “资源管理:try-with-resources”I/O 的一个关键方面是完成使用后要关闭资源(如流),以释放系统资源并防止泄漏。未能关闭流可能导致问题。
try-with-resources 语句(在 Java 7 中引入)自动化资源管理。任何在 try 语句括号内声明的资源(该资源必须实现 java.lang.AutoCloseable 接口,大多数 I/O 流都实现了该接口)都会在 try 块结束时自动关闭,无论它是正常完成还是抛出异常。
这是处理 I/O 资源的标准且强烈推荐的方式。 除非是 Java 7 之前的代码,否则应避免在 finally 块中手动调用 close()。
try (ResourceType resource1 = initializeResource1(); ResourceType resource2 = initializeResource2()) { // 使用 resource1 和 resource2} catch (IOException e) { // 处理异常}// 资源在此处自动关闭文件系统操作
Section titled “文件系统操作”Files 类提供了许多有用的方法:
import java.nio.file.*;import java.io.IOException;
public class FileOperations { public static void main(String[] args) throws IOException { // 简单示例,实际代码需要 try-catch Path source = Paths.get("input.txt"); Path destination = Paths.get("backup", "input_copy.txt"); Path newDir = Paths.get("data", "subdir"); Path fileToCheck = Paths.get("output.txt");
// 检查是否存在 boolean exists = Files.exists(fileToCheck); System.out.println(fileToCheck + " exists: " + exists); System.out.println(fileToCheck + " does not exist: " + Files.notExists(fileToCheck)); System.out.println(fileToCheck + " is readable: " + Files.isReadable(fileToCheck));
// 创建目录(等同于 mkdirs) if (Files.notExists(newDir.getParent())) { Files.createDirectories(newDir.getParent()); // 如果需要,创建父目录 } if (Files.notExists(newDir)) { Files.createDirectory(newDir); System.out.println("Created directory: " + newDir); }
// 复制文件 if (Files.exists(source) && Files.exists(destination.getParent())) { Files.copy(source, destination, StandardCopyOption.REPLACE_EXISTING); System.out.println("Copied " + source + " to " + destination); }
// 移动(重命名)文件 // Path target = Paths.get("data", "renamed_input.txt"); // Files.move(source, target, StandardCopyOption.REPLACE_EXISTING); // System.out.println("Moved " + source + " to " + target);
// 删除文件 // if (Files.exists(destination)) { // Files.delete(destination); // System.out.println("Deleted: " + destination); // }
// 列出目录内容 Path dirToList = Paths.get("."); // 当前目录 System.out.println("\nListing contents of: " + dirToList.toAbsolutePath()); try (DirectoryStream<Path> stream = Files.newDirectoryStream(dirToList)) { for (Path entry : stream) { System.out.println(entry.getFileName()); } } catch (IOException e) { System.err.println("Error listing directory: " + e); // 列出目录时出错: } }}标准流(控制台 I/O)
Section titled “标准流(控制台 I/O)”Java 通过 System 类提供了三个标准流:
System.in:标准输入流(通常是键盘输入)。一个InputStream。System.out:标准输出流(通常是控制台输出)。一个PrintStream。System.err:标准错误流(通常是控制台输出,用于错误消息)。一个PrintStream。
从标准输入 (System.in) 读取
Section titled “从标准输入 (System.in) 读取”从控制台读取格式化输入(如数字、单词、行)最简单的方法是使用 java.util.Scanner 类。
import java.util.Scanner;
public class ReadConsole { public static void main(String[] args) { // 对 Scanner 使用 try-with-resources try (Scanner scanner = new Scanner(System.in)) { System.out.print("Enter your name: "); String name = scanner.nextLine(); // 读取一整行
System.out.print("Enter your age: "); int age = scanner.nextInt(); // 读取一个整数
System.out.println("\nHello, " + name + "! You are " + age + " years old."); } catch (java.util.InputMismatchException e) { System.err.println("Invalid input. Please enter a number for age."); } // Scanner 在此处自动关闭 }}写入标准输出/错误 (System.out, System.err)
Section titled “写入标准输出/错误 (System.out, System.err)”你已经大量使用过 System.out.println()。System.err 的工作方式相同,但习惯上用于错误消息。
public class StandardOutput { public static void main(String[] args) { System.out.println("This is standard output."); System.err.println("This is an error message.");
int value = 10; System.out.printf("The value is %d%n", value); // 格式化输出 }}遗留文件 I/O (java.io)
Section titled “遗留文件 I/O (java.io)”虽然新代码优先使用 java.nio.file,但你可能会遇到使用 java.io.File、FileInputStream、FileOutputStream、FileReader 和 FileWriter 等类的旧代码。认识这些类是有用的。
java.io.File:表示文件或目录路径,但与 java.nio.file.Path 相比存在局限性。它的许多方法在失败时不会抛出异常,这使得错误处理不够健壮。
FileInputStream/FileOutputStream:用于读取/写入原始字节。
FileReader/FileWriter:用于读取/写入字符,使用平台的默认字符编码(这可能存在问题)。通常最好使用 InputStreamReader/OutputStreamWriter 包装字节流,并显式指定编码(例如,StandardCharsets.UTF_8)。
示例(使用 try-with-resources 的遗留复制):
import java.io.*;
public class LegacyCopyFile { public static void main(String[] args) { File sourceFile = new File("input.txt"); File destFile = new File("output_legacy.txt");
// 仍然使用 try-with-resources! try (InputStream in = new FileInputStream(sourceFile); OutputStream out = new FileOutputStream(destFile)) {
byte[] buffer = new byte[1024]; // 分块读写 int lengthRead; while ((lengthRead = in.read(buffer)) > 0) { out.write(buffer, 0, lengthRead); } System.out.println("File copied successfully using legacy IO.");
} catch (IOException e) { System.err.println("Error copying file: " + e.getMessage()); } }}对于现代 Java I/O 操作,始终优先使用 java.nio.file 和 try-with-resources。
接下来是什么?
Section titled “接下来是什么?”理解如何在 I/O 或其它操作期间处理潜在错误至关重要。下一章将介绍 Java 异常 (Exceptions),解释如何在程序中优雅地预期、捕获和处理错误。