`
Technoboy
  • 浏览: 154005 次
  • 性别: Icon_minigender_1
  • 来自: 大连
社区版块
存档分类
最新评论

Java Object Serialization

    博客分类:
  • J2SE
阅读更多
1. Overview
  Java中的序列化就是将Java对象的状态转化为字节序列,以便存储和传输的机制,在未来的某个时间,可以通过字节序列重新构造对象。把Java对象转换为字节序列的过程称为对象的序列化。把字节序列恢复为Java对象的过程称为对象的反序列化。这一切都归功于java.io包下的ObjectInputStream和ObjectOutputStream这两个类。

2. Serializable
  要想实现序列化,类必须实现Serializable接口,这是一个标记接口,没有定义任何方法。如果一个类实现了Serializable接口,那么一旦这个类发布,“改变这个类的实现”的灵活性将大大降低。以下是一个序列化的小例子:
class Message implements Serializable{

	private static final long serialVersionUID = 1L;
	
	private String id;
	
	private String content;
	
	public Message(String id, String content){
		this.id = id;
		this.content = content;
	}

	public String getId() {
		return id;
	}

	public void setId(String id) {
		this.id = id;
	}

	public String getContent() {
		return content;
	}

	public void setContent(String content) {
		this.content = content;
	}
	
	public String toString(){
		return "id = " + id + " content = " + content;
	}
}

public class Test{
	
	public static void main(String[] args) {
		serialize();
		deserialize();
	}
	
	private static void serialize(){
		Message message = new Message("1", "serializable test");
		try {
			ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("Message"));
			oos.writeObject(message);
			oos.close();
		} catch (FileNotFoundException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		}
		System.out.println("over");
	}
	
	private static void deserialize(){
		try {
			ObjectInputStream ois = new ObjectInputStream(new FileInputStream("Message"));
			Message message = (Message)ois.readObject();
			System.out.println(message.toString());
		} catch (FileNotFoundException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		} catch (ClassNotFoundException e) {
			e.printStackTrace();
		}
	}
	
}

  需要注意,序列化机制只保存对象的类型信息,属性的类型以及属性值,与方法没有关系。对于静态的变量,序列化机制也是不保存的。因为,静态变量属于类变量,而不是对象变量。而且,并不是所有的Java对象都可以被序列化,例如:Thread,Socket。内部类很少甚至没有实现Serializable接口的。关于容器类的序列化,可以遵循Hashtable的实现方式,即存储键和值的形式,而非一个大的哈希表的数据结构类型。

3. Serial Version UID
  每一个可序列化的类都有一个与之关联的唯一的序列化版本UID。其有两种生成策略,一种是固定的1L(private static final long serialVersionUID = 1L),另一种是依据类名、它实现的接口的名字、以及所有公共和受保护的成员的名字,利用JDK提供的工具随机的生成一个不重复的long类型数据。因此,假如你在已经序列化的类中,添加了新方法或属性,其随机UID的值也许会改变,如果此时在反序列化时,将会抛出java.io.InvalidClassException。因此建议,如果没有特殊需求,就是用默认的 1L 就可以,这样可以确保代码一致时反序列化成功。

4. Serialization Storage Rules
  我们修改上面列子的代码,将message对象写入Message.obj文件中两遍,然后我们查看文件大小,以及从文件中反序列化出两个对象,比较是否相等,代码如下:
public class Test{
	
	public static void main(String[] args) {
		try {
			Message message = new Message("1", "serializable test");
			ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("Message.obj"));
			oos.writeObject(message);
			oos.flush();
			System.out.println(new File("Message.obj").length());
			oos.writeObject(message);
			System.out.println(new File("Message.obj").length());
			
			//
			ObjectInputStream ois = new ObjectInputStream(new FileInputStream("Message.obj"));
			Message message1 = (Message)ois.readObject();
			Message message2 = (Message)ois.readObject();
			System.out.println(message1 == message2);
		} catch (FileNotFoundException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		} catch (ClassNotFoundException e) {
			e.printStackTrace();
		}
	}
}

  输出结果:
115
120
true

  从结果,我们可以看出,对相同对象的第二次序列化后,文件的大小只增加了5字节,且反序列化出来的两个对象相等。原因在于,Java序列化机制为了节省磁盘空间,具有特定的存储规则,当写入文件的为同一对象时,并不会再将对象的内容进行存储,而只是再次存储一份引用,上面增加的5字节的存储空间就是新增引用和一些控制信息的空间。反序列化时,恢复引用关系,使得message1和message2指向唯一的对象,二者相等,输出 true。该存储规则极大的节省了存储空间。
  我们修改上面main方法中的代码,使第一次序列化id值为2,第二次序列化id值为3,然后反序列化出两个对象,并打印id值:
public static void main(String[] args) {
		try {
			Message message = new Message("1", "serializable test");
			ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("Message.obj"));
			message.setId("2");
			oos.writeObject(message);
			oos.flush();
			System.out.println(new File("Message.obj").length());
                        message.setId("3");
			oos.writeObject(message);
			System.out.println(new File("Message.obj").length());
			
			//
			ObjectInputStream ois = new ObjectInputStream(new FileInputStream("Message.obj"));
			Message message1 = (Message)ois.readObject();
			Message message2 = (Message)ois.readObject();
			System.out.println(message1 == message2);
			System.out.println("message1.id = " + message1.getId());
			System.out.println("message2.id = " + message2.getId());
		} catch (FileNotFoundException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		} catch (ClassNotFoundException e) {
			e.printStackTrace();
		}
	}

  输出结果:
115
120
true
message1.id = 2
message2.id = 2

  结果两次输出,id值都为2。原因就是第一次写入对象后,第二次再试图写的时候,JVM根据引用关系知道已经有一个相同对象已经写入文件,因此只保存第二次写的引用。所以读取时都是第一次保存的对象。因此,我们必须要注意,在同一个对象进行多次序列化到相同文件中时所产生的这个问题。
  
5. Serialization and Inheritance
  假设有如下情形: 一个父类实现了Serializable接口,子类继承父类同时也实现了Serializable接口。那么在反序列化的时候,结果如何呢?请看下面这个小例子:
public class Base implements Serializable{

	private static final long serialVersionUID = 1L;
	
	private int x;
	
	public Base(){
		System.out.println("Base Class : no-arg constructor");
	}
	
	public Base(int x){
		this.x = x;
		System.out.println("Base Class : one-arg constructor");
	}
	
	public int getX(){
		return x;
	}
	
	public String toString(){
		return "x = " + this.x;
	}
}

public class Child extends Base implements Serializable{

	private static final long serialVersionUID = 1L;
	
	private int y;
	
	public Child(){
		System.out.println("Child Class : default no-arg constructor");
	}
	
	public Child(int x, int y){
		super(x);
		this.y = y;
		System.out.println("Child Class : two-args constructor");
	}
	
	public String toString(){
		return "x = " + getX() + " , y = " + this.y;
	}
	
	public static void main(String[] args) {
		try {
			Child child =  new Child(1, 2);
			ObjectOutputStream oos =  new ObjectOutputStream(new FileOutputStream("BaseAndChild"));
			oos.writeObject(child);
			oos.close();
			System.out.println("over");
			
			//
			ObjectInputStream ois = new ObjectInputStream(new FileInputStream("BaseAndChild"));
			Child child2 = (Child)ois.readObject();
			ois.close();
			System.out.println(child2);
		} catch (FileNotFoundException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		} catch (ClassNotFoundException e) {
			e.printStackTrace();
		}
	}
}

  输出结果为:
Base Class : one-arg constructor
Child Class : two-args constructor
over
x = 1 , y = 2

  从输出的结果,我们可以看出,当父子类同时实现Serializable接口,反序列化时,不调用构造函数,且子类中的x,y值都被正确的设置。当父类没有实现Serializable接口时,输出结果变为:
Base Class : two-args constructor
Child Class : one-arg constructor
over
Base Class : no-arg constructor
x = 0 , y = 2

  说明,在反序列化时,调用了父类的无参构造函数,且子类从父类继承的x也没有被正确的设置。我们可以这样理解,一个Java对象的构造必须先有父对象,才有子对象,反序列化也不例外。所以反序列化时,为了构造父对象,只能调用父类的无参构造函数作为默认的父对象。因此当我们取父对象的变量值时,它的值是调用父类无参构造函数后的值,所以我们x的取值为0。如果在反序列化时,父类没有提供可访问的,无参的构造函数,将抛出java.io.InvalidClassException异常。

6. Serialization and Proxy
  序列化允许将代理放在流中。看下面这个列子:
public class Product implements Serializable {

	private static final long serialVersionUID = 1L;
	
	private String description;
	
	public Product(String description){
		this.description = description;
	}

	public String getDescription() {
		return description;
	}

	public void setDescription(String description) {
		this.description = description;
	}
}

public class ProductProxyFactory implements Serializable, MethodInterceptor{

	private static final long serialVersionUID = 1L;

	public Object intercept(Object obj, Method m, Object[] args,
			MethodProxy mp) throws Throwable {
		
		System.out.println("before interception");
		try {
			return mp.invokeSuper(obj, args);
		} finally {
			System.out.println("after interception");
		}
	}
	
	public static Product getProxy(String description) throws Exception{
		Enhancer enhancer = new Enhancer();
		enhancer.setSuperclass(Product.class);
		enhancer.setCallback(new ProductProxyFactory());
		return (Product)enhancer.create(new Class[]{String.class}, new Object[]{description});
	}
}

public class Serialization {
	
	public static void main(String[] args) throws Exception {
		Product p = ProductProxyFactory.getProxy("first product");
		ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("Product"));
		oos.writeObject(p);
		oos.flush();
		oos.close();
		System.out.println("over");
	}
}

public class Deserialization {
	
	public static void main(String[] args) throws Exception {
		ObjectInputStream ois = new ObjectInputStream(new FileInputStream("Product"));
		Product p1 = (Product)ois.readObject();
		ois.close();
		System.out.println("deserialize: " + p1.getDescription());
	}
}

先运行Serialization类,然后运行Deserialization类(也就是说在不同的JVM间序列化/反序列化用cglib生成的代理对象),会抛出java.lang.ClassNotFoundException。原因在于,使用cglib生成的代理类是Product子类,而这些子类在不同的JVM间是不同的,因此抛出异常。解决方法是在Product类中加入以下两个方法:
public Object writeReplace() throws ObjectStreamException {
	return new Product(getDescription());
}
public Object readResolve() throws ObjectStreamException {
	return ProductProxyFactory.getProxy(getDescription());
}

  在对代理类进行序列化时,父类(Product类)中的writeReplace()方法会被调用。writeReplace()方法返回的是个Product类的对象,因此实际序列化的对象不是代理类的对象;在对代理类进行反序列化时,父类(Product类)中的readResolve方法会被调用。readResolve方法返回了个新的代理类的对象。
需要注意的是,使用Java 动态代理(Dynamic Proxy)创建代理类有特殊的类标识符,因此在ObjectInputStream对其进行反序列化时,会识别这个特殊的标识符,并调用ObjectInputStream.resolveProxyClass方法对其进行处理,因此会自动返回一个新的代理类的对象,也就是说如果使用动态代理创建代理类,那么不必添加writeReplace和readResolve方法。在另一方面,使用cglib创建的代理类只有普通的类标识符,ObjectInputStream对其进行反序列化时只是调用ObjectInputStream.resolveClass方法对其进行处理,因此需要以上的技巧。
  如果一个类同时改写了这两个方法,以及writeObject()和readObject()方法,那么在序列化时的调用顺序是:
1.writeReplace()
2.writeObject()
3.readObject()
4.readResolve()

7. Externalizable
  Externalizable接口继承自Serializable接口,实现Externalizable接口的类进行序列化时,只保存对象的标识符信息,因此序列化的速度更快,序列化后的字节流更小。Externalizable接口的定义如下:
public interface Externalizable extends java.io.Serializable {
void writeExternal(ObjectOutput out) throws IOException;
void readExternal(ObjectInput in) throws IOException, ClassNotFoundException;
}

  子类必须提供读取和写出方法的实现。在序列化的时候,通过writerExternal方法序列化对象,通过readExternal方法反序列化一个对象。跟实现了Serializable接口的类不同,在反序列化时,会调用类的无参构造函数,所以该实现类必须提供一个可访问的无参构造函数。当一个类同时实现了Externalizable接口和Serializable接口时,那么实际的序列化形式是由Externalizable接口中声明的方法决定。以下是一个列子:
public class Person implements Externalizable {
	
	private static final long serialVersionUID = 1L;

	private int age;
	
	public Person(){
	}
	
	public Person(int age){
		this.age = age;
	}
	
	public int getAge() {
		return age;
	}

	public void setAge(int age) {
		this.age = age;
	}
	
	public String toString(){
		return "age = " + this.age;
	}
	
	public void readExternal(ObjectInput in) throws IOException,
			ClassNotFoundException {
		setAge(in.readInt());

	}

	public void writeExternal(ObjectOutput out) throws IOException {
		out.writeInt(getAge());
	}

	public static void main(String[] args) {
		try {
			Person p = new Person(20);
			ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("Person.obj"));
			oos.writeObject(p);
			oos.close();
			System.out.println("over");
			
			//
			ObjectInputStream ois = new ObjectInputStream(new FileInputStream("Person.obj"));
			Person p1 = (Person)ois.readObject();
			System.out.println(p1);
			ois.close();
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
}

8. Transient关键字
  Transient关键字的作用就是控制变量的序列化,在变量声明前加上transient关键字,可以阻止变量被序列化到文件中,在被反序列化后,transient关键字修饰的变量的值被设置为初始值,如int型为0,对象型为null。以下是一个例子:
public class Person implements Externalizable {
	
	private static final long serialVersionUID = 1L;

	private int age;
	
	private transient String name;
	
	public Person(){
	}
	
	public Person(int age, String name){
		this.age = age;
		this.name = name;
	}
	
	public int getAge() {
		return age;
	}

	public void setAge(int age) {
		this.age = age;
	}
	
	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}

	public String toString(){
		return "age = " + this.age + " , name = " + this.name;
	}
	
	public void readExternal(ObjectInput in) throws IOException,
			ClassNotFoundException {
		setAge(in.readInt());
	}

	public void writeExternal(ObjectOutput out) throws IOException {
		out.writeInt(getAge());
	}

	public static void main(String[] args) {
		try {
			Person p = new Person(20, "remy");
			ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("Person.obj"));
			oos.writeObject(p);
			oos.close();
			System.out.println("over");
			
			//
			ObjectInputStream ois = new ObjectInputStream(new FileInputStream("Person.obj"));
			Person p1 = (Person)ois.readObject();
			System.out.println(p1);
			ois.close();
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
}

  输出结果:
over
age = 20 , name = null

9. ObjectInputValidation
  我们可以使用ObjectInputValidation接口验证序列化流中的数据是否与最初写到流中的数据一致。我们需要覆盖validateObject()方法,如果调用该方法时发现某处有错误,则抛出一个 InvalidObjectException。
2
1
分享到:
评论

相关推荐

    深入浅析Java Object Serialization与 Hadoop 序列化

    序列化是指将结构化对象转化为字节流以便在网络上传输或者写到磁盘永久存储的过程。下面通过本文给大家分享Java Object Serialization与 Hadoop 序列化,需要的朋友可以参考下

    Java 9 for Programmers (Deitel Developer Series) 完整高清azw3版

    streams, functional interfaces, object serialization, concurrency, generics, generic collections, database with JDBC™ and JPA, and compelling new Java 9 features, such as the Java Platform Module ...

    msgpack-java-master

    msgpack - MessagePack is an extremely efficient object serialization library. It's like JSON, but very fast and small.

    json序列化jar包

    Most JSON serializers mimic object serialization libraries and try to serialize the entire object graph from the object being turned into JSON. This causes problems when you want a connected object ...

    Java IO, NIO and NIO.2 原版pdf by Friesen

    RandomAccessFile classes along with streams (including object serialization and externalization) and writers/readers. Chapters 6 through 11 focus on NIO. You explore buffers, channels, selectors, ...

    JavaIO.ppt

    1,methods for accessing file, text data, object serialization and internationalization Sequential and Random access 2,Reading and writing of primitive values 3,Applications and applets are ...

    《Pearson.Java.How.To.Program.late.objects.version.10th.Edition》 高清完整英文PDF版

    Chapter 15 Files, Streams and Object Serialization Chapter 16 Generic Collections Chapter 17 Java SE 8 Lambdas and Streams Chapter 18 Recursion Chapter 19 Searching, Sorting and Big O Chapter 20 ...

    Java2核心技术卷I+卷2:基础知识(第8版) 代码

    Object Streams and Serialization 39 File Management 59 New I/O 65 Regular Expressions 75 Chapter 2: XML 87 Introducing XML 88 Parsing an XML Document 93 Validating XML Documents 105 ...

    java高手必备知识点

    你需要学习Java语言的基础知识以及它的核心类库(collections、serialization、streams、networking、multithreading、reflection、event、handling、NIO、localization以及其他)。  3. 你应该了解JVM、class...

    Java快速序列化库FST.zip

    FST fast-serialization 是重新实现的 Java 快速对象序列化的开发包。序列化速度更快(2-10倍)、体积更小,而且兼容 JDK 原生的序列化。要求 JDK 1.7 支持。 Maven:  <groupId>de.ruedigermoeller  ...

    Twitter 开源了一套全新的对象序列化方案.zip

    Twitter 开源了一套全新的对象序列化方案。.zip,Light-weight, fast framework for object serialization in Java, with Android support.

    java编程基础,应用与实例

    13.6 对象序列化(object serialization) 199 13.6.1 ObjectInput与ObjectOutput 199 13.6.2 ObjectOutputStream 200 13.6.3 ObjectInputStream 200 13.7 巩固练习 201 第14章 线程 203 14.1 Runtime类...

    Learn.JAVA.PROGRAMMING.from.scratch.B0140CLLFG

    Today, there are so many resources to learn programming; and this sheer amount of resources can be confusing and ...Chapter 13: Serialization Chapter 14: Operators Chapter 15: Lambda expression

    学习java的30个目标

    你需要学习JAVA语言的基础知识以及它的核心类库(collections,serialization,streams,networking, multithreading,reflection,event,handling,NIO,localization,以及其他)。  3.你应该了解JVM,class...

    JavaSE-6.0-英文手册(2008/11/30_FullUpdate)

    Object Serialization Networking Endorsed Standards Override Mechanism Extension Mechanism Internationalization JavaBeansTM Component API Java Management Extensions (JMX) Java Native ...

    Effective Java 3rd edition(Effective Java第三版英文原版)附第二版

    Item 85: Prefer alternatives to Java serialization Item 86: Implement Serializable with great caution Item 87: Consider using a custom serialized form Item 88: Write readObject methods defensively ...

    JDK_API_1_6

    ObjectStreamConstants 写入 Object Serialization Stream 的常量。 Serializable 类通过实现 java.io.Serializable 接口以启用其序列化功能。 类摘要 BufferedInputStream BufferedInputStream 为另一个输入...

    JDK 1.5的泛型實現(Generics in JDK 1.5)

    java.lang.Object,因此任何 Java objects都可以被放進 上述各種容器。換句話說 Java容器是一種異質容器,從「泛型」的字面意義來說, 其實這(原本的設計)才是「泛型」。 然而有時候,而且是大半時候,我們不...

    欲为Java技术大牛所需的25个学习要点

    1. 你需要精通面向对象分析与...2. 你需要学习Java语言的基础知识以及它的核心类库(collections、serialization、streams、networking、 multithreading、reflection、event、handling、NIO、localization以及其他)。

    Thinking in Java 4th Edition

    Java SE5 and SE6 .................. 2 Java SE6 ......................................... 2 The 4th edition........................ 2 Changes .......................................... 3 Note on the ...

Global site tag (gtag.js) - Google Analytics