本文主要了解Java的对象序列化的使用,序列化与反序列化源码追踪,Serializable接口、Externalizable接口、transient关键字、ObjectOutputStream、ObjectInputStream的运用。
说说我个人的理解,对象序列化就是将程序中的对象转换为其他形式的存在,某介质的存储于对象之间是可以随意转换,如人民币可以转换为黄金,可以转换为美元等等,序列化就是将数据转换为其他形式的存在。
那么在程序设计中存储数据的格式存在有多种,如:JSON、XML、YAML 是数据的存储格式,也是序列的一种,再者如编码字符集、base64、或者其他等等,使用自定义的编码规则,对数据进行转义编码,类比 y=f(x),x经f序列化得到y,y经f-1反序列化则可以得到x,但在Java中,有一种专属与Java的的序列化工具ObjectOutputStream,可以将Java的对象直接序列化为数据,且该数据还能反序列化为Java对象。
Java中的对象要能被序列化,在class中必须实现Serializable接口(若不实现Serializable接口,在序列化则会出现异常java.io.NotSerializableException)
// remaining cases
if (obj instanceof String) {
writeString((String) obj, unshared);
} else if (cl.isArray()) {
writeArray(obj, desc, unshared);
} else if (obj instanceof Enum) {
writeEnum((Enum<?>) obj, desc, unshared);
} else if (obj instanceof Serializable) {
writeOrdinaryObject(obj, desc, unshared);
} else {
if (extendedDebugInfo) {
throw new NotSerializableException(
cl.getName() + "\n" + debugInfoStack.toString());
} else {
throw new NotSerializableException(cl.getName());//异常就是在这里抛出的
}
}
开始上代码,新建一个User类,包含属性,实现序列化接口,如果有不需要序列化的字段可以加transient修饰
package csdn.io;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.ObjectStreamClass;
import java.io.OutputStream;
import java.io.Serializable;
public class User implements Serializable {
private static final long serialVersionUID = -6775433616501122464L;
String name;
transient String id;
public User() {
}
public User(String name, String id) {
super();
this.name = name;
this.id = id;
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "User [name=" + name + ", id=" + id + "]";
}
public static void main(String[] args) {
try {
User user = new User("denglintao", "1");
String file = "E://1.txt";
OutputStream out = new FileOutputStream(file);
ObjectOutputStream objectOutputStream = new ObjectOutputStream(out);
objectOutputStream.writeObject(user);
objectOutputStream.close();
FileInputStream fileInputStream = new FileInputStream(file);
ObjectInputStream objectInputStream = new ObjectInputStream(fileInputStream);
Object readObject = objectInputStream.readObject();
System.out.println(readObject);
} catch (Exception e) {
e.printStackTrace();
}
}
}
主测试类debug调试观察
debug运行调试可以发现,打开序列化的文件,使用的是记事本默认编码,
可以简单看出 好像有个java类的包名加类名 csdn.io.User 还有一个字符串属性String值为denglintao,别的看不懂了,简单来说该文件就是记录User类的元数据结构信息,与各个结构数据对应的值,16进制显示如下:
可以发现transient修饰的id字段没有被序列化,console输出如下:
User [name=denglintao, id=null]
此时如果属性使用transient修饰就真的无法序列化吗?答案是非,接下来上代码,新建User1继承User实现 Externalizable接口,重写了writeExternal方法与readExernal方法,这两个方法分别实现的功能是将java对象序列化与反序列化的属性自行设置
package csdn.io;
import java.io.Externalizable;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInput;
import java.io.ObjectInputStream;
import java.io.ObjectOutput;
import java.io.ObjectOutputStream;
import java.io.ObjectStreamClass;
import java.io.OutputStream;
public class User1 extends User implements Externalizable{
public User1() {
}
public User1(String string, String string2) {
super(string,string2);
}
@Override
public void writeExternal(ObjectOutput out) throws IOException {
out.writeObject(this.id);
out.writeObject(this.name);//自定义序列化的字段 transient 修饰也无用
}
@Override
public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
//顺序与序列化一致
this.id = (String) in.readObject();
this.name = (String) in.readObject();
}
public static void main(String[] args) {
try {
User1 user = new User1("denglintao", "1");
String file = "E://1.txt";
OutputStream out = new FileOutputStream(file);
ObjectOutputStream objectOutputStream = new ObjectOutputStream(out);
objectOutputStream.writeObject(user);
objectOutputStream.close();
FileInputStream fileInputStream = new FileInputStream(file);
ObjectInputStream objectInputStream = new ObjectInputStream(fileInputStream);
Object readObject = objectInputStream.readObject();
System.out.println(readObject);
} catch (Exception e) {
e.printStackTrace();
}
}
}
经debug调试可以发现transient修饰的属性也是可以被序列化的,Externalizable重写的两个方法就是手工配置自定义序列化的属性,输出结果如下:
User [name=denglintao, id=1]
跟踪源码可以发现Externalizable是继承了Serializable接口,而外新增了两个方法
package java.io;
import java.io.ObjectOutput;
import java.io.ObjectInput;
public interface Externalizable extends java.io.Serializable {
void writeExternal(ObjectOutput out) throws IOException;
void readExternal(ObjectInput in) throws IOException, ClassNotFoundException;
}
然后在ObjectOutputStream发现如下,当是Externalizable与Serializable走的是不同的分支实现
/**
* Writes representation of a "ordinary" (i.e., not a String, Class,
* ObjectStreamClass, array, or enum constant) serializable object to the
* stream.
*/
private void writeOrdinaryObject(Object obj,
ObjectStreamClass desc,
boolean unshared)
throws IOException
{
if (extendedDebugInfo) {
debugInfoStack.push(
(depth == 1 ? "root " : "") + "object (class \"" +
obj.getClass().getName() + "\", " + obj.toString() + ")");
}
try {
desc.checkSerialize();
bout.writeByte(TC_OBJECT);
writeClassDesc(desc, false);
handles.assign(unshared ? null : obj);
if (desc.isExternalizable() && !desc.isProxy()) {//此处进行了不同的序列化策略分支处理
writeExternalData((Externalizable) obj);
} else {
writeSerialData(obj, desc);
}
} finally {
if (extendedDebugInfo) {
debugInfoStack.pop();
}
}
}
那么writeSerialData序列化兴趣了可以继续往下追踪,这里就简单说下,如何选择过滤transient关键字修饰的字段,可以使用类反射拿到访问修饰符的值class.getModifiers()
那么如何判断属性是否有transient关键字修饰,可使用Modifier.isTransient(Modifier.TRANSIENT) 来进行判断,java语言的内部修饰符使用了位图来标识是否有某关键字,可以看见Modifier.TRANSIENT=128 二进制位图10000000只要进行"与"运算结果是1,那么该位图值就是1,所以存在该修饰符,同理判断是否含有其他修饰符原理一致
/**
* Return {@code true} if the integer argument includes the
* {@code transient} modifier, {@code false} otherwise.
*
* @param mod a set of modifiers
* @return {@code true} if {@code mod} includes the
* {@code transient} modifier; {@code false} otherwise.
*/
public static boolean isTransient(int mod) {
return (mod & TRANSIENT) != 0;
}
反之,反序列化也是同理,使用ObjectInputStream读取序列化输出的文件,进行反序列化,这里,我们重写ObjectInputStream的一个方法打个断点
public static void main(String[] args) {
try {
User1 user = new User1("denglintao", "1");
String file = "E://1.txt";
OutputStream out = new FileOutputStream(file);
ObjectOutputStream objectOutputStream = new ObjectOutputStream(out);
objectOutputStream.writeObject(user);
objectOutputStream.close();
FileInputStream fileInputStream = new FileInputStream(file);
ObjectInputStream objectInputStream = new ObjectInputStream(fileInputStream) {
@Override
protected Class<?> resolveClass(ObjectStreamClass desc) throws IOException, ClassNotFoundException {
String name = desc.getName();//断点
return super.resolveClass(desc);
}
};
Object readObject = objectInputStream.readObject();
System.out.println(readObject);
} catch (Exception e) {
e.printStackTrace();
}
}
debug调试可以发现ObjectStreamClass存储着反序列化class的元数据信息,resolveClass方法就是使用类反射加载得到一个class,后续就是使用该class创建一个实例对象,往对象里面set值
protected Class<?> resolveClass(ObjectStreamClass desc)
throws IOException, ClassNotFoundException
{
String name = desc.getName();
try {
return Class.forName(name, false, latestUserDefinedLoader());
} catch (ClassNotFoundException ex) {
Class<?> cl = primClasses.get(name);
if (cl != null) {
return cl;
} else {
throw ex;
}
}
}
如果是Externalizable接口的反序列化就手工反序列化设置值,否则就是普通根据transient修饰然后去除属性之类的设值
if (desc.isExternalizable()) {
readExternalData((Externalizable) obj, desc);
} else {
readSerialData(obj, desc);
}