哈喽大家好,欢迎继续收听Java入门播客!上一期我们学习了异常处理机制,而今天的IO流几乎处处都会用到异常,它是Java中负责数据传输的核心技术,日常的文件读写、文件拷贝、上传下载、数据持久化都离不开IO流。本期我们从流的分类、字节流、字符流、编码、桥接流再到File类,完整带大家掌握IO全套知识点。
首先我们先理解IO流的基础概念。流可以通俗理解为内存和外部设备之间的数据传输通道,就像一根数据管道。所有IO相关的类和接口,都统一存放在 java.io 包中。
按照三大维度,我们给流做分类,这也是面试常考内容。
第一,按传输方向划分,以JVM内存为参照:把外部设备的数据读到内存,叫做输入流,核心动作是“读”;把内存的数据写到外部设备,叫做输出流,核心动作是“写”。
第二,按传输单位划分:以字节为单位的是字节流,可以处理图片、视频、音频、文档所有类型文件;以字符为单位的是字符流,专门用来操作纯文本文件,比如txt、java、html这类能用记事本正常打开的文件。
第三,按功能划分:只具备基础读写能力的是节点流;在节点流基础上增强功能、提升效率的,统称为过滤流(包装流)。
一、字节流
字节流的两大顶层抽象父类: InputStream 字节输入流、 OutputStream 字节输出流,所有字节流实现类都继承自它们。
首先讲解最常用的文件字节节点流: FileOutputStream 和 FileInputStream 。
先说文件字节输出流 FileOutputStream ,用于向文件写入数据。
构造方法支持绝对路径和相对路径:文件不存在时会自动创建,但如果上级文件夹不存在,会直接抛出文件找不到异常。构造方法还可以传入布尔参数, true 表示在原有内容追加写入, false 代表覆盖原有内容,默认就是覆盖。
核心写入方法: write(int) 写入单个字节; write(byte[]) 写入整个字节数组; write(byte[], 起始下标, 长度) 写入数组指定区间内容。使用完毕必须调用 close() 关闭资源。
再看文件字节输入流 FileInputStream ,用于读取文件内容。
它的构造方法只接收文件路径,如果文件不存在,直接报错,不会自动创建。
核心读取方法: read() 读取单个字节,读到文件末尾返回 -1 ; read(byte[]) 批量读取字节到数组,返回实际读取的字节数,末尾同样返回 -1 。利用循环配合读取方法,就能实现整个文件的读取,而文件拷贝、上传下载的底层原理,就是一边读、一边写的组合操作。
接下来介绍字节过滤流,用来拓展功能、提升效率。
第一组是缓冲流 BufferedInputStream 、 BufferedOutputStream 。它内置缓冲区,减少磁盘访问次数,大幅提升读写速度。缓冲区存满会自动写入,也可以手动调用 flush() 刷新缓冲区; close() 方法在关闭流之前,也会自动刷新缓冲区,流关闭后无法继续使用。
第二组是 DataInputStream 、 DataOutputStream ,专门用来读写八大基本数据类型,可以精准存取基础类型数据。
第三组是对象流 ObjectInputStream 、 ObjectOutputStream ,核心作用是读写对象,也是重点考点。把对象通过流传输的过程,叫做对象序列化。
序列化有硬性要求:对象所属的类必须实现 java.io.Serializable 这个标记接口,否则抛出不可序列化异常;被 transient 关键字修饰的成员变量,不会参与序列化;如果类中包含自定义类型的成员,该自定义类也必须实现序列化接口。读取对象到文件末尾时,会抛出 EOFException 。另外集合类本身已经实现序列化,但若集合内存放自定义对象,同样要遵守上述规则。
二、字符编码与乱码问题
在学习字符流之前,我们先了解字符编码,这是解决乱码的关键。
常见编码格式:ISO8859-1 西欧编码;GB2312、GBK、GB18030 都是中文编码,彼此相互兼容;Big5 繁体编码;UTF-8 通用万国码,字符占用1~3个字节。
简单区分:编码是字符转二进制,解码是二进制转回字符。编解码格式不一致,就一定会出现乱码,开发中务必保证两者统一。
三、字符流
字符流两大顶层抽象父类: Reader 字符输入流、 Writer 字符输出流,专门处理文本文件。
- 字符节点流
FileWriter 字符输出流、 FileReader 字符输入流,用法和字节节点流高度相似。
FileWriter 支持写入单个字符、字符数组、整串字符串; FileReader 可以单次读一个字符,或者批量读到字符数组中,读到文件末尾依旧返回 -1 。
- 字符过滤流
第一个重点是缓冲字符流: BufferedReader 和 PrintWriter 。
BufferedReader 有一个王牌方法 readLine() ,可以一次性读取一整行文本,读到文件末尾返回 null ,遍历文本文件非常方便。
PrintWriter 支持 print 和 println 方法,自带换行功能,还可以直接输出八大基本类型和对象。这里区分一个高频问题:
ObjectOutputStream 的 writeObject 是完整序列化对象;而 PrintWriter 输出对象时,只是调用对象 toString() 方法转为字符串写入,二者原理完全不同。
- 字符桥转流(转换流)
InputStreamReader 和 OutputStreamWriter 是字节流与字符流之间的桥梁。
核心作用:把字节流包装成字符流,同时可以手动指定编解码格式。当项目需要统一编码、解决乱码问题时,优先使用桥转流。
标准使用流程:先创建字节节点流 → 桥转流指定编码 → 外层包装缓冲流 → 执行读写操作 → 只关闭最外层流即可。
四、File类
很多同学容易混淆IO流和File类,这里明确区分:IO流操作的是文件内部内容;File类操作的是文件/文件夹本身,比如创建、删除、重命名、遍历目录。
File类常用核心方法: getName() 获取文件名; getAbsolutePath() 获取绝对路径;配合 listFiles() 可以获取文件夹下所有文件和子目录,结合递归算法,能够实现多级文件夹遍历、筛选指定后缀文件等经典需求。
(语气放缓,总结梳理)
最后我们整体梳理IO流的学习脉络:
按传输单位分为字节流和字符流:字节流通吃所有文件,字符流专攻文本文件;节点流负责基础读写,过滤流负责增强功能。缓冲流提升效率、对象流实现序列化、转换流解决编码乱码,各司其职。再搭配File类管理文件与目录,就构成了完整的IO体系。
IO流代码普遍需要处理异常,而且使用后一定要关闭资源,养成良好编码习惯。文件拷贝、序列化、编码转换、目录遍历都是面试和实操高频考点,大家一定要多动手练习案例。
IO流学完之后,JavaSE核心内容就接近尾声了,下一期我们将开启多线程的学习。本期播客就到这里,我们下期再见!