java io流的深入理解,细到极致!!!

1. 引言

Java 的核心库java.io提供了全面的 IO 接口,涵盖了文件读写、标准设备输出等功能。在 Java 中,IO 是以流为基础进行输入输出的,所有数据被串行化写入输出流,或者从输入流读入。流是一个形象的概念,当程序需要读取数据时,就会开启一个通向数据源的流,而当程序需要写入数据时,就会开启一个通向目的地的流,数据就像在其中 “流动” 一样。

2. IO 流的分类

  • 按流向分

    • 输入流:程序可以从中读取数据的流,用于将数据从外部数据源(如文件、网络连接、内存等)读入到程序中。
    • 输出流:程序能向其中写入数据的流,用于将程序中的数据输出到外部目标(如文件、网络连接、控制台等)。
  • 按数据传输单位分

    • 字节流:以字节为单位传输数据的流,一次读取或写入一个字节。字节流可以处理任何类型的数据,包括二进制数据,如图片、音频、视频等,是一种万能流,但在处理文本文件中的汉字时,如果不注意编码问题,可能会出现乱码。
    • 字符流:以字符为单位传输数据的流,一次读取或写入一个字符。字符流主要用于处理文本数据,它会根据指定的字符编码将字节转换为字符,在读写普通文本文件时,不容易出现乱码,但不适合处理图片、声音、视频等非文本文件。
  • 按功能分

    • 节点流:用于直接操作目标设备的流,如FileInputStreamFileOutputStream直接操作文件,ByteArrayInputStreamByteArrayOutputStream直接操作字节数组。
    • 过滤流:也称为包装流,是对一个已存在的流的链接和封装,通过对数据进行处理为程序提供功能强大、灵活的读写功能。例如,BufferedInputStreamBufferedOutputStream为其他输入输出流添加了缓冲功能,DataInputStreamDataOutputStream可以按照特定的数据格式读写基本数据类型。

3. 字节流

  • 抽象基类

    • InputStream:字节输入流的抽象类,是所有表示字节输入流的类的超类。继承自InputStream的流都是用于向程序中输入数据的,且数据单位都是字节(8 位)。它定义了一系列读取数据的方法,如read()方法用于从输入流中读取一个字节的数据,read(byte[] b)方法用于将数据读取到字节数组中,available()方法用于返回输入流中可读取的字节数等。
    • OutputStream:字节输出流的抽象类,是所有表示字节输出流的类的超类。继承自OutputStream的流都是程序用于向外输出数据的,且数据单位都是字节(8 位)。它定义了write()方法用于将字节数据写入输出流,flush()方法用于刷新输出流,将缓冲区中的数据强制输出到目标设备,close()方法用于关闭输出流,释放相关资源。
  • 常用的字节流类

    • FileInputStream3:从文件系统中的某个文件中获得输入字节,用于读取文件中的数据。在创建FileInputStream对象时,如果文件不存在,会抛出FileNotFoundException异常。可以使用read()方法一次读取一个字节,也可以使用read(byte[] b)方法将数据读取到字节数组中。
    • FileOutputStream3:用于将数据写入FileFileDescriptor的输出流。如果指定的文件不存在,会创建一个新的文件;如果文件已存在,默认情况下会清空文件内容再写入数据。可以通过构造方法的第二个参数设置为true来实现追加写入。使用write()方法将字节数据写入文件。
    • BufferedInputStream4:为另一个输入流添加缓冲功能,以及支持markreset方法的能力。它内部维护了一个缓冲区,当从流中读取数据时,会先将数据读取到缓冲区中,然后程序从缓冲区中获取数据,这样可以减少对底层输入流的读取次数,提高读取效率。
    • BufferedOutputStream4:实现缓冲的输出流。它将数据先写入缓冲区,当缓冲区满或者调用flush()方法时,才将缓冲区中的数据写入到目标输出流中,从而减少对底层输出流的写入次数,提高写入效率。

4. 字符流

  • 抽象基类

    • Reader:字符输入流的抽象类,继承自Reader的流都是用于向程序中输入数据的,且数据单位都是字符(16 位)。它定义了read()方法用于读取单个字符,read(char[] cbuf)方法用于将字符数据读取到字符数组中,read(CharBuffer target)方法用于将字符数据读取到CharBuffer中。
    • Writer:字符输出流的抽象类,继承自Writer的流都是程序用于向外输出数据的,且数据单位都是字符(16 位)。它定义了write()方法用于写入字符数据,flush()方法用于刷新输出流,close()方法用于关闭输出流。
  • 常用的字符流类

    • FileReader:用来读取字符文件的便捷类,是Reader的子类。它使用默认的字符编码将文件中的字节数据转换为字符数据读取到程序中。在创建FileReader对象时,如果文件不存在,会抛出FileNotFoundException异常。
    • FileWriter:用来写入字符文件的便捷类,是Writer的子类。它使用默认的字符编码将程序中的字符数据转换为字节数据写入到文件中。如果指定的文件不存在,会创建一个新的文件;如果文件已存在,默认情况下会清空文件内容再写入数据,可以通过构造方法的第二个参数设置为true来实现追加写入。
    • BufferedReader:从字符输入流中读取文本,缓冲各个字符,从而实现字符、数组和行的高效读取。它提供了readLine()方法用于读取一行文本,方便处理文本文件中的每行数据。
    • BufferedWriter:将文本写入字符输出流,缓冲各个字符,从而提供单个字符、数组和字符串的高效写入。它提供了newLine()方法用于写入一个换行符,不同操作系统的换行符不同,使用newLine()方法可以确保在不同系统上正确地写入换行符。

