JAVA之IO

File 对象详解

1. File 类概述

File 类是 Java 中用于表示文件和目录路径名的抽象类。File 对象既可以表示文件,也可以表示目录。它不仅仅是文件或目录的路径表示,还提供了对文件系统的抽象操作。File 类并不涉及文件的内容读写,它主要用于处理文件的属性、管理文件和目录结构。

2. File 类的构造方法

File 类提供了多种构造方法,可以通过不同的方式创建 File 对象:

  • 绝对路径创建 File 对象:

    1
    File file = new File("/path/to/file.txt");
  • 相对路径创建 File 对象:

    1
    File file = new File("file.txt");
  • 通过父路径和子路径创建 File 对象:

    1
    File file = new File("/path/to", "file.txt");
  • 通过 File 对象和子路径创建 File 对象:

    1
    2
    File parent = new File("/path/to");
    File file = new File(parent, "file.txt");

3. File 类的主要方法

以下是 File 类的一些常用方法及其详细说明:

  • exists(): 检查文件或目录是否存在。

    1
    2
    3
    4
    5
    6
    File file = new File("example.txt");
    if (file.exists()) {
    System.out.println("File exists.");
    } else {
    System.out.println("File does not exist.");
    }
  • createNewFile(): 创建新文件,如果文件已经存在,则返回 false

    1
    2
    3
    4
    5
    6
    File file = new File("example.txt");
    if (file.createNewFile()) {
    System.out.println("File created successfully.");
    } else {
    System.out.println("File already exists.");
    }
  • mkdir(): 创建新目录。如果目录已经存在或父目录不存在,则返回 false

    1
    2
    3
    4
    5
    6
    File directory = new File("newDir");
    if (directory.mkdir()) {
    System.out.println("Directory created successfully.");
    } else {
    System.out.println("Failed to create directory.");
    }
  • mkdirs(): 创建目录,包括必要但不存在的父目录。如果目录已经存在,则返回 false

    1
    2
    3
    4
    5
    6
    File directory = new File("parentDir/newDir");
    if (directory.mkdirs()) {
    System.out.println("Directories created successfully.");
    } else {
    System.out.println("Failed to create directories.");
    }
  • delete(): 删除文件或目录。如果删除的是目录,则该目录必须为空才能被删除。

    1
    2
    3
    4
    5
    6
    File file = new File("example.txt");
    if (file.delete()) {
    System.out.println("File deleted successfully.");
    } else {
    System.out.println("Failed to delete file.");
    }
  • length(): 返回文件的大小(字节数)。对于目录,返回值不确定。

    1
    2
    File file = new File("example.txt");
    System.out.println("File size: " + file.length() + " bytes");
  • listFiles(): 返回目录中所有文件和目录的数组。如果 File 对象不是目录,返回 null

    1
    2
    3
    4
    5
    6
    7
    8
    9
    File directory = new File("someDir");
    File[] files = directory.listFiles();
    if (files != null) {
    for (File f : files) {
    System.out.println(f.getName());
    }
    } else {
    System.out.println("Directory is empty or not a directory.");
    }
  • renameTo(File dest): 重命名文件或目录。如果目标文件已经存在,重命名操作将失败。

    1
    2
    3
    4
    5
    6
    7
    File oldFile = new File("oldName.txt");
    File newFile = new File("newName.txt");
    if (oldFile.renameTo(newFile)) {
    System.out.println("File renamed successfully.");
    } else {
    System.out.println("Failed to rename file.");
    }
  • isFile()isDirectory(): 检查 File 对象是否是一个文件或目录。

    1
    2
    3
    4
    5
    6
    File file = new File("example.txt");
    if (file.isFile()) {
    System.out.println("This is a file.");
    } else if (file.isDirectory()) {
    System.out.println("This is a directory.");
    }
  • getAbsolutePath(): 返回 File 对象的绝对路径名。

    1
    2
    File file = new File("example.txt");
    System.out.println("Absolute path: " + file.getAbsolutePath());
  • getName(): 返回文件或目录的名称。

    1
    2
    File file = new File("example.txt");
    System.out.println("File name: " + file.getName());
  • getParent(): 返回父路径名,如果没有父路径,则返回 null

    1
    2
    File file = new File("/path/to/example.txt");
    System.out.println("Parent path: " + file.getParent());

4. File 类的实际应用

  • 创建目录结构: 可以通过 mkdirs() 方法创建深层次的目录结构,适合自动化部署或文件管理系统。

    1
    2
    3
    4
    5
    6
    File dirs = new File("/data/logs/2024/08");
    if (dirs.mkdirs()) {
    System.out.println("Directories created.");
    } else {
    System.out.println("Failed to create directories.");
    }
  • 文件管理和过滤: 使用 listFiles(FileFilter filter)list(FilenameFilter filter) 可以对文件进行过滤,例如筛选出特定类型的文件。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    File dir = new File("someDir");
    File[] txtFiles = dir.listFiles(new FilenameFilter() {
    public boolean accept(File dir, String name) {
    return name.endsWith(".txt");
    }
    });
    for (File txtFile : txtFiles) {
    System.out.println(txtFile.getName());
    }
  • 递归删除目录: 删除目录时,可以递归删除其所有子目录和文件。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    public static boolean deleteDirectory(File dir) {
    if (dir.isDirectory()) {
    File[] children = dir.listFiles();
    if (children != null) {
    for (File child : children) {
    deleteDirectory(child);
    }
    }
    }
    return dir.delete();
    }

通过 File 类,Java 提供了对文件系统的丰富操作接口。尽管 File 类不直接处理文件内容的读写,但它是管理文件和目录结构的基础,可以用于各种文件管理任务,如创建、删除、重命名文件或目录,查询文件属性等。

InputStream

1. InputStream 概述

InputStream 是 Java 中用于读取字节流的抽象类。它是所有字节输入流类的超类,提供了从不同的数据源(如文件、网络连接、内存中的字节数组等)读取字节数据的基本功能。InputStream 类本身是抽象的,不能直接实例化。要使用它,必须通过其子类来具体化。

