抱砖引玉:
x<?php
class serialize{
public $public_str;
public $public_int;
function __construct(){
$this->public_str = "test";
$this->public_int = 7777;
}
}
$class = new serialize();
$ser_class = serialize($class);
var_dump($ser_class);
?>
以上是一段简单的php序列化代码,输出为
xxxxxxxxxx
string(74) "O:9:"serialize":2:{s:10:"public_str";s:4:"test";s:10:"public_int";i:7777;}"
"O:9:"serialize":一个字符串长度为9名为
serialize`的对象2:{...}
: 序列化数据中有2个字段s:10:"public_str";s:4:"test";
: 长度为10
的字符串(s
)public_str
的值为长度为4
的字符串(s
):test
s:10:"public_int";i:7777;
: 同理,值为i
类型(int):7777
php序列化后的字符串可以很容易看清楚参数和其赋值
序列化机制允许将实现序列化的Java对象转换位字节序列,这些字节序列可以保存在磁盘上,或通过网络传输,以达到以后恢复成原来的对象。序列化机制使得对象可以脱离程序的运行而独立存在。
现在我们利用java
来实现序列化
创建Serialize.java
来序列化(ObjectOutputStream
类用来序列化一个对象),并将序列化结果存入Serialize.ser
文件
步骤一:创建一个ObjectOutputStream
输出流;
步骤二:调用ObjectOutputStream
对象的writeObject
输出可序列化对象
xxxxxxxxxx
import java.io.*;
public class Serialize implements java.io.Serializable
{
public String public_str;
public int public_int;
public Serialize(){
this.public_str = "test";
this.public_int = 7777;
}
public static void main(String[] args) throws IOException{
try( //自动关闭资源
FileOutputStream fileOut = new FileOutputStream("./Serialize.ser");
ObjectOutputStream out = new ObjectOutputStream(fileOut)
) {
out.writeObject(new Serialize());
};
}
}
输出文件不能直接可读,利用xxd
命令查看文件,查看输出结果:
xxxxxxxxxx
00000000: aced 0005 7372 0009 5365 7269 616c 697a ....sr..Serializ
00000010: 6505 c43d d622 f99a 5b02 0002 4900 0a70 e..=."..[...I..p
00000020: 7562 6c69 635f 696e 744c 000a 7075 626c ublic_intL..publ
00000030: 6963 5f73 7472 7400 124c 6a61 7661 2f6c ic_strt..Ljava/l
00000040: 616e 672f 5374 7269 6e67 3b78 7000 001e ang/String;xp...
00000050: 6174 0004 7465 7374 at..test
aced
: 魔术头 (可在ObjectStreamConstants
接口中找到)
0005
: 序列化格式的版本号(可在ObjectStreamConstants
接口中找到)。序列化版本号可自由指定,如果不指定,JVM会根据类信息自己计算一个版本号。如果反序列化使用的版本号与序列化时使用的不一致,反序列化会报InvalidClassException
异常。
73
: 接下来读取到的将是一个对象 (final static byte TC_OBJECT = (byte)0x73;
)(参考文档)
72
: 该对象是一个对类的描述 (final static byte TC_CLASSDESC = (byte)0x72;
)(参考文档)
0009 5365 7269 616c 697a 65
: 类名长度(9
)及其名称(Serialize
)
05 c43d d622 f99a 5b
: 这八位是用来验证该类是否被修改过的验证码. 因为我们没有在实现Serializable
接口后添加serialVersionUID
字段, 所以JVM会自动帮助我们生成一个
02
: 序列化中标识类版本 ; 该数值也是可以在ObjectStreamConstants
接口中找到. (final static byte SC_SERIALIZABLE = 0x02;
)。如是否是Block Data模式、自定义writeObject()
,Serializable
、Externalizable
或Enum
类型等
0002
: 类的字段个数(2
个,public_int
和public_str
)
4900 0a70 7562 6c69 635f 696e 744c 000a 7075 626c 6963 5f73 7472 7400 124c 6a61 7661 2f6c 616e 672f 5374 7269 6e67 3b
: 字段名字符串长度和值,非原始数据类型的字段还会在后面加上数据类型标识、完整类型签名长度和值。
49
: 整数类型(int
)签名的第一个字节,之后的4c
为字符串类型签名的第一个字节 (类型签名表示与JVM规范中的定义相同)00 0a
: 第一个字段名长度(len('public_int')=0x0a
)70 7562 6c69 635f 696e 74
: 字符串 public_int
4c
: 表示一个对象类型(String
属于对象, 不属于基本类型)000a
: 第二个字段名长度(len('public_str')=0x0a
)7075 626c 6963 5f73 7472
: 字符串 public_str
74
: 字段类型为00 12
: 字段类型类名长度为18(len('Ljava/lang/String;')
)4c 6a61 7661 2f6c 616e 672f 5374 7269 6e67 3b
: 字符串 Ljava/lang/String;
78
Block Data结束标识
70
父类描述符标识,此处没有就为70
00 001e 61
: 7777
的16进制形式,整数字段public_int
的值 (Java序列化中的整数格式标准)
74 0004 7465 7374
: 非原始数据类型的字段则会按对象的方式处理,字符串字段public_str
被识别为字符串类型,输出字符串类型标识、字符串长度和值
74
: 字段类型0004
: 字符串长度len('test')=0x04
7465 7374
: 字符串test
通过对比,可以看出java和php序列化结果的一些异同。值得注意的是,Java序列化中对字段进行封装时,会按原始和非原始数据类型排序,其中又会按字段名再排序。
PS. 如果想让某个变量不被序列化,可使用transient
修饰。
恢复对象,反序列化读取的仅仅是Java对象的数据,而不是Java类,因此采用反序列化恢复Java对象时,必须提供该Java对象所属类的class
文件,否则将会引发ClassNotFoundException
异常
在刚才的Serialize.java
目录下创建Unserialize.java
步骤一:读取序列化文件./Serialize.ser
并创建一个ObjectInputStream
输入流
步骤二:调用ObjectInputStream
对象的readObject()
得到序列化的对象。
xxxxxxxxxx
import java.io.*;
public class Unserialize{
public static void main(String[] args) throws Exception{
try(
FileInputStream fileIn = new FileInputStream("./Serialize.ser");
ObjectInputStream in = new ObjectInputStream(fileIn)
){
Serialize ser = (Serialize) in.readObject();
System.out.println(ser.public_str);
System.out.println(ser.public_int);
}
}
}
输出为
xxxxxxxxxx
$ java Unserialize
test
7777
xxxxxxxxxx
ANY-ACCESS-MODIFIER Object writeReplace() throws ObjectStreamException;
ANY-ACCESS-MODIFIER Object readResolve() throws ObjectStreamException;
writeReplace
: 在序列化时,会先调用此方法,再调用writeObject
方法。实际序列化的对象将是作为writeReplace
方法返回值的对象
xxxxxxxxxx
import java.io.*;
public class SerializeReplace implements java.io.Serializable{
private Object writeReplace() throws ObjectStreamException{
SerializeReplace sr = new SerializeReplace();
sr.public_str = "replace";
sr.public_int = 6666666;
return sr;
}
public String public_str;
public int public_int;
public SerializeReplace(){
this.public_str = "test";
this.public_int = 7777;
}
public static void main(String[] args) throws IOException{
try(
FileOutputStream fileOut = new FileOutputStream("./SerializeReplace.ser");
ObjectOutputStream out = new ObjectOutputStream(fileOut)
){
out.writeObject(new SerializeReplace());
}
}
}
序列化内容:
xxxxxxxxxx
00000000: aced 0005 7372 0010 5365 7269 616c 697a ....sr..Serializ
00000010: 6552 6570 6c61 6365 ee6b 5166 4541 08f2 eReplace.kQfEA..
00000020: 0200 0249 000a 7075 626c 6963 5f69 6e74 ...I..public_int
00000030: 4c00 0a70 7562 6c69 635f 7374 7274 0012 L..public_strt..
00000040: 4c6a 6176 612f 6c61 6e67 2f53 7472 696e Ljava/lang/Strin
00000050: 673b 7870 0065 b9aa 7400 0772 6570 6c61 g;xp.e..t..repla
00000060: 6365 ce
readResolve
: 反序列化时替换反序列化出的对象,反序列化出来的对象被立即丢弃。此方法在readeObject
后调用。
xxxxxxxxxx
import java.io.*;
public class UnserializeResolve implements java.io.Serializable{
private Object readResolve() throws ObjectStreamException{
UnserializeResolve ur = new UnserializeResolve();
ur.public_str = "cccc";
return ur;
}
public String public_str;
public UnserializeResolve(){
this.public_str = "test";
}
public static void main(String[] args) throws Exception{
write();
read();
}
static void write() throws Exception{
try(
FileOutputStream fileOut = new FileOutputStream("./UnserializeResolve.ser");
ObjectOutputStream out = new ObjectOutputStream(fileOut)
){
out.writeObject(new UnserializeResolve());
}
}
static void read() throws Exception{
try(
FileInputStream fileIn = new FileInputStream("./UnserializeResolve.ser");
ObjectInputStream in = new ObjectInputStream(fileIn)
){
UnserializeResolve ur = (UnserializeResolve) in.readObject();
System.out.println(ur.public_str);
}
}
}
序列化内容(最后的数据为test
):
xxxxxxxxxx
$ xxd UnserializeResolve.ser
00000000: aced 0005 7372 0012 556e 7365 7269 616c ....sr..Unserial
00000010: 697a 6552 6573 6f6c 7665 c036 34b8 8c87 izeResolve.64...
00000020: 0ad2 0200 014c 000a 7075 626c 6963 5f73 .....L..public_s
00000030: 7472 7400 124c 6a61 7661 2f6c 616e 672f trt..Ljava/lang/
00000040: 5374 7269 6e67 3b78 7074 0004 7465 7374 String;xpt..test
输出为cccc
:
xxxxxxxxxx
$ java UnserializeResolve
cccc
PS. readResolve
与writeReplace
的访问修饰符可以是private
、protected
、public
,如果父类重写了这两个方法,子类都需要根据自身需求重写,这显然不是一个好的设计。通常建议对于final
修饰的类重写readResolve
方法没有问题;否则,重写readResolve
使用private
修饰。
通过实现Externalizable
接口,必须实现writeExternal
、readExternal
方法。
xxxxxxxxxx
public void writeExternal(ObjectOutput out) throws IOException;
public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException;
Externalizable
是Serializable
的一个子类,但不同于Serializable
接口,实现此接口必须实现接口中的两个方法实现自定义序列化;同时还必须提供public
的无参构造器,因为在反序列化的时候需要反射创建对象。
按顺序地写入public_int = 7777
和public_str = "test"
xxxxxxxxxx
import java.io.*;
public class Externalize implements java.io.Externalizable{
public int public_int;
public String public_str;
public void writeExternal(ObjectOutput out) throws IOException{
out.writeInt(this.public_int);
out.writeObject(this.public_str);
}
public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException{
}
public Externalize(){
this.public_int = 7777;
this.public_str = "test";
}
public static void main(String[] args) throws Exception{
try(
FileOutputStream fileOut = new FileOutputStream("./Externalize.ser");
ObjectOutputStream out = new ObjectOutputStream(fileOut)
){
out.writeObject(new Externalize());
}
}
}
序列化内容(0x1e61 == 7777
):
xxxxxxxxxx
00000000: aced 0005 7372 000b 4578 7465 726e 616c ....sr..External
00000010: 697a 65e0 c97e 590d 77b9 c10c 0000 7870 ize..~Y.w.....xp
00000020: 7704 0000 1e61 7400 0474 6573 7478 w....at..testx
相比之前的序列化,明显可以看出这里并未写入字段名(public_int
或public_str
),仅有其值。
下面是按顺序写入public_int = 7777
, public_str = "test"
, public_int2 = 8888
. 自定义只读取第一个写入的7777
xxxxxxxxxx
import java.io.*;
public class ExternalizeTest implements java.io.Externalizable{
public String public_str;
public int public_int;
public int public_int2;
public void writeExternal(ObjectOutput out) throws IOException{
out.writeInt(this.public_int);
out.writeObject(this.public_str);
out.writeInt(this.public_int2);
}
public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException{
System.out.println(in.readInt());
// System.out.println(in.readObject());
// System.out.println(in.readInt());
}
public ExternalizeTest(){
this.public_int = 7777;
this.public_str = "test";
this.public_int2 = 8888;
}
public static void main(String[] args) throws Exception{
write();
read();
}
static void write() throws Exception{
try(
FileOutputStream fileOut = new FileOutputStream("./ExternalizeTest.ser");
ObjectOutputStream out = new ObjectOutputStream(fileOut)
){
out.writeObject(new ExternalizeTest());
}
}
static void read() throws Exception{
try(
FileInputStream fileIn = new FileInputStream("./ExternalizeTest.ser");
ObjectInputStream in = new ObjectInputStream(fileIn)
){
ExternalizeTest ex = (ExternalizeTest) in.readObject();
}
}
}
此时序列化内容为( 0x1e61 == 7777
;0x22b8 == 8888
):
xxxxxxxxxx
$ xxd ExternalizeTest.ser
00000000: aced 0005 7372 000b 4578 7465 726e 616c ....sr..External
00000010: 697a 659f d4ba 3d0a 0758 fa0c 0000 7870 ize...=..X....xp
00000020: 7704 0000 1e61 7400 0474 6573 7477 0400 w....at..testw..
00000030: 0022 b878 .".x
输出:
xxxxxxxxxx
$ java ExternalizeTest
7777
PS. 使用Externalizable
时,必须按照写入时的确切顺序读取所有字段状态。否则会产生异常。