5. 转换流

  • InputStreamReader:是字节流通向字符流的桥梁,它使用指定的字符集读取字节并将其解码为字符。可以在创建InputStreamReader对象时指定字符编码,如new InputStreamReader(new FileInputStream("input.txt"), "UTF-8"),这样就可以按照指定的编码格式读取文件中的字节数据并转换为字符数据。
  • OutputStreamWriter:是字符流通向字节流的桥梁,它使用指定的字符集将字符编码为字节并写入到输出流中。同样可以在创建OutputStreamWriter对象时指定字符编码,如new OutputStreamWriter(new FileOutputStream("output.txt"), "GBK"),将程序中的字符数据按照指定的编码格式转换为字节数据写入到文件中。

6. 缓冲流

  • 字节缓冲流

    • BufferedInputStreamBufferedOutputStream:前面已经介绍过,它们分别为字节输入流和字节输出流添加了缓冲功能,提高了读写效率。在读取数据时,BufferedInputStream会一次性从底层输入流中读取多个字节到缓冲区中,然后程序从缓冲区中获取数据,减少了对底层输入流的读取次数;在写入数据时,BufferedOutputStream会先将数据写入缓冲区,当缓冲区满或者调用flush()方法时,才将缓冲区中的数据写入到底层输出流中,减少了对底层输出流的写入次数。
  • 字符缓冲流

    • BufferedReaderBufferedWriter:为字符输入流和字符输出流添加了缓冲功能。BufferedReader可以高效地读取字符数据,提供了readLine()方法方便读取文本文件中的一行数据;BufferedWriter可以高效地写入字符数据,提供了newLine()方法方便写入换行符。

7. 数据流

  • DataInputStream:数据输入流允许应用程序以与机器无关的方式从底层输入流中读取基本 Java 数据类型,如readInt()方法用于读取一个整数,readDouble()方法用于读取一个双精度浮点数,readUTF()方法用于读取一个 UTF-8 编码的字符串等。它通常与FileInputStream等字节输入流一起使用,先将文件中的字节数据读取到DataInputStream中,然后再按照特定的数据类型读取数据。
  • DataOutputStream:数据输出流允许应用程序以适当的方式将基本 Java 数据类型写入输出流中,如writeInt()方法用于写入一个整数,writeDouble()方法用于写入一个双精度浮点数,writeUTF()方法用于写入一个 UTF-8 编码的字符串等。它通常与FileOutputStream等字节输出流一起使用,先将程序中的数据转换为字节数据写入到DataOutputStream中,然后再将字节数据写入到文件中。

8. 打印流

  • PrintStream:字节打印流,用于将数据以文本形式输出到指定的输出流中,通常用于向控制台输出信息。它提供了一系列的print()println()方法,可以方便地输出各种数据类型,并且会自动将数据转换为字符串形式输出。例如,System.out就是一个PrintStream对象,用于在控制台输出信息。
  • PrintWriter:字符打印流,与PrintStream类似,用于将字符数据以文本形式输出到指定的输出流中。它也提供了print()println()方法,并且可以指定字符编码。在处理文本输出时,PrintWriterPrintStream更适合,因为它可以正确地处理字符编码问题。

9. 对象流

  • ObjectInputStream4:用于从输入流中读取对象,实现对象的反序列化。在读取对象时,它会根据对象的序列化格式将字节数据转换为对象实例。通常与FileInputStream一起使用,从文件中读取序列化的对象数据。
  • ObjectOutputStream4:用于将对象写入输出流,实现对象的序列化。在写入对象时,它会将对象转换为字节数据,并按照特定的序列化格式写入到输出流中。通常与FileOutputStream一起使用,将对象数据写入到文件中进行保存。

10. 文件操作

  • 文件读取:使用FileInputStreamFileReader来读取文件。如果是读取二进制文件,如图片、音频、视频等,应使用FileInputStream;如果是读取文本文件,可以使用FileReader,并且可以结合BufferedReader来提高读取效率,例如使用BufferedReaderreadLine()方法逐行读取文本文件内容。
  • 文件写入:使用FileOutputStreamFileWriter来写入文件。如果是写入二进制数据,如图片、音频、视频等的原始数据,应使用FileOutputStream;如果是写入文本数据,可以使用FileWriter,并且可以结合BufferedWriter来提高写入效率,例如使用BufferedWriternewLine()方法写入换行符。
  • 文件复制:可以使用字节流或字符流来实现文件复制。以下是使用字节流复制文件的示例代码:
