Java 对象序列化机制详解
对象序列化的目标:将对象保存到磁盘中,或允许在网络中直接传输对象。
对象序列化机制允许把内存中的Java对象转换成平台无关的二进制流,从而允许把这种二进制流持久的保存在磁盘上,通过网络将这种二进制流传输到另一个网络节点。其他程序一旦获得了这种二进制流,都可以讲这种二进制流恢复成原来的Java对象。
如果需要让某个对象支持序列化机制,则必须让它的类是可序列化的,则这个类必须实现如下两个接口之一:
· Serializable
· Externalizable
Serializable是一个标志接口,它只是表明该类的实例是可序列化的。
一、 使用对象流实现序列化
一旦某个类实现了Serializable接口,则该类的对象就是可序列化的,程序通过如下步骤创建可序列化对象:
1) 创建一个ObjectOutStream,这个输出流是一个处理流:
ObjectOutputStream oos = new ObjectOutputStream("object.txt");
2) 调用ObjectOutputStream对象的writeObject()方法输出可序列化对象:
public class Person implements java.io.Serializable
{
public String name; public int age; // 构造方法 // setter和getter方法
}
使用ObjectOutputStream将一个Person对象写入磁盘文件:
public class WriteObject
{
public static void main(String[] args) throws Exception
{
ObjectOutputStream oos = new ObjectOutputStream("object.txt");
Person per = new Person("沉缘",25);
oos.writeObject(per);
}
}
通过ObjectOutputStream,我们将Person对象保存到了文件中(硬盘),我们可以看到在当前目录中已经有了object.txt文件。
如果希望从二进制流中恢复对象,则可以通过反序列化机制,步骤如下:
1) 创建一个ObjectInputStream输入流,这个输入流是一个处理流,所以必须建立在其他节点流的基础上。
FileInputStream fis = new FileInputStream("object.txt");
ObjectInputStream ois = new ObjectInputStream(fis);
2) 调用ObjectInputStream对象的readObject()方法读取流中的对象,该方法返回一个Object类型的Java对象,可对该对象进行强制转换:
Person per= (Person) ois.readObject();
ois.close();
实例:
public class ReadObject
{
public static void main(String[] args)
{
try(
// 创建一个ObjectInputStream输入流
ObjectInputStream ois = new ObjectInputStream(
new FileInputStream("object.txt")))
{
// 从输入流中读取一个Java对象,并将其强制类型转换为Person类
Person p = (Person)ois.readObject();
System.out.println("名字为:" + p.getName()
+ "\n年龄为:" + p.getAge());
}
catch (Exception ex)
{
ex.printStackTrace();
}
}
}
反序列化读取的仅仅是Java对象的数据,而不是Java类,因此采用反序列化恢复Java对象时,必须提供该对象所属类的class文件,否则将会引发ClassNotFoundException异常。
反序列化机制无须通过构造器来初始化Java对象。
二、 对象引用的序列化
在对象的嵌套过程中,比如Teacher类中有Person对象,如果希望Teacher对象是可序列化的,则Person对象也必须是可序列化的。
class Teacher implements java.io.Serializable
{
private String name; private Person student; //构造方法 //setter、getter方法
}
序列化机制的算法:
· 所有保存到磁盘中的对象都有一个序列化编号。
· 当程序试图序列化一个对象时,程序将先检查该对象是否已经被序列化过,只有该对象从未被序列化过,系统才会将该对象转换成字节序列输出。
· 如果某个对象已经被序列化过,程序将只是直接输出一个序列化编号,而不是再次重新序列化该对象。
下面程序序列化两个Teacher对象,两个Teacher对象都持有一个引用到同一个Person对象的引用,而且程序两次调用writeObject()方法输出同一个Teacher对象。
public class WriteTeacher
{
public static void main(String[] args) throws Exception{
ObjectOutputStream oos = new ObjectOutputStream("object.txt");
Person per = new Person("沉缘",25);
Teacher t1 = new Teacher("无情",<span style="font-family: SimSun;">per</span>);
Teacher t2 = new Teacher("无缘",per); oos.writeObject(t1);
oos.writeObject(t2);
oos.writeObject(per);
oos.writeObject(t2); oos.close();
}
}
上述程序,我们看着是序列化了四个对象,实际上只有三个,而且序列中的两个Teacher对象的student引用实际上时用一个Person对象。
接下来,我们读取引用:
public class ReadTeacher
{
public static void main(String[] args)
{
try(
// 创建一个ObjectInputStream输出流
ObjectInputStream ois = new ObjectInputStream(
new FileInputStream("teacher.txt")))
{
// 依次读取ObjectInputStream输入流中的四个对象
Teacher t1 = (Teacher)ois.readObject();
Teacher t2 = (Teacher)ois.readObject();
Person p = (Person)ois.readObject();
Teacher t3 = (Teacher)ois.readObject();
// 输出true
System.out.println("t1的student引用和p是否相同:"
+ (t1.getStudent() == p));
// 输出true
System.out.println("t2的student引用和p是否相同:"
+ (t2.getStudent() == p));
// 输出true
System.out.println("t2和t3是否是同一个对象:"
+ (t2 == t3));
}
catch (Exception ex)
{
ex.printStackTrace();
}
}
}
此时,我们应该注意到一个问题,那就是,序列化一个可变对象时,只有第一次使用writeObject()方法输出时才会将该对象转换成字节序列并输出,当程序再次调用writeObject()方法时,程序只是输出前面的序列化编号,及时后面对象的Field值已改变,改变的Field值也不会被输出。
public class SerializeMutable
{
public static void main(String[] args)
{ try(
// 创建一个ObjectOutputStream输入流
ObjectOutputStream oos = new ObjectOutputStream(
new FileOutputStream("mutable.txt"));
// 创建一个ObjectInputStream输入流
ObjectInputStream ois = new ObjectInputStream(
new FileInputStream("mutable.txt")))
{
Person per = new Person("孙悟空", 500);
// 系统会per对象转换字节序列并输出
oos.writeObject(per);
// 改变per对象的name Field
per.setName("猪八戒");
// 系统只是输出序列化编号,所以改变后的name不会被序列化
oos.writeObject(per);
Person p1 = (Person)ois.readObject(); //①
Person p2 = (Person)ois.readObject(); //②
// 下面输出true,即反序列化后p1等于p2
System.out.println(p1 == p2);
// 下面依然看到输出"孙悟空",即改变后的Field没有被序列化
System.out.println(p2.getName());
}
catch (Exception ex)
{
ex.printStackTrace();
}
}
}
三、 自定义序列化
通过在Field(属性)前使用transient关键字,可以指定Java序列化时无须理会该Field。
public class Person
implements java.io.Serializable
{
private String name;
private transient int age;
// 注意此处没有提供无参数的构造器!
public Person(String name , int age)
{
System.out.println("有参数的构造器");
this.name = name;
this.age = age;
}
// 省略name与age的setter和getter方法 }
测试该Person对象:
public class TransientTest
{
public static void main(String[] args)
{
try(
// 创建一个ObjectOutputStream输出流
ObjectOutputStream oos = new ObjectOutputStream(
new FileOutputStream("transient.txt"));
// 创建一个ObjectInputStream输入流
ObjectInputStream ois = new ObjectInputStream(
new FileInputStream("transient.txt")))
{
Person per = new Person("孙悟空", 500);
// 系统会per对象转换字节序列并输出
oos.writeObject(per);
Person p = (Person)ois.readObject();
System.out.println(p.getName());
System.out.println(p.getAge());
}
catch (Exception ex)
{
ex.printStackTrace();
}
}
}
输出:
有参数的构造器
孙悟空0
观察输出,获取的age值为0, 说明在序列化时,该age属性并未被序列化。
四、 Externalizable接口
该接口提供的序列化机制,完全由程序员决定存储和恢复对象数据。Externalizable接口中两个需实现的方法。
void |
readExternal(ObjectInput in)
The object implements the readExternal method to restore its contents by calling the methods of DataInput for primitive types and readObject for objects, strings and arrays.
|
void |
writeExternal(ObjectOutput out)
The object implements the writeExternal method to save its contents by calling the methods of DataOutput for its primitive values or calling the writeObject method of ObjectOutput for objects, strings, and arrays.
|
我们举个例子,看下如何使用该接口来序列化对象。
public class Person
implements java.io.Externalizable
{
private String name;
private int age;
// 注意此处没有提供无参数的构造器!
public Person(String name , int age)
{
System.out.println("有参数的构造器");
this.name = name;
this.age = age;
}
// 省略name与age的setter和getter方法 // name的setter和getter方法
public void setName(String name)
{
this.name = name;
}
public String getName()
{
return this.name;
} // age的setter和getter方法
public void setAge(int age)
{
this.age = age;
}
public int getAge()
{
return this.age;
} public void writeExternal(java.io.ObjectOutput out)
throws IOException
{
// 将name Field的值反转后写入二进制流
out.writeObject(new StringBuffer(name).reverse());
out.writeInt(age);
}
public void readExternal(java.io.ObjectInput in)
throws IOException, ClassNotFoundException
{
// 将读取的字符串反转后赋给name Field
this.name = ((StringBuffer)in.readObject()).reverse().toString();
this.age = in.readInt();
}
}
两种序列化机制对比:
对象序列化需要注意:
1. 对象的类名、Field都会被序列化; 方法、static Field、transient Field都不会被序列化。
2. 实现Serializable接口的类如果需要让某个Field不被序列化,则可以在该Field前添加transient私事符。
3. 保证序列化对象的Field类型也是可序列化的。
4. 反序列化对象时必须有序列化对象的class文件。
5. 当通过文件、网络来读取序列化后的对象时,必须按实际写入的顺序读取。
五、 版本
在对象进行序列化或者反序列化操作的时候,要考虑JDK版本问题。如果序列化的JDK版本和反序列化的版本不一致,则可能出现异常。
因此,可以在序列化操作中引入一个serialVersionUID的长了,通过此常量验证版本的一致性。
import java.io.Serializable ;
public class Person implements Serializable{
private static final long serialVersionUID = 1L;
private String name ; // 声明name属性,但是此属性不被序列化
private int age ; // 声明age属性
public Person(String name,int age){ // 通过构造设置内容
this.name = name ;
this.age = age ;
}
public String toString(){ // 覆写toString()方法
return "姓名:" + this.name + ";年龄:" + this.age ;
}
};
Java 对象序列化机制详解的更多相关文章
- Java中反射机制详解
序言 在学习java基础时,由于学的不扎实,讲的实用性不强,就觉得没用,很多重要的知识就那样一笔带过了,像这个马上要讲的反射机制一样,当时学的时候就忽略了,到后来学习的知识中,很多东西动不动就用反射, ...
- Java 动态代理机制详解
在学习Spring的时候,我们知道Spring主要有两大思想,一个是IoC,另一个就是AOP,对于IoC,依赖注入就不用多说了,而对于Spring的核心AOP来说,我们不但要知道怎么通过AOP来满足的 ...
- 《精通Hibernate:Java对象持久化技术详解》目录
图书信息:孙卫琴 电子工业出版社 第1章 Java应用分层架构及软件模型: 1.1 应用程序的分层体系结构 1.1.1 区分物理层和逻辑层 1.1.2 软件层的特征 1.1.3 软件分层的优点 1.1 ...
- Java中String对象创建机制详解()
一String 使用 private final char value来实现字符串存储 二Java中String的创建方法四种 三在深入了解String创建机制之前要先了解一个重要概念常量池Const ...
- Java动态代理机制详解(JDK 和CGLIB,Javassist,ASM)
class文件简介及加载 Java编译器编译好Java文件之后,产生.class 文件在磁盘中.这种class文件是二进制文件,内容是只有JVM虚拟机能够识别的机器码.JVM虚拟机读取字节码文件,取出 ...
- Java 动态代理机制详解(JDK 和CGLIB,Javassist,ASM)
class文件简介及加载 Java编译器编译好Java文件之后,产生.class 文件在磁盘中.这种class文件是二进制文件,内容是只有JVM虚拟机能够识别的机器码.JVM虚拟机读取字节码文件,取出 ...
- Java的内存机制详解
Java把内存分为两种:一种是栈内存,另一种是堆内存.在函数中定义的一些基本类型的变量和对象的引用变量都是在函数的栈内存中分配,当在一段代码块定义一个变量时,Java 就在栈中为这个变量分配内存空间, ...
- Java动态代理机制详解(类加载,JDK 和CGLIB,Javassist,ASM)
class文件简介及加载 Java编译器编译好Java文件之后,产生.class 文件在磁盘中.这种class文件是二进制文件,内容是只有JVM虚拟机能够识别的机器码.JVM虚拟机读取字节码文件,取出 ...
- Java垃圾回收机制详解和调优
gc即垃圾收集机制是指jvm用于释放那些不再使用的对象所占用的内存.java语言并不要求jvm有gc,也没有规定gc如何工作.不过常用的jvm都有gc,而且大多数gc都使用类似的算法管理内存和执行收集 ...
随机推荐
- LUGOU 3959 宝藏 (noip 2017 day2 T2)
传送门 解题思路 去年noip现在拿来写..思路还是听清楚的,记忆化搜索,f[S]表示现在选了集合S时的最小代价,dis[i]表示达到最优时i这个点的深度.f[S| (1< < i-1) ...
- sql server 创建视图添加表时出现从其他数据库导入的表未显示出来
创建视图添加表时出现从其他数据库导入的表未显示出来,通过数据库刷新,也不能解决.关闭SQL server management studio 后,再次进入,在创建视图的时候添加表的列表就出现了新导入的 ...
- 痞子衡嵌入式:飞思卡尔i.MX RTyyyy系列MCU外设那些事(2)- 善变的FlexRAM
大家好,我是痞子衡,是正经搞技术的痞子.今天痞子衡给大家介绍的是飞思卡尔i.MX RTyyyy系列MCU的FlexRAM外设. 本文是外设系列第二篇,上一篇讲的是离内核最近的高速缓存L1 Cache, ...
- FPGA按键功能
1.如何判断按键成功按下? 2.在什么时候采集数据? 按键在按下的过程中会产生大约2ms-3ms抖动,如果此时此刻采集数据来判断按键是不准确的,那么为了采集到准确的数据需要设置一个大约10ms左右的计 ...
- Sublime Text编辑器运行Python程序控制台输入
将文件保存为 .py后,安装插件: 1)按ctrl+shift+p快捷键呼出一个输入框,输入Install Package,回车,在新出现的输入框里输入“SublimeREPL”并安装. 2)点击To ...
- oracle包头包体
补充说明:包头和包体可以以java的接口来理解,包头像java的接口,包体像java接口的实现类. 一 包的组成 包头(package):包头部分申明包内数据类型,常量,变量,游标,子程序和异常错误处 ...
- 【水滴石穿】react-native-aze
说个题外话,早上打开电脑的时候,电脑变成彩色的了,锅是我曾经安装的一个chrome扩展,没有经过我的同意开启了 (也许是昨天迷迷糊糊开启了) 上午运行项目都不成功,还以为被黑客攻击了---然后下午就排 ...
- HTML5拖放API实现拖放排序的实例代码
想要拖放某个元素,必须设置该元素的 draggable 属性为 true,当该属性为 false 时,将不允许拖放.而 img 元素和 a 元素都默认设置了 draggable 属性为 true,可直 ...
- 《2019年上半年Web应用安全报告》发布:90%以上攻击流量来源于扫描器,IP身份不再可信
Web应用安全依然是互联网安全的最大威胁来源之一,除了传统的网页和APP,API和各种小程序也作为新的流量入口快速崛起,更多的流量入口和更易用的调用方式在提高web应用开发效率的同时也带来了更多和更复 ...
- 数据库lib7第2, 3题(创建索引和触发器)
2. 分别为上述建立的表格建立适当的索引,请描述建立索引的过程(可以截图或者写SQL).其中,要求对SPJ标中的SNo, PNo字段各建立一个索引,为(PNo, JNo)的组合建立一个索引.请问,SN ...