Java 知识点:序列化
首先明确一点:默认的序列化方法速度很慢,因为需要对整个对象和他的类都进行保存,因此我们建议自定义序列化格式。
ObjectInputStream和ObjectOutputStream
| 用途 | ObjectInputStream | ObjectOutputStream |
| 整数 | readInt() | writeInt(int) |
| 浮点数 | readDouble() | writeDouble(double) |
| 字符串 | readUTF() | writeUTF(String) |
| 字节数组 | read(byte[] buf, int off,int length) | write(byte[]) |
| 对象 | readObject() | writeObject(Object) |
哪些数据不会被序列化
- 被标记为 transient 的域。
- 静态变量。
查看某个类的SerialVersionUID
如果类A实现了Serializable接口,则可以使用Java 提供的serialver命令: serialver A 。
SerialVersionUID变量的作用
场景:你在公司开发一个类(记为LogManager,用来管理日志,拥有变量Date date,String description,我们使用序列化保存日志信息),并且已经上线使用(已经积累了一些数据),随着时间的推移,你发现你这个类设计的不够完善,因此你需要添加一个实例变量 int errorID,那么你如果按照一般反序列化方法(readObject),则会抛出:InvalidClassException。那么怎么能够成功将原始的数据转换成新版本的LogManager对象呢?
解决:
- 使用 Java 提供的serialver命令: serialver LogManager 计算出原始LogManager的 serialVersionUID(比如为123L)。
- 在新版本的LogManager中添加: static final long serialVersionUID = 123L;
目的:保持不同版本类的序列化的兼容性。
序列化类A(类A继承自类B),但是类B不可序列化,怎么办?
默认情况:
- 先将类A的实例变量全部还原。
- 因为类A继承类B,因此类A的对象也会有类B的实例变量,对于类B的实例变量,调用类B的默认无参构造函数初始化类B的实例变量。(一定要定义超类无参构造函数,不然会抛 no valid constructor)
解决:自定义readObject和writeObject。
对序列化的数据加密
问题:我们知道,序列化主要用于数据传输,但是序列化的数据是可以反序列化的,因此黑客可以直接把你的数据截下来(比如你的序列化文件为data.txt,用WinHeX打开后,基本就能看到所涉及的类和你传输的数据)。那么怎么样能够加密序列化的数据呢?
解决:通过自定义的序列化方法(在要序列化的对象中实现readObject和writeObject方法)。
- private void writeObject(ObjectOutputStream os)throws IOException // 你如果重写时必须是private的。
- private void readObject(ObjectInputStream is)throws IOException,ClassNotFoundException //你如果重写时必须是private的
import java.io.*;
public class Serialize05
{
public static void main(String[] args) throws Exception{
ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("object.txt"));
out.writeObject(new Person("admin","abc123"));
out.close();
ObjectInputStream in = new ObjectInputStream(new FileInputStream("object.txt"));
Person person = (Person)in.readObject();
System.out.println(person);
}
}
class Person implements Serializable
{
String name;
String password;
public Person(String name,String password)
{
this.name = name;
this.password = password;
}
private void writeObject(ObjectOutputStream os)throws IOException
{
os.writeUTF(name);
byte[] pass = password.getBytes("UTF-8");
for(int i=0;i<pass.length;i++)
{
pass[i] = (byte)(pass[i] ^ 32); //对密码加密
}
os.write(pass);
}
private void readObject(ObjectInputStream is)throws IOException,ClassNotFoundException
{
name = is.readUTF();
byte[] pass = new byte[1024];
int size = is.read(pass,0,1024);
for(int i=0;i<pass.length;i++)
{
pass[i] = (byte)(pass[i] ^ 32); //对密码解密
}
String password = new String(pass,0,size,"UTF-8");
this.password = password;
}
public String toString()
{
return "name="+name+",password="+password;
}
}
利用序列化实现深层复制
/*
用序列化实现深层复制
*/
import java.io.*;
public class Serialize09
{
public static void main(String[] args) throws Exception{
ByteArrayOutputStream bout = new ByteArrayOutputStream();
ObjectOutputStream out = new ObjectOutputStream(bout);
Person f1 = new Person("f1",null);
Person p1 = new Person("u1",f1);
out.writeObject(p1);
byte[] b = bout.toByteArray();
ObjectInputStream in = new ObjectInputStream(new ByteArrayInputStream(b));
Person p2 = (Person)in.readObject();
p2.friend.name = "f2";
System.out.println(p1.friend.name); //输出:f1.虽然p2的朋友名字改变了,但是p1的朋友没改。 }
}
class Person implements Serializable
{
String name;
Person friend;
public Person(String name,Person friend)
{
this.name = name;
this.friend = friend;
}
}
序列化包含别人开发过的不可序列化的类
问题:如果你想要开发一个 House 类,此时别人已经开发好的 Furniture 类(Furniture 类中有实例变量int size,没有类Furniture的源代码,只有class文件,且类Furniture不是可序列化的)。因为House HAS-A Furniture,因此需要组合,因此在House类中需要声明一个Furniture的实例变量,但是Furniture并不能实例化,如果按照一般的方法,则会抛“NoSerializableException”。
解决:
- 将Furniture实例变量设为transient。
- 在House类中实现readObject和writeObject方法。
实现方法:
- private void writeObject(ObjectOutputStream os)throws IOException
- private void readObject(ObjectInputStream is)throws IOException,ClassNotFoundException
import java.io.*;
public class Serialize03
{
public static void main(String[] args) throws Exception {
ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("object.txt"));
out.writeObject(new A(new B(10),100));
out.close();
ObjectInputStream in = new ObjectInputStream(new FileInputStream("object.txt"));
A a = (A)in.readObject();
System.out.println(a); //输出:b=10,a=100
}
}
class B //别人已经开发好的类,且假设看不到B的源代码
{
int bb;
public B(int bb)
{
this.bb = bb;
}
}
class A implements Serializable
{
transient B b;
int a;
public A(B b,int a)
{
this.b = b;
this.a = a;
}
public String toString()
{
return "b="+b.bb+",a="+a;
}
private void writeObject(ObjectOutputStream os)throws IOException //手工序列化B b
{
os.defaultWriteObject();
os.writeInt(b.bb);
}
private void readObject(ObjectInputStream is)throws IOException,ClassNotFoundException
{
is.defaultReadObject();
b = new B(is.readInt());
}
}
writeReplace和readResolve方法
如果有一个类A、类AProxy,如果想要实现以下任务:当 writeObject(A a) 时,实际写入的是AProxy对象(代理类),则可以使用readResolve和writeReplace。
- 在类A中实现writeReplace方法(当ObjectOutputStream.writeObject(A)时调用该方法)
- Object writeReplace() throws ObjectStreamException。
- 在类AProxy中实现 readResolve 方法(当ObjectInputStream.readObject(A)时调用该方法)
- Object readResolve() throws ObjectStreamException。
import java.io.*;
public class Serialize07
{
public static void main(String[] args) throws Exception{
ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("object.txt"));
out.writeObject(new Person("admin",20));
out.close();
ObjectInputStream in = new ObjectInputStream(new FileInputStream("object.txt"));
Person person = (Person)in.readObject();
System.out.println(person);
}
} class PersonProxy implements Serializable
{
String data;
public PersonProxy(Person person)
{
data = person.name+","+person.age;
}
private Object readResolve() throws ObjectStreamException
{
System.out.println("调用了readResolve方法");
String name = data.split(",")[0];
int age = Integer.parseInt(data.split(",")[1]);
Person person = new Person(name,age);
return person;
}
}
class Person implements Serializable
{
String name;
int age;
public Person(String name,int age)
{
this.name = name;
this.age = age;
}
private Object writeReplace() throws ObjectStreamException
{
System.out.println("调用了writeReplace方法");
return new PersonProxy(this);
}
public String toString()
{
return "name=" + name + ",age=" + age;
}
}
Externalizable接口
用途:自定义流格式,比如类A没有实现序列化,而类B继承类A,而类B需要负责包括超类数据的保存和恢复。
实现方法:
- void writeExternal(ObjectOutput out) throws IOException
- void readExternal(ObjectInput in) throws IOException,ClassNotFoundException
从API中可以看出,Externalizable接口实现了Serializable接口。
如果一个类A实现了Externalizable接口,则
- ObjectInputStream.readObject()时会调用readExternal(),而不是readObject()
- ObjectOutputStream.writeObject()时会调用writeExternal(),而不是writeObject()
当ObjectInputStream.readObject()时,过程如下:
- 假设读取类A的对象,则首先调用类A的无参构造函数(只有在实现Externalizable接口时才会调用无参构造函数),因此我们必须要实现这个构造函数。
- 因为类A实现了Externalizable接口,则调用readExternal()。
import java.io.*;
public class Serialize06
{
public static void main(String[] args) throws Exception{
ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("object.txt"));
out.writeObject(new Employee("admin",20,1000));
out.close();
ObjectInputStream in = new ObjectInputStream(new FileInputStream("object.txt"));
Employee person = (Employee)in.readObject();
System.out.println(person);
}
} class Person
{
String name;
int age;
public Person()
{
}
}
class Employee extends Person implements Externalizable
{
transient double salary;
public Person()
{
}
public Employee(String name,int age,double salary)
{
this.name = name;
this.age = age;
this.salary = salary;
}
public void readExternal(ObjectInput in) throws IOException,ClassNotFoundException
{
name = in.readUTF();
age = in.readInt();
salary = in.readDouble();
}
public void writeExternal(ObjectOutput out)throws IOException
{
out.writeUTF(name);
out.writeInt(age);
out.writeDouble(salary);
}
public String toString()
{
return "name=" + name + ",age=" + age + ",salary=" + salary;
}
}
Reference
[1]http://www.ibm.com/developerworks/cn/java/j-5things1/
[2]Java核心技术(第7版) P617~637
Java 知识点:序列化的更多相关文章
- 学Android开发,入门语言java知识点
学Android开发,入门语言java知识点 Android是一种以Linux为基础的开源码操作系统,主要使用于便携设备,而linux是用c语言和少量汇编语言写成的,如果你想研究Android,就去学 ...
- java梳理-序列化与反序列化
一背景: 之前笔记关于rpc框架介绍中,提到为了调用远程服务,需要再确定消息结构后考虑序列化与反序列化,序列化主要是把对象转换成二进制码便于网络传输,反序列化就是相反的,主要目的是生成对象便于后续处理 ...
- Java对象序列化剖析
对象序列化的目的 1)希望将Java对象持久化在文件中 2)将Java对象用于网络传输 实现方式 如果希望一个类的对象可以被序列化/反序列化,那该类必须实现java.io.Serializable接口 ...
- JAVA的序列化和持久化的区别与联系
持久化(Persistence) 即把数据(如内存中的对象)保存到可永久保存的存储设备中(如磁盘).持久化的主要应用是将内存中的对象存储在关系型的数据库中,当然也可以存储在磁盘文件中.XML数据文 ...
- 理解Java对象序列化
http://www.blogjava.net/jiangshachina/archive/2012/02/13/369898.html 1. 什么是Java对象序列化 Java平台允许我们在内存中创 ...
- java 对象序列化与反序列化
Java序列化与反序列化是什么? 为什么需要序列化与反序列化? 如何实现Java序列化与反序列化? 本文围绕这些问题进行了探讨. 1.Java序列化与反序列化 Java序列化是指把Java对象转换为 ...
- Java的序列化ID的作用
Java的序列化ID的作用 简单来说,Java的序列化机制是通过在运行时判断类的serialVersionUID来验证版本一致性的.在进行反序列化时,JVM会把传来的字节流中的serialVersio ...
- Java基础-序列化
Java序列化是将一个对象编码成一个字节流,反序列化将字节流编码转换成一个对象. 序列化是Java中实现持久化存储的一种方法: 为数据传输提供了线路级对象表示法. Java的序列化机制是通过在运行时判 ...
- 给Java新手的一些建议----Java知识点归纳(Java基础部分)
写这篇文章的目的是想总结一下自己这么多年来使用java的一些心得体会,主要是和一些java基础知识点相关的,所以也希望能分享给刚刚入门的Java程序员和打算入Java开发这个行当的准新手们,希望可以给 ...
- java 对象序列化
java 对象序列化 package org.rui.io.serializable; import java.io.ByteArrayInputStream; import java.io.Byte ...
随机推荐
- sampler state
昨天遇到一个非常诡异的错误 samplerstate 无法加大括弧定义 编译器非要一个: 而不要{ 去掉吧'''之后的编译似乎又会报某些ss没method 现在想想 也许是 samplerstate要 ...
- 不安装Oracle客户端远程连接Orcale数据库
本方法是通过使用ORACLE官方提供的精简版客户端,即绿色免安装的客户端. 下载地址(此处提供的是官方各版本下载地址): Windows 32位系统中使用的客户端下载地址其他系统环境中使用的客户端下载 ...
- hdu 3807
很好的思路 枚举有多少人有ipad 判是否满足题目给出的条件 #include <iostream> #include <cstring> #include <c ...
- C/C++框架和库
http://blog.csdn.net/xiaoxiaoyeyaya/article/details/42541419 值得学习的C语言开源项目 - 1. Webbench Webbench是一个在 ...
- SQO2008配置管理工具服务显示远程过程调用失败
前两天,装了VS2012后,打开SQL2008配置管理工具,发现SQL服务名称里什么也没有,只有一个提示:(如图) 卸载了一个叫"Microsoft SQL Server 2012Local ...
- ASP .net(照片列表详细功能演示)
大家好,今天我们需要讲解的内容就是把上篇文章当中提到的照片列表的很多功能细化去做. 那么之间我们两篇文章的目的就是要让大家深刻体会get,post的使用场景极其作用.像一般处理程序的使用,隐藏域的使用 ...
- java split IP地址要用双斜杠
示例代码: public void test() { String address = "11.12.13.14:800"; System.out.println(address. ...
- 李洪强iOS开之【零基础学习iOS开发】【02-C语言】04-常量、变量
在我们使用计算机的过程中,会接触到各种各样的数据,有文档数据.图片数据.视频数据,还有聊QQ时产生的文字数据.用迅雷下载的文件数据等.这讲我们就来介绍C语言中数据的处理. 一.数据的存储 1.数据类型 ...
- lintcode :nth to Last Node In List 链表倒数第n个节点
题目: 链表倒数第n个节点 找到单链表倒数第n个节点,保证链表中节点的最少数量为n. 样例 给出链表 3->2->1->5->null和n = 2,返回倒数第二个节点的值1. ...
- 【Linux高频命令专题(5)】rmdir
简述 rmdir是常用的命令,该命令的功能是删除空目录,一个目录被删除之前必须是空的.(注意,rm - r dir命令可代替rmdir,但是有很大危险性.)删除某目录时也必须具有对父目录的写权限. 命 ...