Loading... > 流是一种抽象概念,它代表了数据的无结构化传递。按照流的方式进行输入输出,数据被当成无结构的字节序列或字符序列。从流中取得数据的操作称为提取操作,而向流中添加数据的操作称为插入操作。用来进行输入输出操作的流就称为IO流。换句话说,IO流就是以流的方式进行输入输出。 # 1、IO概述 Java中I/O操作指使用 java.io 包下的类或者接口,进行输入、输出操作。输入即读取数据,输出即写 出数据。 ## 1.1、IO分类 按数据流的方向: * 输入流 :把数据从 其他设备 上读取到 内存 中的流。 * 输出流 :把数据从 内存 中写出到 其他设备 上的流。 按数据的类型: * 字节流 :以字节为单位,读写数据的流。 * 字符流 :以字符为单位,读写数据的流。 IO的顶级父类 | | 输入流 | 输出流 | | -------- | ------------------------- | -------------------------- | | 字节流 | 字节输入流:InputStream | 字节输出流:OutoutStream | | 字符流 | 字符输入流:Reader | 字符输出流:Writer | # 2、字节流 字节流可以传输任意类型的文件数据。流在传递时底层传递的都是二进制数据。 ## 2.1、字节输出流OutputStream java.io.OutputStream:抽象类,表示字节输出流的所有类的父类,将指定的字节信息写出到目的地。它定义了字节输出流的基本共性功能方法。 `public void close()` :关闭此输出流并释放与此流相关联的任何系统资源。 `public void flush()` :刷新此输出流并强制任何缓冲的输出字节被写出。 `public void write(byte[] b)` :将 b.length字节从指定的字节数组写入此输出流。 * 正数(0-127):查询ASCII码表; * 负数:两个字节一组,查系统默认码表(gbk)。 `public void write(byte[] b, int off, int len)` ::从指定的字节数组写入 len字节,从偏移量 off开始输 出到此输出流。 `public abstract void write(int b)` :将指定的字节输出流。每次可以写出一个字节数据。 【注意】 * 虽然参数为int类型四个字节,但是只会保留一个字节的信息写出。 * 当完成流的操作时,必须调用close()方法,释放系统资源。 ## 2.2、FileOutputStream java.io.FileOutputStream:类,是文件输出流,用于将数据写出到文件。 * 构造方法 `public FileOutputStream(File file)` :创建文件输出流以写入由指定的 File对象表示的文件。 `public FileOutputStream(String name)` :创建文件输出流以指定的名称写入文件。 【注意】创建一个流对象时,必须传入一个文件路径。该路径下,如果没有这个文件,会创建该文件。如果有这个文 件,会清空这个文件的数据。 `public FileOutputStream(File file, boolean append)` :创建文件输出流以写入由指定的 File对象表示的 文件。 `public FileOutputStream(String name, boolean append)` :创建文件输出流以指定的名称写入文件。 【注意】这两个构造方法,参数中都需要传入一个boolean类型的值, true 表示追加数据, false 表示清空原有数据。 * 写出换行 * 回车符 `\r` 和换行符 `\n` * 回车符:回到一行的开头(return)。 * 换行符:下一行(newline)。 * 系统中的换行 * Windows系统里,每行结尾是 回车+换行 ,即 `\r\n` * Unix系统里,每行结尾只有 换行 ,即 `\n` * Mac系统里,每行结尾是 回车 ,即 `\r` 。从 Mac OS X开始与Linux统一。 ## 2.3、字节输入流InputStream java.io.InputStream:抽象类是表示字节输入流的所有类的超类,可以读取字节信息到内存中。它定义了字节输入 流的基本共性功能方法。 `public void close()` :关闭此输入流并释放与此流相关联的任何系统资源。 `public abstract int read()` :从输入流读取数据的下一个字节。 * 每次可以读取一个字节的数据,提升为int类型,读取到文件末尾,返回 -1。 `public int read(byte[] b)` :从输入流中读取一些字节数,并将它们存储到字节数组 b中 。 * 每次读取b的长度(一般定义为1024的整数倍)个字节到数组中,返回读取到的有效字节个数,读 取到末尾时,返回 -1 。 * 使用数组读取,每次读取多个字节,减少了系统间的IO操作次数,从而提高了读写的效率,建议开发中使 用。 【注意】,当完成流的操作时,必须调用close()方法,释放系统资源。 ## 2.4、FileInputStream java.io.FileInputStream:类,是文件输入流,从文件中读取字节。 * 构造方法 `public FileInputStream(File file)` :通过打开与实际文件的连接来创建一个 FileInputStream ,该文件由文件系 统中的 File对象 file命名。 `public FileInputStream(String name)` : 通过打开与实际文件的连接来创建一个 FileInputStream ,该文件由文件 系统中的路径名 name命名。 ## 2.5、图片复制案例 ```java has-numbering public static void main(String[] args) throws IOException { // 1 创建流对象 // 1.1 指定数据源 FileInputStream fis = new FileInputStream("D:\\a.jpeg"); // 1.2 指定目的地 FileOutputStream fos = new FileOutputStream("aa.jpeg"); // 2 读取数据 // 2.1 定义数组 byte[] bytes = new byte[1024]; // 2.2 定义长度 int len; // 2.3 循环读取 while ((len = fis.read(bytes)) != -1) { // 写出数据,每次写有效长度个 fos.write(bytes, 0, len); } // 3 关闭资源 fos.close(); fis.close(); } ``` # 3、字符流 一个中文字符可能占用多个字节存储。所以Java提供一些字符流类,以字符为单位读写数据,专门用于处理文本文件。 字符流,只能操作文本文件,不能操作图片,视频等非文本文件。 单纯读或者写文本文件时 使用字符流 其他情况使用字节流。 ## 3.1、字符输入流Reader java.io.Reader:抽象类是表示用于读取字符流的所有类的超类,可以读取字符信息到内存中。它定义了字符输入 流的基本共性功能方法。 `public void close()` :关闭此流并释放与此流相关联的任何系统资源。 `public int read()` :从输入流读取一个字符。每次可以读取一个字符的数据,提升为int类型,读取到文件末尾,返回 -1。 `public int read(char[] cbuf)` :从输入流中读取一些字符,并将它们存储到字符数组 cbuf中 。每次读取b的长度个字符到数组中,返回读取到的有效字符个数, 读取到末尾时,返回 -1。 ## 3.2、FileReader java.io.FileReader:类,是读取字符文件的便利类。构造时使用系统默认的字符编码和默认字节缓冲区。 【注意】 * 继承自InputStreamReader extends Reader。 * 字符编码:字节与字符的对应规则。Windows系统的中文编码默认是GBK编码表;idea中UTF-8。 * 字节缓冲区:一个字节数组,用来临时存储字节数据。 * 构造方法 `public FileReader(File file)` :创建一个新的 FileReader ,给定要读取的File对象。 `public FileReader(String name)` :创建一个新的 FileReader ,给定要读取的文件的名称。 【注意】创建一个流对象时,必须传入一个文件路径。类似于FileInputStream 。 ## 3.3、字符输出流Writer java.io.Writer:抽象类是表示用于写出字符流的所有类的超类,将指定的字符信息写出到目的地。它定义了字节 输出流的基本共性功能方法。 `public void close()` :先刷新缓冲区,然后通知系统释放资源。流对象不可以再被使用了。 `public void flush()` :刷新该流的缓冲区,流对象可以继续使用。 `public void write(int c)` :写入单个字符。 `public abstract void write(char[] cbuf)` :写入字符数组。 `public void write(char[] cbuf, int off, int len)` :写入字符数组的某一部分,off数组的开始索引,len 写的字符个数。 `public void write(String str)` :写入字符串。 `public void write(String str, int off, int len)` :写入字符串的某一部分,off字符串的开始索引,len写的字符个数。 ## 3.4、FileWriter java.io.FileWriter:类,是写出字符到文件的便利类。构造时使用系统默认的字符编码和默认字节缓冲区。 【注意】集成自 OutputStreamWriter extends Writer * 构造方法 `public FileWriter(File file)` :创建一个新的 FileWriter,给定要读取的File对象。 `public FileWriter(String name)` :创建一个新的 FileWriter,给定要读取的文件的名称。 【注意】 * 创建一个流对象时,必须传入一个文件路径,类似于FileOutputStream。 * 虽然参数为int类型四个字节,但是只会保留一个字符的信息写出。 * 未调用close方法,数据只是保存到了缓冲区,并未写出到文件中。 * 追加写和换行与FileOutoutStream类似。 # 4、IO异常处理 * JDK1.7以前:在finally中释放资源 `try{...}catch(异常变量){...}finally{...}` * jdk1.7: `try-with-resource` 语句,确保了每个资源在语句结束时关闭。所谓的资源 (resource)是指在程序完成后,必须关闭的对象。 ```java has-numbering try(创建流对象的语句,如果有多个语句,使用;隔开) { // 读写数据 } catch(异常变量) { // 异常处理 } ``` * JDK1.9: `try-with-resource` 语句增加了引入对象的方式。被引入的对象,同样可以自动关闭, 无需手动close。 ```java has-numbering // 被final修饰的对象 final Resource resource1 = new Resource("resource1"); // 普通对象 Resource resource2 = new Resource("resource2"); // 引入方式:创建新的变量保存 try (Resource r1 = resource1; Resource r2 = resource2) { // 使用对象 } ``` ```java has-numbering // 被final修饰的对象 final Resource resource1 = new Resource("resource1"); // 普通对象 Resource resource2 = new Resource("resource2"); // 引入方式:直接引入 try (resource1; resource2) { // 使用对象 } ``` # 5、缓冲流 缓冲流(高效流),对基本的流进行增强,增加缓冲区,提高效率,减少系统的IO次数。 * 字节缓冲流:BufferedInputStream,BufferedOutputStream * 字符缓冲流:BufferedReader,BufferedWriter 缓冲流的原理:在创建流对象时,创建一个内置的默认大小的缓冲区数组,通过缓冲区读写,减少系统IO 次数,从而提高读写的效率。 关闭缓冲流时会自动关闭基本的流。 * 注意 输出流需要flush()。 ## 5.1、字节缓冲流 * 字节缓冲输入流 * 构造方法 `public BufferedInputStream(InputStream in)` :创建一个新的缓冲输入流。 `public BufferedInputStream(InputStream in, int size)` :创建一个新的缓冲输入流。并指定缓冲区大小。 * 字节缓冲输出流 * 构造方法 `public BufferedOutputStream(OutputStream out)` :创建一个新的缓冲输出流。 `public BufferedOutputStream(OutputStream out, int size)` :创建一个新的缓冲输出流。并指定缓冲区大 小。 ## 5.2、字符缓冲流 * 字符缓冲输入流 * 构造方法 `public BufferedReader(Reader in)` :创建一个新的缓冲输入流。 `public BufferedReader(Reader in, int size)` :创建一个新的缓冲输入流。并指定缓冲区大小。 * 特有方法 `public String readLine()` :读一行文字。不包含终止符,末尾返回null。 * 字符缓冲输出流 * 构造方法 `public BufferedWriter(Writer out)` :创建一个新的缓冲输出流。 `public BufferedWriter(Writer out, int size)` :创建一个新的缓冲输出流。并指定缓冲区大小。 * 特有方法 `public void newLine()` :写行分隔符,由系统属性定义符号。 ## 5.3、文本排序练习 ``` 3.侍中、侍郎郭攸之、费祎、董允等,此皆良实,志虑忠纯,是以先帝简拔以遗陛下。愚以为宫中之事,事无大小,悉以咨之,然后施行,必得裨补阙漏,有所广益。 8.愿陛下托臣以讨贼兴复之效,不效,则治臣之罪,以告先帝之灵。若无兴德之言,则责攸之、祎、允等之慢,以彰其咎;陛下亦宜自谋,以咨诹善道,察纳雅言,深追先帝遗诏,臣不胜受恩感激。 4.将军向宠,性行淑均,晓畅军事,试用之于昔日,先帝称之曰能,是以众议举宠为督。愚以为营中之事,悉以咨之,必能使行阵和睦,优劣得所。 2.宫中府中,俱为一体,陟罚臧否,不宜异同。若有作奸犯科及为忠善者,宜付有司论其刑赏,以昭陛下平明之理,不宜偏私,使内外异法也。 1.先帝创业未半而中道崩殂,今天下三分,益州疲弊,此诚危急存亡之秋也。然侍卫之臣不懈于内,忠志之士忘身于外者,盖追先帝之殊遇,欲报之于陛下也。诚宜开张圣听,以光先帝遗德,恢弘志士之气,不宜妄自菲薄,引喻失义,以塞忠谏之路也。 9.今当远离,临表涕零,不知所言。 6.臣本布衣,躬耕于南阳,苟全性命于乱世,不求闻达于诸侯。先帝不以臣卑鄙,猥自枉屈,三顾臣于草庐之中,咨臣以当世之事,由是感激,遂许先帝以驱驰。后值倾覆,受任于败军之际,奉命于危难之间,尔来二十有一年矣。 7.先帝知臣谨慎,故临崩寄臣以大事也。受命以来,夙夜忧叹,恐付托不效,以伤先帝之明,故五月渡泸,深入不毛。今南方已定,兵甲已足,当奖率三军,北定中原,庶竭驽钝,攘除奸凶,兴复汉室,还于旧都。此臣所以报先帝而忠陛下之职分也。至于斟酌损益,进尽忠言,则攸之、祎、允之任也。 5.亲贤臣,远小人,此先汉所以兴隆也;亲小人,远贤臣,此后汉所以倾颓也。先帝在时,每与臣论此事,未尝不叹息痛恨于桓、灵也。侍中、尚书、长史、参军,此悉贞良死节之臣,愿陛下亲之信之,则汉室之隆,可计日而待也。 ``` ```java has-numbering public static void main(String[] args) throws IOException { // 创建map集合,存储读取到的行信息,用于排序。key是序号,value是文字。 HashMap<String, String> lineMap = new HashMap<>(); // 创建字符缓冲输入流 BufferedReader br = new BufferedReader(new FileReader("in.txt")); // 创建字符缓冲输出流 BufferedWriter bw = new BufferedWriter(new FileWriter("out.txt")); // 读取数据 String line = null; while ((line = br.readLine()) != null) { // 解析文本 String[] split = line.split("\\."); // 保存到map集合中 lineMap.put(split[0], split[1]); } // 关闭输入流 br.close(); // 写出数据 for (int i = 1; i < lineMap.size(); i++) { String key = String.valueOf(i); String value = lineMap.get(key); bw.write(key + ". " + value); bw.newLine(); } // 关闭输出流 bw.close(); } ``` ## 5.4、学习案例 <div class="tip inlineBlock info"> 继承自父类的共性成员方法: </div> - `public void close()` :关闭此输出流并释放与此流相关联的任何系统资源。 - `public void flush()` :刷新此输出流并强制任何缓冲的输出字节被写出。 - `public void write(byte[] b)`:将 b.length字节从指定的字节数组写入此输出流。 - `public void write(byte[] b, int off, int len)` :从指定的字节数组写入 len字节,从偏移量 off开始输出到此输出流。 - `public abstract void write(int b)` :将指定的字节输出流。 <div class="tip inlineBlock info"> 构造方法: </div> BufferedOutputStream(OutputStream out) 创建一个新的缓冲输出流,以将数据写入指定的底层输出流。 BufferedOutputStream(OutputStream out, int size) 创建一个新的缓冲输出流,以将具有指定缓冲区大小的数据写入指定的底层输出流。 <div class="tip inlineBlock info"> 参数: </div> OutputStream out:字节输出流 我们可以传递FileOutputStream,缓冲流会给FileOutputStream增加一个缓冲区,提高FileOutputStream的写入效率 int size:指定缓冲流内部缓冲区的大小,不指定默认 <div class="tip inlineBlock info"> 使用步骤(重点): </div> 1. 创建FileOutputStream对象,构造方法中绑定要输出的目的地 2. 创建BufferedOutputStream对象,构造方法中传递FileOutputStream对象对象,提高FileOutputStream对象效率 3. 使用BufferedOutputStream对象中的方法write,把数据写入到内部缓冲区中 4. 使用BufferedOutputStream对象中的方法flush,把内部缓冲区中的数据,刷新到文件中 5. 释放资源(会先调用flush方法刷新数据,第4部可以省略) # 6、转换流 按照某种规则,将字符存储到计算机中,称为编码 。反之,将存储在计算机中的二进制数按照 某种规则解析显示出来,称为解码 。 * 字符编码 Character Encoding : 就是一套自然语言的字符与二进制数之间的对应规则。 * 字符集 Charset :也叫编码表。是一个系统支持的所有字符的集合,包括各国家文字、标点符号、图形符 号、数字等。 计算机要准确的存储和识别各种字符集符号,需要进行字符编码,一套字符集必然至少有一套字符编码。常见字符 集有ASCII字符集、GBK字符集、Unicode字符集等。当指定了编码,它所对应的字符集自然就指定了。 ![](https://blog.fivk.cn/usr/uploads/2021/11/3297536959.jpg) ![](https://blog.fivk.cn/usr/uploads/2021/11/2143158521.jpg) ## 6.1、InputStreamReader java.io.InputStreamReader:是Reader的子类,是从字节流到字符流的桥梁。它读取字节,并使用指定 的字符集将其解码为字符。它的字符集可以由名称指定,也可以接受平台的默认字符集。 * 构造方法 `public InputStreamReader(InputStream in)` :创建一个使用默认字符集的字符流。 `public InputStreamReader(InputStream in, String charsetName)` :创建一个指定字符集的字符流。 ## 6.2、OutputStreamWriter java.io.OutputStreamWriter:是Writer的子类,是从字符流到字节流的桥梁。使用指定的字符集将字符 编码为字节。它的字符集可以由名称指定,也可以接受平台的默认字符集。 * 构造方法 `public OutputStreamWriter(OutputStream out)` :创建一个使用默认字符集的字符流。 `public OutputStreamWriter(OutputStream out, String charsetName)` :创建一个指定字符集的字符流。 ## 6.3、转换文件编码练习 将GBK编码的文本文件,转换为UTF-8编码的文本文件。 1. 指定GBK编码的转换流,读取文本文件。 2. 使用UTF-8编码的转换流,写出文本文件。 ```java has-numbering public static void main(String[] args) throws IOException { // 定义文件路径 String srcFile = "file_GBK.txt"; String destFile = "file_UTF8.txt"; // 创建转换输入流 InputStreamReader in = new InputStreamReader(new FileInputStream(srcFile), "GBK"); // 创建转换输出流 OutputStreamWriter out = new OutputStreamWriter(new FileOutputStream(destFile)); // 读取数据 char[] cbuf = new char[1024]; int len; while ((len = in.read(cbuf)) != -1) { // 写出数据 out.write(cbuf, 0, len); } // 释放资源 out.close(); in.close(); } ``` # 7、序列化流 ## 7.1、概述 Java 提供了一种对象序列化的机制。用一个字节序列可以表示一个对象,该字节序列包含该 对象的数据 、 对象的 类型 和 对象中存储的属性 等信息。字节序列写出到文件之后,相当于文件中持久保存了一个对象的信息。 反之,该字节序列还可以从文件中读取回来,重构对象,对它进行反序列化。 对象的数据 、 对象的类型 和 对象中 存储的数据 信息,都可以用来在内存中创建对象。 【注意】 * 被static修饰的成员变量先于对象存在,不能被序列化; * 被transient修饰的成员变量不能被序列化。 ![](https://blog.fivk.cn/usr/uploads/2021/11/1287227611.jpg) ## 7.2、ObjectOutputStream java.io.ObjectOutputStream:类,继承自OuputStream,将Java对象的原始数据类型写出到文件,实现对象的持久存储。 * 构造方法 `public ObjectOutputStream(OutputStream out)` :创建一个指定OutputStream的ObjectOutputStream。 * 特有成员方法 `public final void writeObject(Object obj)` :将指定的对象写出。 * 序列化操作(序列化操作必须满足以下两个条件) * 该类必须实现 java.io.Serializable 接口, `Serializable` 是一个标记接口,不实现此接口的类将不会使任 何状态序列化或反序列化,会抛出 NotSerializableException 。 * 该类的所有属性必须是可序列化的。如果有一个属性不需要可序列化的,则该属性必须注明是瞬态的,使用 `transient` 关键字修饰。 ## 7.3、ObjectInputStream java.io.ObjectInputStream:反序列化流,将之前使用ObjectOutputStream序列化的原始数据恢复为对象。 * 构造方法 `public ObjectInputStream(InputStream in)` :创建一个指定InputStream的ObjectInputStream。 * 特有成员方法 `public final Object readObject()` :读取一个对象。 * 反序列化操作(反序列化操作必须满足以下两个条件) * 该类必须实现Serializable接口; * 该类必须存在对应的class文件。如果找不到对应的class文件,则会抛出一个 `ClassNotFoundException` 异常。 【注意】当JVM反序列化对象时,能找到class文件,但是class文件在序列化对象之后发生了修改,那么反序列化操 作也会失败,抛出一个 `InvalidClassException` 异常。发生这个异常的原因如下: * 该类的序列版本号与从流中读取的类描述符的版本号不匹配;解决: `private static final long serialVersionUID = 1L;` * 该类包含未知数据类型; * 该类没有可访问的无参数构造方法。 Serializable 接口给需要序列化的类,提供了一个序列版本号。 serialVersionUID 该版本号的目的在于验证序 列化的对象和对应类是否版本匹配。 ## 7.4、序列化结合练习 1. 将存有多个自定义对象的集合序列化操作,保存到 list.txt 文件中。 2. 反序列化 list.txt ,并遍历集合,打印对象信息。 ```java has-numbering public class Student implements Serializable { private static final long serialVersionUID = 1L; private String name; private int age; public Student() { } public Student(String name, int age) { this.name = name; this.age = age; } public String getName() { return name; } public void setName(String name) { this.name = name; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } @Override public String toString() { return "Student{" + "name='" + name + '\'' + ", age=" + age + '}'; } } ``` ```java has-numbering public static void main(String[] args) { // 创建list集合 ArrayList<Student> students = new ArrayList<>(); // 创建Student对象 students.add(new Student("张三", 18)); students.add(new Student("李四", 18)); students.add(new Student("王五", 18)); students.add(new Student("小师妹", 18)); // 序列化 serialize(students); // 反序列化 reSerialize("list.txt"); } private static void serialize(ArrayList<Student> students) { ObjectOutputStream out = null; try { out = new ObjectOutputStream(new FileOutputStream("list.txt")); out.writeObject(students); } catch (IOException e) { e.printStackTrace(); } finally { try { if (out != null) { out.close(); } } catch (IOException e) { e.printStackTrace(); } } } private static void reSerialize(String s) { ObjectInputStream in = null; try { in = new ObjectInputStream(new FileInputStream(s)); try { ArrayList<Student> list = (ArrayList<Student>) in.readObject(); for (Student student : list) { System.out.println(student); } } catch (ClassNotFoundException e) { e.printStackTrace(); } } catch (IOException e) { e.printStackTrace(); }finally { try { if (in != null) { in.close(); } } catch (IOException e) { e.printStackTrace(); } } } ``` # 8、打印流 java.io.PrintStream:类,继承自OutputStream,该类能够方便地打印各种数据类型的值,是一种便捷的输出方式。 【注意】 1. 该流只写,不读; 2. 不会抛出IOException; * 构造方法 `public PrintStream(File file)` :使用指定的文件对象创建一个新的打印流。 `public PrintStream(OutputStream out)` :使用指定的输出流创建一个新的打印流。 `public PrintStream(String fileName)` :使用指定的文件名创建一个新的打印流。 * 特有方法 `public void print()` `public void println()` 【注意】 ``` 1. 这两个方法在控制台输出任意类型数据,不查编码表,原样输出; 1. 如果调用父类的write方法,则会查询编码表。 12 ``` ## 8.1、改变打印流的方向 System.out 就是 PrintStream 类型的,只不过它的流向是系统规定的,打印在控制台上。使用 `System.setOut(PrintStream ps)` 方法可以改变打印流的方向。 ```java has-numbering public static void main(String[] args) { PrintStream ps = null; try { ps = new PrintStream("ps.txt"); System.setOut(ps); System.out.println(48); ps.println(65); ps.write(97); } catch (FileNotFoundException e) { e.printStackTrace(); } finally { if (ps != null) { ps.close(); } } } ``` > 感谢小伙伴们的关注! > 你的点赞、评论、关注、收藏是对博主的最大鼓励! > 持续更新JavaSE学习笔记!欢迎订阅专栏! 最后修改:2021 年 11 月 21 日 © 允许规范转载 打赏 赞赏作者 支付宝微信 赞 如果觉得我的文章对你有用,请随意赞赏