InputStream 的设计目标是处理任何类型的输入源,无论是文件、网络连接还是其他数据流。它通常与 OutputStream 一起使用来实现数据的输入和输出操作。

2. InputStream 类的主要方法

  • int read(): 从输入流中读取一个字节的数据。返回读取的字节,返回值范围为 0 到 255。如果到达流的末尾,返回 -1

    1
    2
    3
    4
    5
    6
    7
    InputStream inputStream = new FileInputStream("example.txt");
    int data = inputStream.read();
    while (data != -1) {
    System.out.print((char) data); // 将字节数据转换为字符
    data = inputStream.read();
    }
    inputStream.close();
  • int read(byte[] b): 从输入流中读取多个字节并将它们存储到给定的字节数组 b 中,返回读取的字节数。如果到达流的末尾,返回 -1

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    byte[] buffer = new byte[1024];
    InputStream inputStream = new FileInputStream("example.txt");
    int bytesRead = inputStream.read(buffer);
    while (bytesRead != -1) {
    for (int i = 0; i < bytesRead; i++) {
    System.out.print((char) buffer[i]);
    }
    bytesRead = inputStream.read(buffer);
    }
    inputStream.close();
  • int read(byte[] b, int off, int len): 从输入流中读取最多 len 个字节,并将它们存储到字节数组 b 的指定位置 off 开始。返回读取的字节数,如果到达流的末尾,返回 -1

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    byte[] buffer = new byte[1024];
    InputStream inputStream = new FileInputStream("example.txt");
    int bytesRead = inputStream.read(buffer, 0, buffer.length);
    while (bytesRead != -1) {
    for (int i = 0; i < bytesRead; i++) {
    System.out.print((char) buffer[i]);
    }
    bytesRead = inputStream.read(buffer, 0, buffer.length);
    }
    inputStream.close();
  • long skip(long n): 跳过并丢弃输入流中 n 个字节的数据。返回实际跳过的字节数。

    1
    2
    3
    4
    5
    6
    7
    8
    InputStream inputStream = new FileInputStream("example.txt");
    inputStream.skip(10); // 跳过前10个字节
    int data = inputStream.read();
    while (data != -1) {
    System.out.print((char) data);
    data = inputStream.read();
    }
    inputStream.close();
  • int available(): 返回可以从输入流中读取的字节数,而不会阻塞。

    1
    2
    InputStream inputStream = new FileInputStream("example.txt");
    System.out.println("Available bytes: " + inputStream.available());
  • void close(): 关闭输入流并释放与之关联的任何系统资源。

    1
    2
    3
    4
    5
    6
    InputStream inputStream = new FileInputStream("example.txt");
    try {
    // 执行读取操作
    } finally {
    inputStream.close(); // 确保流在使用完后被关闭
    }

3. InputStream 的主要子类

InputStream 的许多子类实现了特定的功能,这里介绍几个常用的子类:

  • FileInputStream: 用于从文件中读取字节数据。它是读取文件内容的常用类。

    1
    InputStream inputStream = new FileInputStream("example.txt");
  • ByteArrayInputStream: 用于从字节数组中读取字节数据。它在处理内存中数据时非常有用,例如在测试中模拟输入流。

    1
    2
    byte[] byteArray = "Hello, World!".getBytes();
    InputStream inputStream = new ByteArrayInputStream(byteArray);
  • BufferedInputStream: 为另一个输入流提供缓冲功能,以提高读取效率。适合频繁的小数据读取操作。

    1
    InputStream inputStream = new BufferedInputStream(new FileInputStream("example.txt"));
  • DataInputStream: 允许以机器无关的方式读取 Java 基本数据类型(如 int, float, double 等),适合处理二进制数据格式。

    1
    2
    DataInputStream dataInputStream = new DataInputStream(new FileInputStream("data.bin"));
    int number = dataInputStream.readInt();

4. 实际应用

  • 读取文件内容: InputStream 最常见的用法之一就是从文件中读取内容,如读取配置文件、日志文件或其他文本文件。

    1
    2
    3
    4
    5
    InputStream inputStream = new FileInputStream("config.properties");
    Properties properties = new Properties();
    properties.load(inputStream);
    System.out.println(properties.getProperty("key"));
    inputStream.close();
  • 网络数据读取: 在网络编程中,InputStream 可以用于读取从服务器或客户端发送的数据流。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    Socket socket = new Socket("example.com", 80);
    InputStream inputStream = socket.getInputStream();
    int data = inputStream.read();
    while (data != -1) {
    System.out.print((char) data);
    data = inputStream.read();
    }
    inputStream.close();
    socket.close();
  • 处理二进制文件: 使用 DataInputStream 从二进制文件中读取数据,例如读取图像文件的元数据。

    1
    2
    3
    4
    5
    DataInputStream dataInputStream = new DataInputStream(new FileInputStream("image.dat"));
    int width = dataInputStream.readInt();
    int height = dataInputStream.readInt();
    System.out.println("Image width: " + width + ", height: " + height);
    dataInputStream.close();

通过 InputStream 及其子类,Java 提供了一种统一的接口来读取各种类型的数据流,无论是文本数据还是二进制数据。理解 InputStream 的工作机制和常用方法是掌握 Java IO 编程的基础。

OutputStream

1. OutputStream 概述

OutputStream 是 Java 中所有字节输出流的抽象基类。它用于将数据以字节的形式写入到各种输出目标,如文件、内存、网络连接等。OutputStream 是一个抽象类,因此不能直接实例化。其主要目的是提供一个通用接口,供子类实现实际的输出操作。

OutputStream 的设计目标是处理所有类型的输出目标,不论是文件、网络连接、还是字节数组。通常与 InputStream 搭配使用,以实现数据的输入输出操作。

