你了解Java序列化与反序列化吗
2021/5/14 20:32:00
本文主要是介绍你了解Java序列化与反序列化吗,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!
本文主要了解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); }
这篇关于你了解Java序列化与反序列化吗的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!
- 2024-06-26结对编程到底难不难?答案在这里
- 2024-06-19《2023版Java工程师》课程升级公告
- 2024-06-15matplotlib作图不显示3D图,怎么办?
- 2024-06-1503-Loki 日志监控
- 2024-06-1504-让LLM理解知识 -Prompt
- 2024-06-05做软件测试需要懂代码吗?
- 2024-06-0514-ShardingSphere的分布式主键实现
- 2024-06-03为什么以及如何要进行架构设计权衡?
- 2024-05-31全网首发第二弹!软考2024年5月《软件设计师》真题+解析+答案!(11-20题)
- 2024-05-31全网首发!软考2024年5月《软件设计师》真题+解析+答案!(21-30题)