序列化
读写一个对象的前提是这个类型的对象是可序列化的
对象的序列化简单的来说就是将对象可以直接转换为二进制数据流
对象的反序列化将二进制数据流转换为对象
针对对象的序列化和反序列化是通过JVM实现的,编程中只做声明,序列化的目标就是将对象保存到磁
盘中或者允许在网络中直接传动
编程应用
1、依靠Serializable接口进行声明,如果需要特殊操作可以实现Externalizable接口。Serializable接口
属于旗标接口【标识接口】,仅仅只起到说明的作用,没有需要实现的方法
重新定义Account类
2、可以通过ObjectInputStream和ObjectOutputStream提供的方法直接读写对象
通过序列化将一个对象持久化存储到文件中
通过反序列化将一个对象从文件中读取到内存
1 public interface Serializable { }
public class Account implements Serializable{
private Long id;//账户编号
private String username;//账户名称
private String password;//账户口令
private Double balance;//余额
Account account = new Account();
account.setId(99L);
account.setUsername("赵小胖");
account.setPassword("123456");
account.setBalance(1234.56);
// 存储对象到文件中
ObjectOutputStream oos = new ObjectOutputStream(new BufferedOutputStream(new
FileOutputStream("out/account.data")));
oos.writeObject(account);
oos.close();
System.out.println("序列化存储对象完成");
1、接口问题
需要通过对象流读写的对象必须实现序列化接口,否则NotSerializableException
public class Account implements Serializable
潜在要求:要求类必须由无参构造器,因为反序列化时需要调用无参构造器构建对象。否则 Exception
in thread “main” java.io.InvalidClassException: com.yan.test01.Account; no valid
constructor
最佳软件实践:如果需要定义构造器,一般建议提供无参构造器
如果需要针对某个类型的对象进行序列化,要求该类型的所有属性也需要支持序列化,否则出现类似
Exception in thread “main” java.io.NotSerializableException: com.yan.test03.Role 报
错
ublic class Account implements Serializable {
private static final long serialVersionUID = 4093553615911145103L;
private Long id;// 账户编号
private String username;// 账户名称
private transient String password;// 账户口令
private Role role;
private Double balance;// 余额
2、特殊接口
Serializable接口属于标识接口,没有需要实现的方法,所有的序列化和反序列化操作都是由虚拟机负责
实现。
Externalizable接口定义,可以支持用户自定义实现序列化的细节操作,除非特殊需求一般不使用
Externalizable接口,因为没有必要自定义
特殊的应用场景
某个类型的属性是不可序列化。例如对象中包含InputStream/OutputStream之类的资源类型的属性,
不仅不能进行序列化,而且在序列化之前还应该释放资源,在反序列化时应该重新创建资源连接
特殊的应用场景
某个类型的属性是不可序列化。例如对象中包含InputStream/OutputStream之类的资源类型的属性,
不仅不能进行序列化,而且在序列化之前还应该释放资源,在反序列化时应该重新创建资源连接。。
ublic class Account implements Externalizable{
private Long id;
private String username;
private transient String password;//可以被序列,但是按照业务需求不进行序列化
private InputStream is; //不能序列化
public void writeExternal(ObjectOutput out) throws IOException {
is.close(); //将特定属性资源链接关闭
//执行对象的序列化操作,就是将需要写出的数据写入到out对象中
out.writeLong(this.id);
out.writeUTF(this.username);
}
public void readExternal(ObjectInput in) throws IOException,
ClassNotFoundException {
this.is=new FileInputStream("photo.jpg");
//执行对象的反序列化操作,就是从in对象中读取数据,不需要自行new对象。注意读写顺序
必须一致,否则出错
this.id=in.readLong();
this.username=in.readUTF();
}
}
3、类型转换
通过反序列化获取的对象是Object类型,如果调用对象中特殊的方法则必须先进行窄化操作
4、序列号问题
Eclipse中针对实现了序列化接口的类定义会有这样一个警告信息,要求提供一个序列号
serialiVersionUID。这不是错误信息
在Eclipse中可以使用鼠标点击黄色叹号,则会弹出菜单然后选择Add generated serial version ID会自
动生成永不重复的序列版本编号
序列版本号可以不用添加,这个序列版本号是一个中在序列化和反序列化操作中快速识别类型的简单方
法。
上面在执行反序列时修改类定义添加了toString方法,读取对象时报错是因为系统发现了类的修改。系
统是通过类定义的hash值识别类定义的,修改了类定义,则类型对应的hash值就发生了变化,所以报
错。如果用户自定义了序列版本号则提供通过这个序列版本号识别类型,可以提高效率,不会再使用类
型的 hash码进行类型识别。所以修改类添加方法后则不报错
5、特殊关键字
一般针对敏感数据不应该进行序列化操作,例如当前账户的口令属性。针对不需要进行序列化操作的属
性可以添加一个关键字transient,表示该属性不参与序列化和反序列化操作
当使用readObject反序列化读取数据时会发现
6、流结束
读文件可以通过EOFException异常来判断读取文件结束
读取一个文件中所存储的所有对象
追加文件处理的问题
/ 存储对象到文件中
ObjectOutputStream oos = new ObjectOutputStream(new BufferedOutputStream(new
FileOutputStream("out/account.data",true)));//true表示对文件进行追加操作
oos.writeObject(account);
oos.close();
写入数据不会有任何问题,但是读取数据则会出现异常Exception in thread “main”
java.io.StreamCorruptedException: invalid type code: AC
解决方案:如果需要添加对象数据时,不要使用FileOutputStream中的true进行追加,而是先读取所有
对象数据,然后再次写出覆盖
Object[] arr=new Object[100];
int counter=0;
File ff=new File("out/account.data");
if(ff.exists()){
ObjectInputStream ois=new ObjectInputStream(new FileInputStream(ff));
while(true){
try{
Object tmp=ois.readObject();
acc[counter++]=tmp;
} catch(EOFException e){
break;
}
}
ois.close();
}
//追加新数据
Account newAccount=new Account();
newAccount.setId(50L);
newAccount.setUsername("中方"); ......
arr[counter++]=newAccount;
//统一将数组中的所有对象写出到文件中
序列化总结
java序列化就是将一个对象转换为一个二进制表示的字节数组,通过保存或者转移这些数组达到传递对
象或者持久化存放对象的目的。序列化要求类必须实现对应的序列接口,两个序列化接口
Serializable【标识接口】和Externalizable【自定义方法实现属性的序列化操作】。反序列化就是将将
二进制数组转换为对象的过程,在执行反序列化时必须有原始的类定义才能将对象进行还原。
当父类实现了Serializable接口,则所有的子类都可以序列化;子类实现了Serializable接口父类没
有实现,则父类中的属性不能被序列化,会有数据丢失
如果实现了Serializable接口,类中包含引用类型的属性,则该属性必须可序列化,否则报错。可以
通过Externalizable接口进行自定义的序列化
反序列化时如果serialVersionUID修改的化,则反序列化失败
在Java环境下使用序列化机制JVM支持的都是很好的,但是在多语言环境下可以使用别的序列化机
制,例如xml、json等
serialVersionUID用于确保类序列化和反序列化的兼容性问题。编译器推荐2中方式,一种是生成默认的
versioID,还有一种是根据类名、接口名、成员方法和属性生成一个64为的哈希字段
对象克隆
Object类中定义一个clone方法
native方法用于声明一个非java语言实现的代码,供java程序调用。因为java语言程序是运行在JVM上,
如果要访问比较底层的与操作系统相关的方法就没有办法了,只能通过比较靠近系统的语言来实现,比
如C/C++。
Java中有两种不同的克隆方法:浅克隆shallowClone和深克隆deepclone。浅克隆不支持引用类型成员
变量的复制,仅仅只是克隆的地址;深克隆支持引用类型成员变量的复制
浅克隆
1、被复制的类需要实现Cloneable接口,该接口也是旗标接口,不包含任何方法
2、在当前类种覆盖定义clone方法,将访问限定词设置为public。具体实现调用super.clone实现
ObjectOutputStream ois=new ObjectOutputStream(new
FileOutputStream("out/account.data")); //没有true则表示覆盖
for(int i=0;i<arr.length;i++){
ois.writeObject(arr[i]);
}
ois.close();