什么是序列化,什么时候要进行序列化?

  序列化就是一种用来处理对象流的机制,所谓对象流也就是将对象的内容进行流化,将数据分解成字节流,以便存储在文件中或在网络上传输。

  我们在对java对象进行IO流操作或者进行网络传输的时候就要进行序列化。

Java对象序列化的方式

  一、实现Serializable接口

package org.burning.sport.javase.serializable.demo1;

import java.io.Serializable;
import java.util.Date; public class User implements Serializable { private static final long serialVersionUID = -6330597348767194997L; private String name; private int age; private Date birthday; private transient String gender; setters();getters();toString();
}
package org.burning.sport.javase.serializable.demo1;

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.util.Date; public class SerializableDemo {
public static void main(String[] args) {
User user = new User();
user.setName("hollis");
user.setGender("male");
user.setAge(23);
user.setBirthday(new Date());
System.out.println("serializable before:" + user); /**
* 将对象持久化到文件中
*/
ObjectOutputStream oos = null;
try {
oos = new ObjectOutputStream(new FileOutputStream("temp"));
oos.writeObject(user);
} catch (IOException e) {
e.printStackTrace();
} finally {
if(oos != null) {
try {
oos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
} /**
* 从文件中读取对象
*/
ObjectInputStream ois = null;
try {
ois = new ObjectInputStream(new FileInputStream("temp"));
User readUser = (User)ois.readObject();
System.out.println("serializable after:" + readUser);
} catch (Exception e) {
e.printStackTrace();
} finally {
if (ois != null) {
try {
ois.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
} // output
//serializable before:User{name='hollis', age=23, birthday=Sat May 26 11:35:06 CST 2018, gender='male'}
//serializable after:User{name='hollis', age=23, birthday=Sat May 26 11:35:06 CST 2018, gender='null'}

小结:

  1、在Java中,只要一个类实现了 java.io.Serializable 接口,那么它就可以被序列化。

  2、通过 ObjectOutputStream 和 ObjectInputStream 对对象进行序列化及反序列化。

  3、虚拟机是否允许反序列化,不仅取决于类路径和功能代码是否一致,一个非常重要的一点是两个类的序列化 ID 是否一致(就是 private static final long serialVersionUID )

  4、序列化并不保存静态变量。

  Demo:

package org.burning.sport.javase.serializable.demo2;

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable; public class StaticVariableTest implements Serializable { private static final long serialVersionUID = 1L;
/**初始化静态变量为5*/
public static int staticVar = 5; public static void main(String[] args) throws Exception{
ObjectOutputStream outputStream = new ObjectOutputStream(new FileOutputStream("result.obj"));
outputStream.writeObject(new StaticVariableTest());
outputStream.close(); // 将静态变量值改为10,再读入
StaticVariableTest.staticVar = 10;
ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream("result.obj"));
StaticVariableTest st = (StaticVariableTest)objectInputStream.readObject();
objectInputStream.close(); System.out.println(st.staticVar);
}
}
// output 10

  5、要想将父类对象也序列化,就需要让父类也实现 Serializable 接口。

  6、Transient 关键字的作用是控制变量的序列化,在变量声明前加上该关键字,可以阻止该变量被序列化到文件中,在被反序列化后,transient 变量的值被设为初始值,如 int 型的是 0,对象型的是 null。

  7、服务器端给客户端发送序列化对象数据,对象中有一些数据是敏感的,比如密码字符串等,希望对该密码字段在序列化时,进行加密,而客户端如果拥有解密的密钥,只有在客户端进行反序列化时,才可以对密码进行读取,这样可以一定程度保证序列化对象的数据安全。

ArrayList序列化有什么不一样?

  我们先看看ArrayList的一段源码:

public class ArrayList<E> extends AbstractList<E>
implements List<E>, RandomAccess, Cloneable, java.io.Serializable
{
private static final long serialVersionUID = 8683452581122892189L;
transient Object[] elementData; // non-private to simplify nested class access
private int size;
}

  我们看到elementData是transient的,所以我们认为elementData是不会被序列化保留下来的,下面做个Demo:

package org.burning.sport.javase.serializable.demo1;

import org.apache.commons.io.IOUtils;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.util.ArrayList;
import java.util.List; public class ArrayListSerializableDemo {
public static void main(String[] args) throws Exception{
List<String> stringList = new ArrayList<String>();
stringList.add("hello");
stringList.add("world");
stringList.add("hollis");
stringList.add("chuang");
System.out.println("init StringList" + stringList);
ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream("stringlist"));
objectOutputStream.writeObject(stringList); IOUtils.closeQuietly(objectOutputStream);
File file = new File("stringlist");
ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream(file));
List<String> newStringList = (List<String>)objectInputStream.readObject();
IOUtils.closeQuietly(objectInputStream);
if(file.exists()){
file.delete();
}
System.out.println("new StringList" + newStringList);
}
} // output~
//init StringList[hello, world, hollis, chuang]
//new StringList[hello, world, hollis, chuang]

  了解ArrayList的人都知道,ArrayList底层是通过数组实现的。那么数组 elementData 其实就是用来保存列表中的元素的。通过该属性的声明方式我们知道,他是无法通过序列化持久化下来的。那么为什么code 4的结果却通过序列化和反序列化把List中的元素保留下来了呢?

  答案就在ArrayList中的writeObject和readObject两个方法中:

private void readObject(java.io.ObjectInputStream s)
throws java.io.IOException, ClassNotFoundException {
elementData = EMPTY_ELEMENTDATA; // Read in size, and any hidden stuff
s.defaultReadObject(); // Read in capacity
s.readInt(); // ignored if (size > 0) {
// be like clone(), allocate array based upon size not capacity
ensureCapacityInternal(size); Object[] a = elementData;
// Read in all elements in the proper order.
for (int i=0; i<size; i++) {
a[i] = s.readObject();
}
}
}
private void writeObject(java.io.ObjectOutputStream s)
throws java.io.IOException{
// Write out element count, and any hidden stuff
int expectedModCount = modCount;
s.defaultWriteObject(); // Write out size as capacity for behavioural compatibility with clone()
s.writeInt(size); // Write out all elements in the proper order.
for (int i=0; i<size; i++) {
s.writeObject(elementData[i]);
} if (modCount != expectedModCount) {
throw new ConcurrentModificationException();
}
}

小结:

  在序列化过程中,如果被序列化的类中定义了writeObject 和 readObject 方法,虚拟机会试图调用对象类里的 writeObject 和 readObject 方法,进行用户自定义的序列化和反序列化。如果没有这样的方法,则默认调用是 ObjectOutputStream 的 defaultWriteObject 方法以及 ObjectInputStream 的 defaultReadObject 方法。用户自定义的 writeObject 和 readObject 方法可以允许用户控制序列化的过程,比如可以在序列化的过程中动态改变序列化的数值。

  那有个问题,为什么AarrayList要把elementDate给transient呢?

  ArrayList实际上是动态数组,每次在放满以后自动增长设定的长度值,如果数组自动增长长度设为100,而实际只放了一个元素,那就会序列化99个null元素。为了保证在序列化的时候不会将这么多null同时进行序列化,ArrayList把元素数组设置为transient。但是,作为一个集合,在序列化过程中还必须保证其中的元素可以被持久化下来,所以,通过重写 writeObject 和 readObject 方法的方式把其中的元素保留下来。

  writeObject 方法把 elementData 数组中的元素遍历的保存到输出流(ObjectOutputStream)中。

  readObject 方法从输入流(ObjectInputStream)中读出对象并保存赋值到 elementData 数组中。

  所以如果想自定义序列化和反序列化策略就可以模仿ArrayList的做法。  

ObjectOuptStream&ObjectInputStream

  在上个面讲到的ArrayList的例子中,有个问题就是,我们没有看到writeObject和readObject的调用啊,这两个方法是怎么被调用的呢?原因就是ObjectOuptStream&ObjectInputStream

  看一下ObjectOuptStream的调用栈是怎样的?writeObject —> writeObject0 —> writeOrdinaryObject —> writeSerialData —> invokeWriteObject  。这里看一下invokeWriteObject:

void invokeWriteObject(Object obj, ObjectOutputStream out)
throws IOException, UnsupportedOperationException
{
if (writeObjectMethod != null) {
try {
writeObjectMethod.invoke(obj, new Object[]{ out });
} catch (InvocationTargetException ex) {
Throwable th = ex.getTargetException();
if (th instanceof IOException) {
throw (IOException) th;
} else {
throwMiscException(th);
}
} catch (IllegalAccessException ex) {
// should not occur, as access checks have been suppressed
throw new InternalError(ex);
}
} else {
throw new UnsupportedOperationException();
}
}

  其中 writeObjectMethod.invoke(obj, new Object[]{ out }); 是关键,通过反射的方式调用writeObjectMethod方法。官方是这么解释这个writeObjectMethod的:

  class-defined writeObject method, or null if none

  在我们的例子中,这个方法就是我们在ArrayList中定义的writeObject方法。通过反射的方式被调用了。

  至此,我们先试着来回答刚刚提出的问题:

  如果一个类中包含writeObject 和 readObject 方法,那么这两个方法是怎么被调用的?

  答:在使用ObjectOutputStream的writeObject方法和ObjectInputStream的readObject方法时,会通过反射的方式调用。

  至此,我们已经介绍完了ArrayList的序列化方式。

总结:

  1、如果一个类想被序列化,需要实现Serializable接口。否则将抛出 NotSerializableException 异常,这是因为,在序列化操作过程中会对类型进行检查,要求被序列化的类必须属于Enum、Array和Serializable类型其中的任何一种。

   2、在变量声明前加上该关键字,可以阻止该变量被序列化到文件中。

   3、在类中增加writeObject 和 readObject 方法可以实现自定义序列化策略

https://gitee.com/play-happy/base-project

参考:

【1】博客,https://www.cnblogs.com/Qian123/articles/5665671.html

【2】博客,https://blog.csdn.net/tlycherry/article/details/8986720

【3】博客,https://blog.csdn.net/jiangwei0910410003/article/details/18989711/

JavaSE-序列化和反序列化的更多相关文章

  1. JavaSE——序列化和反序列化

    序列化: 序列化对应写的操作.(读与写都是站在应用的角度上) 序列化就是一种用来处理对象流的机制,所谓对象流也就是将对象的内容进行流化.可以对流化后的对象进行读写操作,也可将流化后的对象传输于网络之间 ...

  2. C# 序列化与反序列化几种格式的转换

    这里介绍了几种方式之间的序列化与反序列化之间的转换 首先介绍的如何序列化,将object对象序列化常见的两种方式即string和xml对象; 第一种将object转换为string对象,这种比较简单没 ...

  3. 使用Newtonsoft.Json.dll(JSON.NET)动态解析JSON、.net 的json的序列化与反序列化(一)

    在开发中,我非常喜欢动态语言和匿名对象带来的方便,JSON.NET具有动态序列化和反序列化任意JSON内容的能力,不必将它映射到具体的强类型对象,它可以处理不确定的类型(集合.字典.动态对象和匿名对象 ...

  4. Java 序列化与反序列化

    1.什么是序列化?为什么要序列化? Java 序列化就是指将对象转换为字节序列的过程,而反序列化则是只将字节序列转换成目标对象的过程. 我们都知道,在进行浏览器访问的时候,我们看到的文本.图片.音频. ...

  5. C#中怎样实现序列化和反序列化

    我们想要将数据进行持久化的操作的话,也就是将数据写入到文件中,我们在C#中可以通过IO流来操作,同时也可以通过序列化来操作,本人是比较推荐使用序列化操作的 因为我们如果想要将一个对象持久化到文件中 如 ...

  6. Java序列化与反序列化

    Java序列化与反序列化是什么?为什么需要序列化与反序列化?如何实现Java序列化与反序列化?本文围绕这些问题进行了探讨. 1.Java序列化与反序列化 Java序列化是指把Java对象转换为字节序列 ...

  7. XPatchLib 对象增量数据序列化及反序列化器 For .Net

    在日常的软件开发和使用过程中,我们发现同一套系统的同一配置项在不同的客户环境中是存在各种各样的差异的.在差异较为分散时,如何较好的管理这些差异,使得维护过程能够更加安全和快速,一直在这样那样的困扰着开 ...

  8. c# Json 自定义类作为字典键时,序列化和反序列化的处理方法

    一般情况下,Newtonsoft.Json.dll 对 Dictionary<int,object>.Dictionary<string,object>等序列化与反序列化都是成 ...

  9. java 对象序列化与反序列化

    Java序列化与反序列化是什么? 为什么需要序列化与反序列化? 如何实现Java序列化与反序列化? 本文围绕这些问题进行了探讨. 1.Java序列化与反序列化  Java序列化是指把Java对象转换为 ...

  10. 序列化,反序列化和transient关键字

    一.序列化和反序列化的概念 序列化:指把java对象转换为字节序列的过程. 反序列化:指把字节序列恢复为java对象的过程. 对象的序列化主要有两种用途: 1) 把对象的字节序列保存到硬盘上,通常存放 ...

随机推荐

  1. 学以致用八---centos7.2 安装vim8+支持python3

    目的:打造基于python的vim环境 也是在地铁上突然产生的一个想法,平时都是在pycharm上练习python,但有时候会提示激活码过期,又得上网找激活码,够折腾的.那何不在linux环境下来搭建 ...

  2. 转:Ubuntu 10.10 安装后上不了网的原因

    最近新装了个Ubuntu10.10 发现上不了网,折腾了很久,在网上找了很多办法都不行,最后试了一招居然管用了.特此总结下Ubuntu了网的原因及对策分析. 环境:Ubuntu 10.10网络: 通过 ...

  3. 点聚weboffice插件自定义菜单

    https://blog.csdn.net/u014547764/article/details/53818637 1.在jsp中引用excel插件:首先项目中要有:WebOffice.zip.web ...

  4. ubuntu16.04 LTS把下载源改为阿里云的源

    为什么要切换下载源到国内的源上? Ubuntu的中国服务器下载速度很慢,我们可以尝试修改软件更新源,这样下载和更新软件的速度会加快很多. 一.linux系统版本: ubuntukylin-16.04- ...

  5. 第7章 "敏捷+"项目管理

    7.1  导入敏捷项目管理的步骤 1.导入敏捷的步骤 (1).培训 (2).教练与引导 (3).内化 2.敏捷混合型模式 7.2  项目启动与敏捷合同 1.敏捷项目启动 2.敏捷签约模式 在传统项目管 ...

  6. StringBuffer 详解 (String系列之3)

    本章介绍StringBuffer以及它的API的详细使用方法. 转载请注明出处:http://www.cnblogs.com/skywang12345/p/string03.html StringBu ...

  7. 一个docker容器中运行多个服务还是弄一堆docker容器运行?

    不建议直接在单个 Docker 容器中运行多个程序. 以 2017年 10 月18 日 Docker 官方支持 Kubernetes 为分水岭计算,Kubernetes 赢得容器编排之战的最终胜利已经 ...

  8. 【计算机网络】数据交换技术和多路复用技术的正(nao)确(can)打开方式

    交换的作用   数据交换是计算机网络中两个终端进行数据传输的方式,它又可以分成两种类型:电路交换和分组交换.很显然,问题的核心在于“交换”,那么我们首先要思考的是:交换的作用是什么?   “交换”的作 ...

  9. 「PKUSC2018」最大前缀和(状压dp)

    前言 考试被\(hyj\)吊着打... Solution 考虑一下如果前缀和如果在某一个位置的后面的任意一个前缀和都<=0,肯定这就是最大的. 然后这样子就考虑左右两边的状压dp,然后就好了. ...

  10. JQuery Mobile - html5+CSS 禁止IOS长按复制粘贴实现

    因为在移动端APP需要实现长按执行别的事件,但是在IOS系统有默认的长按选择复制粘贴,禁止此功能在网上找了很多资料,最终整理出目前最好的解决方法.实际测试,也并不是很理想,但是可能没有更好办法了! / ...