2. OutputStream 类的主要方法

  • void write(int b): 将指定的字节写入输出流。参数 b 是要写入的字节,范围为 0 到 255。

    1
    2
    3
    OutputStream outputStream = new FileOutputStream("example.txt");
    outputStream.write(65); // 写入字符 'A'
    outputStream.close();
  • void write(byte[] b): 将指定的字节数组 b 中的所有字节写入输出流。

    1
    2
    3
    4
    byte[] data = "Hello, World!".getBytes();
    OutputStream outputStream = new FileOutputStream("example.txt");
    outputStream.write(data);
    outputStream.close();
  • void write(byte[] b, int off, int len): 将指定的字节数组 b 中从偏移量 off 开始的 len 个字节写入输出流。

    1
    2
    3
    4
    byte[] data = "Hello, World!".getBytes();
    OutputStream outputStream = new FileOutputStream("example.txt");
    outputStream.write(data, 0, 5); // 只写入 "Hello"
    outputStream.close();
  • void flush(): 刷新此输出流并强制写出所有缓冲的输出字节。如果流中有数据被缓冲而未实际写出,此方法会将这些数据写入目标输出。

    1
    2
    3
    4
    OutputStream outputStream = new BufferedOutputStream(new FileOutputStream("example.txt"));
    outputStream.write("Buffered output".getBytes());
    outputStream.flush(); // 确保所有数据被写入
    outputStream.close();
  • void close(): 关闭输出流并释放与此流相关的所有系统资源。如果有未写出的数据,此方法会先调用 flush() 方法,然后关闭流。

    1
    2
    3
    4
    5
    6
    OutputStream outputStream = new FileOutputStream("example.txt");
    try {
    outputStream.write("Closing the stream".getBytes());
    } finally {
    outputStream.close(); // 确保在完成操作后关闭流
    }

3. OutputStream 的主要子类

OutputStream 的许多子类实现了特定的功能,以下是一些常用子类的介绍:

  • FileOutputStream: 用于将字节数据写入文件。它是处理文件输出的常用类,通常用来创建或修改文件。

    1
    2
    3
    OutputStream outputStream = new FileOutputStream("example.txt");
    outputStream.write("File output example".getBytes());
    outputStream.close();
  • ByteArrayOutputStream: 将字节数据写入到字节数组中。适合在内存中操作字节流数据,特别是需要频繁操作字节数据的场景。

    1
    2
    3
    ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
    byteArrayOutputStream.write("Byte array output".getBytes());
    byte[] result = byteArrayOutputStream.toByteArray(); // 获取写入的数据
  • BufferedOutputStream: 为另一个输出流提供缓冲功能,以提高写入效率。适合频繁的小数据写入操作。

    1
    2
    3
    4
    OutputStream outputStream = new BufferedOutputStream(new FileOutputStream("example.txt"));
    outputStream.write("Buffered output example".getBytes());
    outputStream.flush(); // 刷新缓冲区,确保数据写入文件
    outputStream.close();
  • DataOutputStream: 允许将 Java 的基本数据类型(如 int, float, double 等)以机器无关的方式写入输出流。适合处理二进制数据格式。

    1
    2
    3
    4
    DataOutputStream dataOutputStream = new DataOutputStream(new FileOutputStream("data.bin"));
    dataOutputStream.writeInt(123);
    dataOutputStream.writeDouble(45.67);
    dataOutputStream.close();

4. 实际应用

  • 写入文件内容: OutputStream 最常见的用途之一是将数据写入文件,如日志记录、生成报告等。

    1
    2
    3
    OutputStream outputStream = new FileOutputStream("log.txt", true); // 以追加模式打开文件
    outputStream.write("Log entry: application started.\n".getBytes());
    outputStream.close();
  • 发送网络数据: 在网络编程中,OutputStream 常用于发送数据到服务器或客户端。

    1
    2
    3
    4
    5
    Socket socket = new Socket("example.com", 80);
    OutputStream outputStream = socket.getOutputStream();
    outputStream.write("GET / HTTP/1.1\n\n".getBytes());
    outputStream.flush();
    socket.close();
  • 处理二进制文件: 使用 DataOutputStream 可以将复杂的数据结构(如图片、音频文件的元数据)写入二进制文件。

    1
    2
    3
    4
    5
    DataOutputStream dataOutputStream = new DataOutputStream(new FileOutputStream("output.dat"));
    dataOutputStream.writeInt(256);
    dataOutputStream.writeFloat(3.14f);
    dataOutputStream.writeUTF("Binary data");
    dataOutputStream.close();

Filter 模式详解

1. Filter 模式概述

Filter 模式(也称为装饰者模式)是一种设计模式,允许开发者在不改变对象本身的情况下动态地为对象添加新功能。该模式通过将一个对象嵌套在另一个对象中来实现增强功能。这在 Java 的 I/O 系统中广泛使用,特别是通过各种输入流和输出流的组合。

在 Java 的 I/O 中,FilterInputStreamFilterOutputStream 是过滤流的基类,它们为其他输入流或输出流提供附加功能(如缓冲、数据类型转换、加密等)。这些过滤流可以嵌套使用,从而实现多重功能增强。

2. 主要过滤器类

