Java Serializable的使用和transient关键字使用小记(转载)
一:Serializable
1、持久化的简单介绍:
“持久化”意味着对象的“生存时间”并不取决于程序是否正在执行——它存在或“生存”于程序的每一次调用之间。通过序列化一个对象,将其写入磁盘,以后在程序再次调用时重新恢复那个对象,就能圆满实现一种“持久”效果。
2、什么情况下需要序列化
a)当你想把的内存中的对象状态保存到一个文件中或者数据库中时候;
b)当你想用套接字在网络上传送对象的时候;
c)当你想通过RMI传输对象的时候;
3、当对一个对象实现序列化时,究竟发生了什么?
在没有序列化前,每个保存在堆(Heap)中的对象都有相应的状态(state),即实例变量(instance ariable)比如:
- Foo myFoo = new Foo();
- myFoo .setWidth(37);
- myFoo.setHeight(70);
当通过下面的代码序列化之后,MyFoo对象中的width和Height实例变量的值(37,70)都被保存到foo.ser文件中,这样以后又可以把它 从文件中读出来,重新在堆中创建原来的对象。当然保存时候不仅仅是保存对象的实例变量的值,JVM还要保存一些小量信息,比如类的类型等以便恢复原来的对象。
- FileOutputStream fs = new FileOutputStream("foo.ser");
- ObjectOutputStream os = new ObjectOutputStream(fs);
- os.writeObject(myFoo);
- os.close();
- ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream("foo.ser"));
- int width= (Integer) objectInputStream.readObject();
- int height = (Integer) objectInputStream.readObject();
- objectInputStream.close();
4、语言里增加了对象序列化的概念后,可提供对两种主要特性的支持:
- 远程方法调用(RMI)使本来存在于其他机器的对象可以表现出好象就在本地机器上的行为。将消息发给远程对象时,需要通过对象序列化来传输参数和返回值。
- 使用一个Java Bean 时,它的状态信息通常在设计期间配置好。程序启动以后,这种状态信息必须保存下来,以便程序启动以后恢复;具体工作由对象序列化完成。
5、Serializable的一些说明:
- 对象的序列化处理非常简单,只需对象实现了Serializable 接口即可(该接口仅是一个标记,没有方法)
- 序列化的对象包括基本数据类型,所有集合类以及其他许多东西,还有Class 对象
- 对象序列化不仅保存了对象的“全景图”,而且能追踪对象内包含的所有句柄并保存那些对象;接着又能对每个对象内包含的句柄进行追踪
- 使用transient关键字修饰的的变量,在序列化对象的过程中,该属性不会被序列化。
6、序列化和反序列化的步骤:
序列化:
- 首先要创建某些OutputStream对象:OutputStream outputStream = new FileOutputStream("output.txt")
- 将其封装到ObjectOutputStream对象内:ObjectOutputStream objectOutputStream = new ObjectOutputStream(outputStream);
- 此后只需调用writeObject()即可完成对象的序列化,并将其发送给OutputStream:objectOutputStream.writeObject(Object);
- 最后不要忘记关闭资源:objectOutputStream.close(), outputStream .close();
反序列化:
- 首先要创建某些OutputStream对象:InputStream inputStream= new FileInputStream("output.txt")
- 将其封装到ObjectInputStream对象内:ObjectInputStream objectInputStream= new ObjectInputStream(inputStream);
- 此后只需调用readObject()即可完成对象的反序列化:objectInputStream.readObject();
- 最后不要忘记关闭资源:objectInputStream.close(),inputStream.close();
7、serialVersionUID
实现了Serializable接口之后,Eclipse就会提示你增加一个 serialVersionUID,虽然不加的话上述程序依然能够正常运行。
序列化 ID 在 Eclipse 下提供了两种生成策略
一个是固定的 1L
一个是随机生成一个不重复的 long 类型数据(实际上是使用 JDK 工具,根据类名、接口名、成员方法及属性等来生成)
如果是通过网络传输的话,如果被序列化类的serialVersionUID不一致,那么反序列化就不能正常进行。例如在客户端A中Foo类的serialVersionUID=1L,而在客户端B中Foo类的serialVersionUID=2L 那么就不能重构这个Person对象。
二. transient的作用及使用方法
我们都知道一个对象只要实现了Serilizable接口,这个对象就可以被序列化,java的这种序列化模式为开发者提供了很多便利,我们可以不必关系具体序列化的过程,只要这个类实现了Serilizable接口,这个类的所有属性和方法都会自动序列化。
然而在实际开发过程中,我们常常会遇到这样的问题,这个类的有些属性需要序列化,而其他属性不需要被序列化,打个比方,如果一个用户有一些敏感信息(如密码,银行卡号等),为了安全起见,不希望在网络操作(主要涉及到序列化操作,本地序列化缓存也适用)中被传输,这些信息对应的变量就可以加上transient关键字。换句话说,这个字段的生命周期仅存于调用者的内存中而不会写到磁盘里持久化。
总之,java 的transient关键字为我们提供了便利,你只需要实现Serilizable接口,将不需要序列化的属性前添加关键字transient,序列化对象的时候,这个属性就不会序列化到指定的目的地中。
示例code如下:
- import java.io.FileInputStream;
- import java.io.FileNotFoundException;
- import java.io.FileOutputStream;
- import java.io.IOException;
- import java.io.ObjectInputStream;
- import java.io.ObjectOutputStream;
- import java.io.Serializable;
- /**
- * @description 使用transient关键字不序列化某个变量
- * 注意读取的时候,读取数据的顺序一定要和存放数据的顺序保持一致
- *
- * @author Alexia
- * @date 2013-10-15
- */
- public class TransientTest {
- public static void main(String[] args) {
- User user = new User();
- user.setUsername("Alexia");
- user.setPasswd("123456");
- System.out.println("read before Serializable: ");
- System.out.println("username: " + user.getUsername());
- System.err.println("password: " + user.getPasswd());
- try {
- ObjectOutputStream os = new ObjectOutputStream(
- new FileOutputStream("C:/user.txt"));
- os.writeObject(user); // 将User对象写进文件
- os.flush();
- os.close();
- } catch (FileNotFoundException e) {
- e.printStackTrace();
- } catch (IOException e) {
- e.printStackTrace();
- }
- try {
- ObjectInputStream is = new ObjectInputStream(new FileInputStream(
- "C:/user.txt"));
- user = (User) is.readObject(); // 从流中读取User的数据
- is.close();
- System.out.println("\nread after Serializable: ");
- System.out.println("username: " + user.getUsername());
- System.err.println("password: " + user.getPasswd());
- } catch (FileNotFoundException e) {
- e.printStackTrace();
- } catch (IOException e) {
- e.printStackTrace();
- } catch (ClassNotFoundException e) {
- e.printStackTrace();
- }
- }
- }
- class User implements Serializable {
- private static final long serialVersionUID = 8294180014912103005L;
- private String username;
- private transient String passwd;
- public String getUsername() {
- return username;
- }
- public void setUsername(String username) {
- this.username = username;
- }
- public String getPasswd() {
- return passwd;
- }
- public void setPasswd(String passwd) {
- this.passwd = passwd;
- }
- }
输出为:
- read before Serializable:
- username: Alexia
- password: 123456
- read after Serializable:
- username: Alexia
- password: null
密码字段为null,说明反序列化时根本没有从文件中获取到信息。
2. transient使用小结
1)一旦变量被transient修饰,变量将不再是对象持久化的一部分,该变量内容在序列化后无法获得访问。
2)transient关键字只能修饰变量,而不能修饰方法和类。注意,本地变量是不能被transient关键字修饰的。变量如果是用户自定义类变量,则该类需要实现Serializable接口。
3)被transient关键字修饰的变量不再能被序列化,一个静态变量不管是否被transient修饰,均不能被序列化。
第三点可能有些人很迷惑,因为发现在User类中的username字段前加上static关键字后,程序运行结果依然不变,即static类型的username也读出来为“Alexia”了,这不与第三点说的矛盾吗?实际上是这样的:第三点确实没错(一个静态变量不管是否被transient修饰,均不能被序列化),反序列化后类中static型变量username的值为当前JVM中对应static变量的值,这个值是JVM中的不是反序列化得出的,不相信?好吧,下面我来证明:
- import java.io.FileInputStream;
- import java.io.FileNotFoundException;
- import java.io.FileOutputStream;
- import java.io.IOException;
- import java.io.ObjectInputStream;
- import java.io.ObjectOutputStream;
- import java.io.Serializable;
- /**
- * @description 使用transient关键字不序列化某个变量
- * 注意读取的时候,读取数据的顺序一定要和存放数据的顺序保持一致
- *
- * @author Alexia
- * @date 2013-10-15
- */
- public class TransientTest {
- public static void main(String[] args) {
- User user = new User();
- user.setUsername("Alexia");
- user.setPasswd("123456");
- System.out.println("read before Serializable: ");
- System.out.println("username: " + user.getUsername());
- System.err.println("password: " + user.getPasswd());
- try {
- ObjectOutputStream os = new ObjectOutputStream(
- new FileOutputStream("C:/user.txt"));
- os.writeObject(user); // 将User对象写进文件
- os.flush();
- os.close();
- } catch (FileNotFoundException e) {
- e.printStackTrace();
- } catch (IOException e) {
- e.printStackTrace();
- }
- try {
- // 在反序列化之前改变username的值
- User.username = "jmwang";
- ObjectInputStream is = new ObjectInputStream(new FileInputStream(
- "C:/user.txt"));
- user = (User) is.readObject(); // 从流中读取User的数据
- is.close();
- System.out.println("\nread after Serializable: ");
- System.out.println("username: " + user.getUsername());
- System.err.println("password: " + user.getPasswd());
- } catch (FileNotFoundException e) {
- e.printStackTrace();
- } catch (IOException e) {
- e.printStackTrace();
- } catch (ClassNotFoundException e) {
- e.printStackTrace();
- }
- }
- }
- class User implements Serializable {
- private static final long serialVersionUID = 8294180014912103005L;
- public static String username;
- private transient String passwd;
- public String getUsername() {
- return username;
- }
- public void setUsername(String username) {
- this.username = username;
- }
- public String getPasswd() {
- return passwd;
- }
- public void setPasswd(String passwd) {
- this.passwd = passwd;
- }
- }
运行结果为:
- read before Serializable:
- username: Alexia
- password: 123456
- read after Serializable:
- username: jmwang
- password: null
这说明反序列化后类中static型变量username的值为当前JVM中对应static变量的值,为修改后jmwang,而不是序列化时的值Alexia。
3. transient使用细节——被transient关键字修饰的变量真的不能被序列化吗?
思考下面的例子:
- import java.io.Externalizable;
- import java.io.File;
- 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;
- /**
- * @descripiton Externalizable接口的使用
- *
- * @author Alexia
- * @date 2013-10-15
- *
- */
- public class ExternalizableTest implements Externalizable {
- private transient String content = "是的,我将会被序列化,不管我是否被transient关键字修饰";
- @Override
- public void writeExternal(ObjectOutput out) throws IOException {
- out.writeObject(content);
- }
- @Override
- public void readExternal(ObjectInput in) throws IOException,
- ClassNotFoundException {
- content = (String) in.readObject();
- }
- public static void main(String[] args) throws Exception {
- ExternalizableTest et = new ExternalizableTest();
- ObjectOutput out = new ObjectOutputStream(new FileOutputStream(
- new File("test")));
- out.writeObject(et);
- ObjectInput in = new ObjectInputStream(new FileInputStream(new File(
- "test")));
- et = (ExternalizableTest) in.readObject();
- System.out.println(et.content);
- out.close();
- in.close();
- }
- }
content变量会被序列化吗?好吧,我把答案都输出来了,是的,运行结果就是:
是的,我将会被序列化,不管我是否被
transient
关键字修饰
这是为什么呢,不是说类的变量被transient关键字修饰以后将不能序列化了吗?
我们知道在Java中,对象的序列化可以通过实现两种接口来实现,若实现的是Serializable接口,则所有的序列化将会自动进行,若实现的是Externalizable接口,则没有任何东西可以自动序列化,需要在writeExternal方法中进行手工指定所要序列化的变量,这与是否被transient修饰无关。因此第二个例子输出的是变量content初始化的内容,而不是null。
序列化-二进制文件格式详解
样例代码
- public class Employee implements Serializable {
- private String name;
- private double salary;
- private Date hireDay;
- public Employee(String n, double s, int year, int month, int day) {
- name = n;
- salary = s;
- GregorianCalendar calendar = new GregorianCalendar(year, month - 1, day);
- hireDay = calendar.getTime();
- }
- // 略
- }
- public class Tester{
- private static final String SAVED_PATH = "src/main/java/objectStream/employee.dat";
- public static void main(String[] args) throws IOException, ClassNotFoundException {
- // 持久化到本地存储中
- Employee harry = new Employee("Harry Hacker", 50000, 1989, 10, 1);
- try (ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream(SAVED_PATH))) {
- out.writeObject(obj);
- }
- }
- }
分析
以下是在notepad++下以hex模式打开employee.dat的文件截图
接下来按照顺序逐一解读
1:魔法数 AC ED (可在ObjectStreamConstants接口中找到)
2:序列化格式的版本号 00 05 (可在ObjectStreamConstants接口中找到)
3:接下来的 73 72 解读如下 (参见 协议文档)
73 代表接下来读取到的将是一个对象 (final static byte TC_OBJECT = (byte)0x73;)
72 代表该对象是一个对类的描述 (final static byte TC_CLASSDESC = (byte)0x72;)
4:接下来的 00 15, 指代该类描述信息的长度, 经过转换计算, 内容正好是类Employee的完整命名objectStream.Employee;
5:然后是ed 65 0f 78 f9 97 ff b6,这八位是用来验证该类是否被修改过的验证码. 因为我们没有在实现Serializable接口后, 添加serialVersionUID 字段, 所以JVM会自动帮助我们生成一个.
6:接下来就是 02, 该一个字节长度的标志信息代表了 序列化中标识类版本 ; 该数值也是可以在ObjectStreamConstants接口中找到. (final static byte SC_SERIALIZABLE = 0x02;)
7:继续往下就是 00 03 , 这两个字节长度的标志信息指代的是 该类型中字段的个数. 如这里所见, 正好对应了 Employee 中的三个字段.
8:接着往下就是对这三个字段的逐一解读了,
1:如上所示, 以上标注出的是 double 类型的 salary 字段的解读.
1.1: 即 D ; 正好对应的是 double
1.2:00 06 代表该字段名称所占的长度
1.3:接下来的6字节长度的73 61 6c 61 72 79 正好是 salary 字符串的16进制版本.
2:接下来的 4c 00 07 68 ... 解读如下
2.1: 即 L, 所代表的是 对象 , 正好和 java.util.Date 匹配
2.2:00 07 依然是长度
2.3:接下来的7位 也就是 字段名 hireDay 字符的内容了.
2.4:接下来对 Date类型的字段解读如下: 即从 74 00 10 4c 开始
2.5:Date类型的hireDay字段 : 类型 L (4c) , 字段名7位长度, 名称为hireDay, 字段类型为 74(字段类型以74开头), 字段类型 类名长度16, java.lang.String
2.6:最后就是name字段了: 以下就是 字段 name 类型 L (4c)(String属于对象, 不属于基本类型) , 字段名4位长度, 名称为 name, 字段类型为 74, 字段类型 类名长度18, java.lang.String;
9:接下来就是 类型描述信息结束的标识了
10:接下来就是对象信息的描述了
1:首先是double类型的salary, 所以 78 70之后的 40 e8 6a 00 00 00 00 00正是它的值.
2:接下来是Date类型的 hireDay, 注意选中的部分, 前面四个字符73 72 00 0e正是 一个字节长度的 对象标识, 一个字节长度的类描述符标识, 两个字节长度的 长度标识
这里还要注意的一点是, 和我上面红线标出来的不同的是
1:红色标识出来的是 其解析出来, 内容是 java/util/Date
2:而选中部分解析出来, 其内容是 java.util.Date
3:最后是 name 实例字段 数据
出处资料:https://blog.csdn.net/lqzkcx3/article/details/79463450
https://mp.weixin.qq.com/s/7sRNnY7ZFklCW38_fEjA9g
Java Serializable的使用和transient关键字使用小记(转载)的更多相关文章
- Java基础之instanceof和transient关键字用法
instanceof 用于检测指定对象是否是某个类(本类.父类.子类.接口)的实例.Java中的instanceof也称为类型比较运算符,因为它将类型与实例进行比较. 返回true或false. 如果 ...
- Java transient关键字使用小记
哎,虽然自己最熟的是Java,但很多Java基础知识都不知道,比如transient关键字以前都没用到过,所以不知道它的作用是什么,今天做笔试题时发现有一题是关于这个的,于是花个时间整理下transi ...
- (转)Java transient关键字使用小记
背景:最近在看java底层的源码实现,看到一个关键字,不是很熟悉,专门做个记录. 原文出处:http://www.importnew.com/21517.html#comment-637072 哎,虽 ...
- 【转】Java transient关键字使用小记
哎,虽然自己最熟的是Java,但很多Java基础知识都不知道,比如transient关键字以前都没用到过,所以不知道它的作用是什么,今天做笔试题时发现有一题是关于这个的,于是花个时间整理下transi ...
- Java transient关键字使用小记(转)
哎,虽然自己最熟的是Java,但很多Java基础知识都不知道,比如transient关键字以前都没用到过,所以不知道它的作用是什么,今天做笔试题时发现有一题是关于这个的,于是花个时间整理下transi ...
- 【java】Java transient关键字使用小记【转】
转载地址:https://www.cnblogs.com/lanxuezaipiao/p/3369962.html 1. transient的作用及使用方法 我们都知道一个对象只要实现了Seriliz ...
- 【Java编程】volatile和transient关键字的理解
理解volatile volatile是java提供的一种轻量级的同步机制,所谓轻量级,是相对于synchronized(重量级锁,开销比较大)而言的. 根据java虚拟机的内存模型,我们知道 ...
- 转:Java transient关键字使用小记
哎,虽然自己最熟的是Java,但很多Java基础知识都不知道,比如transient关键字以前都没用到过,所以不知道它的作用是什么,今天做笔试题时发现有一题是关于这个的,于是花个时间整理下transi ...
- transient关键字的用法
本篇博客转自 一直在路上 Java transient关键字使用小记 1. transient的作用及使用方法 我们都知道一个对象只要实现了Serilizable接口,这个对象就可以被序列化,Java ...
随机推荐
- 微服务---Eureka注册中心(服务治理)
在上一篇的初识SpringCloud微服务中,我们简单讲解到服务的提供者与消费者,当服务多了之后,会存在依赖与管理之间混乱的问题,以及需要对外暴露自己的地址,为了解决此等问题,我们学习Eureka注册 ...
- SpringBoot2.0 最简单的 idea 快速创建项目
第一步 第二步 第三步 以上就是idea快速创建springboot的方法,创建之后等maven 依赖下载完成,就可以使用
- C# SHA256加密算法记录
using System.Text; using System.Diagnostics; using System.Security; using System.Security.Cryptograp ...
- C#基础の迭代器详解
一.什么是迭代器 迭代器(iterator)有时又称游标(cursor)是程序设计的软件设计模式,可在容器(container,例如链表或阵列)上遍访的接口,设计人员无需关心容器的内容. 迭代器模式是 ...
- Qt编译错误GL/gl.h: No such file or directory
近期把系统换成ubuntu14.04的了.在安装Qt后,我执行了里面的一个演示样例,发现编译有错: watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQ ...
- P2080 增进感情(背包DP)
思路:将好感度x+y作为体积, 幸福度x-y作为作为价值, 然后就是一个经典的背包问题了.emmmmm,还可以特判一下,因为幸福度为0时就是最小了,没有必要看后面的了吧. 其实,我自己做的时候,沙雕的 ...
- flask_sqlalchemy中根据聚合分组后的结果进行排序,根据日期(datetime)按天分组
from sqlalchemy import func, desc # 根据聚合查询总收入,按总收入逆序 s= db.session.query(TpOrders.room_type_id, (fun ...
- 如果redis没有设置expire,他是否默认永不过期?
版权声明:本文为博主原创文章,未经博主允许不得转载. https://blog.csdn.net/soulmate_P/article/details/81136054如果没有设置有效期,即使内存用完 ...
- Keepalive工作原理
Keepalive工作原理 1.1软件介绍 Keepalived软件起初是专为LVS负载均衡软件设计的,用来管理并监控LVS集群系统中各个服务节点的状态,后来又加入了可以实现高可用的VRRP功能.因此 ...
- P3200 [HNOI2009]有趣的数列--洛谷luogu
---恢复内容开始--- 题目描述 我们称一个长度为2n的数列是有趣的,当且仅当该数列满足以下三个条件: (1)它是从1到2n共2n个整数的一个排列{ai}: (2)所有的奇数项满足a1<a3& ...