NIO-上-灵析社区

菜鸟码转

一、旧版 IO 总结

Java 的旧的 IO 是指在 Java NIO 出现之前,Java 标准库中提供的传统 IO API。旧的 IO API 在 Java NIO 出现之前是主要的 IO 技术,它的优点是简单易用、容易理解,适合于处理少量数据的场景。旧的 IO API 在处理大量数据时效率较低,且不支持非阻塞 IO 操作,在处理大量数据和高并发的场景下效率较低,因此在实际开发中,应该根据具体的需求选择合适的 IO 技术。

字节流(Byte Stream)

字节流(Byte Stream)是 Java IO 操作中处理字节数据的基本流类型,它可以读取和写入字节数据。

字节流主要用于读取和写入二进制数据,如图像、声音、视频等。在使用字节流进行输入输出时,数据以字节为单位进行读取和写入,比字符流更为底层和高效。因此,字节流在处理一些二进制数据的场景下很常用。

字节流有两个抽象基类:InputStream 和 OutputStream。InputStream 和 OutputStream 都是抽象类,不能直接使用。Java 提供了一些常见的子类,用于处理不同的输入输出场景:

  • FileInputStream 和 FileOutputStream:用于读取和写入文件数据的流;
  • ByteArrayInputStream 和 ByteArrayOutputStream:用于读取和写入内存中的字节数据的流;
  • PipedInputStream 和 PipedOutputStream:用于连接两个线程之间的管道的流;
  • FilterInputStream 和 FilterOutputStream:用于对其他输入输出流进行包装的装饰器流。

下面的例子演示了如何从文件中读取二进制数据并将其写入到另一个文件中:

package cn.leetcode;

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;

public class ByteStreamExample {