以下是 Java 中一些常用的过滤器类,它们继承自 FilterInputStreamFilterOutputStream

  • BufferedInputStreamBufferedOutputStream:
    • 概述: 提供缓冲功能以提高 I/O 操作的效率。默认情况下,BufferedInputStreamBufferedOutputStream 提供 8KB 的缓冲区,减少了直接访问底层硬件的次数。
    • 主要方法:
      • read()write():从缓冲区读取或向缓冲区写入数据。
      • flush():强制将缓冲区内容写入底层输出流。
    1
    2
    3
    4
    5
    6
    7
    InputStream inputStream = new BufferedInputStream(new FileInputStream("example.txt"));
    int data = inputStream.read();
    while (data != -1) {
    System.out.print((char) data);
    data = inputStream.read();
    }
    inputStream.close();
  • DataInputStreamDataOutputStream:
    • 概述: 允许以平台无关的方式读取和写入 Java 的基本数据类型(如 intlongfloat 等)。它们对底层字节流进行封装,使数据的读写更加方便。
    • 主要方法:
      • readInt()writeInt():读取和写入整数。
      • readDouble()writeDouble():读取和写入双精度浮点数。
      • readUTF()writeUTF():读取和写入 UTF-8 编码的字符串。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    DataOutputStream dataOutputStream = new DataOutputStream(new FileOutputStream("data.bin"));
    dataOutputStream.writeInt(42);
    dataOutputStream.writeUTF("Hello");
    dataOutputStream.close();

    DataInputStream dataInputStream = new DataInputStream(new FileInputStream("data.bin"));
    int number = dataInputStream.readInt();
    String text = dataInputStream.readUTF();
    dataInputStream.close();
    System.out.println("Number: " + number + ", Text: " + text);
  • CheckedInputStreamCheckedOutputStream:
    • 概述: 提供校验和功能,确保数据在传输过程中未被意外修改。使用 CheckedInputStreamCheckedOutputStream 可以计算数据流的校验和,如 CRC32 或 Adler32。
    • 主要方法:
      • getChecksum():返回当前计算的校验和。
    1
    2
    3
    4
    5
    6
    7
    CheckedOutputStream checkedOutputStream = new CheckedOutputStream(
    new FileOutputStream("example.zip"), new Adler32());
    byte[] data = "Data with checksum".getBytes();
    checkedOutputStream.write(data);
    checkedOutputStream.close();
    long checksum = checkedOutputStream.getChecksum().getValue();
    System.out.println("Checksum: " + checksum);

3. 实际应用场景

  • 文件读取和写入的性能优化: 使用 BufferedInputStreamBufferedOutputStream 可以减少磁盘访问的次数,提高文件读写的性能。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    // 使用缓冲流进行文件复制
    try (InputStream in = new BufferedInputStream(new FileInputStream("source.txt"));
    OutputStream out = new BufferedOutputStream(new FileOutputStream("destination.txt"))) {
    byte[] buffer = new byte[1024];
    int bytesRead;
    while ((bytesRead = in.read(buffer)) != -1) {
    out.write(buffer, 0, bytesRead);
    }
    }
  • 数据的序列化和反序列化: 使用 DataInputStreamDataOutputStream 可以方便地读写 Java 的基本数据类型,适合数据的序列化与反序列化操作。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    // 将基本数据类型写入文件
    try (DataOutputStream out = new DataOutputStream(new FileOutputStream("data.bin"))) {
    out.writeInt(100);
    out.writeDouble(12.34);
    out.writeBoolean(true);
    }

    // 从文件中读取基本数据类型
    try (DataInputStream in = new DataInputStream(new FileInputStream("data.bin"))) {
    int intValue = in.readInt();
    double doubleValue = in.readDouble();
    boolean boolValue = in.readBoolean();
    System.out.println("Int: " + intValue + ", Double: " + doubleValue + ", Boolean: " + boolValue);
    }
  • 数据校验和计算: 在需要保证数据完整性的场景中,可以使用 CheckedInputStreamCheckedOutputStream 对数据流进行校验和计算。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    // 计算文件的校验和
    try (CheckedInputStream in = new CheckedInputStream(new FileInputStream("data.bin"), new CRC32())) {
    byte[] buffer = new byte[1024];
    while (in.read(buffer) != -1) {
    // 读取数据并自动更新校验和
    }
    long checksum = in.getChecksum().getValue();
    System.out.println("File checksum: " + checksum);
    }

4. 组合使用示例

Java 的 I/O 过滤器流可以通过层层包装的方式组合使用,以实现多种功能。例如,读取经过缓冲处理的数据并计算其校验和:

1
2
3
4
5
6
7
8
9
10
11
12
InputStream inputStream = new CheckedInputStream(
new BufferedInputStream(
new FileInputStream("example.txt")),
new CRC32());
int data = inputStream.read();
while (data != -1) {
// 处理数据
data = inputStream.read();
}
long checksum = ((CheckedInputStream) inputStream).getChecksum().getValue();
System.out.println("Checksum: " + checksum);
inputStream.close();

在上述示例中,FileInputStream 被包装在 BufferedInputStream 中以提高读取效率,随后又被包装在 CheckedInputStream 中以便计算校验和。

操作 Zip详解

Java 提供了 java.util.zip 包,用于处理 ZIP 文件的压缩和解压缩。该包包含了处理 ZIP 文件的核心类,使得压缩和解压缩操作变得简便。

1. ZipInputStream

  • 概述: ZipInputStream 类用于从 ZIP 文件中读取压缩数据。它可以逐个读取 ZIP 文件中的条目(entries),并解压缩它们。每个条目可以是文件或目录。

  • 主要方法:

    • ZipEntry getNextEntry(): 返回下一个 ZIP 文件条目(ZipEntry)并将流的位置移到条目数据的开始处。如果没有更多条目,则返回 null
    • int read(byte[] b): 从当前条目中读取数据到指定的字节数组中,返回读取的字节数。
    • void closeEntry(): 关闭当前条目。这个方法必须在完成对条目数据的读取后调用。
    • void close(): 关闭 ZIP 输入流并释放系统资源。
  • 示例:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    try (ZipInputStream zis = new ZipInputStream(new FileInputStream("example.zip"))) {
    ZipEntry entry;
    while ((entry = zis.getNextEntry()) != null) {
    System.out.println("Extracting file: " + entry.getName());
    byte[] buffer = new byte[1024];
    int len;
    while ((len = zis.read(buffer)) > 0) {
    System.out.write(buffer, 0, len);
    }
    zis.closeEntry();
    }
    } catch (IOException e) {
    e.printStackTrace();
    }

