java的serializable序列化,serializable序列化
分享于 点击 43854 次 点评:42
java的serializable序列化,serializable序列化
引言
java序列化就是java类实现Serializable接口,然后利用ObjectOutputStream将java对象写到二进制文件中,然后将该二进制文件通过网络传输到其它java虚拟机环境中,再利用ObjectInputStream将该二进制文件还原为java对象。本质上是将java虚拟机中生成的对象转移到其它java虚拟机中使用。前提条件
情境:两个客户端 A 和 B 试图通过网络传递对象数据,A 端将对象 C 序列化为二进制数据再传给 B,B 反序列化得到 C。那么要想序列化和反序列化成功需要具备以下条件: 1.A端和B端都要有C类,而且类路径和功能代码一致。 2.C类实现Serailizable接口 3.C类的序列化id一致。序列化id的问题
java序列化机制是通过在运行时判断类的序列化id(serialVersionUID)来验证版本一致性的。在进行反序列化时,jvm会把传来的字节流中的serialVersionUID与本地相应java类的serialVersionUID进行比较,如果相同就认为是一致的,可以进行反序列化,否则就会出现序列化版本不致的异常(InvalidClassException)。 1.不显示设置id jdk文档中有解释,建议我们显示声明,因为如果不声明,Java序列化机制会根据编译的class自动生成一个serialVersionUID,这种情况下,只有同一次编译生成的class才会生成相同的serialVersionUID,这样的话很容易导致序列化版本不一致的异常。 2.显示设置id 在eclipse中显示设置序列化id有两种方式: 1.add default serial version ID: 生成1L,对于这一种,需要了解哪些情况是兼容的,哪些是不兼容的,在可兼容的前提下,可以保留旧版本号,如果不兼容,或者想让它不兼容,就手工递增版本号。1》2》3 2.add generated serial version ID: 生成一个很大的数,这种方式是根据类的结构产生的hash值,增减属性、方法等,都可能会导致这个值产生变化。如果开发人员认为每次修改类后都需要生成新的版本号,不想向下兼容,操作就是删除原有的SerialVersionUid声明语句,再自动生成一下。静态化变量序列化
java在序列化时,并不保存静态变量信息,这其实比较容易理解,序列化保存的是对象的状态,静态变量属于类的状态。Transient关键字
Transient 关键字的作用是控制变量的序列化,在变量声明前加上该关键字,可以阻止该变量被序列化到文件中,在被反序列化后,transient 变量的值被设为初始值,如 int 型的是 0,对象型的是 null。父类的序列化
情境:一个子类实现了Serializable接口,它的父类没有实现,序列化该子类对象,然后反序列化输出父类定义的变量的数值,该变量数值跟Transient关键字的效果一样,都是初始值。 解决:要想将父类对象的属性也序列化,就需要让父类也实现Serializalbe接口。 如果父类不实现序列化,就需要有默认的无参的构造函数。在父类没有实现Serializable接口时,虚拟机不会序列化父类对象,而一个java对象的构造必须先有父类对象,反序列化时也不例外。所以反序列化时,为了构造父对象,只能调用父类的无参构造函数作为默认的父对象。如果你考虑到这种序列化的情况,在父类的无参构造函数中对变量进行初始化,否则的话,父类变量值都是默认声明的值,如 int 型的是 0,对象型的是 null。 结论:根据以上, Transient 关键字可以使得字段不被序列化,那么还有别的方法吗?根据父类对象序列化的规则,我们可以将不需要被序列化的字段抽取出来放到父类中,子类实现 Serializable 接口,父类不实现,根据父类序列化规则,父类的字段数据将不被序列化。定制序列化
原理:在序列化过程中,虚拟机会试图调用对象类里面的writeObject(private)和readObject(private)方法,进行用户自定义的序列化和反序列化,如果没有定义该方法,则默认调用是 ObjectOutputStream 的 defaultWriteObject 方法以及 ObjectInputStream 的 defaultReadObject 方法。用户自定义的 writeObject 和 readObject 方法可以允许用户控制序列化的过程,当然用户自定义了writeObject也可以调用defaultWriteObject来处理默认的序列化方法。 关于对象里面的writeObject和readObject必须是private类型的,这个吗,ObjectOutputStream使用了反射来寻找是否声明了这两个方法,因为ObjectOutputStream使用getPrivateMethod,所以这些方法不得不被声明为priate以至于供ObjectOutputStream来使用。 定制序列化的过程有两种试,一种是key,value型,一种是value型。 第一种: 序列化:key是对象中必须存在的字段的名称,value是指定的值。private void writeObject(ObjectOutputStream oos) {
try {
PutField putField = oos.putFields();
putField.put("name", "bao");
putField.put("name2", "elva");
oos.writeFields();
} catch (IOException e) {
e.printStackTrace();
}
}
反序列化:key是写的时候的值,不一定是类中必须存在的
private void readObject(ObjectInputStream ois) {
try{
GetField getField = ois.readFields();
String name = (String)getField.get("name", "");
System.out.println(name);
String name2 = (String)getField.get("name2", "");
System.out.println(name2);
} catch (IOException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
第二种: 序列化:依次写入不同字段的值,这里只有值,没有字段名(key)
private void writeObject(ObjectOutputStream oos) {
try {
oos.defaultWriteObject(); // 执行默认的序列化过程
oos.writeObject("aaaaaaa"); // 添加值aaaaaa
oos.writeObject("bbbbbbb");// 添加值bbbbbb
} catch (IOException e) {
e.printStackTrace();
}
}
反序列化:依昭序列化时的顺序和个数获取相应的字段值
private void readObject(ObjectInputStream ois) {
try{
ois.defaultReadObject();
Object obj1 = ois.readObject();
Object obj2 = ois.readObject();
} catch (IOException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
使用场景:如果序列化的对象数据中,有些数据是敏感的,比如密码字符串等,希望对密码字段在序列化时,进行加密,在反序列化时,对密码字段进行解密,这样一定程序上保证序列化对象的数据安全。
序列化存储规则
原理:当序列化同一个对象多次时,序列化存储的二进制文件中该对象只存一份,剩下的全部存引用,这样可以减少存储空间。如果序列化同一个对象多次,当反序列化时获得的对象是同一个,都指向同一个内在地址。 序列化同一个对象多次时,存储的二进制文件空间并没有增加2倍,而只增加一点点(存储对象地址的那部分空间):public static void main(String[] args) throws Exception {
FileOutputStream fos = new FileOutputStream(new File("D:/serializable.txt"));
ObjectOutputStream oos = new ObjectOutputStream(fos);
SerializableEntity entity = new SerializableEntity("elva", "29", "151********");
oos.writeObject(entity);
oos.flush();
System.out.println(new File("D:/serializable.txt").length()); // 126
oos.writeObject(entity);
oos.flush();
System.out.println(new File("D:/serializable.txt").length()); // 131
oos.close();
fos.close();
}
反序列化时:
public static void main(String[] args) throws Exception {
FileInputStream fis = new FileInputStream(new File("D:/serializable.txt"));
ObjectInputStream ois = new ObjectInputStream(fis);
SerializableEntity entity = (SerializableEntity)ois.readObject();
SerializableEntity entity2 = (SerializableEntity)ois.readObject();
System.out.println(entity == entity2); // true
ois.close();
fis.close();
}
如果序列化多次同一个对象的过程中修改了该对象的某个字段值,那么反序列化时获取的对象的值保持不变:因为序列化多次同一个对象只会序列化一次对象数据,剩下的是引用。
public static void main(String[] args) throws Exception {
FileOutputStream fos = new FileOutputStream(new File("D:/serializable.txt"));
ObjectOutputStream oos = new ObjectOutputStream(fos);
SerializableEntity entity = new SerializableEntity("elva", "29", "151********");
oos.writeObject(entity);
oos.flush();
System.out.println(new File("D:/serializable.txt").length()); // 126
entity.setName("bao");
oos.writeObject(entity);
oos.flush();
System.out.println(new File("D:/serializable.txt").length()); // 131
oos.close();
fos.close();
}
public static void main(String[] args) throws Exception {
FileInputStream fis = new FileInputStream(new File("D:/serializable.txt"));
ObjectInputStream ois = new ObjectInputStream(fis);
SerializableEntity entity = (SerializableEntity)ois.readObject();
System.out.println(entity.getName()); // elva
SerializableEntity entity2 = (SerializableEntity)ois.readObject();
System.out.println(entity.getName()); // elva
System.out.println(entity == entity2); // true
ois.close();
fis.close();
}
参考:http://blog.csdn.net/jiangwei0910410003/article/details/18989711/ http://blog.csdn.net/mashangyou/article/details/21827613
相关文章
- 暂无相关文章
用户点评