一: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关键字使用小记(转载)的更多相关文章

  1. Java基础之instanceof和transient关键字用法

    instanceof 用于检测指定对象是否是某个类(本类.父类.子类.接口)的实例.Java中的instanceof也称为类型比较运算符,因为它将类型与实例进行比较. 返回true或false. 如果 ...

  2. Java transient关键字使用小记

    哎,虽然自己最熟的是Java,但很多Java基础知识都不知道,比如transient关键字以前都没用到过,所以不知道它的作用是什么,今天做笔试题时发现有一题是关于这个的,于是花个时间整理下transi ...

  3. (转)Java transient关键字使用小记

    背景:最近在看java底层的源码实现,看到一个关键字,不是很熟悉,专门做个记录. 原文出处:http://www.importnew.com/21517.html#comment-637072 哎,虽 ...

  4. 【转】Java transient关键字使用小记

    哎,虽然自己最熟的是Java,但很多Java基础知识都不知道,比如transient关键字以前都没用到过,所以不知道它的作用是什么,今天做笔试题时发现有一题是关于这个的,于是花个时间整理下transi ...

  5. Java transient关键字使用小记(转)

    哎,虽然自己最熟的是Java,但很多Java基础知识都不知道,比如transient关键字以前都没用到过,所以不知道它的作用是什么,今天做笔试题时发现有一题是关于这个的,于是花个时间整理下transi ...

  6. 【java】Java transient关键字使用小记【转】

    转载地址:https://www.cnblogs.com/lanxuezaipiao/p/3369962.html 1. transient的作用及使用方法 我们都知道一个对象只要实现了Seriliz ...

  7. 【Java编程】volatile和transient关键字的理解

    理解volatile   volatile是java提供的一种轻量级的同步机制,所谓轻量级,是相对于synchronized(重量级锁,开销比较大)而言的.   根据java虚拟机的内存模型,我们知道 ...

  8. 转:Java transient关键字使用小记

    哎,虽然自己最熟的是Java,但很多Java基础知识都不知道,比如transient关键字以前都没用到过,所以不知道它的作用是什么,今天做笔试题时发现有一题是关于这个的,于是花个时间整理下transi ...

  9. transient关键字的用法

    本篇博客转自 一直在路上 Java transient关键字使用小记 1. transient的作用及使用方法 我们都知道一个对象只要实现了Serilizable接口,这个对象就可以被序列化,Java ...

随机推荐

  1. Java多线程(一) 什么是线程

    声明:本系列大多是翻译自https://www.javatpoint.com,加上自己的增删改,尽力写的系统而通俗易懂,后文不再重复声明. 点我跳过黑哥的卑鄙广告行为,进入正文. Java多线程系列更 ...

  2. LDAP概念和原理

    LDAP概念和原理介绍 相信对于许多的朋友来说,可能听说过LDAP,但是实际中对LDAP的了解和具体的原理可能还比较模糊,今天就从“什么是LDAP”.“LDAP的主要产品”.“LDAP的基本模型”.“ ...

  3. Leetcode:0002(两数之和)

    LeetCode:0002(两数之和) 题目描述:给定两个非空链表来表示两个非负整数.位数按照逆序方式存储,它们的每个节点只存储单个数字.将两数相加返回一个新的链表.你可以假设除了数字 0 之外,这两 ...

  4. 裸奔的智能插座:博联Broadlink SP2/SP mini的分析、破解

    https://www.jianshu.com/p/028b10bc3dd1 智能设备的联动通常采用IFTTT的方式,但这种方式受限于官方软件提供的功能.想要自主地灵活控制,需要有官方SDK,或知道协 ...

  5. Flask 框架中 上下文基础理念,包括cookie,session存储方法,requset属性,current_app模块和g模块

    Flask中上下文,分为请求上下文和应用上下文.既状态留存 ,就是把变量存在某一个地方可以调用 请求上下文:实际就是request和session用法理念,既都是可以存储东西. 应用上下文:既变量共享 ...

  6. AWS re:Invent(2019.01.09)

    时间:2019.01.09地点:北京国际饭店

  7. python五子棋

    以后不更新了,把以前的一些东西发出来. 这是一个命令行环境的五子棋程序.使用了minimax算法. 除了百度各个棋型的打分方式,所有代码皆为本人所撸.本程序结构与之前的井字棋.黑白棋一模一样. 有一点 ...

  8. Vue.js 系列教程 2:组件,Props,Slots

    原文:intro-to-vue-2-components-props-slots 译者:nzbin 这是关于 JavaScript 框架 Vue.js 五个教程的第二部分.在这一部分,我们将学习组件, ...

  9. Linux 特殊权限 SUID,SGID,SBIT

    setuid 和 setgid 分别是 set uid ID upon execution 和 set group ID upon execution 的缩写.我们一般会再次把它们缩写为 suid 和 ...

  10. IT江湖--这个冬天注定横尸遍野

    今年江湖大事繁起,又至寒冬,冻的不仅是温度,更是人心. 这两天上班途中看到多个公众号和媒体发了很多 "XXX公司裁员50%" 等等诸如此类的文章,也真是撼动人心.寒冬,比以往来的更 ...