程序员:Java对象流、字节流、文件流的Serializable和深拷贝
流
在Java中是指计算中流动的缓冲区,从外部设备流向中央处理器的数据流成为“输入流”,反之成为“输出流”。
字符流和字节流的主要区别:
1.字节流读取的时候,读到一个字节就返回一个字节;字符流使用了字节流读到一个或多个字节(中文对应的字节数是两个,在UTF-8码表中是3个字节)时。先去查指定的编码表,将查到的字符返回。
2.字节流可以处理所有类型数据,如:图片,MP3,AVI视频文件,而字符流只能处理字符数据。只要是处理纯文本数据,就要优先考虑使用字符流,除此之外都用字节流。
Serializable接口概述
有时候我们有一种需求,保存在内存中的各种对象的状态(也就是实例变量,不是方法),并且想把保存的对象状态再读出来,这样既可以实现对象的传递。
实现这一需求的方式有很多,比如,使用objectMapper序列化为Json字符串/对象,再进行传递或者保存,Java的设计指出也考虑到了这种需求,就设计出了Serializable接口,该接口没有必要实现的方法,默认继承了该接口后会就能自动调用java默认的序列化和放序列化。
Serializable接口代码实践
定义一个类用于序列化:UserInfo .java
@Getter
@Setter
public class UserInfo implements Serializable{ //实现Serializable接口才能被序列化
private String userName;
private String usePass;
private transient int userAge;//使用transient关键字修饰的变量不会被序列化
public UserInfo() {
userAge=20;
}
public UserInfo(String userName, String usePass, int userAge) {
super();
this.userName = userName;
this.usePass = usePass;
this.userAge = userAge;
}
@Override
public String toString() {
return "UserInfo [userName=" + userName + ", usePass=" + usePass + ",userAge="+(userAge==0?"NOT SET":userAge)+"]";
}
/**
* 静态方法,序列化对象到文件
* @param fileName
*/
public static void serialize(String fileName){
try {
//对象输出流和文件输出流结合使用,实现将对象持久化/序列化到本地
ObjectOutputStream out=new ObjectOutputStream(new FileOutputStream(fileName));
out.writeObject("序列化的日期是:");//序列化一个字符串到文件
out.writeObject(new Date());//序列化一个当前日期对象到文件
UserInfo userInfo=new UserInfo("郭大侠","961012",21);
out.writeObject(userInfo);//序列化一个会员对象
out.close(); //资源释放要放到finally中,这里并不规范
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* 静态方法,从文件中反序列化对象
* @param fileName
*/
public static void deserialize(String fileName){
try {
ObjectInputStream in=new ObjectInputStream(new FileInputStream(fileName));
String str=(String) in.readObject();//刚才的字符串对象
Date date=(Date) in.readObject();//日期对象
UserInfo userInfo=(UserInfo) in.readObject();//会员对象
System.out.println(str);
System.out.println(date);
System.out.println(userInfo);
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
}
主方法,运行实验一下:
class Main{
public static main(String[] args){
UserInfo.serialize("test");
sleep(2*60*1000)
UserInfo.deserialize("test");//这里userAge取读不到是因为使用了transient修饰,所以得到的是默认值
/**
* UserInfo的无参构造中给userAge属性赋值蛋反序列化得到的结果还是一样。
* 得出结论:
* 当从序列化的文件中读出某个类的实例时,实际上并不会执行这个类的构造函数,
* 而是载入了一个该类对象的持久化状态,并将这个状态赋值给该类的另一个对象。
*/
}
}
}
原始输出:
序列化的日期是:
Sun Dec 15 11:13:05 CST 2019
UserInfo [userName=郭大侠, usePass=961012,userAge=NOT SET]
基于字节流、对象流的深拷贝
public class Main {
public static void main(String[] args) throws IOException, ClassNotFoundException {
UserInfo userInfo1 = new UserInfo("123", "456", 111);
System.out.println(userInfo1);
UserInfo userInfo2 = deepClone(userInfo1);
System.out.println(userInfo2);
}
@SuppressWarnings("unchecked")
public static <T> T deepClone(T object) throws IOException, ClassNotFoundException {
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
ObjectOutputStream objectOutputStream = new ObjectOutputStream(byteArrayOutputStream);
objectOutputStream.writeObject(object);
ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(byteArrayOutputStream.toByteArray());
ObjectInputStream objectInputStream = new ObjectInputStream(byteArrayInputStream);
return (T) objectInputStream.readObject();
/*
原始输出:
UserInfo [userName=123, usePass=456,userAge=111]
UserInfo [userName=123, usePass=456,userAge=NOT SET]
*/
}
}
从原始输出可以看出,transient 关键字可以可以抑制Object输入输出流对实例被transient 修饰属性的读写。
总结
以上两种应用场景,本质上Object输入输出流和其他输入输出流的配合使用:
- 和字节数组输入输入输出流配合使用,实现了对象的深拷贝
- 和文件输入输出流的配合使用,实现了对象持久化到的本地文件再进行读取的功能