2. ZipOutputStream

  • 概述: ZipOutputStream 类用于将数据压缩并写入到 ZIP 文件中。它允许向 ZIP 文件中添加多个条目,每个条目对应 ZIP 文件中的一个文件或目录。

  • 主要方法:

    • void putNextEntry(ZipEntry e): 开始写入新的 ZIP 条目。ZipEntry 对象描述了条目的名称和其他属性。
    • void write(byte[] b): 将数据写入当前条目中。
    • void closeEntry(): 关闭当前条目,并准备写入下一个条目。
    • void close(): 关闭 ZIP 输出流并释放系统资源。此方法会自动关闭所有未关闭的条目。
  • 示例:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    try (ZipOutputStream zos = new ZipOutputStream(new FileOutputStream("example.zip"))) {
    // 创建一个 ZIP 条目
    ZipEntry entry = new ZipEntry("example.txt");
    zos.putNextEntry(entry);
    // 写入数据到 ZIP 条目中
    zos.write("Hello, World!".getBytes());
    // 关闭当前条目
    zos.closeEntry();
    // 关闭 ZIP 输出流
    zos.close();
    } catch (IOException e) {
    e.printStackTrace();
    }

3. ZipEntry

  • 概述: ZipEntry 类表示 ZIP 文件中的一个条目。条目可以是文件或目录。它包含条目的名称、大小、压缩方法和其他属性。

  • 主要方法:

    • String getName(): 返回条目的名称。
    • long getSize(): 返回条目的未压缩大小。
    • long getCompressedSize(): 返回条目的压缩大小。
    • int getMethod(): 返回条目的压缩方法(如 ZipEntry.DEFLATED)。
    • long getTime(): 返回条目的最后修改时间。
  • 示例:

    1
    2
    3
    4
    5
    6
    7
    ZipEntry entry = new ZipEntry("example.txt");
    entry.setSize(12345); // 设置条目的大小
    entry.setCompressedSize(2345); // 设置条目的压缩大小
    entry.setTime(System.currentTimeMillis()); // 设置条目的时间
    System.out.println("Entry Name: " + entry.getName());
    System.out.println("Size: " + entry.getSize());
    System.out.println("Compressed Size: " + entry.getCompressedSize());

4. 实际应用

  • 压缩多个文件到一个 ZIP 文件:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    try (ZipOutputStream zos = new ZipOutputStream(new FileOutputStream("archive.zip"))) {
    String[] files = {"file1.txt", "file2.txt"};
    for (String fileName : files) {
    try (FileInputStream fis = new FileInputStream(fileName)) {
    ZipEntry entry = new ZipEntry(fileName);
    zos.putNextEntry(entry);
    byte[] buffer = new byte[1024];
    int length;
    while ((length = fis.read(buffer)) > 0) {
    zos.write(buffer, 0, length);
    }
    zos.closeEntry();
    }
    }
    } catch (IOException e) {
    e.printStackTrace();
    }

  • 解压缩 ZIP 文件:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    try (ZipInputStream zis = new ZipInputStream(new FileInputStream("archive.zip"))) {
    ZipEntry entry;
    while ((entry = zis.getNextEntry()) != null) {
    File file = new File(entry.getName());
    try (FileOutputStream fos = new FileOutputStream(file)) {
    byte[] buffer = new byte[1024];
    int length;
    while ((length = zis.read(buffer)) > 0) {
    fos.write(buffer, 0, length);
    }
    }
    zis.closeEntry();
    }
    } catch (IOException e) {
    e.printStackTrace();
    }

  • 查看 ZIP 文件内容:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    try (ZipInputStream zis = new ZipInputStream(new FileInputStream("archive.zip"))) {
    ZipEntry entry;
    while ((entry = zis.getNextEntry()) != null) {
    System.out.println("File Name: " + entry.getName());
    System.out.println("Compressed Size: " + entry.getCompressedSize());
    System.out.println("Size: " + entry.getSize());
    zis.closeEntry();
    }
    } catch (IOException e) {
    e.printStackTrace();
    }

通过 java.util.zip 包,Java 提供了一套完整的 API 来处理 ZIP 文件的创建、修改和提取操作,能够满足各种常见的压缩和解压缩需求。

classpath 资源

读取类路径资源文件

在 Java 中,类路径(classpath)是一个重要的概念,它定义了 Java 虚拟机和类加载器搜索类和资源的位置。读取类路径中的资源文件(如配置文件、静态资源等)是 Java 应用程序常见的操作。为了方便地读取这些资源,Java 提供了 ClassLoader 类和相关方法。

1. ClassLoader.getResourceAsStream()

  • 概述: ClassLoader.getResourceAsStream() 方法允许你从类路径中读取资源文件。该方法返回一个 InputStream,通过它可以读取资源文件的内容。资源文件的路径是相对于类路径的,不需要包含类路径前缀(如 src/main/resources/)。

  • 使用场景:

    • 读取配置文件(如 config.properties)。
    • 读取静态资源(如图片、JSON 文件等)。
    • 访问类路径中打包的资源文件(如 JAR 文件中的资源)。
  • 示例:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    // 读取配置文件 config.properties
    InputStream inputStream = getClass().getClassLoader().getResourceAsStream("config.properties");
    if (inputStream == null) {
    System.out.println("Resource not found");
    return;
    }

    Properties properties = new Properties();
    try {
    properties.load(inputStream);
    String value = properties.getProperty("key");
    System.out.println("Value: " + value);
    } catch (IOException e) {
    e.printStackTrace();
    } finally {
    try {
    inputStream.close();
    } catch (IOException e) {
    e.printStackTrace();
    }
    }

2. 使用 getClass().getResourceAsStream()

  • 概述: Class.getResourceAsStream() 方法也是用于读取类路径中的资源,但它是相对于调用该方法的类的包路径的。即,如果你从一个类中调用它,资源路径是相对于该类的包路径的。

  • 使用场景:

    • 读取类路径中的资源文件,并且资源文件路径相对于调用方法的类的包路径。
  • 示例:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    // 从当前类所在的包路径读取资源文件 config.properties
    InputStream inputStream = getClass().getResourceAsStream("/config.properties");
    if (inputStream == null) {
    System.out.println("Resource not found");
    return;
    }

    Properties properties = new Properties();
    try {
    properties.load(inputStream);
    String value = properties.getProperty("key");
    System.out.println("Value: " + value);
    } catch (IOException e) {
    e.printStackTrace();
    } finally {
    try {
    inputStream.close();
    } catch (IOException e) {
    e.printStackTrace();
    }
    }