    public static void main(String[] args) {
        // 这里的输入输出不一定是文本类型的文件,可以是任意类型的文件
        String inputFile = "input.txt";
        String outputFile = "output.txt";
        try (
                // 创建输入流读取 input.txt 文件的内容
                FileInputStream inStream = new FileInputStream(inputFile);
                // 创建输出流将读取到的内容写入到 output.txt 文件中
                FileOutputStream outStream = new FileOutputStream(outputFile)) {
            // 读取和写入操作
            int bytesRead;
            byte[] buffer = new byte[1024];
            while ((bytesRead = inStream.read(buffer)) != -1) {
                outStream.write(buffer, 0, bytesRead);
            }

            System.out.println("文件读取并复制完成.");
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

}

说明:从Java 7开始,引入了 try-with-resources 语句,使得在使用完毕后自动关闭资源变得更加容易。该语句可以自动关闭在 try 语句块中声明的资源,而无需使用显式的 finally 块或 try-catch 语句。

try-with-resources 语句的语法如下:

try (Resource1 res1 = new Resource1();
     Resource2 res2 = new Resource2();
     /* ... */) {
    // 使用 res1、res2 等资源进行操作
} catch (Exception e) {
    // 处理异常
}

在 try 语句块中,我们声明需要使用的资源,这些资源必须是 java.lang.AutoCloseable 接口的实现类或其子类。当程序执行到 try 语句块的结尾时,无论是否发生异常,Java 都会自动调用这些资源的 close() 方法来关闭它们。

字符流

字符流(Character Stream)是 Java IO 操作中处理字符数据的基本流类型,它可以读取和写入字符数据。虽然 Java 提供了字节流来处理二进制数据,但是在处理文本数据时,使用字符流比使用字节流更加方便和高效。

字符流可以将字节转换为字符,以便于对文本进行处理。与字节流不同的是,字符流可以直接读写 Unicode 字符,而不需要自己实现字节到字符的转换。在处理文本文件时,字符流比字节流更加方便,因为它可以正确地处理字符集和换行符等问题。

字符流主要用于读取和写入文本数据,如文本文件、XML、HTML 等。在使用字符流进行输入输出时,数据以字符为单位进行读取和写入,比字节流更为高级和方便。因此,字符流在处理一些文本数据的场景下很常用。

字符流有两个抽象基类:Reader 和 Writer。Reader 和 Writer 都是抽象类,不能直接使用。Java 提供了一些常见的子类,用于处理不同的输入输出场景:

  • FileReader 和 FileWriter:用于读取和写入文件数据的流;
  • CharArrayReader 和 CharArrayWriter:用于读取和写入内存中的字符数据的流;
  • InputStreamReader 和 OutputStreamWriter:用于将字节流转换为字符流的桥梁;
  • StringReader 和 StringWriter:用于读取和写入字符串数据的流。

字符流还有一个重要的优点,即在读取和写入文件时,字符流可以通过内部缓冲区实现读写操作的优化。这可以减少对底层文件系统的访问,从而提高程序的性能。

下面的例子演示了如何从文件中读取文本数据并将其写入到另一个文件中:

package cn.leetcode;

import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;

public class CharStreamExample {

    public static void main(String[] args) {
        String inputFile = "input.txt";
        String outputFile = "output.txt";
        try (
                // 创建输入流读取 input.txt 文件的内容
                FileReader inReader = new FileReader(inputFile);
                // 创建输出流将读取到的内容写入到 output.txt 文件中
                FileWriter outWriter = new FileWriter(outputFile)) {
            // 读取和写入操作
            int c;
            while ((c = inReader.read()) != -1) {
                outWriter.write(c);
            }
            System.out.println("文件读取并复制完成.");
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

}

缓冲流(Buffered Stream)

缓冲流(Buffered Stream)是 Java IO 操作中提供的一种包装流,用于提高 IO 的效率。缓冲流可以在读取或写入数据时提高效率,通过内部缓冲区来减少对底层输入输出流的访问次数,从而减少 IO 操作的开销。

缓冲流包含两个基本类:BufferedInputStream 和 BufferedOutputStream,以及两个字符流的缓冲类:BufferedReader 和 BufferedWriter。

缓冲流的作用主要有以下几点:

  • 提高读写效率:缓冲流通过内部缓冲区来减少对底层输入输出流的访问次数,从而提高 IO 操作的效率;
  • 提供额外的功能:缓冲流可以提供一些额外的功能,如自动刷新、按行读取等。

以下是几个典型的使用例子:

  • 使用缓冲流写入文件内容:
package cn.leetcode;

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;

public class BufferWriterExample {

    public static void main(String[] args) {
        try (BufferedWriter writer = new BufferedWriter(new FileWriter("input.txt"))) {
            // 「力扣」第 1 题英文题面
            writer.write("Given an array of integers nums and an integer target, return indices of the two numbers such that they add up to target.\n");
            writer.newLine();
            writer.write("You may assume that each input would have exactly one solution, and you may not use the same element twice.\n");
            writer.newLine();
            writer.write("You can return the answer in any order.");
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

}
  • 使用缓冲流读取文件内容:
package cn.leetcode;

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

public class BufferReaderExample {

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

}

对象流(Object Stream)

对象流(Object Stream)是 Java IO 操作中用于序列化和反序列化 Java 对象的一种流类型,它可以将 Java 对象写入到流中或从流中读取 Java 对象。

Java 中提供了两个对象流类:ObjectInputStream 和 ObjectOutputStream。ObjectInputStream 和 ObjectOutputStream 都是处理二进制数据的流,它们支持将 Java 对象序列化为字节流,或将字节流反序列化为 Java 对象。在进行对象序列化和反序列化时,需要实现 java.io.Serializable 接口,这个接口没有任何方法,只是一个标记接口,表示这个类可以被序列化。

对象流的作用主要有以下几点:

  • 方便地进行对象的序列化和反序列化:对象流可以方便地将 Java 对象序列化为字节流或反序列化为 Java 对象,简化了对象序列化和反序列化的操作;
  • 用于网络传输和持久化:对象流可以用于网络传输和持久化,方便地将 Java 对象保存到文件或数据库中,或通过网络传输 Java 对象;
  • 提高效率:对象流通过二进制序列化方式进行数据传输,相比于文本方式可以提高传输效率。

下面是使用对象流的例子:

  • 序列化对象到文件
package cn.leetcode.objectstreamdemo;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.ToString;

import java.io.Serializable;

@Data
@AllArgsConstructor
@NoArgsConstructor
@ToString
public class LeetCoder implements Serializable {

    private static final long serialVersionUID = 1L;

    private String name;

    private Integer age;

    private Double salary;

}
package cn.leetcode.objectstreamdemo;

import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;

public class SerializeDemo {

    public static void main(String[] args) {
        LeetCoder leetCoder = new LeetCoder();
        leetCoder.setName("LeetCoder1");
        leetCoder.setAge(22);
        leetCoder.setSalary(10000.0);
        try (FileOutputStream fileOut = new FileOutputStream("LeetCoder.ser"); ObjectOutputStream out = new ObjectOutputStream(fileOut)) {
            out.writeObject(leetCoder);
            out.close();
            fileOut.close();
            System.out.println("Serialized data is saved in LeetCoder.ser");
        } catch (IOException i) {
            i.printStackTrace();
        }
    }

}

这个例子中,我们创建了一个 LeetCoder 对象,并使用对象输出流将它序列化到文件中。

  • 从文件中反序列化对象
package cn.leetcode.objectstreamdemo;

import java.io.FileInputStream;
import java.io.IOException;
import java.io.ObjectInputStream;

public class DeserializeDemo {

    public static void main(String[] args) {
        LeetCoder leetCoder;
        try (FileInputStream fileIn = new FileInputStream("LeetCoder.ser"); ObjectInputStream in = new ObjectInputStream(fileIn)) {
            leetCoder = (LeetCoder) in.readObject();
        } catch (IOException i) {
            i.printStackTrace();
            return;
        } catch (ClassNotFoundException c) {
            System.out.println("LeetCoder class not found");
            c.printStackTrace();
            return;
        }
        System.out.println("Deserialized LeetCoder...");
        System.out.println("leetCoder = " + leetCoder);
    }
}

这个例子中,我们从文件中读取序列化后的 LeetCoder 对象,并使用对象输入流将它反序列化成一个新的 LeetCoder 对象。最后,我们打印出这个新的 LeetCoder 对象的属性值。

NIO.2

值得注意的是,Java 在 JDK 7 之后对旧 IO 进行了优化,称为 NIO.2,新增了许多新的类和功能。因此,现在的 Java 开发人员可以选择使用新 IO 或者 NIO.2,也可以使用旧 IO,根据具体的场景选择使用合适的 I/O 库。

以下是 NIO.2 相对于旧 IO 的一些变化:

  • 引入了 Path 和 Files 类。Path 类用于代表文件路径,Files 类提供了一组用于文件操作的静态方法,例如:创建、复制、移动、删除、读写等等;
  • 引入了 DirectoryStream 接口,用于表示目录流,可以用于枚举目录中的所有文件;

示例代码:

package cn.leetcode;

import java.io.IOException;
import java.nio.file.DirectoryStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;

public class DirectoryStreamExample {

    public static void main(String[] args) throws IOException {
        Path dir = Paths.get("C:/Users/username/Documents");
        try (DirectoryStream<Path> stream = Files.newDirectoryStream(dir)) {
            for (Path file : stream) {
                System.out.println(file.getFileName());
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    
}

在上述示例代码中,首先创建一个Path对象来表示需要枚举的目录,这里是用户文档目录。然后,使用 Files 类的 newDirectoryStream() 方法创建一个 DirectoryStream<Path> 对象。这个方法的参数是需要枚举的目录,返回一个包含目录中所有文件的 DirectoryStream<Path> 对象。

接着,使用 try-with-resources 语句打开一个 try 块,该语句会在使用完 DirectoryStream 对象后自动关闭流。然后,使用 for-each 循环遍历 DirectoryStream 对象,对于每个文件,使用 getFileName() 方法获取文件名,并将其输出到控制台。

需要注意的是,在使用 DirectoryStream 对象时,必须使用 try-with-resources 语句或者手动关闭流,以便在使用完对象后释放资源。

此外,DirectoryStream 还支持使用过滤器过滤目录中的文件。可以使用 Files 类的 newDirectoryStream() 方法的重载版本来指定过滤器。例如:

Path dir = Paths.get("C:/Users/username/Documents");
DirectoryStream<Path> stream = Files.newDirectoryStream(dir, "*.txt");

总之,Java NIO.2 扩展了Java I/O 的功能,使得 Java 应用程序可以更方便地访问文件系统和网络资源,提高了系统的性能和可扩展性。

注意:NIO.2 是 Java SE 7 引入的一组新的 I/O API,而 NIO 是 Java SE 1.4 中引入的非阻塞I/O(即 New I/O)API,是我们下一节要和大家介绍的内容。

阅读量:2028

点赞量:0

收藏量:0