public static void copyFile(String source, String dest) throws IOException {
    try (InputStream in = new FileInputStream(source);
         OutputStream out = new FileOutputStream(dest)) {
        byte[] buffer = new byte[1024];
        int length;
        while ((length = in.read(buffer)) > 0) {
            out.write(buffer, 0, length);
        }
    }
}

在这个示例中,使用FileInputStream从源文件中读取数据,使用FileOutputStream将数据写入到目标文件中。通过循环读取和写入字节数组,实现了文件的复制。

11. 序列化与反序列化

  • 序列化:将对象转换为字节序列的过程称为序列化。通过实现java.io.Serializable接口来启用对象的序列化功能。当一个对象被序列化时,它的状态信息会被保存到一个字节流中,以便在需要时可以将其恢复为原来的对象。例如,可以使用ObjectOutputStream将对象写入到文件中进行保存,或者通过网络发送给其他计算机。
  • 反序列化:将字节序列转换为对象的过程称为反序列化。使用ObjectInputStream从字节流中读取数据,并将其转换为对象实例。在反序列化时,需要确保读取的字节流与序列化时的对象类型和版本兼容,否则可能会抛出异常。

12. 性能优化

  • 使用缓冲流4:如前面提到的BufferedInputStreamBufferedOutputStreamBufferedReaderBufferedWriter,它们可以减少对底层系统的直接调用次数,提高读写效率。特别是在处理大量数据时,缓冲流的性能优势更加明显。
  • 合理选择流类型:根据数据的类型和特点选择合适的流。如果是处理二进制数据,应选择字节流;如果是处理文本数据,应选择字符流。并且在处理文本数据时,要注意字符编码的设置,避免出现乱码问题。
  • 及时关闭流:在使用完流之后,一定要及时调用close()方法关闭流,释放相关资源。如果不关闭流,可能会导致资源泄露,影响程序的性能和稳定性。可以使用try-with-resources语句来自动关闭流,确保流在使用完毕后被正确关闭。

13. NIO 简介

Java NIO(New IO)是 Java 1.4 引入的新的 IO API,提供了更高效的 IO 操作方式。NIO 与传统的 IO 流有所不同,它引入了通道(Channel)和缓冲区(Buffer)的概念,支持非阻塞 IO 和选择器(Selector)。

  • 通道(Channel)4:是双向的,允许同时进行读写操作。它直接与操作系统的 IO 操作交互,底层依赖于文件描述符。在高性能应用中,通道能够有效地传输数据,例如使用FileChannel可以进行文件的高效读写,使用SocketChannel可以进行网络套接字的读写。
  • 缓冲区(Buffer)4:是一个连续的内存区域,提供了读写操作的基本单元。数据在通道和缓冲区之间进行传输,程序通过操作缓冲区来读写数据。常见的缓冲区类型有ByteBufferCharBufferIntBuffer等,可以根据需要选择合适的缓冲区类型。
  • 选择器(Selector):用于监控多个通道的 IO 状况,可以实现非阻塞的 IO 操作。在高并发场景下,使用选择器能够处理大量的通道而不占用过多线程资源,从而提高系统的性能和可扩展性。例如,在构建高并发的网络服务器时,使用选择器可以有效地管理大量的客户端连接。

NIO 的出现为 Java 开发者提供了更灵活、高效的 IO 处理方式,尤其在处理高并发、大规模数据传输等场景下具有明显的优势。但 NIO 的编程模型相对传统 IO 流来说更加复杂,需要开发者对通道、缓冲区和选择器等概念有深入的理解和掌握。

14.总结

本文是关于 Java IO 流的技术文档。首先介绍了 Java IO 流的概念,它是数据输入输出的基础。IO 流按流向分输入流和输出流,按单位分字节流和字符流,按功能分节点流和过滤流。接着阐述了字节流、字符流相关的抽象基类与常用类,以及转换流、缓冲流、数据流、打印流、对象流的作用。然后讲解了文件操作和序列化反序列化的方法。还提到了性能优化策略,如用缓冲流、选合适流类型和及时关流等。最后简介了 NIO 的通道、缓冲区、选择器等概念。 这份 Java IO 流技术文档全面介绍了 Java 中 IO 流的相关知识。首先说明了 IO 流是以流为基础进行输入输出,按流向、数据传输单位和功能可分类。接着详细阐述字节流、字符流等不同类型流的抽象基类和常用类,如字节流的FileInputStream,字符流的FileReader等。还介绍了转换流、缓冲流、数据流、打印流和对象流的作用。同时讲解了文件操作、序列化与反序列化。最后提及性能优化要点,以及 Java NIO 的概念,它提供了更高效的 IO 操作方式。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值