3. 资源路径注意事项

  • 资源路径的前缀:
    • 使用 ClassLoader.getResourceAsStream("config.properties") 时,路径是相对于类路径的根目录。
    • 使用 Class.getResourceAsStream("/config.properties") 时,路径是从类路径的根目录开始的,前导斜杠表示从根开始。
  • 文件位置:
    • 将资源文件放置在 src/main/resourcessrc/test/resources 目录下,打包时会自动包含在 JAR 文件的根目录中。
  • 异常处理:
    • 在处理 InputStream 时,要处理可能的 IOException 异常。
    • 关闭流是非常重要的,使用 try-with-resources 语句可以自动关闭资源。

4. 实际应用

  • 配置文件读取:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    InputStream inputStream = getClass().getClassLoader().getResourceAsStream("config.properties");
    Properties properties = new Properties();
    try {
    properties.load(inputStream);
    String dbUrl = properties.getProperty("db.url");
    String dbUser = properties.getProperty("db.user");
    System.out.println("Database URL: " + dbUrl);
    System.out.println("Database User: " + dbUser);
    } catch (IOException e) {
    e.printStackTrace();
    } finally {
    try {
    inputStream.close();
    } catch (IOException e) {
    e.printStackTrace();
    }
    }

  • 读取静态资源:

    1
    2
    3
    4
    5
    6
    7
    InputStream inputStream = getClass().getResourceAsStream("/images/logo.png");
    if (inputStream != null) {
    BufferedImage image = ImageIO.read(inputStream);
    // 使用 image 对象
    } else {
    System.out.println("Image not found");
    }

通过这些方法,Java 应用程序可以方便地访问和读取类路径中的资源文件,这对于配置管理、资源加载和文件操作非常有用。

序列化

序列化和反序列化

在 Java 中,序列化是将对象转换为字节流的过程,使对象能够被存储在文件中、通过网络传输或保存到数据库中。反序列化则是将字节流转换回原始对象的过程。

1. 序列化

概述: 序列化允许将对象的状态写入 OutputStream,将其转换为字节流。序列化的对象可以存储到文件、数据库,或通过网络发送。

  • 实现 Serializable 接口:
    • 任何需要序列化的对象都必须实现 java.io.Serializable 接口。这个接口是一个标记接口,没有方法定义。
    • 只有实现了 Serializable 接口的类的对象才能被序列化。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import java.io.Serializable;

public class MyObject implements Serializable {
private static final long serialVersionUID = 1L; // 推荐定义版本号

private String name;
private transient int age; // transient 修饰符标记不需要序列化的字段

public MyObject(String name, int age) {
this.name = name;
this.age = age;
}

// Getters and setters...
}

步骤: 1. 创建 ObjectOutputStream: - ObjectOutputStream 用于将对象写入 OutputStream。 - 通过 writeObject() 方法将对象序列化到流中。

  1. 关闭流:
    • 在完成写入后,确保关闭流以释放资源。

示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;

public class SerializeExample {
public static void main(String[] args) {
MyObject obj = new MyObject("John Doe", 30);
try (ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("object.ser"))) {
oos.writeObject(obj);
} catch (IOException e) {
e.printStackTrace();
}
}
}

2. 反序列化

概述: 反序列化是将字节流转换为对象的过程。它恢复了序列化时保存的对象状态。

  • 步骤:
    1. 创建 ObjectInputStream:
      • ObjectInputStream 用于从 InputStream 中读取对象。
      • 通过 readObject() 方法将对象从流中反序列化回来。
    2. 处理反序列化的异常:
      • 反序列化过程中可能会抛出 ClassNotFoundExceptionIOException 异常。
    3. 关闭流:
      • 反序列化完成后,确保关闭流以释放资源。

示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import java.io.FileInputStream;
import java.io.IOException;
import java.io.ObjectInputStream;

public class DeserializeExample {
public static void main(String[] args) {
try (ObjectInputStream ois = new ObjectInputStream(new FileInputStream("object.ser"))) {
MyObject obj = (MyObject) ois.readObject();
System.out.println("Name: " + obj.getName());
System.out.println("Age: " + obj.getAge());
} catch (IOException | ClassNotFoundException e) {
e.printStackTrace();
}
}
}

3. 关键点和注意事项

  • serialVersionUID:
    • 定义 serialVersionUID 是推荐的做法,它是一个版本控制标识符,用于确保序列化和反序列化过程中的版本一致性。
    • 如果 serialVersionUID 不一致,反序列化可能会失败,导致 InvalidClassException 异常。
  • transient 关键字:
    • transient 修饰符用于标记不需要序列化的字段。在反序列化时,这些字段的值将被忽略,可能会被初始化为默认值。
  • 序列化安全:
    • 避免序列化不可信的数据,因为它可能导致反序列化漏洞(例如,攻击者可以创建恶意序列化数据)。
  • 自定义序列化:
    • 实现 writeObject()readObject() 方法来提供自定义的序列化和反序列化逻辑。例如,可以在序列化过程中执行数据验证或转换。

自定义序列化示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.IOException;
import java.io.Serializable;

public class MyCustomObject implements Serializable {
private static final long serialVersionUID = 1L;

private String name;
private transient int age;

public MyCustomObject(String name, int age) {
this.name = name;
this.age = age;
}

private void writeObject(ObjectOutputStream oos) throws IOException {
oos.defaultWriteObject(); // Write default fields
oos.writeInt(age * 2); // Custom serialization
}

private void readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException {
ois.defaultReadObject(); // Read default fields
this.age = ois.readInt() / 2; // Custom deserialization
}

// Getters and setters...
}

通过序列化和反序列化,Java 提供了一种有效的机制来持久化对象状态和在不同系统之间传输对象。这些功能在许多应用场景中都非常重要,如数据存储、分布式计算和网络通信。

Reader

Reader 是 Java 中用于处理字符输入流的抽象类。它用于读取字符数据而不是字节数据。Reader 提供了一种读取字符的方式,通常用于处理文本数据。

