什么是流?流表示任何有能力产生数据的数据源对象或者是有能力接收数据的接收端对象,它屏蔽了实际的I/O设备中处理数据的细节 。IO流是实现输入输出的基础,它可以很方便地实现数据的输入输出操作,即读写什么是流?流表示任何有能力产生数据的数据源对象或者是有能力接收数据的接收端对象 , 它屏蔽了实际的 I/O 设备中处理数据的细节 。
IO 流是实现输入输出的基础,它可以很方便地实现数据的输入输出操作,即读写操作 。
本片要点
- 介绍流的定义和基本分类 。
- 介绍文件字符流、字节流、转换流、合并流、打印流等使用 。
- 介绍序列化的意义 。
- 介绍两种自定义序列化方式 。
- 根据方向输入流:数据从外部流向程序,例如从文件中读取数据 。输出流:数据从程序流向外部,例如向文件中写数据 。
- 根据形式字符流:字符类文件,【如 txt、 java、 html】,操作 16 位的字符 。字节流:【图片、视频、音频】 , 操作 8 位的字节 。
- 根据功能节点流:直接从/向数据源【如磁盘、网络】进行数据读写处理流:封装其他的流,来提供增强流的功能 。
文章插图
文章插图
- 上面四大基本流都是抽象类,都不能直接创建实例对象 。
- 数据的来源/目的地:磁盘、网络、内存、外部设备 。
- java1.0 版本中,I/O 库中与输入有关的所有类都将继承 InputStream , 与输出有关的所有类继承 OutputStream,用以操作二进制数据 。
- java1.1 版本对 I/O 库进行了修改:在原先的库中新增了新类,如 ObjectInputStream 和 ObjectOutputStream 。增加了 Reader 和 Writer,提供了兼容 Unicode 与面向字符的 I/O 功能 。在 Reader 和 Writer 类层次结构中,提供了使字符与字节相互转化的类,OutputStreamWriter 和 InputStreamReader 。
- 两个不同的继承层次结构拥有相似的行为,它们都提供了读(read)和写(write)的方法,针对不同的情况,提供的方法也是类似的 。
- java1.4 版本的 java.nio.*包中引入新的 I/O 类库 , 这部分以后再做学习 。
- 文件字符输出流 FileWriter:自带缓冲区,数据先写到到缓冲区上 , 然后从缓冲区写入文件 。
- 文件字符输入流 FileReader:没有缓冲区,可以单个字符的读?。?也可以自定义数组缓冲区 。
//将流对象放在 try 之外声明,并附为 null,保证编译,可以调用 closeFileWriter writer = null;try {//将流对象放在里面初始化writer = new FileWriter(\"D:\\\\b.txt\");writer.write(\"abc\");//防止关流失败,没有自动冲刷,导致数据丢失writer.flush();} catch (IOException e) {e.printStackTrace();} finally {//判断 writer 对象是否成功初始化if(writer!=null) {//关流,无论成功与否try {writer.close();} catch (IOException e) {e.printStackTrace();}finally {//无论关流成功与否 , 都是有意义的:标为垃圾对象,强制回收writer = null;}}}
- 并不会直接将数据写入文件中,而是先写入缓冲区,待缓冲区满了之后才将缓冲区的数据写入文件 。
- 假设数据写入缓冲区时且缓冲区还没满,数据还没能够写入文件时,程序就已经结束,会导致数据惨死缓冲区 , 这时需要手动冲刷缓冲区,将缓冲区内的数据冲刷进文件中 。writer.flush(); 。
- 数据写入完毕 , 释放文件以允许别的流来操作该文件 。关闭流可以调用 close()方法,值得注意的是,在 close 执行之前,流会自动进行一次 flush 的操作以避免数据还残存在缓冲区中,但这并不意味着 flush 操作是多余的 。
- 无论流操作成功与否,关流操作都需要进行,所以需要将关流操作放到 finally 代码块中 。
- 为了让流对象在 finally 中依然能够使用,所以需要将流对象放在 try 之外声明并且赋值为 null,然后在 try 之内进行实际的初始化过程 。
- 在关流之前要判断流对象是否初始化成功,实际就是判断流对象是否为 null 。writer!=null 时才执行关流操作 。
- 关流可能会失败,此时流依然会占用文件,所以需要将流对象置为 null,标记为垃圾对象进行强制回收以释放文件 。
- 如果流有缓冲区,为了防止关流失败导致没有进行自动冲刷,所以需要手动冲刷一次,以防止有数据死在缓冲区而产生数据的丢失 。
要求处理的对象的声明过程必须在 try 后跟的()中,在 try 代码块之外 。
【详解java中的io输入输出流 简述java中输入输出流的分类】
try(FileWriter writer = new FileWriter(\"D:\\\\c.txt\")){writer.write(\"abc\");}catch (IOException e){e.printStackTrace();}
读取的基本结构
public static void main(String[] args) throws IOException {FileReader reader = new FileReader(\"D:\\\\b.txt\");//定义数组作为缓冲区char[] cs = new char[5];//定义一个变量记录每次读取的字符int hasRead;//读取到末尾为-1while ((hasRead = reader.read(cs)) != -1) {System.out.println(new String(cs, 0, hasRead));}reader.close();}
- read 方法可以传入字符数组,每次读取一个字符数组的长度 。
- 定义变量 m 记录读取的字符,以达到末尾为终止条件 。m!=-1 时,终止循环 。
- 读取结束,执行关流操作 。
public static void copyFile(FileReader reader, FileWriter writer) throws IOException {//利用字符数组作为缓冲区char[] cs = new char[5];//定义变量记录读取到的字符个数int hasRead;while((hasRead = reader.read(cs)) != -1){//将读取到的内容写入新的文件中writer.write(cs, 0, hasRead));}reader.close();writer.close();}
文件字节流
- 文件字节输出流 FileOutputStream 在输出的时候没有缓冲区,所以不需要进行 flush 操作 。
public static void main(String[] args) throws Exception {FileOutputStream out = new FileOutputStream(\"D:\\\\b.txt\");//写入数据//字节输出流没有缓冲区out.write(\"天乔巴夏\".getBytes());//关流是为了释放文件out.close();}
- 文件字节输入流 FileInputStream,可以定义字节数组作为缓冲区 。
public static void main(String[] args) throws Exception{FileInputStream in = new FileInputStream(\"E:\\\\1myblog\\\\Node.png\");//1.读取字节int i;while((i = in.read()) ! =-1)System.out.println(i);//2.定义字节数组作为缓冲区byte[] bs = new byte[10];//定义变量记录每次实际读取的字节个数int len;while((len = in.read(bs)) != -1){System.out.println(new String(bs, 0, len));}in.close();}
缓冲流字符缓冲流
- BufferedReader:在构建的时候需要传入一个 Reader 对象,真正读取数据依靠的是传入的这个 Reader 对象,BufferedRead 从 Reader 对象中获取数据提供缓冲区 。
public static void main(String[] args) throws IOException {//真正读取文件的流是 FileReader,它本身并没有缓冲区FileReader reader = new FileReader(\"D:\\\\b.txt\");BufferedReader br = new BufferedReader(reader);//读取一行//String str = br.readLine();//System.out.println(str);//定义一个变量来记录读取的每一行的数据(回车)String str;//读取到末尾返回 nullwhile((str = br.readLine())!=null){System.out.println(str);}//关外层流即可br.close();}
- BufferedWriter:提供了一个更大的缓冲区,提供了一个 newLine 的方法用于换行 , 以屏蔽不同操作系统的差异性 。
public static void main(String[] args) throws Exception {//真正向文件中写数据的流是 FileWriter,本身具有缓冲区//BufferedWriter 提供了更大的缓冲区BufferedWriter writer = new BufferedWriter(new FileWriter(\"E:\\\\b.txt\"));writer.write(\"天乔\");//换行: Windows 中换行是 \\r\\nlinux 中只有\\n//提供 newLine() 统一换行writer.newLine();writer.write(\"巴夏\");writer.close();}
装饰设计模式缓冲流基于装饰设计模式,即利用同类对象构建本类对象,在本类中进行功能的改变或者增强 。
例如,BufferedReader 本身就是 Reader 对象,它接收了一个 Reader 对象构建自身,自身提供缓冲区和其他新增方法,通过减少磁盘读写次数来提高输入和输出的速度 。
文章插图
文章插图
除此之外,字节流同样也存在缓冲流,分别是 BufferedInputStream 和 BufferedOutputStream 。
转换流(适配器)利用转换流可以实现字符流和字节流之间的转换 。
- OutputStreamWriter
public static void main(String[] args) throws Exception {//在构建转换流时需要传入一个 OutputStream字节流OutputStreamWriter ow =new OutputStreamWriter(new FileOutputStream(\"D:\\\\b.txt\"),\"utf-8\");//给定字符--> OutputStreamWriter 转化为字节-->以字节流形式传入文件 FileOutputStream//如果没有指定编码 , 默认使用当前工程的编码ow.write(\"天乔巴夏\");ow.close();}
最终与文件接触的是字节流 , 意味着将传入的字符转换为字节 。
- InputStreamReader
public static void main(String[] args) throws IOException {//以字节形式 FileInputStream 读取,经过转换 InputStreamReader -->字符//如果没有指定编码 。使用的是默认的工程的编码InputStreamReader ir =new InputStreamReader(new FileInputStream(\"D:\\\\b.txt\"));char[] cs = new char[5];int len;while((len=ir.read(cs))!=-1){System.out.println(new String(cs,0,len));}ir.close();}
最初与文件接触的是字节流 , 意味着将读取的字节转化为字符 。
适配器设计模式缓冲流基于适配器设计模式 , 将某个类的接口转换另一个用户所希望的类的接口,让原本由于接口不兼容而不能在一起工作的类可以在一起进行工作 。
以 OutputStreamWriter 为例,构建该转换流时需要传入一个字节流,而写入的数据最开始是由字符形式给定的 , 也就是说该转换流实现了从字符向字节的转换,让两个不同的类在一起共同办事 。
文章插图
文章插图
标准流/系统流程序的所有输入都可以来自于标准输入 , 所有输出都可以发送到标准输出,所有错误信息都可以发送到标准错误 。
标准流分类
文章插图
文章插图
可以直接使用 System.out 和 System.err,但是在读取 System.in 之前必须对其进行封装,例如我们之前经常会使用的读取输入:Scanner sc = new Scanner(System.in);实际上就封装了 System.in 对象 。
- 标准流都是字节流 。
- 标准流对应的不是类而是对象 。
- 标准流在使用的时候不用关闭 。
/*** 从控制台获取一行数据* @throws IOExceptionreadLine 可能会抛出异常*/public static void getLine() throws IOException {//获取一行字符数据 -- BufferedReader//从控制台获取数据 -- System.in//System 是字节流,BufferedReader 在构建的时候需要传入字符流//将字节流转换为字符流BufferedReader br =new BufferedReader(new InputStreamReader(System.in));//接收标准输入并转换为大写String str = br.readLine().toUpperCase();//发送到标准输出System.out.println(str);}
通过转换流,将 System.in 读取的标准输入字节流转化为字符流,发送到标准输出,打印显示 。
打印流打印流只有输出流没有输入流
- PrintStream: 打印字节流
public static void main(String[] args) throws IOException {//创建 PrintStream 对象PrintStream p = new PrintStream(\"D:\\\\b.txt\");p.write(\"abc\".getBytes());p.write(\"def\".getBytes());p.println(\"abc\");p.println(\"def\");//如果打印对象,默认调用对象身上的 toString 方法p.println(new Object());p.close();}
- PrintWriter:打印字符流
//将 System.out 转换为 PrintStreampublic static void main(String[] args) {//第二个参数 autoFlash 设置为 true,否则看不到结果PrintWriter p = new PrintWriter(System.out,true);p.println(\"hello,world!\");}
合并流
- SequenceInputStream 用于将多个字节流合并为一个字节流的流 。
- 有两种构建方式:将多个合并的字节流放入一个 Enumeration 中来进行 。传入两个 InputStream 对象 。
- 合并流只有输入流没有输出流 。
public static void main(String[] args) throws IOException {FileInputStream in1 = new FileInputStream(\"D:\\\\1.txt\");FileInputStream in2 = new FileInputStream(\"D:\\\\a.txt\");FileInputStream in3 = new FileInputStream(\"D:\\\\b.txt\");FileInputStream in4 = new FileInputStream(\"D:\\\\m.txt\");FileOutputStream out = new FileOutputStream(\"D:\\\\union.txt\");//准备一个 Vector 存储输入流Vector v = new Vector();v.add(in1);v.add(in2);v.add(in3);v.add(in4);//利用 Vector 产生 Enumeration 对象Enumeration e = v.elements();//利用迭代器构建合并流SequenceInputStream s = new SequenceInputStream(e);//读取byte[] bs = new byte[10];int len;while((len = s.read(bs))!=-1){out.write(bs,0,len);}out.close();s.close();}
序列化/反序列化流
- 序列化:将对象转化为字节数组的过程 。
- 反序列化:将字节数组还原回对象的过程 。
让某个对象支持序列化的方法很简单,让它实现 Serializable 接口即可:
public interface Serializable {}
这个接口没有任何的方法声明 , 只是一个标记接口,表明实现该接口的类是可序列化的 。
我们通常在 Web 开发的时候,JavaBean 可能会作为参数或返回在远程方法调用中,如果对象不可序列化会出错 , 因此 , JavaBean 需要实现 Serializable 接口 。
序列化对象创建一个 Person 类 。
//必须实现 Serializable 接口 class Person implements Serializable {//序列化 ID serialVersionUIDprivate static final long serialVersionUID = 6402392549803169300L;private String name;private int age;public int getAge() {return age;}public void setAge(int age) {this.age = age;}public String getName() {return name;}public void setName(String name) {this.name = name;}}
创建序列化流,将对象转化为字节,并写入\"D:\\1.data\" 。
public class ObjectOutputStreamDemo {public static void main(String[] args) throws IOException {Person p = new Person();p.setAge(18);p.setName(\"Niu\");//创建序列化流//真正将数据写出的流是 FileOutputStream//ObjectOutputStream 将对象转化为字节ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream(\"D:\\\\1.data\"));out.writeObject(p);out.close();}}
创建反序列化流 , 将从\"D:\\1.data\"中读取的字节转化为对象 。
public static void main(String[] args) throws IOException, ClassNotFoundException {//创建反序列化流//真正读取文件的是 FileInputStream//ObjectInputStream 将读取的字节转化为对象ObjectInputStream in = new ObjectInputStream(new FileInputStream(\"D:\\\\1.data\"));//读取数据必须进行数据类型的强制转换Person p = (Person)in.readObject();in.close();System.out.println(p.getName());//NiuSystem.out.println(p.getAge());//18}
需要注意的是:
- 如果一个对象要想被序列化,那么对应的类必须实现接口 serializable , 该接口没有任何方法,仅仅作为标记使用 。
- 被 static 或 transient 修饰的属性不会进行序列化 。如果属性的类型没有实现 serializable 接口但是也没有用这两者修饰,会抛出 NotSerializableException 。
- 在对象序列化的时候,版本号会随着对象一起序列化出去 , 在反序列化的时候,对象中的版本号和类中的版本号进行比较,如果版本号一致,则允许反序列化 。如果不一致,则抛出 InvalidClassException 。
- 集合允许被整体序列化,集合及其中元素会一起序列化出去 。
- 如果对象的成员变量是引用类型,这个引用类型也需要是可序列化的 。
- 当一个可序列化类存在父类时,这些父类要么有无参构造器,要么是需要可序列化的,否则将抛出 InvalidClassException 的异常 。
- 一个类如果允许被序列化 , 那么这个类中会产生一个版本号 serialVersonUID 。如果没有手动指定版本号 , 那么在编译的时候自动根据当前类中的属性和方法计算一个版本号,也就意味着一旦类中的属性发生改变,就会重新计算新的 , 导致前后不一致 。但是,手动指定版本号的好处就是,不需要再计算版本号 。
- 版本号的意义在于防止类产生改动导致已经序列化出去的对象无法反序列化回来 。版本号必须用 static final 修饰,本身必须是 long 类型 。
// 实现 writeObject 和 readObject 两个方法@Data@AllArgsConstructor@NoArgsConstructorpublic class Person implements Serializable {private String name;private int age;// 将 name 的值反转后写入二进制流private void writeObject(ObjectOutputStream out) throws IOException {out.writeObject(new StringBuffer(name).reverse());out.writeInt(age);}// 将读取的字符串反转后赋给 nameprivate void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {this.name = ((StringBuffer) in.readObject()).reverse().toString();this.age = in.readInt();}}
还有一种更加彻底的自定义机制,直接将序列化对象替换成其他的对象,需要定义 writeReplace:
@Data@AllArgsConstructor@NoArgsConstructorpublic class Person implements Serializable {private String name;private int age;private Object writeReplace(){ArrayList
Externalizable 自定义Externalizable 实现了 Seriablizable 接口,并规定了两个方法:
public interface Externalizable extends java.io.Serializable {void writeExternal(ObjectOutput out) throws IOException;void readExternal(ObjectInput in) throws IOException, ClassNotFoundException;}
实现该接口,并给出两个方法的实现,也可以实现自定义序列化 。
@Data@NoArgsConstructor@AllArgsConstructorpublic class User implements Externalizable {String name;int age;@Overridepublic void writeExternal(ObjectOutput out) throws IOException {out.writeObject(new StringBuffer(name).reverse());out.writeInt(age);}@Overridepublic void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {this.name = ((StringBuffer) in.readObject()).reverse().toString();this.age = in.readInt();}}
最后如果本文有叙述错误之处,还望评论区批评指正,共同进步 。
原文链接:https://www.cnblogs.com/summerday152/p/14152925.html
如果觉得本文对你有帮助,可以转发关注支持一下