您的位置:首页 > 编程语言 > Java开发

【Java学习笔记】对象的序列化和反序列化

2014-10-30 11:00 645 查看
作者:gnuhpc

出处:http://www.cnblogs.com/gnuhpc/

1.定义:
序列化,也叫串行化--将对象写到一个输出流(一般是一个文件)中。反序列化则是从一个输入流中读取一个对象。类中的成员必须是可序列化的,而且要实现Serializable接口,这样的类的对象才能被序列化和反序列化。这个接口是一个表示型的接口。serialVersionUID
是一个串行化类的通用标示符,反串行化就是使用这个标示符确保一个加载的类对应一个可串行化的对象。
若自己指定了serialVersionUID,就可以在序列化后,去添加一个字段,或者方法,而不会影响到后期的还原,还原后的对象照样可以使用,而且还多了方法可以用。serialVersionUID的生成,可以写1,也可以写2,但最好还是按照摘要算法,生成一个惟一的指纹数字,eclipse可以自动生成的,jdk也自带了这个工具。一般写法类似于

private static final long serialVersionUID = -763618247875550322L;

简单的使用请参加我的另一篇习作:http://blog.csdn.net/gnuhpc/archive/2009/08/01/4396149.aspx

2.序列化步骤:

1)创建一个对象输出流:可以包装一个其他类型的输出流。
2)通过对象输出流的writeObject()写对象。注意使用类型转化,转换为相应的类的对象。

3.反序列化步骤:

1)创建一个对象输入流:可以包装一个其他类型的输出流。
2)通过对象输出流的readObject()写对象。

4.什么对象需要被序列化?
序列化的基本想法是完成对实例信息的保证。因为实例信息在运行结束后就消失了。要储存什么信息呢?一定不是关于方法的信息。

5.使用注意事项:
a.有些属性处于安全考虑不能被序列化(或者不能被序列化,比如Thread),则用transit修饰,若想进一步控制序列化和反序列化,则类中提供readObject()方法和writeObject()方法,(反)序列化时就会调用自定义的方法。注意这两个方法不是在java.io.Serializable接口定义的,而是在ObjectInputStream和ObjectOutputStream类中定义的,这两个类实现ObjectInput
和ObjectOutput两个接口。下边是一个可变数组的例子,取自Java in a Nutshell 2rd Edition,它在串行化实现了高效的针对可变数组程度调整的方法:
public class IntList implements Serializable
{
private int[] nums = new int[8]; // An array to store the numbers.
private transient int size = 0;  // Index of next unused element of nums[].
/** Return an element of the array */
public int elementAt(int index) throws ArrayIndexOutOfBoundsException {
if (index >= size) throw new ArrayIndexOutOfBoundsException(index);
else return nums[index];
}
/** Add an int to the array, growing the array if necessary. */
public void add(int x) {
if (nums.length == size) resize(nums.length*2); // Grow array, if needed.
nums[size++] = x;                               // Store the int in it.
}
/** An internal method to change the allocated size of the array. */
protected void resize(int newsize) {
int[] oldnums = nums;
nums = new int[newsize];                     // Create a new array.
System.arraycopy(oldnums, 0, nums, 0, size); // Copy array elements.
}
/** Get rid of unused array elements before serializing the array. */
private void writeObject(ObjectOutputStream out) throws IOException {
if (nums.length > size) resize(size);  // Compact the array.
out.defaultWriteObject();              // Then write it out normally.
}
/** Compute the transient size field after deserializing the array. */
private void readObject(ObjectInputStream in)
throws IOException, ClassNotFoundException {
in.defaultReadObject();                // Read the array normally.
size = nums.length;                    // Restore the transient field.
}
}

b.由于串行化是针对类的实例,也就是对象的,所以static这样的成员是类相关的,也就不会被串行化。

c.注意,API中一个类一旦被串行化了,那么它的父类都是串行化的。你自己设计的类要是想被串行化,那么其父类要确保都串行化。父类一旦串行化,则子类们都自动实现了串行化。所以内部类和可扩展类不建议串行化。
d. 若想加密序列化的数据,可以重写writeObject和readObject,并将加密算法写入其中,最后调用stream.defaultWriteObject();或者stream.defaultReadObject();实现模糊化——当然,你若是想在序列化和反序列化前后执行任何操作都可以在这里定制,比如,这个类的父类实现了串行化,但你并不希望这个类可以被串行化,那么就可以在串行化和反串行化的时候抛出异常:

private void writeObject(ObjectOutputStream out) throws IOException
{
throw new NotSerializableException("Not today!");
}
private void readObject(ObjectInputStream in) throws IOException
{
throw new NotSerializableException("Not today!");
}


如果需要对整个对象进行加密和签名,最简单的是将它放在一个 javax.crypto.SealedObject 和/或 java.security.SignedObject 包装器中。两者都是可序列化的,所以将对象包装在 SealedObject 中可以围绕原对象创建一种
“包装盒”。必须有对称密钥才能解密,而且密钥必须单独管理。同样,也可以将 SignedObject 用于数据验证,并且对称密钥也必须单独管理。
e.序列化不是针对的对象的文件和方法,而是对象的状态,因此在反序列化时对象的类文件必须存在。
f.若想创建自己的串行化方式请实现Externalizable接口,而不是Serializable接口,重写下边两个方法:

public void writeExternal(ObjectOutput out) throws IOException;
public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException;
注意这里你没有了默认的动作可以做,一切都要靠自己。除此之外这个接口与串行化接口没有区别。默认的串行化方法可能会带来时间开销,若你想优化的话,不妨从自定义简单串行化方法开始。这个接口的实际用途在于若你想串行化父类的数据成员,那么依靠原始的串行化接口是无法满足的,你必须自定义串行化方法:

import java.io.*;

public class TestExternalizable

{
FileOutputStream fout = null;

ObjectOutputStream objOut =
null;
public TestExternalizable() {

try{

TestSub testSub = new TestSub();

testSub.setName(NAME);

testSub.setAge(AGE);
fout = new FileOutputStream(FILE_NAME);

objOut = new ObjectOutputStream(fout);

objOut.writeObject(testSub);

objOut.flush();
objOut.close();
fout.close();
FileInputStream fin = new FileInputStream(FILE_NAME);

ObjectInputStream objIn = new ObjectInputStream(fin);

TestSub ts = (TestSub)objIn.readObject();

objIn.close();
fin.close();
System.out.println(ts.name+" "+ts.age);

} catch(Exception ex) {

ex.printStackTrace();
}
}
public static void main(String[] args)

{
new TestExternalizable();

}
}
class TestSuper{

String name = null;

int age =
0;

public void setName(String name) {

this.name = name;

}
public void setAge(int age) {

this.age = age;

}
public void readExternal(ObjectInput objectInput) {

try{

name = (String)objectInput.readObject();

age = objectInput.readInt();
} catch(Exception ex) {

ex.printStackTrace();
}
}
public void writeExternal(ObjectOutput objectOutput) {

try

{
objectOutput.writeObject(name);
objectOutput.writeInt(age);

} catch (Exception ex) {

ex.printStackTrace();

}
}
}
class TestSub
extends TestSuper implements Externalizable {

public TestSub() {

}
}

h.你若是在一个打开的流中写了两次,第二次是不会被保存的,若想重新写要调用
ObjectOutputStream.reset()
方法。

作者:gnuhpc

出处:http://www.cnblogs.com/gnuhpc/
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: