对象流
- 使用DataInputStream或者DataOutputStream可以读写对象数据,但是操作比较繁琐
// 从文件中按照 id 值查找对应的对象int id = dis . readInt (); // 用户 id-- 用户标识int len = dis . readInt (); // 用户名称的字符数StringBuilder username = new StringBuilder (); // 用户名称for ( int i = 0 ; i < len ; i ++ ) // 一次读取一个字符,然后拼接成完整的字符串username . append ( dis . readChar ());len = dis . readInt ();StringBuilder password = new StringBuilder (); // 用户口令for ( int i = 0 ; i < len ; i ++ )password . append ( dis . readChar ());double balance = dis . readDouble (); // 用户余额if ( dis == id ){res = new Account (); //Account 是一个自定义类型,用于封装账户信息res . setUsername ( username . toString ());res . setPassword ( password . toString ());res . setBalance ( balance );break ;}
- SUN提供了ObjectInputStream/ObjectOutputStream可以直接将Object写入或读出
- 这里实际上还有针对8种简单类型及其包装类的操作方法,以及针对String类型的操作方法
readObject():ObjectwriteObject(Object):void
package com.yan2;
import java.io.BufferedInputStream;
import java.io.DataInputStream;
import java.io.FileInputStream;
public class Test1 {
public static void main(String[] args) throws Exception {
// User user = new User();
// user.setId(1L);
// user.setUsername("内蒙");
// user.setPassword("666666");
// DataOutputStream oos = new DataOutputStream(new BufferedOutputStream(new FileOutputStream("data/users.data")));
// oos.writeLong(user.getId());
// oos.writeUTF(user.getUsername());
// oos.writeUTF(user.getPassword());
//
// oos.close();
DataInputStream ois = new DataInputStream(new BufferedInputStream(new FileInputStream("data/users.data")));
Long id = ois.readLong();
String username = ois.readUTF();
String password = ois.readUTF();
User user = new User();
user.setId(id);
user.setUsername(username);
user.setPassword(password);
ois.close();
System.out.println(user);
}
}
读写一个对象的前提是这个类型的对象是可以被序列化的
- NotSerializableException
对象序列化【简单来说就是将对象可以直接转换为二进制数据流】 / 对象的反序列化【可以将二进制数据流转换为对象】,这一般依靠JVM 实现,编程中只做声明对象序列化的目标是将对象保存到磁盘中,或允许在网络中直接传输对象。对象序列化机制允许把内存中的Java对象转换成平台无关的二进制流,从而允许把这种二进制流持久地保存在磁盘上,通过网络将这种二进制流传输到另一个网络节点,其他程序一旦获取到这种二进制流,都可以将这种二进制流恢复成原来的 Java 对象
- 如何声明对象所属于的类可以进行序列化和反序列化Serializable/Externalizable接口 其中的接口没有任何定义,仅仅只起到了说明的作用,这种接口叫做标志接口或者旗标接口
- 可以通过ObjectInputStream【readObject():Object】和ObjectOutputStream【writeObject(Object):void】提供的方法直接操作对象
- 输出对象
User user = new User ();user . setId ( 100L );user . setUsername ( "zhangsan" );user . setPassword ( "123456" );ObjectOutputStream oos = new ObjectOutputStream ( newFileOutputStream ( "users.data" ));oos . writeObject ( user );oos . close (); 读取对象ObjectInputStream ois = new ObjectInputStream ( newFileInputStream ( "users.data" ));Object temp = ois . readObject ();if ( temp != null && temp instanceof User ) {User user = ( User ) temp ;System . out . println ( user );}ois . close ();
编码细节
- 需要通过对象流读写的对象必须实现了序列化接口,否则java.io.NotSerializableException class User implements Serializable
- Serializable接口是标志接口,没有需要实现的方法,所有的序列化和反序列化操作由VM负责实现。 Externalizable接口定义为public interface Externalizable extends java.io.Serializable,这个接口中包含两个方法需要实现writeExternal自定义实现对象的序列化,readExternal自定义实现对象的反序列化。除非特殊需求一般不使用Externalizable接口,因为没有必要自定义
class User implements Externalizable {@Overridepublic void writeExternal ( ObjectOutput out ) throws IOException {// 写出对象的操作System . out . println ( " 现在需要写出对象 :" + this );out . writeLong ( this . id );out . writeUTF ( this . username );out . writeUTF ( this . password );}@Overridepublic void readExternal ( ObjectInput in ) throws IOException , ClassNotFoundException {// 读取对象的操作this . id = in . readLong ();this . username = in . readUTF ();this . password = in . readUTF ();}public User (){}}- 转换类型问题
Object temp = ois . readObject ();if ( temp != null && temp instanceof User ) {User user = ( User ) temp ;System . out . println ( user );}
- private static final long serialVersionUID = 6889840055394511246L 如果不添加序列号,则会有警告信息,但是不是错误信息 一般选择Add generated serial version ID会生成一个在项目中永不重复的的序列版本编号 序列版本号可以不用添加,这个序列版本号是一种序列化和反序列化中快速识别类型的简单方法,比不加序列号的识别效率高。引入的功能是如果版本号不对应,不会进行类型识别,而是直接报异常InvalidClassException
- 一般针对敏感数据不应该进行序列化操作,针对不需要进行序列操作的属性可以添加一个关键字transient,表示该属性不参与序列化和反序列化操作
class User implements Serializable {private transient String password ; //transient用于声明该属性不支持序列化操作class User implements Serializable {private String username ;private transient String password ;private Role role ; // 因为 Role 没有实现序列化接口,所以写出 user 对象时会有报错 NotSerializableException。处理报错的方法有: 1 、可以给 Role 类定义添加序列接口。 2 、在 role属性上添加transient 表示这个属性不序列化处理 读文件的判断:读取文件时可以通过 EOFException异常来判断文件读取结束
- 已经向文件中写入数据后,继续追加存储,则读取数据会出现StreamCorruptedExceptionObjectOutputStream oos=new ObjectOutputStream(new BufferedOutputStream(new FileOutputStream("users.data",true)));
序列化总结
Java 序列化就是将一个对象转化为一个二进制表示的字节数组,通过保存或则转移这些二进制数组达到 持久化的目的。要实现序列化,需要实现java.io.Serializable 接口。反序列化是和序列化相反的过程,就是把二进制数组转化为对象的过程。在反序列化的时候,必须有原始类的模板才能将对象还原。
- 当父类实现了Serializable接口的时候,所有的子类都能序列化
- 子类实现了Serializable接口,父类没有,父类中的属性不能被序列化(不报错,但是数据会丢失)
- 如果序列化的属性是对象,对象必须也能序列化,否则会报错
- 反序列化的时候,如果对象的属性有修改或则删减,修改的部分属性会丢失,但是不会报错
- 在反序列化的时候serialVersionUID被修改的话,会反序列化失败
- 在存Java环境下使用Java的序列化机制会支持的很好,但是在多语言环境下需要考虑别的序列化机制,比如xml、json或protobuf等
serialVersionUID 值是用于确保类序列化与反序列化的兼容性问题的,如果序列化和反序列化过程中这两个值不一样,那么将导致序列化失败可以看到编译器推荐两种方式,一种是生成默认的 versionID ,这个值为 1L ,还有一种方式是根据类名、接口名、成员方法及属性等来生成一个 64 位的哈希字段,只要类名、方法名、变量有修改或者有空格、注释、换行等操作,计算出来的哈希字段都会不同,当然这里需要注意,每次有以上的操作的时候尽量都要重新生成一次serialVerionUID ,编译器并不会自动修改
- Java 序列化只是针对对象的属性的传递,至于方法和序列化过程无关
- 当一个父类实现了序列化,那么子类会自动实现序列化,不需要显示实现序列化接口,反过来,子类实现序列化,而父类没有实现序列化则序列化会失败---即序列化具有传递性
- 当一个对象的实例变量引用了其他对象,序列化这个对象的时候会自动把引用的对象也进行序列化(实现深度克隆)
- 当某个字段被申明为 transient 后,默认的序列化机制会忽略这个字段
- 被申明为 transient 的字段,如果需要序列化,可以添加两个私有方法writeObject和readObject或者实现Externalizable接口