- 浏览: 189942 次
- 性别:
- 来自: 上海
文章分类
最新评论
Java序列化是指把Java对象转换为字节序列的过程;而Java反序列化是指把字节序列恢复为Java对象的过程。
为什么需要序列化与反序列化
我们知道,当两个进程进行远程通信时,可以相互发送各种类型的数据,包括文本、图片、音频、视频等, 而这些数据都会以二进制序列的形式在网络上传送。那么当两个Java进程进行通信时,能否实现进程间的对象传送呢?答案是可以的。如何做到呢?这就需要Java序列化与反序列化了。换句话说,一方面,发送方需要把这个Java对象转换为字节序列,然后在网络上传送;另一方面,接收方需要从字节序列中恢复出Java对象。
当我们明晰了为什么需要Java序列化和反序列化后,我们很自然地会想Java序列化的好处。其好处一是实现了数据的持久化,通过序列化可以把数据永久地保存到硬盘上(通常存放在文件里),二是,利用序列化实现远程通信,即在网络上传送对象的字节序列。
实现序列化的要求
只有实现了Serializable或Externalizable接口的类的对象才能被序列化,否则抛出异常。
只有实现了Serializable或者Externalizable接口的类的对象才能够被序列化。否则当调用writeObject方法的时候会出现IOException。需要注意的是Externalizable接口继承自Serializable接口。两者的区别如下:
仅仅实现Serializable接口的类可应采用默认的序列化方式。比如String类。
假设有一个Customer类的对象需要序列化,如果这个类仅仅实现了这个接口,那么序列化和反序列化的方式如下:ObjectOutputStream采用默认的序列化方式,对于这个类的非static,非transient的实例变量进行序列化。ObjectInputStream采用默认的反序列化方式,对于这个类的非static,非transient的实例变量进行反序列化。
如果这个类不仅实现了Serializable接口,而且定义了readObject(ObjectInputStream in)和 writeObject(ObjectOutputStream out)方法,那么将按照如下的方式进行序列化和反序列化:ObjectOutputStream会调用这个类的writeObject方法进行序列化,ObjectInputStream会调用相应的readObject方法进行反序列化。
实现Externalizable接口的类完全由自身来控制序列化的行为。而且必须实现writeExternal(ObjectOutput out)和readExternal(ObjectInput in)。那么将按照如下的方式进行序列化和反序列化:ObjectOutputStream会调用这个类的writeExternal方法进行序列化,ObjectInputStream会调用相应的readExternal方法进行反序列化。
把Java对象转换为字节序列的过程称为对象的序列化。
把字节序列恢复为Java对象的过程称为对象的反序列化。
java中引入序列化机制主要是为了支持两种重要技术:RMI和JavaBean技术。
只有实现了Serializable和Externalizable接口的类的对象才能被序列化。
1)Serializable接口可使类中的所有成员变量自动被序列化(transient和static修饰的变量除外),默认的
序列化方式会序列化整个对象图,这需要递归遍历对象图。如果对象图很复杂,递归遍历操作需要消耗很多
的空间和时间,它的内部数据结构为双向列表。在应用时,如果对某些成员变量都改为transient类型,将
节省空间和时间,提高序列化的性能。
2)Externalizable接口继承自Serializable接口,实现Externalizable接口的类完全由自身来控制序列化
的行为,即Externalizable对象默认情况下不保存任何它的字段,而仅实现Serializable接口的类可以
采用默认的序列化方式 。
那我们如何对一个Serializable对象的序列化和反序列化行为进行控制呢?
1)加transient修饰符,这样改变量就不会被序列化了
2)添加(不是“实现”和“重载” )writeObject和readObject方法: private void writeObject(ObjectOutputStream stream) throws IOException; private void readObject(ObjectOutputStream stream) throws IOException;
这样一旦对象被序列化或者被反序列化,就会自动分别调用这两个方法,而不会调用默认的序列化和反序列化方法。(这一点确实感觉有点奇怪,或者叫混乱!)
当ObjectOutputStream对一个Serializable对象进行序列化时,如果该对象具有writeObject()方法,那么就会执行这一方法,否则就按默认方式序列化。在该对象的writeObjectt()方法中,可以先调用ObjectOutputStream的defaultWriteObject()方法,使得对象输出流先执行默认的序列化操作。同理可得出反序列化的情况,不过这次是defaultReadObject()方法。
对Serializable对象反序列化时,并不会调用任何构造函数 ,因此Serializable类无需默认构造函数,但是当Serializable类的父类没有实现Serializable接口时,反序列化过程会调用父类的默认构造函数,因此该父类必需有默认构造函数,否则会抛异常。
对Externalizable对象反序列化时,会先调用类的不带参数的构造方法 ,这是有别于默认反序列方式的。如果把类的不带参数的构造方法删除,或者把该构造方法的访问权限设置为private、默认或protected级别,会抛出java.io.InvalidException: no valid constructor异常,因此Externalizable对象必须有默认构造函数,而且必需是public的。
对象序列化包括如下步骤:
1) 创建一个对象输出流,它可以包装一个其他类型的目标输出流,如文件输出流;
2) 通过对象输出流的writeObject()方法写对象。
对于父类的处理,如果父类没有实现串行化接口,则其必须有默认的构造函数(即没有参数的构造函数)。否则编译的时候就会报错。在反串行化的时候,默认构造函数会被调用。但是若把父类标记为可以串行化,则在反串行化的时候,其默认构造函数不会被调用。这是为什么呢?这是因为Java 对串行化的对象进行反串行化的时候,直接从流里获取其对象数据来生成一个对象实例,而不是通过其构造函数来完成。
serialVersionUID
简单来说,Java的序列化机制是通过在运行时判断类的serialVersionUID来验证版本一致性的。在进行反序列化时,JVM会把传来的字节流中的serialVersionUID与本地相应实体(类)的serialVersionUID进行比较,如果相同就认为是一致的,可以进行反序列化,否则就会出现序列化版本不一致的异常。(InvalidCastException)
下面来看一个最简单的例子:
package com.java;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
public class simpleSerializableTest {
public static void main(String[] args) throws Exception {
ObjectOutputStream out=new ObjectOutputStream(new FileOutputStream("d:\\objectFile.obj"));
String strObj="name";
Customer customer=new Customer("rollen");
//序列化,此处故意将同一对象序列化2次
out.writeObject(strObj);
out.writeObject(customer);
out.writeObject(customer);
out.close();
//反序列化
ObjectInputStream in=new ObjectInputStream(new FileInputStream("d:\\objectFile.obj"));
String strobj1=(String)in.readObject();
Customer cus1=(Customer)in.readObject();
Customer cus2=(Customer)in.readObject();<br> in.close();
System.out.println(strobj1+": "+cus1);
System.out.println(strObj==strobj1);
System.out.println(cus1==customer);
System.out.println(cus1==cus2);
}
}
class Customer implements Serializable {
private static final long serialVersionUID = 1L;
private String name;
public Customer() {
System.out.println("无参构造方法");
}
public Customer(String name) {
System.out.println("有参构造方法");
this.name = name;
}
public String toString() {
return "[ "+name+" ]";
}
}
输出结果为:
有参构造方法
name: [ rollen ]
false
false
true
可以看出,在进行反序列话的时候,并没有调用类的构造方法。而是直接根据他们的序列化数据在内存中创建新的对象。另外需要注意的是,如果由一个ObjectOutputStream对象多次序列化同一个对象,那么右一个objectInputStream对象反序列化后的也是同一个对象。(cus1==cus2结果为true可以看出)
看一段代码,证明static是不会被序列化的:
package com.java;
import java.io.IOException;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.net.ServerSocket;
import java.net.Socket;
public class SerializableServer {
public void send(Object obj) throws IOException {
ServerSocket serverSocket = new ServerSocket(8000);
while (true) {
Socket socket = serverSocket.accept();
ObjectOutputStream out = new ObjectOutputStream(
socket.getOutputStream());
out.writeObject(obj);
out.writeObject(obj);
out.close();
socket.close();
}
}
public static void main(String[] args) throws Exception {
Customer customer = new Customer("rollen", "male");
new SerializableServer().send(customer);
}
}
class Customer implements Serializable {
private static final long serialVersionUID = 1L;
private String name;
private static int count;
private transient String sex;
static {
System.out.println("调用静态代码块");
}
public Customer() {
System.out.println("无参构造方法");
}
public Customer(String name, String sex) {
System.out.println("有参构造方法");
this.name = name;
this.sex = sex;
count++;
}
public String toString() {
return "[ " + count + " " + name + " " + sex + " ]";
}
}
package com.java;
import java.io.ObjectInputStream;
import java.net.Socket;
public class SerializableClient {
public void recive() throws Exception {
Socket socket = new Socket("localhost", 8000);
ObjectInputStream in = new ObjectInputStream(socket.getInputStream());
Object obj1 = in.readObject();
Object obj2 = in.readObject();
System.out.println(obj1);
System.out.println(obj1==obj2);
}
public static void main(String[] args) {
try {
new SerializableClient().recive();
} catch (Exception e) {
e.printStackTrace();
}
}
}
运行结果中,count的值为0.
我们来看另外一种情况:
class A implements Serializable{
B b;
//...
}
class B implements Serializable{
//...
}
当我们在序列化A的对象的时候,也会自动序列化和他相关联的B的对象。也就是说在默认的情况下,对象输出流会对整个对象图进行序列化。因此会导致出现下面的问题,看代码(例子中是使用双向列表作为内部结构的,只是给出了demo,并没有完整的实现,只是为了说明情况):
package com.java;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
public class SeriListTest implements Serializable {
private static final long serialVersionUID = 1L;
private int size;
private Node head = null;
private Node end = null;
private static class Node implements Serializable {
private static final long serialVersionUID = 1L;
String data;
Node next;
Node previous;
}
// 列表末尾添加一个字符串
public void add(String data) {
Node node = new Node();
node.data = data;
node.next = null;
node.previous = end;
if (null != end) {
end.next = node;
}
size++;
end = node;
if (size == 1) {
head = end;
}
}
public int getSize() {
return size;
}
// other methods...
public static void main(String[] args) throws Exception {
SeriListTest list = new SeriListTest();
for (int i = 0; i < 10000; ++i) {
list.add("rollen" + i);
}
ByteArrayOutputStream buf = new ByteArrayOutputStream();
ObjectOutputStream out = new ObjectOutputStream(buf);
out.writeObject(list);
ObjectInputStream in = new ObjectInputStream(new ByteArrayInputStream(
buf.toByteArray()));
list = (SeriListTest) in.readObject();
System.out.println("size is :" + list.getSize());
}
}
这段代码会出现如下错误:
Exception in thread "main" java.lang.StackOverflowError
at java.lang.ref.ReferenceQueue.poll(ReferenceQueue.java:82)
at java.io.ObjectStreamClass.processQueue(ObjectStreamClass.java:2234)
....
整个就是因为序列化的时候,对整个对象图进行序列化引起的问题。在这种情况下啊,我们需要自定义序列化的过程:
package com.java;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
public class SeriListTest implements Serializable {
private static final long serialVersionUID = 1L;
transient private int size;
transient private Node head = null;
transient private Node end = null;
private static class Node implements Serializable {
private static final long serialVersionUID = 1L;
String data;
Node next;
Node previous;
}
// 列表末尾添加一个字符串
public void add(String data) {
Node node = new Node();
node.data = data;
node.next = null;
node.previous = end;
if (null != end) {
end.next = node;
}
size++;
end = node;
if (size == 1) {
head = end;
}
}
public int getSize() {
return size;
}
// other methods...
private void writeObject(ObjectOutputStream outStream) throws IOException {
outStream.defaultWriteObject();
outStream.writeInt(size);
for (Node node = head; node != null; node = node.next) {
outStream.writeObject(node.data);
}
}
private void readObject(ObjectInputStream inStream) throws IOException,
ClassNotFoundException {
inStream.defaultReadObject();
int count = inStream.readInt();
for (int i = 0; i < count; ++i) {
add((String) inStream.readObject());
}
}
public static void main(String[] args) throws Exception {
SeriListTest list = new SeriListTest();
for (int i = 0; i < 10000; ++i) {
list.add("rollen" + i);
}
ByteArrayOutputStream buf = new ByteArrayOutputStream();
ObjectOutputStream out = new ObjectOutputStream(buf);
out.writeObject(list);
ObjectInputStream in = new ObjectInputStream(new ByteArrayInputStream(
buf.toByteArray()));
list = (SeriListTest) in.readObject();
System.out.println("size is :" + list.getSize());
}
}
运行结果为:10000
现在我们总结一下,在什么情况下我们需要自定义序列化的方式:
1)为了确保序列化的安全性,对于一些敏感信息加密
2)确保对象的成员变量符合正确的约束条件
3)优化序列化的性能(之前的那个例子已经解释了这种情况)
下面我们来用例子解释一下这些:
先来看看:为了确保序列化的安全性,对于一些敏感信息加密
package com.java;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
public class SeriDemo1 implements Serializable {
private String name;
transient private String password; // 注意此处的transient
public SeriDemo1() {
}
public SeriDemo1(String name, String password) {
this.name = name;
this.password = password;
}
// 此处模拟对密码进行加密,进行了简化
private String change(String password) {
return password + "rollen";
}
private void writeObject(ObjectOutputStream outStream) throws IOException {
outStream.defaultWriteObject();
outStream.writeObject(change(password));
}
private void readObject(ObjectInputStream inStream) throws IOException,
ClassNotFoundException {
inStream.defaultReadObject();
String strPassowrd = (String) inStream.readObject();
//此处模拟对密码解密
password = strPassowrd.substring(0, strPassowrd.length() - 6);
}
@Override
public String toString() {
return "SeriDemo1 [name=" + name + ", password=" + password + "]";
}
public static void main(String[] args) throws Exception {
SeriDemo1 demo = new SeriDemo1("hello", "1234");
ByteArrayOutputStream buf = new ByteArrayOutputStream();
ObjectOutputStream out = new ObjectOutputStream(buf);
out.writeObject(demo);
ObjectInputStream in = new ObjectInputStream(new ByteArrayInputStream(
buf.toByteArray()));
demo = (SeriDemo1) in.readObject();
System.out.println(demo);
}
}
然后我们看看:确保对象的成员变量符合正确的约束条件。比如一般情况下我们会在构造函数中对于参数进行合法性检查,但是默认的序列化并不会调用类的构造函数,直接由对象的序列化数据来构造出一个对象,这个我们就有可能提供遗传非法的序列化数据,来构造一个不满足约束条件的对象。
为了避免这种情况,我们可以自定义反序列话的方式。比如在readObject方法中,进行检查。当数据不满足约束的时候(比如年龄小于0等等不满足约束的情况),可以抛出异常之类的。
接下来我们看看readResolve()方法在单例模式中的使用:
单例模式大家应该都清楚,我就不多说了,看看下面的代
package com.java;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
public class ReadResolveDemo implements Serializable {
private static final long serialVersionUID = 1L;
private ReadResolveDemo() {
}
public static ReadResolveDemo getInstance() {
return new ReadResolveDemo();
}
public static void main(String[] args) throws Exception {
ReadResolveDemo demo=ReadResolveDemo.getInstance();
ByteArrayOutputStream buf = new ByteArrayOutputStream();
ObjectOutputStream out = new ObjectOutputStream(buf);
out.writeObject(demo);
ObjectInputStream in = new ObjectInputStream(new ByteArrayInputStream(
buf.toByteArray()));
ReadResolveDemo demo1 = (ReadResolveDemo) in.readObject();
System.out.println(demo==demo1); //false
}
}
本来单例模式中,只有一个实例,但是在序列化的时候,无论采用默认的方式,还是自定义的方式,在反序列化的时候都会产生一个新的对象,所以上面的程序运行输出false。
因此可以看出反序列化打破了单例模式只有一个实例的约定,为了避免这种情况,我们可以使用readReslove:
package com.java;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
public class ReadResolveDemo implements Serializable {
private static final long serialVersionUID = 1L;
private static final ReadResolveDemo INSTANCE = new ReadResolveDemo();
private ReadResolveDemo() {
}
public static ReadResolveDemo getInstance() {
return INSTANCE;
}
private Object readResolve() {
return INSTANCE;
}
public static void main(String[] args) throws Exception {
ReadResolveDemo demo = ReadResolveDemo.getInstance();
ByteArrayOutputStream buf = new ByteArrayOutputStream();
ObjectOutputStream out = new ObjectOutputStream(buf);
out.writeObject(demo);
ObjectInputStream in = new ObjectInputStream(new ByteArrayInputStream(
buf.toByteArray()));
ReadResolveDemo demo1 = (ReadResolveDemo) in.readObject();
System.out.println(demo == demo1); // true
}
}
最后我们简单的说一下实现Externalizable接口。 实现Externalizable接口的类完全由自身来控制序列化的行为。而且必须实现writeExternal(ObjectOutput out)和readExternal(ObjectInput in)。
注意在对实现了这个接口的对象进行反序列化的时候,会先调用类的不带参数的构造函数,这个和之前的默认反序列化方式是不一样的。
例子如下:
package com.java;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.Externalizable;
import java.io.IOException;
import java.io.ObjectInput;
import java.io.ObjectInputStream;
import java.io.ObjectOutput;
import java.io.ObjectOutputStream;
public class ExternalizableDemo implements Externalizable {
private String name;
static {
System.out.println("调用静态代码块");
}
public ExternalizableDemo() {
System.out.println("调用默认无参构造函数");
}
public ExternalizableDemo(String name) {
this.name = name;
System.out.println("调用有参构造函数");
}
@Override
public void writeExternal(ObjectOutput out) throws IOException {
out.writeObject(name);
}
@Override
public void readExternal(ObjectInput in) throws IOException,
ClassNotFoundException {
name = (String) in.readObject();
}
@Override
public String toString() {
return "[" + name + "]";
}
public static void main(String[] args) throws Exception {
ExternalizableDemo demo = new ExternalizableDemo("rollen");
ByteArrayOutputStream buf = new ByteArrayOutputStream();
ObjectOutputStream out = new ObjectOutputStream(buf);
out.writeObject(demo);
ObjectInputStream in = new ObjectInputStream(new ByteArrayInputStream(
buf.toByteArray()));
demo = (ExternalizableDemo) in.readObject();
System.out.println(demo);
}
}
输出:
调用静态代码块
调用有参构造函数
调用默认无参构造函数
[rollen]
以上部分转自http://www.cnblogs.com/rollenholt/archive/2012/11/26/2789445.html
------------------------------------------------------------------------
一起看看这些问题:
1)Java中的Serializable接口和Externalizable接口有什么区别?
这个是面试中关于Java序列化问的最多的问题。我的回答是,Externalizable接口提供了两个方法writeExternal()和readExternal()。这两个方法给我们提供了灵活处理Java序列化的方法,通过实现这个接口中的两个方法进行对象序列化可以替代Java中默认的序列化方法。正确的实现Externalizable接口可以大幅度的提高应用程序的性能。
2)Serializable接口中有借个方法?如果没有方法的话,那么这么设计Serializable接口的目的是什么?
Serializable接口在java.lang包中,是Java序列化机制的核心组成部分。它里面没有包含任何方法,我们称这样的接口为标识接口。如果你的类实现了Serializable接口,这意味着你的类被打上了“可以进行序列化”的标签,并且也给了编译器指示,可以使用序列化机制对这个对象进行序列化处理。
3)什么是serialVersionUID?如果你没有定义serialVersionUID意味着什么?
SerialVersionUID应该是你的类中的一个publicstatic final类型的常量,如果你的类中没有定义的话,那么编译器将抛出警告。如果你的类中没有制定serialVersionUID,那么Java编译器会根据类的成员变量和一定的算法生成用来表达对象的serialVersionUID ,通常是用来表示类的哈希值(hash code)。结论是,如果你的类没有实现SerialVersionUID,那么如果你的类中如果加入或者改变成员变量,那么已经序列化的对象将无法反序列化。这是以为,类的成员变量的改变意味这编译器生成的SerialVersionUID的值不同。Java序列化过程是通过正确SerialVersionUID来对已经序列化的对象进行状态恢复。
4)当对象进行序列化的时候,如果你不希望你的成员变量进行序列化,你怎么办?
这个问题也会这么问,如何使用暂态类型的成员变量?暂态和静态成员变量是否会被序列化等等。如果你不希望你的对象中的成员变量的状态得以保存,你可以根据需求选择transient或者static类型的变量,这样的变量不参与Java序列化处理的过程。
5)如果一个类中的成员变量是其它符合类型的Java类,而这个类没有实现Serializable接口,那么当对象序列化的时候会怎样?
如果你的一个对象进行序列化,而这个对象中包含另外一个引用类型的成员编程,而这个引用的类没有实现Serializable接口,那么当对象进行序列化的时候会抛出“NotSerializableException“的运行时异常。
6)如果一个类是可序列化的,而他的超类没有,那么当进行反序列化的时候,那些从超类继承的实例变量的值是什么?
Java中的序列化处理实例变量只会在所有实现了Serializable接口的继承支路上展开。所以当一个类进行反序列化处理的时候,超类没有实现Serializable接口,那么从超类继承的实例变量会通过为实现序列化接口的超类的构造函数进行初始化。
7)Can you Customize Serialization process or can you override defaultSerialization process in Java?
7)你能够自定义序列化处理的代码吗或者你能重载Java中默认的序列化方法吗?
答案是肯定的,可以。我们都知道可以通过ObjectOutputStream中的writeObject()方法写入序列化对象,通过ObjectInputStream中的readObject()读入反序列化的对象。这些都是Java虚拟机提供给你的两个方法。如果你在你的类中定义了这两个方法,那么JVM就会用你的方法代替原有默认的序列化机制的方法。你可以通过这样的方式类自定义序列化和反序列化的行为。需要注意的一点是,最好将这两个方法定义为private,以防止他们被继承、重写和重载。也只有JVM可以访问到你的类中所有的私有方法,你不用担心方法私有不会被调用到,Java序列化过程会正常工作。
8)假设一个新的类的超类实现了Serializable接口,那么如何让这个新的子类不被序列化?
如果一个超类已经序列化了,那么无法通过是否实现什么接口的方式再避免序列化的过程了,但是也还有一种方式可以使用。那就是需要你在你的类中重新实现writeObject()和readObject()方法,并在方法实现中通过抛出NotSerializableException。
9)在Java进行序列化和反序列化处理的时候,哪些方法被使用了?
这个是面试中常见的问题,主要用来考察你是否对readObject()、writeObject()、readExternal()和writeExternal()方法的使用熟悉。Java序列化是通过java.io.ObjectOutputStream这个类来完成的。这个类是一个过滤器流,这个类完成对底层字节流的包装来进行序列化处理。我们通过ObjectOutputStream.writeObject(obj)进行序列化,通过ObjectInputStream.readObject()进行反序列化。对writeObject()方法的调用会触发Java中的序列化机制。readObject()方法用来将已经持久化的字节数据反向创建Java对象,该方法返回Object类型,需要强制转换成你需要的正确类型。
10)Suppose you have a class which you serialized it and stored in persistence andlater modified that class to add a new field. What will happen if youdeserialize the object already serialized?
10)假设你有一个类并且已经将这个类的某一个对象序列化存储了,那么如果你在这个类中加入了新的成员变量,那么在反序列化刚才那个已经存在的对象的时候会怎么样?
这个取决于这个类是否有serialVersionUID成员。通过上面的,我们已经知道如果你的类没有提供serialVersionUID,那么编译器会自动生成,而这个serialVersionUID就是对象的hash code值。那么如果加入新的成员变量,重新生成的serialVersionUID将和之前的不同,那么在进行反序列化的时候就会产生java.io.InvalidClassException的异常。这就是为什么要建议为你的代码加入serialVersionUID的原因所在了。
11)JAVA反序列化时会将NULL值变成""字符!!
在java中socket传输数据时,数据类型往往比较难选择。可能要考虑带宽、跨语言、版本的兼容等问题。比较常见的做法有两种:一是把对象包装成JSON字符串传输,二是采用java对象的序列化和反序列化。随着Google工具protoBuf的开源,protobuf也是个不错的选择。对JSON,Object Serialize,ProtoBuf 做个对比。
为什么需要序列化与反序列化
我们知道,当两个进程进行远程通信时,可以相互发送各种类型的数据,包括文本、图片、音频、视频等, 而这些数据都会以二进制序列的形式在网络上传送。那么当两个Java进程进行通信时,能否实现进程间的对象传送呢?答案是可以的。如何做到呢?这就需要Java序列化与反序列化了。换句话说,一方面,发送方需要把这个Java对象转换为字节序列,然后在网络上传送;另一方面,接收方需要从字节序列中恢复出Java对象。
当我们明晰了为什么需要Java序列化和反序列化后,我们很自然地会想Java序列化的好处。其好处一是实现了数据的持久化,通过序列化可以把数据永久地保存到硬盘上(通常存放在文件里),二是,利用序列化实现远程通信,即在网络上传送对象的字节序列。
实现序列化的要求
只有实现了Serializable或Externalizable接口的类的对象才能被序列化,否则抛出异常。
只有实现了Serializable或者Externalizable接口的类的对象才能够被序列化。否则当调用writeObject方法的时候会出现IOException。需要注意的是Externalizable接口继承自Serializable接口。两者的区别如下:
仅仅实现Serializable接口的类可应采用默认的序列化方式。比如String类。
假设有一个Customer类的对象需要序列化,如果这个类仅仅实现了这个接口,那么序列化和反序列化的方式如下:ObjectOutputStream采用默认的序列化方式,对于这个类的非static,非transient的实例变量进行序列化。ObjectInputStream采用默认的反序列化方式,对于这个类的非static,非transient的实例变量进行反序列化。
如果这个类不仅实现了Serializable接口,而且定义了readObject(ObjectInputStream in)和 writeObject(ObjectOutputStream out)方法,那么将按照如下的方式进行序列化和反序列化:ObjectOutputStream会调用这个类的writeObject方法进行序列化,ObjectInputStream会调用相应的readObject方法进行反序列化。
实现Externalizable接口的类完全由自身来控制序列化的行为。而且必须实现writeExternal(ObjectOutput out)和readExternal(ObjectInput in)。那么将按照如下的方式进行序列化和反序列化:ObjectOutputStream会调用这个类的writeExternal方法进行序列化,ObjectInputStream会调用相应的readExternal方法进行反序列化。
把Java对象转换为字节序列的过程称为对象的序列化。
把字节序列恢复为Java对象的过程称为对象的反序列化。
java中引入序列化机制主要是为了支持两种重要技术:RMI和JavaBean技术。
只有实现了Serializable和Externalizable接口的类的对象才能被序列化。
1)Serializable接口可使类中的所有成员变量自动被序列化(transient和static修饰的变量除外),默认的
序列化方式会序列化整个对象图,这需要递归遍历对象图。如果对象图很复杂,递归遍历操作需要消耗很多
的空间和时间,它的内部数据结构为双向列表。在应用时,如果对某些成员变量都改为transient类型,将
节省空间和时间,提高序列化的性能。
2)Externalizable接口继承自Serializable接口,实现Externalizable接口的类完全由自身来控制序列化
的行为,即Externalizable对象默认情况下不保存任何它的字段,而仅实现Serializable接口的类可以
采用默认的序列化方式 。
那我们如何对一个Serializable对象的序列化和反序列化行为进行控制呢?
1)加transient修饰符,这样改变量就不会被序列化了
2)添加(不是“实现”和“重载” )writeObject和readObject方法: private void writeObject(ObjectOutputStream stream) throws IOException; private void readObject(ObjectOutputStream stream) throws IOException;
这样一旦对象被序列化或者被反序列化,就会自动分别调用这两个方法,而不会调用默认的序列化和反序列化方法。(这一点确实感觉有点奇怪,或者叫混乱!)
当ObjectOutputStream对一个Serializable对象进行序列化时,如果该对象具有writeObject()方法,那么就会执行这一方法,否则就按默认方式序列化。在该对象的writeObjectt()方法中,可以先调用ObjectOutputStream的defaultWriteObject()方法,使得对象输出流先执行默认的序列化操作。同理可得出反序列化的情况,不过这次是defaultReadObject()方法。
对Serializable对象反序列化时,并不会调用任何构造函数 ,因此Serializable类无需默认构造函数,但是当Serializable类的父类没有实现Serializable接口时,反序列化过程会调用父类的默认构造函数,因此该父类必需有默认构造函数,否则会抛异常。
对Externalizable对象反序列化时,会先调用类的不带参数的构造方法 ,这是有别于默认反序列方式的。如果把类的不带参数的构造方法删除,或者把该构造方法的访问权限设置为private、默认或protected级别,会抛出java.io.InvalidException: no valid constructor异常,因此Externalizable对象必须有默认构造函数,而且必需是public的。
对象序列化包括如下步骤:
1) 创建一个对象输出流,它可以包装一个其他类型的目标输出流,如文件输出流;
2) 通过对象输出流的writeObject()方法写对象。
对于父类的处理,如果父类没有实现串行化接口,则其必须有默认的构造函数(即没有参数的构造函数)。否则编译的时候就会报错。在反串行化的时候,默认构造函数会被调用。但是若把父类标记为可以串行化,则在反串行化的时候,其默认构造函数不会被调用。这是为什么呢?这是因为Java 对串行化的对象进行反串行化的时候,直接从流里获取其对象数据来生成一个对象实例,而不是通过其构造函数来完成。
serialVersionUID
简单来说,Java的序列化机制是通过在运行时判断类的serialVersionUID来验证版本一致性的。在进行反序列化时,JVM会把传来的字节流中的serialVersionUID与本地相应实体(类)的serialVersionUID进行比较,如果相同就认为是一致的,可以进行反序列化,否则就会出现序列化版本不一致的异常。(InvalidCastException)
下面来看一个最简单的例子:
package com.java;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
public class simpleSerializableTest {
public static void main(String[] args) throws Exception {
ObjectOutputStream out=new ObjectOutputStream(new FileOutputStream("d:\\objectFile.obj"));
String strObj="name";
Customer customer=new Customer("rollen");
//序列化,此处故意将同一对象序列化2次
out.writeObject(strObj);
out.writeObject(customer);
out.writeObject(customer);
out.close();
//反序列化
ObjectInputStream in=new ObjectInputStream(new FileInputStream("d:\\objectFile.obj"));
String strobj1=(String)in.readObject();
Customer cus1=(Customer)in.readObject();
Customer cus2=(Customer)in.readObject();<br> in.close();
System.out.println(strobj1+": "+cus1);
System.out.println(strObj==strobj1);
System.out.println(cus1==customer);
System.out.println(cus1==cus2);
}
}
class Customer implements Serializable {
private static final long serialVersionUID = 1L;
private String name;
public Customer() {
System.out.println("无参构造方法");
}
public Customer(String name) {
System.out.println("有参构造方法");
this.name = name;
}
public String toString() {
return "[ "+name+" ]";
}
}
输出结果为:
有参构造方法
name: [ rollen ]
false
false
true
可以看出,在进行反序列话的时候,并没有调用类的构造方法。而是直接根据他们的序列化数据在内存中创建新的对象。另外需要注意的是,如果由一个ObjectOutputStream对象多次序列化同一个对象,那么右一个objectInputStream对象反序列化后的也是同一个对象。(cus1==cus2结果为true可以看出)
看一段代码,证明static是不会被序列化的:
package com.java;
import java.io.IOException;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.net.ServerSocket;
import java.net.Socket;
public class SerializableServer {
public void send(Object obj) throws IOException {
ServerSocket serverSocket = new ServerSocket(8000);
while (true) {
Socket socket = serverSocket.accept();
ObjectOutputStream out = new ObjectOutputStream(
socket.getOutputStream());
out.writeObject(obj);
out.writeObject(obj);
out.close();
socket.close();
}
}
public static void main(String[] args) throws Exception {
Customer customer = new Customer("rollen", "male");
new SerializableServer().send(customer);
}
}
class Customer implements Serializable {
private static final long serialVersionUID = 1L;
private String name;
private static int count;
private transient String sex;
static {
System.out.println("调用静态代码块");
}
public Customer() {
System.out.println("无参构造方法");
}
public Customer(String name, String sex) {
System.out.println("有参构造方法");
this.name = name;
this.sex = sex;
count++;
}
public String toString() {
return "[ " + count + " " + name + " " + sex + " ]";
}
}
package com.java;
import java.io.ObjectInputStream;
import java.net.Socket;
public class SerializableClient {
public void recive() throws Exception {
Socket socket = new Socket("localhost", 8000);
ObjectInputStream in = new ObjectInputStream(socket.getInputStream());
Object obj1 = in.readObject();
Object obj2 = in.readObject();
System.out.println(obj1);
System.out.println(obj1==obj2);
}
public static void main(String[] args) {
try {
new SerializableClient().recive();
} catch (Exception e) {
e.printStackTrace();
}
}
}
运行结果中,count的值为0.
我们来看另外一种情况:
class A implements Serializable{
B b;
//...
}
class B implements Serializable{
//...
}
当我们在序列化A的对象的时候,也会自动序列化和他相关联的B的对象。也就是说在默认的情况下,对象输出流会对整个对象图进行序列化。因此会导致出现下面的问题,看代码(例子中是使用双向列表作为内部结构的,只是给出了demo,并没有完整的实现,只是为了说明情况):
package com.java;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
public class SeriListTest implements Serializable {
private static final long serialVersionUID = 1L;
private int size;
private Node head = null;
private Node end = null;
private static class Node implements Serializable {
private static final long serialVersionUID = 1L;
String data;
Node next;
Node previous;
}
// 列表末尾添加一个字符串
public void add(String data) {
Node node = new Node();
node.data = data;
node.next = null;
node.previous = end;
if (null != end) {
end.next = node;
}
size++;
end = node;
if (size == 1) {
head = end;
}
}
public int getSize() {
return size;
}
// other methods...
public static void main(String[] args) throws Exception {
SeriListTest list = new SeriListTest();
for (int i = 0; i < 10000; ++i) {
list.add("rollen" + i);
}
ByteArrayOutputStream buf = new ByteArrayOutputStream();
ObjectOutputStream out = new ObjectOutputStream(buf);
out.writeObject(list);
ObjectInputStream in = new ObjectInputStream(new ByteArrayInputStream(
buf.toByteArray()));
list = (SeriListTest) in.readObject();
System.out.println("size is :" + list.getSize());
}
}
这段代码会出现如下错误:
Exception in thread "main" java.lang.StackOverflowError
at java.lang.ref.ReferenceQueue.poll(ReferenceQueue.java:82)
at java.io.ObjectStreamClass.processQueue(ObjectStreamClass.java:2234)
....
整个就是因为序列化的时候,对整个对象图进行序列化引起的问题。在这种情况下啊,我们需要自定义序列化的过程:
package com.java;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
public class SeriListTest implements Serializable {
private static final long serialVersionUID = 1L;
transient private int size;
transient private Node head = null;
transient private Node end = null;
private static class Node implements Serializable {
private static final long serialVersionUID = 1L;
String data;
Node next;
Node previous;
}
// 列表末尾添加一个字符串
public void add(String data) {
Node node = new Node();
node.data = data;
node.next = null;
node.previous = end;
if (null != end) {
end.next = node;
}
size++;
end = node;
if (size == 1) {
head = end;
}
}
public int getSize() {
return size;
}
// other methods...
private void writeObject(ObjectOutputStream outStream) throws IOException {
outStream.defaultWriteObject();
outStream.writeInt(size);
for (Node node = head; node != null; node = node.next) {
outStream.writeObject(node.data);
}
}
private void readObject(ObjectInputStream inStream) throws IOException,
ClassNotFoundException {
inStream.defaultReadObject();
int count = inStream.readInt();
for (int i = 0; i < count; ++i) {
add((String) inStream.readObject());
}
}
public static void main(String[] args) throws Exception {
SeriListTest list = new SeriListTest();
for (int i = 0; i < 10000; ++i) {
list.add("rollen" + i);
}
ByteArrayOutputStream buf = new ByteArrayOutputStream();
ObjectOutputStream out = new ObjectOutputStream(buf);
out.writeObject(list);
ObjectInputStream in = new ObjectInputStream(new ByteArrayInputStream(
buf.toByteArray()));
list = (SeriListTest) in.readObject();
System.out.println("size is :" + list.getSize());
}
}
运行结果为:10000
现在我们总结一下,在什么情况下我们需要自定义序列化的方式:
1)为了确保序列化的安全性,对于一些敏感信息加密
2)确保对象的成员变量符合正确的约束条件
3)优化序列化的性能(之前的那个例子已经解释了这种情况)
下面我们来用例子解释一下这些:
先来看看:为了确保序列化的安全性,对于一些敏感信息加密
package com.java;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
public class SeriDemo1 implements Serializable {
private String name;
transient private String password; // 注意此处的transient
public SeriDemo1() {
}
public SeriDemo1(String name, String password) {
this.name = name;
this.password = password;
}
// 此处模拟对密码进行加密,进行了简化
private String change(String password) {
return password + "rollen";
}
private void writeObject(ObjectOutputStream outStream) throws IOException {
outStream.defaultWriteObject();
outStream.writeObject(change(password));
}
private void readObject(ObjectInputStream inStream) throws IOException,
ClassNotFoundException {
inStream.defaultReadObject();
String strPassowrd = (String) inStream.readObject();
//此处模拟对密码解密
password = strPassowrd.substring(0, strPassowrd.length() - 6);
}
@Override
public String toString() {
return "SeriDemo1 [name=" + name + ", password=" + password + "]";
}
public static void main(String[] args) throws Exception {
SeriDemo1 demo = new SeriDemo1("hello", "1234");
ByteArrayOutputStream buf = new ByteArrayOutputStream();
ObjectOutputStream out = new ObjectOutputStream(buf);
out.writeObject(demo);
ObjectInputStream in = new ObjectInputStream(new ByteArrayInputStream(
buf.toByteArray()));
demo = (SeriDemo1) in.readObject();
System.out.println(demo);
}
}
然后我们看看:确保对象的成员变量符合正确的约束条件。比如一般情况下我们会在构造函数中对于参数进行合法性检查,但是默认的序列化并不会调用类的构造函数,直接由对象的序列化数据来构造出一个对象,这个我们就有可能提供遗传非法的序列化数据,来构造一个不满足约束条件的对象。
为了避免这种情况,我们可以自定义反序列话的方式。比如在readObject方法中,进行检查。当数据不满足约束的时候(比如年龄小于0等等不满足约束的情况),可以抛出异常之类的。
接下来我们看看readResolve()方法在单例模式中的使用:
单例模式大家应该都清楚,我就不多说了,看看下面的代
package com.java;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
public class ReadResolveDemo implements Serializable {
private static final long serialVersionUID = 1L;
private ReadResolveDemo() {
}
public static ReadResolveDemo getInstance() {
return new ReadResolveDemo();
}
public static void main(String[] args) throws Exception {
ReadResolveDemo demo=ReadResolveDemo.getInstance();
ByteArrayOutputStream buf = new ByteArrayOutputStream();
ObjectOutputStream out = new ObjectOutputStream(buf);
out.writeObject(demo);
ObjectInputStream in = new ObjectInputStream(new ByteArrayInputStream(
buf.toByteArray()));
ReadResolveDemo demo1 = (ReadResolveDemo) in.readObject();
System.out.println(demo==demo1); //false
}
}
本来单例模式中,只有一个实例,但是在序列化的时候,无论采用默认的方式,还是自定义的方式,在反序列化的时候都会产生一个新的对象,所以上面的程序运行输出false。
因此可以看出反序列化打破了单例模式只有一个实例的约定,为了避免这种情况,我们可以使用readReslove:
package com.java;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
public class ReadResolveDemo implements Serializable {
private static final long serialVersionUID = 1L;
private static final ReadResolveDemo INSTANCE = new ReadResolveDemo();
private ReadResolveDemo() {
}
public static ReadResolveDemo getInstance() {
return INSTANCE;
}
private Object readResolve() {
return INSTANCE;
}
public static void main(String[] args) throws Exception {
ReadResolveDemo demo = ReadResolveDemo.getInstance();
ByteArrayOutputStream buf = new ByteArrayOutputStream();
ObjectOutputStream out = new ObjectOutputStream(buf);
out.writeObject(demo);
ObjectInputStream in = new ObjectInputStream(new ByteArrayInputStream(
buf.toByteArray()));
ReadResolveDemo demo1 = (ReadResolveDemo) in.readObject();
System.out.println(demo == demo1); // true
}
}
最后我们简单的说一下实现Externalizable接口。 实现Externalizable接口的类完全由自身来控制序列化的行为。而且必须实现writeExternal(ObjectOutput out)和readExternal(ObjectInput in)。
注意在对实现了这个接口的对象进行反序列化的时候,会先调用类的不带参数的构造函数,这个和之前的默认反序列化方式是不一样的。
例子如下:
package com.java;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.Externalizable;
import java.io.IOException;
import java.io.ObjectInput;
import java.io.ObjectInputStream;
import java.io.ObjectOutput;
import java.io.ObjectOutputStream;
public class ExternalizableDemo implements Externalizable {
private String name;
static {
System.out.println("调用静态代码块");
}
public ExternalizableDemo() {
System.out.println("调用默认无参构造函数");
}
public ExternalizableDemo(String name) {
this.name = name;
System.out.println("调用有参构造函数");
}
@Override
public void writeExternal(ObjectOutput out) throws IOException {
out.writeObject(name);
}
@Override
public void readExternal(ObjectInput in) throws IOException,
ClassNotFoundException {
name = (String) in.readObject();
}
@Override
public String toString() {
return "[" + name + "]";
}
public static void main(String[] args) throws Exception {
ExternalizableDemo demo = new ExternalizableDemo("rollen");
ByteArrayOutputStream buf = new ByteArrayOutputStream();
ObjectOutputStream out = new ObjectOutputStream(buf);
out.writeObject(demo);
ObjectInputStream in = new ObjectInputStream(new ByteArrayInputStream(
buf.toByteArray()));
demo = (ExternalizableDemo) in.readObject();
System.out.println(demo);
}
}
输出:
调用静态代码块
调用有参构造函数
调用默认无参构造函数
[rollen]
以上部分转自http://www.cnblogs.com/rollenholt/archive/2012/11/26/2789445.html
------------------------------------------------------------------------
一起看看这些问题:
1)Java中的Serializable接口和Externalizable接口有什么区别?
这个是面试中关于Java序列化问的最多的问题。我的回答是,Externalizable接口提供了两个方法writeExternal()和readExternal()。这两个方法给我们提供了灵活处理Java序列化的方法,通过实现这个接口中的两个方法进行对象序列化可以替代Java中默认的序列化方法。正确的实现Externalizable接口可以大幅度的提高应用程序的性能。
2)Serializable接口中有借个方法?如果没有方法的话,那么这么设计Serializable接口的目的是什么?
Serializable接口在java.lang包中,是Java序列化机制的核心组成部分。它里面没有包含任何方法,我们称这样的接口为标识接口。如果你的类实现了Serializable接口,这意味着你的类被打上了“可以进行序列化”的标签,并且也给了编译器指示,可以使用序列化机制对这个对象进行序列化处理。
3)什么是serialVersionUID?如果你没有定义serialVersionUID意味着什么?
SerialVersionUID应该是你的类中的一个publicstatic final类型的常量,如果你的类中没有定义的话,那么编译器将抛出警告。如果你的类中没有制定serialVersionUID,那么Java编译器会根据类的成员变量和一定的算法生成用来表达对象的serialVersionUID ,通常是用来表示类的哈希值(hash code)。结论是,如果你的类没有实现SerialVersionUID,那么如果你的类中如果加入或者改变成员变量,那么已经序列化的对象将无法反序列化。这是以为,类的成员变量的改变意味这编译器生成的SerialVersionUID的值不同。Java序列化过程是通过正确SerialVersionUID来对已经序列化的对象进行状态恢复。
4)当对象进行序列化的时候,如果你不希望你的成员变量进行序列化,你怎么办?
这个问题也会这么问,如何使用暂态类型的成员变量?暂态和静态成员变量是否会被序列化等等。如果你不希望你的对象中的成员变量的状态得以保存,你可以根据需求选择transient或者static类型的变量,这样的变量不参与Java序列化处理的过程。
5)如果一个类中的成员变量是其它符合类型的Java类,而这个类没有实现Serializable接口,那么当对象序列化的时候会怎样?
如果你的一个对象进行序列化,而这个对象中包含另外一个引用类型的成员编程,而这个引用的类没有实现Serializable接口,那么当对象进行序列化的时候会抛出“NotSerializableException“的运行时异常。
6)如果一个类是可序列化的,而他的超类没有,那么当进行反序列化的时候,那些从超类继承的实例变量的值是什么?
Java中的序列化处理实例变量只会在所有实现了Serializable接口的继承支路上展开。所以当一个类进行反序列化处理的时候,超类没有实现Serializable接口,那么从超类继承的实例变量会通过为实现序列化接口的超类的构造函数进行初始化。
7)Can you Customize Serialization process or can you override defaultSerialization process in Java?
7)你能够自定义序列化处理的代码吗或者你能重载Java中默认的序列化方法吗?
答案是肯定的,可以。我们都知道可以通过ObjectOutputStream中的writeObject()方法写入序列化对象,通过ObjectInputStream中的readObject()读入反序列化的对象。这些都是Java虚拟机提供给你的两个方法。如果你在你的类中定义了这两个方法,那么JVM就会用你的方法代替原有默认的序列化机制的方法。你可以通过这样的方式类自定义序列化和反序列化的行为。需要注意的一点是,最好将这两个方法定义为private,以防止他们被继承、重写和重载。也只有JVM可以访问到你的类中所有的私有方法,你不用担心方法私有不会被调用到,Java序列化过程会正常工作。
8)假设一个新的类的超类实现了Serializable接口,那么如何让这个新的子类不被序列化?
如果一个超类已经序列化了,那么无法通过是否实现什么接口的方式再避免序列化的过程了,但是也还有一种方式可以使用。那就是需要你在你的类中重新实现writeObject()和readObject()方法,并在方法实现中通过抛出NotSerializableException。
9)在Java进行序列化和反序列化处理的时候,哪些方法被使用了?
这个是面试中常见的问题,主要用来考察你是否对readObject()、writeObject()、readExternal()和writeExternal()方法的使用熟悉。Java序列化是通过java.io.ObjectOutputStream这个类来完成的。这个类是一个过滤器流,这个类完成对底层字节流的包装来进行序列化处理。我们通过ObjectOutputStream.writeObject(obj)进行序列化,通过ObjectInputStream.readObject()进行反序列化。对writeObject()方法的调用会触发Java中的序列化机制。readObject()方法用来将已经持久化的字节数据反向创建Java对象,该方法返回Object类型,需要强制转换成你需要的正确类型。
10)Suppose you have a class which you serialized it and stored in persistence andlater modified that class to add a new field. What will happen if youdeserialize the object already serialized?
10)假设你有一个类并且已经将这个类的某一个对象序列化存储了,那么如果你在这个类中加入了新的成员变量,那么在反序列化刚才那个已经存在的对象的时候会怎么样?
这个取决于这个类是否有serialVersionUID成员。通过上面的,我们已经知道如果你的类没有提供serialVersionUID,那么编译器会自动生成,而这个serialVersionUID就是对象的hash code值。那么如果加入新的成员变量,重新生成的serialVersionUID将和之前的不同,那么在进行反序列化的时候就会产生java.io.InvalidClassException的异常。这就是为什么要建议为你的代码加入serialVersionUID的原因所在了。
11)JAVA反序列化时会将NULL值变成""字符!!
在java中socket传输数据时,数据类型往往比较难选择。可能要考虑带宽、跨语言、版本的兼容等问题。比较常见的做法有两种:一是把对象包装成JSON字符串传输,二是采用java对象的序列化和反序列化。随着Google工具protoBuf的开源,protobuf也是个不错的选择。对JSON,Object Serialize,ProtoBuf 做个对比。
发表评论
文章已被作者锁定,不允许评论。
-
ReentrantLock与Condition
2017-03-17 14:25 532多线程和并发性并不是什么新内容,但是 Java 语言设计中的创 ... -
java linux监控
2017-03-13 17:49 501http://agapple.iteye.com/blog/1 ... -
transient和volatile两个关键字
2017-02-16 09:47 575transient和volatile两个关 ... -
java 锁机制
2016-12-09 13:43 478一段synchronized的代码被 ... -
java 正则表达式
2016-12-02 10:28 521众所周知,在程序开发中,难免会遇到需要匹配、查找、替换、判断字 ... -
java ClassNotFoundException和NoClassDefFoundException的差别
2016-08-17 19:47 911首先从名字上可以看出一类是异常,一类属于错误。异常可以通过异常 ... -
ThreadLocal
2016-07-19 11:10 333ThreadLocal是什么 Thre ... -
java CAS
2016-07-10 14:55 346cas 乐观锁每次不锁定整个线程,在操作之前进行判断。悲观锁独 ... -
concurrenthashmap
2016-07-10 11:11 424hash table虽然性能上不如 ... -
java 线程池的使用
2016-07-10 09:52 3751. 引言 合理利用线程池能够带来三个好处。第一:降低资源消 ... -
java.util.concurrent
2016-07-03 16:24 411我们都知道,在JDK1.5之 ... -
JVM 配置 以及垃圾收集器的选择
2016-04-15 12:36 733JVM监控的关键指标说明: a) FGC的环比增加次数。Zab ... -
jvm实时监控工具
2016-04-09 09:35 469 -
哈希 、一致性哈希、余数式哈希
2016-04-07 16:10 868什么是Hash Hash,一 ... -
jvm dump 相关
2016-03-22 17:22 684http://www.cnblogs.com/edwardla ... -
深入剖析volatile关键字
2016-03-21 16:02 550深入剖析volatile关键字 ... -
java线程安全问题之静态变量、实例变量、局部变量
2016-03-08 12:52 577java多线程编程中,存在很多线程安全问题,至于什么是线程安全 ... -
有状态的bean和无状态的bean的区别
2016-03-08 11:23 1522有状态会话bean :每个用户有自己特有的一个实例,在用户的生 ... -
Java nio详解
2016-01-20 16:30 555http://www.ibm.com/developerwor ... -
java 不定长数组
2015-11-24 15:00 789在调用某个方法时,若是方法的参数个数事先无法确定该如何处理 ...
相关推荐
Java序列化是Java平台中的一种标准机制,允许将对象的状态转换为字节流,以便存储在磁盘上、通过网络进行传输或者在某些时候恢复原来的对象状态。这一过程包括两个主要步骤:对象的序列化(将对象转换为字节流)和反...
Java序列化是Java平台中的一种持久化机制,它允许对象的状态被转换成字节流,以便存储、网络传输或在不同时间点恢复。这个过程被称为序列化,而反向操作称为反序列化。序列化在许多场景下都非常有用,比如在分布式...
java 序列化和反序列化的方法 Java 序列化和反序列化是 Java 语言中的一种机制,用于将对象转换为字节流,以便在网络上传输或存储。序列化是将对象转换为字节流的过程,而反序列化是将字节流转换回对象的过程。 在...
### Java序列化(Serializable)的作用与反序列化详解 #### 一、序列化的概念 序列化是指将程序中的对象转换为一系列字节流的过程,主要用于保存对象的状态或在网络之间传输对象。序列化的主要目的是为了能够持久化...
【Protocol Buffer序列化对比Java序列化】 Protocol Buffer(简称PB)是Google开发的一种高效的数据序列化协议,而Java序列化是Java平台内置的一种序列化机制。两者的主要目标都是将对象转化为字节数组,便于在网络...
Java序列化是Java平台提供的一种持久化机制,它允许我们将一个Java对象转换为字节流,以便存储到磁盘上,或者通过网络进行传输。这使得我们可以保存和恢复对象的状态。实现序列化的类需要实现`Serializable`接口,...
Java序列化是Java平台中的一种核心机制,它允许对象的状态被转换成字节流,以便存储到磁盘、数据库,或者在网络中进行传输。这对于实现持久化、远程方法调用(RMI)以及Enterprise JavaBeans(EJB)等高级功能至关...
### Java序列化(Serializable)的作用与反序列化详解 #### 一、序列化是什么? 序列化是指将程序中的对象转换为字节流的过程,从而方便存储或传输这些对象。通常,序列化用于将对象的状态(即其实例变量的值,而非...
Java序列化是Java平台中的一种标准机制,它允许将对象的状态转换为字节流,以便存储、传输或恢复。在Java中,一个类如果要实现序列化,需要实现`Serializable`接口,这是一个标记接口,不包含任何方法。下面我们将...
Java序列化是Java平台中的一项重要技术,它允许对象的状态被转换为字节流,以便存储或通过网络进行传输。这种技术在分布式系统、持久化存储以及数据交换等场景中非常常见。本资源包含了三个流行的Java序列化框架:...
Java序列化是Java平台提供的一种将对象转换为字节流,以便存储、在网络上传输或者在后续时间重新创建相同对象的机制。这是Java编程中一个非常重要的概念,尤其是在分布式环境和持久化存储中。让我们深入探讨一下Java...
### Java序列化原理与算法详解 #### 序言 在现代软件开发中,尤其是在网络通信和数据持久化领域,对象的序列化与反序列化扮演着至关重要的角色。Java作为一种广泛应用的编程语言,提供了强大的内置支持来实现序列化...
Java序列化面试题(10题) 在 Java 中,序列化是一种用于处理对象流的机制,它可以将对象的内容进行流化,使其可以被读写和传输。下面是 10 个与 Java 序列化相关的面试题目: 1. 什么是 Java 序列化,如何实现 ...
Java序列化是Java平台中的一种标准机制,允许对象的状态被保存到磁盘或者在网络中进行传输,以便在后续的时间或地点恢复这些对象。这个过程包括两个主要操作:序列化(将对象转换为字节流)和反序列化(将字节流恢复...
Java序列化是Java平台提供的一种持久化对象的机制,它允许我们将对象的状态转换为字节流,以便存储或在网络上传输。在这个特定的场景中,我们关注的是如何使用Java序列化来多次追加对象到一个TXT文件,而不是覆盖...
### Java序列化与反序列化详解 #### 一、Java序列化概述 Java序列化(Serialization)是一项重要的功能,它可以将对象的状态转化为一系列字节,从而实现对象的持久化存储或在网络上传输。序列化机制使得Java对象...
### Java对象序列化标准知识点详解 #### 一、系统架构概览 **1.1 概览** Java 对象序列化是一种将Java对象的...以上内容涵盖了Java序列化标准的关键知识点,深入了解这些概念有助于更好地理解和应用Java序列化技术。
**FST:快速Java序列化的替代方案** 在Java开发中,序列化是一个常见的需求,它允许将对象的状态转换为字节流,以便于存储或网络传输。标准的Java序列化虽然方便,但在处理大量数据时,性能往往成为瓶颈。这时,FST...
Java序列化漏洞是一种常见的安全问题,它出现在Java应用程序中,当对象被转化为字节流以便在网络间或存储中传输时。这种序列化过程如果处理不当,可能会导致远程代码执行(RCE)、信息泄露或者权限提升等严重后果。...
android(包括java)序列化一个对象传给php去做处理,或是接到php的序列化的对象在java中做处理的工具jar包以及使用方法. 使用方法: byte[] b = null; b = PHPSerializer.serialize(一个对象);//将一个对象序列化后返回...