1. 概述

  • 功能: Reader 类主要用于从不同的数据源中读取字符数据,可以读取单个字符、字符数组或字符流。
  • 设计: Reader 是所有字符输入流的超类。它为各种具体的字符输入流(如 FileReader, BufferedReader 等)提供了基本的方法。

2. 主要方法

  • int read():读取单个字符并返回其 Unicode 值,若到达流末尾,则返回 -1
  • int read(char[] cbuf, int off, int len):将最多 len 个字符从流中读入到字符数组 cbuf 中,从 off 位置开始。
  • void close():关闭流并释放与之关联的资源。

3. 主要子类

  • FileReader:
    • 概述: FileReader 直接从文件中读取字符。
    • 用法: 适用于简单的文件读取操作。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    import java.io.FileReader;
    import java.io.IOException;
    import java.io.Reader;

    public class FileReaderExample {
    public static void main(String[] args) {
    try (Reader reader = new FileReader("example.txt")) {
    int data = reader.read();
    while (data != -1) {
    System.out.print((char) data);
    data = reader.read();
    }
    } catch (IOException e) {
    e.printStackTrace();
    }
    }
    }
  • BufferedReader:
    • 概述: BufferedReader 为其他 Reader 类提供缓冲功能,以提高读取效率,尤其在读取大量数据时。
    • 用法: 适用于高效读取文本文件行。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    import java.io.BufferedReader;
    import java.io.FileReader;
    import java.io.IOException;

    public class BufferedReaderExample {
    public static void main(String[] args) {
    try (BufferedReader br = new BufferedReader(new FileReader("example.txt"))) {
    String line;
    while ((line = br.readLine()) != null) {
    System.out.println(line);
    }
    } catch (IOException e) {
    e.printStackTrace();
    }
    }
    }

Writer

Writer 是 Java 中用于处理字符输出流的抽象类。它用于将字符数据写入不同的输出目标。

1. 概述

  • 功能: Writer 类主要用于将字符数据写入到不同的目的地,例如文件、内存等。
  • 设计: Writer 是所有字符输出流的超类。它为各种具体的字符输出流(如 FileWriter, BufferedWriter 等)提供了基本的方法。

2. 主要方法

  • void write(int c):写入单个字符。
  • void write(char[] cbuf, int off, int len):将字符数组的 len 个字符写入流,从 off 位置开始。
  • void write(String str, int off, int len):将字符串的 len 个字符写入流,从 off 位置开始。
  • void flush():刷新流,确保所有缓冲的输出都被写入到目的地。
  • void close():关闭流并释放与之关联的资源。

3. 主要子类

  • FileWriter:
    • 概述: FileWriter 直接将字符写入到文件中。
    • 用法: 适用于简单的文件写入操作。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    import java.io.FileWriter;
    import java.io.IOException;
    import java.io.Writer;

    public class FileWriterExample {
    public static void main(String[] args) {
    try (Writer writer = new FileWriter("example.txt")) {
    writer.write("Hello, World!");
    writer.write(System.lineSeparator());
    writer.write("Welcome to Java IO.");
    } catch (IOException e) {
    e.printStackTrace();
    }
    }
    }
  • BufferedWriter:
    • 概述: BufferedWriter 为其他 Writer 类提供缓冲功能,以提高写入效率,尤其在写入大量数据时。
    • 用法: 适用于高效写入文本文件。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    import java.io.BufferedWriter;
    import java.io.FileWriter;
    import java.io.IOException;

    public class BufferedWriterExample {
    public static void main(String[] args) {
    try (BufferedWriter bw = new BufferedWriter(new FileWriter("example.txt"))) {
    bw.write("Hello, World!");
    bw.newLine();
    bw.write("Welcome to Java IO.");
    } catch (IOException e) {
    e.printStackTrace();
    }
    }
    }

主要区别与选择

  • 缓冲 vs. 不缓冲:
    • BufferedReaderBufferedWriter 提供缓冲功能,适用于读取和写入大量数据时,提高效率。
    • FileReaderFileWriter 是直接读写,没有缓冲,适合小文件或简单的操作。
  • 字符 vs. 字节:
    • ReaderWriter 处理字符数据,适用于文本处理。
    • 对于字节数据的处理,使用 InputStreamOutputStream 类。

这些类在 Java IO 中扮演了重要角色,使得处理文件和其他字符数据变得更加高效和灵活。选择适合的类和方法可以显著提升应用程序的性能和可维护性。

PrintStreamPrintWriter

PrintStreamPrintWriter 都是 Java IO 的类,提供了便捷的输出功能,特别是用于打印数据。它们支持多种打印方法,例如 print()println(),并且可以将数据写入各种目标(文件、控制台等)。尽管它们有许多相似之处,但它们的使用场景和特性略有不同。

1. PrintStream

  • 概述: PrintStream 是基于字节的输出流,通常用于打印字节数据。它继承自 OutputStream 类,提供了多种便捷的方法来写入不同类型的数据。

  • 主要特性:

    • 自动刷新: 可以通过 autoFlush 参数在每次调用 println()printf()format() 时自动刷新流。
    • 字符转换: PrintStream 使用系统默认字符编码进行字符转换,可能导致不同平台间的兼容性问题。
  • 主要方法:

    • print():输出数据(可以是字符串、整数、浮点数等)。
    • println():输出数据后换行。
    • printf():格式化输出。
    • flush():强制刷新流的缓冲区。
  • 示例代码:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    import java.io.FileOutputStream;
    import java.io.IOException;
    import java.io.PrintStream;

    public class PrintStreamExample {
    public static void main(String[] args) {
    try (PrintStream ps = new PrintStream(new FileOutputStream("output.txt"))) {
    ps.println("Hello, PrintStream!");
    ps.printf("Formatted number: %.2f%n", 123.456);
    ps.flush();
    } catch (IOException e) {
    e.printStackTrace();
    }
    }
    }

