1. 缓冲流
1.1 字节缓冲流概述
当对文件或其他数据源进行频繁的读/写操作时,效率比较低,这时如果使用缓存流就能够更高效地读/写信息。
比如,可以使用缓冲输出流来一次性批量写出若干数据减少写出次数来提高写出效率。
如果用生活中的例子做比方,则如下图所示:
相对于每次都直接从原罐中舀取的操作而言,可以先把物品舀取到一个容器中(相当于缓存),再使用容器去运输。
1.2 BIS 与 BOS
BufferedInputStream和BufferedOutputStream称为字节缓存流。它们本身并不具有输入/输出流的读取与写入功能,只是在其他流上加上缓存功能提高效率,就像是把其他流包装起来一样,因此,缓存流是一种处理流。
BufferedInputStream:字节缓存流内置一个缓存区,第一次调用read()方法时尽可能将数据源的数据读取到缓存区中,后续再用read()方法时先确定缓存区中是否有数据,若有则读取缓存区中的数据,当缓冲区中的数据用完后,再实际从数据源读取数据到缓存区中 ,这样可以减少直接读数据源的次数。
BufferedOutputStream:通过输出流调用write()方法写入数据时,先将数据写入缓存区中,缓存区满了之后再将缓冲区中的数据一次性写入数据目的地。使用缓存字节流可以减少输入/输出操作的次数,以提高效率。
1.3 缓冲流文件复制示例
编写代码,使用字节缓冲流实现文件复制。代码示意如下:
package api_03;import java.io.*;public class FileCopyDemo2 {public static void main(String[] args) throws IOException {// 随机选取本地一个文件即可,本例中的文件大小为112MBFileInputStream fis= new FileInputStream("D:/Development/nacos-server-2.0.3.zip");BufferedInputStream bis= new BufferedInputStream(fis); // 默认缓冲区大小 8192字节FileOutputStream fos= new FileOutputStream("D:/Development/nacos-server-2.0.3_cp.zip");BufferedOutputStream bos= new BufferedOutputStream(fos); // 默认缓冲区大小 8192字节int d = -1;long start = System.currentTimeMillis();while((d = bis.read())!=-1) {bos.write(d);}long end = System.currentTimeMillis();System.out.println("复制完毕!耗时"+(end-start)+"ms"); // 1566msbis.close();bos.close();}
}
1.4 flush 方法
输出流缓冲流提供了flush方法:强制将当前缓冲区中已经缓存的字节一次性写出。可以提高数据写出的即时性,但同样也增加了实际写出的次数,一定程度上降低了写出效率。
在输出流缓冲流的close方法中默认也会调用一次flush方法:保证在关流操作之前清空缓冲区,以避免缓冲区中的数据未能全部输出的情况。
编写代码,测试 flush 方法。代码示意如下:
package api_03;import java.io.BufferedOutputStream;
import java.io.FileOutputStream;
import java.io.IOException;public class BOSFlushDemo {public static void main(String[] args) throws IOException {FileOutputStream fos= new FileOutputStream("./src/api_03/fos2.txt");BufferedOutputStream bos= new BufferedOutputStream(fos);String str = "这是我们输出的文字";byte[] data = str.getBytes("utf-8");bos.write(data);// bos.flush();System.out.println("写出完毕!");/** 缓冲流关闭前会调用一次flush方法.*/// bos.close();}
}
2. 序列化与反序列化
2.1 对象序列化概念
对象是存在于内存中的,有时候我们需要将对象保存到硬盘上,又有时我们需要将对象传输到另一台计算机上等等这样的操作。这时我们需要将对象转换为一个字节序列,而这个过程就称为对象序列化
相反,我们有这样一个字节序列需要将其转换为对应的对象,这个过程就称为对象的反序列化。
如下图所示:
2.2 序列化与反序列化
序列化是指先将内存中对象的相关信息(包括类、数字签名、对象除transient和static之外的全部属性值,以及对象的父类信息等)进行编码,再传输到数据目的地的过程。
如果与序列化的顺序相反,就叫反序列化,将序列化的对象信息从数据源中读取出来,并重新解码组装为内存中一个完整的对象。
如下图所示:
2.3 OIS 与 OOS
Java中的序列化和反序列化是通过对象流来实现的,分别是ObjectInputStream和ObjectOutputStream。
ObjectOutputStream:对对象进行序列化的输出流,其实现对象序列化的方法为:
void writeObject(Object o)
该方法可以将给定的对象转换为一个字节序列后写出 。
ObjectInputStream:对对象进行反序列化的输入流,其实现对象反序列化的方法为:
Object readObject(),
该方法可以从流中读取字节并转换为对应的对象。
2.4 Serializable接口
当使用对象流写入或读取对象时,需要保证对象是可序列化的。这是为了保证能把对象写入文件中,并且能再把对象正确地读回到程序中。一个类如果实现了Serializable接口,那么这个类创建的对象就是可序列化的对象。Java中的包装类和String类均实现了Serializable接口。
Serializable接口中的方法对程序是不可见的,因此实现该接口的类不需要实现额外的方法,只是作为可序列化的标志。
如果把一个序列化的对象写入ObjectInputStream中,Java虚拟机就会实现Serializable接口中的方法,将一定格式的数据(对象的序列化信息)写入目的地中。当使用ObjectInputStream从数据源中读取对象时,就会从数据源中读回对象的序列化信息,并根据对象的序列化信息创建一个对象。
2.5 transient关键字
对象在序列化后得到的字节序列往往比较大,有时我们在对一个对象进行序列化时可以忽略某些不必要的属性,从而对序列化后得到的字节序列”瘦身”。此时,可以对不需要序列化的属性使用关键字 transient:被该关键字修饰的属性在序列化时其值将被忽略。
2.6 序列化示例
首先,创建示例使用的Person类:包含4个属性,其中一个属性添加transient关键字修饰。代码示意如下:
package api_03;import java.io.Serializable;
import java.util.Arrays;
public class Person implements Serializable {String name;int age;String gender;// 使用 transient修饰的属性不会参与序列化transient String[] otherInfo;public Person(String name, int age, String gender, String[] otherInfo) {this.name = name;this.age = age;this.gender = gender;this.otherInfo = otherInfo;}@Overridepublic String toString() {return "Person{" +"name='" + name + '\'' +", age=" + age +", gender='" + gender + '\'' +", otherInfo=" + Arrays.toString(otherInfo) +'}';}
}
main方法中添加代码,实现Person 对象的序列化。代码示意如下:
package api_03;import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;public class SerializationDemo {public static void main(String[] args) throws IOException {String name = "苍老师";int age = 40;String gender = "男";String[] otherInfo = {"Java讲师","来自中国","会拍抖音"};Person p = new Person(name, age, gender, otherInfo);FileOutputStream fos= new FileOutputStream("./src/api_03/person.obj");ObjectOutputStream oos= new ObjectOutputStream(fos);/*这里流连接的操作分别为:1:先将给定对象通过对象流写出,此时对象流会将该对象转换为一组字节,这个过程称为对象序列化2:序列化后的字节再通过文件流写入了文件,即:写入磁盘中,这个过程称为数据持久化*/oos.writeObject(p);System.out.println("写出完毕!");oos.close();}
}
2.7 反序列化示例
Main方法中添加代码,实现Person 对象的反序列化。代码示意如下:
package api_03;import java.io.FileInputStream;
import java.io.IOException;
import java.io.ObjectInputStream;public class DeSerializationDemo {public static void main(String[] args) throws IOException, ClassNotFoundException {FileInputStream fis= new FileInputStream("./src/api_03/person.obj");ObjectInputStream ois= new ObjectInputStream(fis);Person p = (Person)ois.readObject();// otherInfo属性值为null,因为是transient修饰的System.out.println(p);ois.close();}
}