CrabNote螃蟹笔记

Java IO 基础知识总结

[IO 流简介](io-流简介) IO 即 `Input/Output`,输入和输出。数据输入到计算机内存的过程即输入,反之输出到外部存储(比如数据库,文件,远程主机)的过程即输出。数据传输过程类似

IO 流简介 IO 即 ,输入和输出。数据输入到计算机内存的过程即输入,反之输出到外部存储(比如数据库,文件,远程主机)的过程即输出。数据传输过程类似于水流,因此称为 IO 流。IO 流在 Java 中分为输入流和输出流,而根据数据的处理方式又分为字节流和字符流。 Java IO 流的 40 多个类都是从如下 4 个抽象类基类中派生出来的。 / : 所有的输入流的基类,前者是字节输入流,后者是字符输入流。 / : 所有输出流的基类,前者是字节输出流,后者是字符输出流。 字节流 InputStream(字节输入流) 用于从源头(通常是文件)读取数据(字节信息)到内存中, 抽象类是所有字节输入流的父类。 常用方法: :返回输入流中下一个字节的数据。返回的值介于 0 到 255 之间。如果未读取任何字节,则代码返回 ,表示文件结束。 : 从输入流中读取一些字节存储到数组 中。如果数组 的长度为零,则不读取。如果没有可用字节读取,返回 。如果有可用字节读取,则最多读取的字节数最多等于 , 返回读取的字节数。这个方法等价于 。 :在 方法的基础上增加了 参数(偏移量)和 参数(要读取的最大字节数)。 :忽略输入流中的 n 个字节 ,返回实际忽略的字节数。 :返回输入流中可以读取的字节数。 :关闭输入流释放相关的系统资源。 从 Java 9 开始, 新增加了多个实用的方法: :读取输入流中的所有字节,返回字节数组。 :阻塞直到读取 个字节。 :将所有字节从一个输入流传递到一个输出流。 是一个比较常用的字节输入流对象,可直接指定文件路径,可以直接读取单字节数据,也可以读取至字节数组中。 代码示例: 文件内容: 输出: 不过,一般我们是不会直接单独使用 ,通常会配合 (字节缓冲输入流,后文会讲到)来使用。 像下面这段代码在我们的项目中就比较常见,我们通过 读取输入流所有字节并将其直接赋值给一个 对象。 用于读取指定类型数据,不能单独使用,必须结合其它流,比如 。 用于从输入流中读取 Java 对象(反序列化), 用于将对象写入到输出流(序列化)。 另外,用于序列化和反序列化的类必须实现 接口,对象中如果有属性不想被序列化,使用 修饰。 OutputStream(字节输出流) 用于将数据(字节信息)写入到目的地(通常是文件), 抽象类是所有字节输出流的父类。 常用方法: :将特定字节写入输出流。 : 将数组 写入到输出流,等价于 。 : 在 方法的基础上增加了 参数(偏移量)和 参数(要读取的最大字节数)。 :刷新此输出流并强制写出所有缓冲的输出字节。 :关闭输出流释放相关的系统资源。 是最常用的字节输出流对象,可直接指定文件路径,可以直接输出单字节数据,也可以输出指定的字节数组。 代码示例: 运行结果: 类似于 , 通常也会配合 (字节缓冲输出流,后文会讲到)来使用。 用于写入指定类型数据,不能单独使用,必须结合其它流,比如 。 用于从输入流中读取 Java 对象(反序列化), 将对象写入到输出流(序列化)。 字符流 不管是文件读写还是网络发送接收,信息的最小存储单元都是字节。 那为什么 I/O 流操作要分为字节流操作和字符流操作呢? 个人认为主要有两点原因: 字符流是由 Java 虚拟机将字节转换得到的,这个过程还算是比较耗时。 如果我们不知道编码类型就很容易出现乱码问题。 乱码问题这个很容易就可以复现,我们只需要将上面提到的 代码示例中的 文件内容改为中文即可,原代码不需要改动。 输出: 可以很明显地看到读取出来的内容已经变成了乱码。 因此,I/O 流就干脆提供了一个直接操作字符的接口,方便我们平时对字符进行流操作。如果音频文件、图片等媒体文件用字节流比较好,如果涉及到字符的话使用字符流比较好。 字符流默认采用的是 编码,我们可以通过构造方法自定义编码。 Unicode 本身只是一种字符集,它为每个字符分配一个唯一的数字编号,并没有规定具体的存储方式。UTF 8、UTF 16、UTF 32 都是 Unicode 的编码方式,它们使用不同的字节数来表示 Unicode 字符。例如,UTF 8 :英文占 1 字节,中文占 3 字节。 Reader(字符输入流) 用于从源头(通常是文件)读取数据(字符信息)到内存中, 抽象类是所有字符输入流的父类。 用于读取文本, 用于读取原始字节。 常用方法: : 从输入流读取一个字符。 : 从输入流中读取一些字符,并将它们存储到字符数组 中,等价于 。 :在 方法的基础上增加了 参数(偏移量)和 参数(要读取的最大字符数)。 :忽略输入流中的 n 个字符 ,返回实际忽略的字符数。 : 关闭输入流并释放相关的系统资源。 是字节流转换为字符流的桥梁,其子类 是基于该基础上的封装,可以直接操作字符文件。 代码示例: 文件内容: 输出: Writer(字符输出流) 用于将数据(字符信息)写入到目的地(通常是文件), 抽象类是所有字符输出流的父类。 常用方法: : 写入单个字符。 :写入字符数组 ,等价于 。 :在 方法的基础上增加了 参数(偏移量)和 参数(要读取的最大字符数)。 :写入字符串,等价于 。 :在 方法的基础上增加了 参数(偏移量)和 参数(要读取的最大字符数)。 :将指定的字符序列附加到指定的 对象并返回该 对象。 :将指定的字符附加到指定的 对象并返回该 对象。 :刷新此输出流并强制写出所有缓冲的输出字符。 :关闭输出流释放相关的系统资源。 是字符流转换为字节流的桥梁,其子类 是基于该基础上的封装,可以直接将字符写入到文件。 代码示例: 输出结果: 字节缓冲流 IO 操作是很消耗性能的,缓冲流将数据加载至缓冲区,一次性读取/写入多个字节,从而避免频繁的 IO 操作,提高流的传输效率。 字节缓冲流这里采用了装饰器模式来增强 和 子类对象的功能。 举个例子,我们可以通过 (字节缓冲输入流)来增强 的功能。 字节流和字节缓冲流的性能差别主要体现在我们使用两者的时候都是调用 和 这两个一次只读取一个字节的方法的时候。由于字节缓冲流内部有缓冲区(字节数组),因此,字节缓冲流会先将读取到的字节存放在缓存区,大幅减少 IO 次数,提高读取效率。 我使用 和 方法,分别通过字节流和字节缓冲流复制一个 的 PDF 文件耗时对比如下: 两者耗时差别非常大,缓冲流耗费的时间是字节流的 1/165。 测试代码如下: 如果是调用 和 这两个写入一个字节数组的方法的话,只要字节数组的大小合适,两者的性能差距其实不大,基本可以忽略。 这次我们使用 和 方法,分别通过字节流和字节缓冲流复制一个 524.9 mb 的 PDF 文件耗时对比如下: 两者耗时差别不是很大,缓冲流的性能要略微好一点点。 测试代码如下: BufferedInputStream(字节缓冲输入流) 从源头(通常是文件)读取数据(字节信息)到内存的过程中不会一个字节一个字节的读取,而是会先将读取到的字节存放在缓存区,并从内部缓冲区中单独读取字节。这样大幅减少了 IO 次数,提高了读取效率。 内部维护了一个缓冲区,这个缓冲区实际就是一个字节数组,通过阅读 源码即可得到这个结论。 缓冲区的大小默认为 8192 字节,当然了,你也可以通过 这个构造方法来指定缓冲区的大小。 BufferedOutputStream(字节缓冲输出流) 将数据(字节信息)写入到目的地(通常是文件)的过程中不会一个字节一个字节的写入,而是会先将要写入的字节存放在缓存区,并从内部缓冲区中单独写入字节。这样大幅减少了 IO 次数,提高了效率 类似于 , 内部也维护了一个缓冲区,并且,这个缓存区的大小也是 8192 字节。 字符缓冲流 (字符缓冲输入流)和 (字符缓冲输出流)类似于 (字节缓冲输入流)和 (字节缓冲输入流),内部都维护了一个字节数组作为缓冲区。不过,前者主要是用来操作字符信息。 打印流 下面这段代码大家经常使用吧? 实际是用于获取一个 对象, 方法实际调用的是 对象的 方法。 属于字节打印流,与之对应的是 (字符打印流)。 是 的子类, 是 的子类。 随机访问流 这里要介绍的随机访问流指的是支持随意跳转到文件的任意位置进行读写的 。 的构造方法如下,我们可以指定 (读写模式)。 读写模式主要有下面四种: : 只读模式。 : 读写模式 : 相对于 , 同步更新对“文件的内容”或“元数据”的修改到外部存储设备。 : 相对于 , 同步更新对“文件的内容”的修改到外部存储设备。 文件内容指的是文件中实际保存的数据,元数据则是用来描述文件属性比如文件的大小信息、创建和修改时间。 中有一个文件指针用来表示下一个将要被写入或者读取的字节所处的位置。我们可以通过 的 方法来设置文件指针的偏移量(距文件开头 个字节处)。如果想要获取文件指针当前的位置的话,可以使用 方法。 代码示例: 文件内容: 输出: 文件内容变为 。 的 方法在写入对象的时候如果对应的位置已经有数据的话,会将其覆盖掉。 假设运行上面这段程序之前 文件内容变为 ,运行之后则变为 。 比较常见的一个应用就是实现大文件的 断点续传 。何谓断点续传?简单来说就是上传文件中途暂停或失败(比如遇到网络问题)之后,不需要重新上传,只需要上传那些未成功上传的文件分片即可。分片(先将文件切分成多个文件分片)上传是断点续传的基础。 可以帮助我们合并文件分片,示例代码如下: 我在《Java 面试指北》中详细介绍了大文件的上传问题。 的实现依赖于 (文件描述符) 和 (内存映射文件)。 写在最后 如果内容对你有帮助的话,欢迎顺手给 JavaGuide 点一个免费的 Star 支持一下:GitHub | Gitee。 JavaGuide 已持续维护近七年,累计 6100+ 次提交,来自 620+ 位贡献者共同完善。你的 Star、反馈和 PR,都是这个项目继续更新的动力。 如果你正在准备后端/AI 应用开发面试,也可以了解一下我的知识星球,里面包括后端和 AI 实战项目、简历优化、一对一提问和高频考点资料,已经持续维护六年。