2. PrintWriter

  • 概述: PrintWriter 是基于字符的输出流,通常用于打印字符数据。它继承自 Writer 类,提供了更丰富的字符处理功能,并支持更多的字符编码。

  • 主要特性:

    • 字符编码: PrintWriter 支持指定字符编码,适合需要控制字符编码的场景。
    • 自动刷新: 可以通过构造函数中的 autoFlush 参数设置自动刷新。
    • 异常处理: PrintWriter 的方法不会直接抛出 IOException,而是通过 checkError() 方法检查是否发生错误。
  • 主要方法:

    • print():输出数据(可以是字符串、整数、浮点数等)。
    • println():输出数据后换行。
    • printf():格式化输出。
    • flush():强制刷新流的缓冲区。
    • checkError():检查是否发生错误。
  • 示例代码:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    import java.io.FileWriter;
    import java.io.IOException;
    import java.io.PrintWriter;

    public class PrintWriterExample {
    public static void main(String[] args) {
    try (PrintWriter pw = new PrintWriter(new FileWriter("output.txt"))) {
    pw.println("Hello, PrintWriter!");
    pw.printf("Formatted number: %.2f%n", 123.456);
    pw.flush();
    } catch (IOException e) {
    e.printStackTrace();
    }
    }
    }

  • 使用场景:
    • 使用 PrintStream 当你需要与字节流配合工作,或在处理原始字节数据时。
    • 使用 PrintWriter 当你处理字符数据,尤其是需要控制字符编码时。
  • 自动刷新:
    • PrintStreamPrintWriter 都支持自动刷新功能,可以根据需要在构造函数中设置 autoFlush 参数。
  • 异常处理:
    • PrintStream 的方法可以直接抛出 IOException
    • PrintWriter 的方法不会抛出 IOException,需要使用 checkError() 方法检查错误。

这两个类提供了高效且方便的方法来处理数据的输出,使得编写输出数据的代码更加简洁和易于维护。

使用 Files

Files 类是 Java NIO (java.nio.file) 包的一部分,提供了一系列静态方法用于方便地操作文件和目录。它简化了许多常见的文件操作,使得代码更加简洁和易于理解。

1. 读取和写入文件

  • readAllBytes(Path path)
    • 功能: 读取指定路径的文件的所有字节。
    • 返回值: 返回一个包含文件内容的字节数组。
    • 示例代码:
      1
      2
      3
      Path path = Paths.get("example.txt");
      byte[] bytes = Files.readAllBytes(path);
      System.out.println(new String(bytes, StandardCharsets.UTF_8));
  • write(Path path, byte[] bytes)
    • 功能: 将字节数组写入指定路径的文件。如果文件已存在,则会被覆盖。
    • 示例代码:
      1
      2
      3
      Path path = Paths.get("example.txt");
      String content = "Hello, World!";
      Files.write(path, content.getBytes(StandardCharsets.UTF_8));
  • readAllLines(Path path)
    • 功能: 读取指定路径的文件的所有行,并返回一个 List<String>
    • 返回值: 返回一个包含文件每一行的 List
    • 示例代码:
      1
      2
      3
      Path path = Paths.get("example.txt");
      List<String> lines = Files.readAllLines(path);
      lines.forEach(System.out::println);

2. 复制和移动文件

  • copy(Path source, Path target)
    • 功能: 复制文件从源路径到目标路径。如果目标文件已存在,可以指定如何处理(例如覆盖)。
    • 参数:
      • StandardCopyOption.REPLACE_EXISTING:覆盖现有文件。
    • 示例代码:
      1
      2
      3
      Path source = Paths.get("example.txt");
      Path target = Paths.get("exampleCopy.txt");
      Files.copy(source, target, StandardCopyOption.REPLACE_EXISTING);
  • move(Path source, Path target)
    • 功能: 移动文件从源路径到目标路径。如果目标路径已存在,可以指定如何处理。
    • 示例代码:
      1
      2
      3
      Path source = Paths.get("example.txt");
      Path target = Paths.get("exampleMoved.txt");
      Files.move(source, target, StandardCopyOption.REPLACE_EXISTING);

3. 删除文件或目录

  • delete(Path path)
    • 功能: 删除指定路径的文件或空目录。如果路径不存在,或者路径是一个非空目录,会抛出异常。
    • 示例代码:
      1
      2
      Path path = Paths.get("example.txt");
      Files.delete(path);
  • deleteIfExists(Path path)
    • 功能: 删除指定路径的文件或目录(如果存在)。不会抛出异常。
    • 示例代码:
      1
      2
      Path path = Paths.get("example.txt");
      Files.deleteIfExists(path);

4. 读取文件的行

  • lines(Path path)
    • 功能: 读取文件的所有行,并返回一个 Stream<String>,可以用于流式处理。
    • 返回值: 返回一个 Stream<String>
    • 示例代码:
      1
      2
      3
      4
      Path path = Paths.get("example.txt");
      try (Stream<String> lines = Files.lines(path)) {
      lines.forEach(System.out::println);
      }

5. 其他功能

  • exists(Path path)
    • 功能: 检查指定路径的文件或目录是否存在。
    • 示例代码:
      1
      2
      3
      Path path = Paths.get("example.txt");
      boolean exists = Files.exists(path);
      System.out.println("File exists: " + exists);
  • createDirectory(Path path)
    • 功能: 创建指定路径的目录。如果目录已存在,则会抛出异常。
    • 示例代码:
      1
      2
      Path path = Paths.get("newDirectory");
      Files.createDirectory(path);
  • createFile(Path path)
    • 功能: 创建指定路径的空文件。如果文件已存在,则会抛出异常。
    • 示例代码:
      1
      2
      Path path = Paths.get("newFile.txt");
      Files.createFile(path);

Files 类简化了文件和目录的操作,包括读取、写入、复制、移动、删除等。它结合了 NIO 的优势,提供了更高效和简洁的文件操作方式。在使用 Files 类时,通常需要处理 IOException,因为许多文件操作会抛出这个异常。通过适当地使用 Files 类的静态方法,可以有效地管理文件系统中的文件和目录。