- 浏览: 7852470 次
- 性别:
- 来自: 广州
文章分类
- 全部博客 (2425)
- 软件工程 (75)
- JAVA相关 (662)
- ajax/web相关 (351)
- 数据库相关/oracle (218)
- PHP (147)
- UNIX/LINUX/FREEBSD/solaris (118)
- 音乐探讨 (1)
- 闲话 (11)
- 网络安全等 (21)
- .NET (153)
- ROR和GOG (10)
- [网站分类]4.其他技术区 (181)
- 算法等 (7)
- [随笔分类]SOA (8)
- 收藏区 (71)
- 金融证券 (4)
- [网站分类]5.企业信息化 (3)
- c&c++学习 (1)
- 读书区 (11)
- 其它 (10)
- 收藏夹 (1)
- 设计模式 (1)
- FLEX (14)
- Android (98)
- 软件工程心理学系列 (4)
- HTML5 (6)
- C/C++ (0)
- 数据结构 (0)
- 书评 (3)
- python (17)
- NOSQL (10)
- MYSQL (85)
- java之各类测试 (18)
- nodejs (1)
- JAVA (1)
- neo4j (3)
- VUE (4)
- docker相关 (1)
最新评论
-
xiaobadi:
jacky~~~~~~~~~
推荐两个不错的mybatis GUI生成工具 -
masuweng:
(转)JAVA获得机器码的实现 -
albert0707:
有些扩展名为null
java 7中可以判断文件的contenttype了 -
albert0707:
非常感谢!!!!!!!!!
java 7中可以判断文件的contenttype了 -
zhangle:
https://zhuban.me竹板共享 - 高效便捷的文档 ...
一个不错的网络白板工具
https://mp.weixin.qq.com/s/T2Bn4pmH3F61Q11_QMvJSw
序列化是一种对象持久化的手段。普遍应用在网络传输、RMI等场景中。本文通过分析ArrayList的序列化来介绍Java序列化的相关内容。主要涉及到以下几个问题:
怎么实现Java的序列化
为什么实现了java.io.Serializable接口才能被序列化
transient的作用是什么
怎么自定义序列化策略
自定义的序列化策略是如何被调用的
ArrayList对序列化的实现有什么好处
Java对象的序列化
Java平台允许我们在内存中创建可复用的Java对象,但一般情况下,只有当JVM处于运行时,这些对象才可能存在,即,这些对象的生命周期不会比JVM的生命周期更长。但在现实应用中,就可能要求在JVM停止运行之后能够保存(持久化)指定的对象,并在将来重新读取被保存的对象。Java对象序列化就能够帮助我们实现该功能。
使用Java对象序列化,在保存对象时,会把其状态保存为一组字节,在未来,再将这些字节组装成对象。必须注意地是,对象序列化保存的是对象的”状态”,即它的成员变量。由此可知, 对象序列化不会关注类中的静态变量 。
除了在持久化对象时会用到对象序列化之外,当使用RMI(远程方法调用),或在网络中传递对象时,都会用到对象序列化。Java序列化API为处理对象序列化提供了一个标准机制,该API简单易用,在本文的后续章节中将会陆续讲到。
1public class ArrayList<E> extends AbstractList<E>
2 implements List<E>, RandomAccess, Cloneable, java.io.Serializable
3{
4 private static final long serialVersionUID = 8683452581122892189L;
5 transient Object[] elementData; // non-private to simplify nested class access
6 private int size;
7}
如何对Java对象进行序列化与反序列化
在Java中,只要一个类实现了 java.io.Serializable 接口,那么它就可以被序列化。这里先来一段代码:
code 1 创建一个User类,用于序列化及反序列化
1package com.hollis;
2import java.io.Serializable;
3import java.util.Date;
4/**
5 * Created by hollis on 16/2/2.
6 */
7public class User implements Serializable{
8 private String name;
9 private int age;
10 private Date birthday;
11 private transient String gender;
12 private static final long serialVersionUID = -6849794470754667710L;
13 public String getName() {
14 return name;
15 }
16 public void setName(String name) {
17 this.name = name;
18 }
19 public int getAge() {
20 return age;
21 }
22 public void setAge(int age) {
23 this.age = age;
24 }
25 public Date getBirthday() {
26 return birthday;
27 }
28 public void setBirthday(Date birthday) {
29 this.birthday = birthday;
30 }
31 public String getGender() {
32 return gender;
33 }
34 public void setGender(String gender) {
35 this.gender = gender;
36 }
37 @Override
38 public String toString() {
39 return "User{" +
40 "name='" + name + '/'' +
41 ", age=" + age +
42 ", gender=" + gender +
43 ", birthday=" + birthday +
44 '}';
45 }
46}
47
code 2 对User进行序列化及反序列化的Demo
1package com.hollis;
2import org.apache.commons.io.FileUtils;
3import org.apache.commons.io.IOUtils;
4import java.io.*;
5import java.util.Date;
6/**
7 * Created by hollis on 16/2/2.
8 */
9public class SerializableDemo {
10 public static void main(String[] args) {
11 //Initializes The Object
12 User user = new User();
13 user.setName("hollis");
14 user.setGender("male");
15 user.setAge(23);
16 user.setBirthday(new Date());
17 System.out.println(user);
18 //Write Obj to File
19 ObjectOutputStream oos = null;
20 try {
21 oos = new ObjectOutputStream(new FileOutputStream("tempFile"));
22 oos.writeObject(user);
23 } catch (IOException e) {
24 e.printStackTrace();
25 } finally {
26 IOUtils.closeQuietly(oos);
27 }
28 //Read Obj from File
29 File file = new File("tempFile");
30 ObjectInputStream ois = null;
31 try {
32 ois = new ObjectInputStream(new FileInputStream(file));
33 User newUser = (User) ois.readObject();
34 System.out.println(newUser);
35 } catch (IOException e) {
36 e.printStackTrace();
37 } catch (ClassNotFoundException e) {
38 e.printStackTrace();
39 } finally {
40 IOUtils.closeQuietly(ois);
41 try {
42 FileUtils.forceDelete(file);
43 } catch (IOException e) {
44 e.printStackTrace();
45 }
46 }
47 }
48}
49//output
50//User{name='hollis', age=23, gender=male, birthday=Tue Feb 02 17:37:38 CST 2016}
51//User{name='hollis', age=23, gender=null, birthday=Tue Feb 02 17:37:38 CST 2016}
序列化及反序列化相关知识
1、在Java中,只要一个类实现了 java.io.Serializable 接口,那么它就可以被序列化。
2、通过 ObjectOutputStream 和 ObjectInputStream 对对象进行序列化及反序列化
3、虚拟机是否允许反序列化,不仅取决于类路径和功能代码是否一致,一个非常重要的一点是两个类的序列化 ID 是否一致(就是 private static final long serialVersionUID )
4、序列化并不保存静态变量。
5、要想将父类对象也序列化,就需要让父类也实现 Serializable 接口。
6、Transient 关键字的作用是控制变量的序列化,在变量声明前加上该关键字,可以阻止该变量被序列化到文件中,在被反序列化后,transient 变量的值被设为初始值,如 int 型的是 0,对象型的是 null。
7、服务器端给客户端发送序列化对象数据,对象中有一些数据是敏感的,比如密码字符串等,希望对该密码字段在序列化时,进行加密,而客户端如果拥有解密的密钥,只有在客户端进行反序列化时,才可以对密码进行读取,这样可以一定程度保证序列化对象的数据安全。
ArrayList的序列化
在介绍ArrayList序列化之前,先来考虑一个问题:
如何自定义的序列化和反序列化策略
带着这个问题,我们来看 java.util.ArrayList 的源码
code 3
1public class ArrayList<E> extends AbstractList<E>
2 implements List<E>, RandomAccess, Cloneable, java.io.Serializable
3{
4 private static final long serialVersionUID = 8683452581122892189L;
5 transient Object[] elementData; // non-private to simplify nested class access
6 private int size;
7}
笔者省略了其他成员变量,从上面的代码中可以知道ArrayList实现了 java.io.Serializable 接口,那么我们就可以对它进行序列化及反序列化。因为elementData是 transient 的,所以我们认为这个成员变量不会被序列化而保留下来。我们写一个Demo,验证一下我们的想法:
code 4
1public static void main(String[] args) throws IOException, ClassNotFoundException {
2 List<String> stringList = new ArrayList<String>();
3 stringList.add("hello");
4 stringList.add("world");
5 stringList.add("hollis");
6 stringList.add("chuang");
7 System.out.println("init StringList" + stringList);
8 ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream("stringlist"));
9 objectOutputStream.writeObject(stringList);
10 IOUtils.close(objectOutputStream);
11 File file = new File("stringlist");
12 ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream(file));
13 List<String> newStringList = (List<String>)objectInputStream.readObject();
14 IOUtils.close(objectInputStream);
15 if(file.exists()){
16 file.delete();
17 }
18 System.out.println("new StringList" + newStringList);
19 }
20//init StringList[hello, world, hollis, chuang]
21//new StringList[hello, world, hollis, chuang]
了解ArrayList的人都知道,ArrayList底层是通过数组实现的。那么数组 elementData 其实就是用来保存列表中的元素的。通过该属性的声明方式我们知道,他是无法通过序列化持久化下来的。那么为什么code 4的结果却通过序列化和反序列化把List中的元素保留下来了呢?
writeObject和readObject方法
在ArrayList中定义了来个方法: writeObject 和 readObject 。
这里先给出结论:
在序列化过程中,如果被序列化的类中定义了writeObject 和 readObject 方法,虚拟机会试图调用对象类里的 writeObject 和 readObject 方法,进行用户自定义的序列化和反序列化。
如果没有这样的方法,则默认调用是 ObjectOutputStream 的 defaultWriteObject 方法以及 ObjectInputStream 的 defaultReadObject 方法。
用户自定义的 writeObject 和 readObject 方法可以允许用户控制序列化的过程,比如可以在序列化的过程中动态改变序列化的数值。
来看一下这两个方法的具体实现:
code 5
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();
}
}
那么为什么ArrayList要用这种方式来实现序列化呢?
why transient
ArrayList实际上是动态数组,每次在放满以后自动增长设定的长度值,如果数组自动增长长度设为100,而实际只放了一个元素,那就会序列化99个null元素。为了保证在序列化的时候不会将这么多null同时进行序列化,ArrayList把元素数组设置为transient。
why writeObject and readObject
前面说过,为了防止一个包含大量空对象的数组被序列化,为了优化存储,所以,ArrayList使用 transient 来声明 elementData 。
但是,作为一个集合,在序列化过程中还必须保证其中的元素可以被持久化下来,所以,通过重写 writeObject 和 readObject 方法的方式把其中的元素保留下来。
writeObject 方法把 elementData 数组中的元素遍历的保存到输出流(ObjectOutputStream)中。
readObject 方法从输入流(ObjectInputStream)中读出对象并保存赋值到 elementData 数组中。
至此,我们先试着来回答刚刚提出的问题:
如何自定义的序列化和反序列化策略
答:可以通过在被序列化的类中增加writeObject 和 readObject方法。那么问题又来了:
虽然ArrayList中写了writeObject 和 readObject 方法,但是这两个方法并没有显示的被调用啊。
那么如果一个类中包含writeObject 和 readObject 方法,那么这两个方法是怎么被调用的呢?
ObjectOutputStream
从code 4中,我们可以看出,对象的序列化过程通过ObjectOutputStream和ObjectInputputStream来实现的,那么带着刚刚的问题,我们来分析一下ArrayList中的writeObject 和 readObject 方法到底是如何被调用的呢?
为了节省篇幅,这里给出ObjectOutputStream的writeObject的调用栈:
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的序列化方式。那么,不知道有没有人提出这样的疑问:
Serializable明明就是一个空的接口,它是怎么保证只有实现了该接口的方法才能进行序列化与反序列化的呢?
Serializable接口的定义:
1 public interface Serializable {
2}
读者可以尝试把code 1中的继承Serializable的代码去掉,再执行code 2,会抛出 java.io.NotSerializableException 。
其实这个问题也很好回答,我们再回到刚刚ObjectOutputStream的writeObject的调用栈:
writeObject — writeObject0 —writeOrdinaryObject—writeSerialData—invokeWriteObject
writeObject0方法中有这么一段代码:
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());
}
}
在进行序列化操作时,会判断要被序列化的类是否是Enum、Array和Serializable类型,如果不是则直接抛出 NotSerializableException 。
总结
1、如果一个类想被序列化,需要实现Serializable接口。否则将抛出 NotSerializableException 异常,这是因为,在序列化操作过程中会对类型进行检查,要求被序列化的类必须属于Enum、Array和Serializable类型其中的任何一种。
2、在变量声明前加上该关键字,可以阻止该变量被序列化到文件中。
3、在类中增加writeObject 和 readObject 方法可以实现自定义序列化策略
序列化是一种对象持久化的手段。普遍应用在网络传输、RMI等场景中。本文通过分析ArrayList的序列化来介绍Java序列化的相关内容。主要涉及到以下几个问题:
怎么实现Java的序列化
为什么实现了java.io.Serializable接口才能被序列化
transient的作用是什么
怎么自定义序列化策略
自定义的序列化策略是如何被调用的
ArrayList对序列化的实现有什么好处
Java对象的序列化
Java平台允许我们在内存中创建可复用的Java对象,但一般情况下,只有当JVM处于运行时,这些对象才可能存在,即,这些对象的生命周期不会比JVM的生命周期更长。但在现实应用中,就可能要求在JVM停止运行之后能够保存(持久化)指定的对象,并在将来重新读取被保存的对象。Java对象序列化就能够帮助我们实现该功能。
使用Java对象序列化,在保存对象时,会把其状态保存为一组字节,在未来,再将这些字节组装成对象。必须注意地是,对象序列化保存的是对象的”状态”,即它的成员变量。由此可知, 对象序列化不会关注类中的静态变量 。
除了在持久化对象时会用到对象序列化之外,当使用RMI(远程方法调用),或在网络中传递对象时,都会用到对象序列化。Java序列化API为处理对象序列化提供了一个标准机制,该API简单易用,在本文的后续章节中将会陆续讲到。
1public class ArrayList<E> extends AbstractList<E>
2 implements List<E>, RandomAccess, Cloneable, java.io.Serializable
3{
4 private static final long serialVersionUID = 8683452581122892189L;
5 transient Object[] elementData; // non-private to simplify nested class access
6 private int size;
7}
如何对Java对象进行序列化与反序列化
在Java中,只要一个类实现了 java.io.Serializable 接口,那么它就可以被序列化。这里先来一段代码:
code 1 创建一个User类,用于序列化及反序列化
1package com.hollis;
2import java.io.Serializable;
3import java.util.Date;
4/**
5 * Created by hollis on 16/2/2.
6 */
7public class User implements Serializable{
8 private String name;
9 private int age;
10 private Date birthday;
11 private transient String gender;
12 private static final long serialVersionUID = -6849794470754667710L;
13 public String getName() {
14 return name;
15 }
16 public void setName(String name) {
17 this.name = name;
18 }
19 public int getAge() {
20 return age;
21 }
22 public void setAge(int age) {
23 this.age = age;
24 }
25 public Date getBirthday() {
26 return birthday;
27 }
28 public void setBirthday(Date birthday) {
29 this.birthday = birthday;
30 }
31 public String getGender() {
32 return gender;
33 }
34 public void setGender(String gender) {
35 this.gender = gender;
36 }
37 @Override
38 public String toString() {
39 return "User{" +
40 "name='" + name + '/'' +
41 ", age=" + age +
42 ", gender=" + gender +
43 ", birthday=" + birthday +
44 '}';
45 }
46}
47
code 2 对User进行序列化及反序列化的Demo
1package com.hollis;
2import org.apache.commons.io.FileUtils;
3import org.apache.commons.io.IOUtils;
4import java.io.*;
5import java.util.Date;
6/**
7 * Created by hollis on 16/2/2.
8 */
9public class SerializableDemo {
10 public static void main(String[] args) {
11 //Initializes The Object
12 User user = new User();
13 user.setName("hollis");
14 user.setGender("male");
15 user.setAge(23);
16 user.setBirthday(new Date());
17 System.out.println(user);
18 //Write Obj to File
19 ObjectOutputStream oos = null;
20 try {
21 oos = new ObjectOutputStream(new FileOutputStream("tempFile"));
22 oos.writeObject(user);
23 } catch (IOException e) {
24 e.printStackTrace();
25 } finally {
26 IOUtils.closeQuietly(oos);
27 }
28 //Read Obj from File
29 File file = new File("tempFile");
30 ObjectInputStream ois = null;
31 try {
32 ois = new ObjectInputStream(new FileInputStream(file));
33 User newUser = (User) ois.readObject();
34 System.out.println(newUser);
35 } catch (IOException e) {
36 e.printStackTrace();
37 } catch (ClassNotFoundException e) {
38 e.printStackTrace();
39 } finally {
40 IOUtils.closeQuietly(ois);
41 try {
42 FileUtils.forceDelete(file);
43 } catch (IOException e) {
44 e.printStackTrace();
45 }
46 }
47 }
48}
49//output
50//User{name='hollis', age=23, gender=male, birthday=Tue Feb 02 17:37:38 CST 2016}
51//User{name='hollis', age=23, gender=null, birthday=Tue Feb 02 17:37:38 CST 2016}
序列化及反序列化相关知识
1、在Java中,只要一个类实现了 java.io.Serializable 接口,那么它就可以被序列化。
2、通过 ObjectOutputStream 和 ObjectInputStream 对对象进行序列化及反序列化
3、虚拟机是否允许反序列化,不仅取决于类路径和功能代码是否一致,一个非常重要的一点是两个类的序列化 ID 是否一致(就是 private static final long serialVersionUID )
4、序列化并不保存静态变量。
5、要想将父类对象也序列化,就需要让父类也实现 Serializable 接口。
6、Transient 关键字的作用是控制变量的序列化,在变量声明前加上该关键字,可以阻止该变量被序列化到文件中,在被反序列化后,transient 变量的值被设为初始值,如 int 型的是 0,对象型的是 null。
7、服务器端给客户端发送序列化对象数据,对象中有一些数据是敏感的,比如密码字符串等,希望对该密码字段在序列化时,进行加密,而客户端如果拥有解密的密钥,只有在客户端进行反序列化时,才可以对密码进行读取,这样可以一定程度保证序列化对象的数据安全。
ArrayList的序列化
在介绍ArrayList序列化之前,先来考虑一个问题:
如何自定义的序列化和反序列化策略
带着这个问题,我们来看 java.util.ArrayList 的源码
code 3
1public class ArrayList<E> extends AbstractList<E>
2 implements List<E>, RandomAccess, Cloneable, java.io.Serializable
3{
4 private static final long serialVersionUID = 8683452581122892189L;
5 transient Object[] elementData; // non-private to simplify nested class access
6 private int size;
7}
笔者省略了其他成员变量,从上面的代码中可以知道ArrayList实现了 java.io.Serializable 接口,那么我们就可以对它进行序列化及反序列化。因为elementData是 transient 的,所以我们认为这个成员变量不会被序列化而保留下来。我们写一个Demo,验证一下我们的想法:
code 4
1public static void main(String[] args) throws IOException, ClassNotFoundException {
2 List<String> stringList = new ArrayList<String>();
3 stringList.add("hello");
4 stringList.add("world");
5 stringList.add("hollis");
6 stringList.add("chuang");
7 System.out.println("init StringList" + stringList);
8 ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream("stringlist"));
9 objectOutputStream.writeObject(stringList);
10 IOUtils.close(objectOutputStream);
11 File file = new File("stringlist");
12 ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream(file));
13 List<String> newStringList = (List<String>)objectInputStream.readObject();
14 IOUtils.close(objectInputStream);
15 if(file.exists()){
16 file.delete();
17 }
18 System.out.println("new StringList" + newStringList);
19 }
20//init StringList[hello, world, hollis, chuang]
21//new StringList[hello, world, hollis, chuang]
了解ArrayList的人都知道,ArrayList底层是通过数组实现的。那么数组 elementData 其实就是用来保存列表中的元素的。通过该属性的声明方式我们知道,他是无法通过序列化持久化下来的。那么为什么code 4的结果却通过序列化和反序列化把List中的元素保留下来了呢?
writeObject和readObject方法
在ArrayList中定义了来个方法: writeObject 和 readObject 。
这里先给出结论:
在序列化过程中,如果被序列化的类中定义了writeObject 和 readObject 方法,虚拟机会试图调用对象类里的 writeObject 和 readObject 方法,进行用户自定义的序列化和反序列化。
如果没有这样的方法,则默认调用是 ObjectOutputStream 的 defaultWriteObject 方法以及 ObjectInputStream 的 defaultReadObject 方法。
用户自定义的 writeObject 和 readObject 方法可以允许用户控制序列化的过程,比如可以在序列化的过程中动态改变序列化的数值。
来看一下这两个方法的具体实现:
code 5
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();
}
}
那么为什么ArrayList要用这种方式来实现序列化呢?
why transient
ArrayList实际上是动态数组,每次在放满以后自动增长设定的长度值,如果数组自动增长长度设为100,而实际只放了一个元素,那就会序列化99个null元素。为了保证在序列化的时候不会将这么多null同时进行序列化,ArrayList把元素数组设置为transient。
why writeObject and readObject
前面说过,为了防止一个包含大量空对象的数组被序列化,为了优化存储,所以,ArrayList使用 transient 来声明 elementData 。
但是,作为一个集合,在序列化过程中还必须保证其中的元素可以被持久化下来,所以,通过重写 writeObject 和 readObject 方法的方式把其中的元素保留下来。
writeObject 方法把 elementData 数组中的元素遍历的保存到输出流(ObjectOutputStream)中。
readObject 方法从输入流(ObjectInputStream)中读出对象并保存赋值到 elementData 数组中。
至此,我们先试着来回答刚刚提出的问题:
如何自定义的序列化和反序列化策略
答:可以通过在被序列化的类中增加writeObject 和 readObject方法。那么问题又来了:
虽然ArrayList中写了writeObject 和 readObject 方法,但是这两个方法并没有显示的被调用啊。
那么如果一个类中包含writeObject 和 readObject 方法,那么这两个方法是怎么被调用的呢?
ObjectOutputStream
从code 4中,我们可以看出,对象的序列化过程通过ObjectOutputStream和ObjectInputputStream来实现的,那么带着刚刚的问题,我们来分析一下ArrayList中的writeObject 和 readObject 方法到底是如何被调用的呢?
为了节省篇幅,这里给出ObjectOutputStream的writeObject的调用栈:
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的序列化方式。那么,不知道有没有人提出这样的疑问:
Serializable明明就是一个空的接口,它是怎么保证只有实现了该接口的方法才能进行序列化与反序列化的呢?
Serializable接口的定义:
1 public interface Serializable {
2}
读者可以尝试把code 1中的继承Serializable的代码去掉,再执行code 2,会抛出 java.io.NotSerializableException 。
其实这个问题也很好回答,我们再回到刚刚ObjectOutputStream的writeObject的调用栈:
writeObject — writeObject0 —writeOrdinaryObject—writeSerialData—invokeWriteObject
writeObject0方法中有这么一段代码:
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());
}
}
在进行序列化操作时,会判断要被序列化的类是否是Enum、Array和Serializable类型,如果不是则直接抛出 NotSerializableException 。
总结
1、如果一个类想被序列化,需要实现Serializable接口。否则将抛出 NotSerializableException 异常,这是因为,在序列化操作过程中会对类型进行检查,要求被序列化的类必须属于Enum、Array和Serializable类型其中的任何一种。
2、在变量声明前加上该关键字,可以阻止该变量被序列化到文件中。
3、在类中增加writeObject 和 readObject 方法可以实现自定义序列化策略
发表评论
-
复习:强迫线程顺序执行方式
2019-01-03 23:42 1478方法1: 三个线程,t1,t2,t3,如果一定要按顺序执行, ... -
(转)不错的前后端处理异常的方法
2019-01-02 23:16 1966前言 在 Web 开发中, 我们经常会需要处理各种异常, 这是 ... -
info q的极客时间大咖说等资料下载
2018-08-15 08:40 3413info q的极客时间大咖说等资料下载,还有不少思维导图 链 ... -
CXF 客户端超时时间设置(非Spring配置方式)
2018-07-03 22:38 2184import org.apache.cxf.endpoint. ... -
(转)synchronized关键字画像:正确打开方式
2018-06-14 09:25 447https://mp.weixin.qq.com/s/b3Sx ... -
CountDownLatch的例子
2018-06-13 14:10 628public class StatsDemo { ... -
两道面试题,带你解析Java类加载机制
2018-06-12 16:29 553https://mp.weixin.qq.com/s/YTa0 ... -
Spring中获取request的几种方法,及其线程安全性分析
2018-06-11 09:03 622https://mp.weixin.qq.com/s/KeFJ ... -
内部类小结
2018-06-06 10:25 396https://mp.weixin.qq.com/s/hErv ... -
JVM虚拟机小结1
2018-06-04 20:43 4631 jps -l //列出详细的类名和进程ID 2)jps ... -
windows下自带命令行工具查看CPU资源情况等
2018-06-04 12:53 3038微软提供了不少命令行 ... -
apache common包中的序列化工具
2018-05-30 09:10 1774什么是序列化 我们的 ... -
JAVA8 JVM的变化: 元空间(Metaspace)
2018-05-24 22:30 909本文将会分享至今为至我收集的关于永久代(Permanent G ... -
(转)服务器性能指标(一)——负载(Load)分析及问题排查
2018-05-21 21:03 1263原创: Hollis Hollis 负载 ... -
(转)对象复用
2018-05-20 15:27 805public class Student { priv ... -
mapreduce中入门中要注意的几点
2018-05-06 08:59 619在 mapreduce中,比如有如下的词: I love b ... -
HDFS的基本操作
2018-05-02 21:47 880-mkdir 在HDFS创建目录 ... -
一个不错的开源工具类,专门用来解析日志头部的,好用
2018-05-02 20:00 707一个不错的开源工具类,专门用来解析日志头部的,好用。 http ... -
介绍个不错的RESTFUL MOCK的工具wiremock
2018-04-27 21:02 1853介绍个不错的RESTFUL MOCK的工具wiremock,地 ... -
LINUX下EPOLL等不错的文章收藏
2018-04-25 09:35 5031 通俗讲解 异步,非阻塞和 IO 复用 https:/ ...
相关推荐
本文通过分析ArrayList的序列化来介绍Java序列化的相关内容。主要涉及到以下几个问题: 怎么实现Java的序列化 为什么实现了java.io.Serializable接口才能被序列化 transient的作用是什么 怎么自定义序列...
深入理解Java虚拟机-Java内存区域透彻分析(序列化、反序列化概念及其使用场景+实现序列化的方式+transient关键字)
本篇文章是对Java中对象的序列化与反序列化进行了详细的分析介绍,需要的朋友参考下
有关Java对象的序列化和反序列化也算是Java基础的一部分,下面对Java序列化的机制和原理进行一些介绍
JAVA对象的序列化和反序列化 161 为什么需要序列化和反序列化 161 对象的序列化主要有两种用途 161 序列化涉及的类和接口 162 序列化/反序列化的步骤和实例 162 综合的序列化和反序列化练习 163 JAVA.IO包相关流对象...
理解通信协议传输过程中的序列化和反序列化机制 基于框架的RPC通信技术 WebService/ApacheCXF RMI/Spring RMI Hession 传统RPC技术在大型分布式架构下面临的问题 分布式架构下的RPC解决方案 Zookeeper ...
避免使用Java序列化14讲多线程之锁优化(下):使用乐观锁优化并行操作16讲多线程调优(下):如何优化多线程上下文切换17讲并发容器的使用:识别不同场景下最优容器21讲深入JVM即时编译器JIT,优化Java编译25讲答疑...
《数据结构与算法分析:Java语言描述(第2版)》把算法分析与最有效率的Java程序的开发有机地结合起来,深入分析每种算法,内容全面、缜密严格,并细致讲解精心构造程序的方法。目录: 译者序前言第1章 引论1.1 本书...
031217_【第12章:JAVA IO】_对象序列化笔记.pdf 031218_〖第12章:JAVA IO〗_实例操作—单人信息管理程序笔记.pdf 031219_〖第12章:JAVA IO〗_实例操作:投票程序笔记.pdf 031301_【第13章:Java类集】_认识类集、...
《数据结构与算法分析:Java语言描述(第2版)》把算法分析与最有效率的Java程序的开发有机地结合起来,深入分析每种算法,内容全面、缜密严格,并细致讲解精心构造程序的方法。内容截图目录:译者序前言第1章 引论...
12.2.8 通过序列化进行深层复制 12.2.9 使克隆具有更大的深度 12.2.10 为什么有这个奇怪的设计 12.3 克隆的控制 12.3.1 副本构建器 12.4 只读类 12.4.1 创建只读类 12.4.2 “一成不变”的弊端 12.4.3 不变字串 ...
12.2.8 通过序列化进行深层复制 12.2.9 使克隆具有更大的深度 12.2.10 为什么有这个奇怪的设计 12.3 克隆的控制 12.3.1 副本构建器 12.4 只读类 12.4.1 创建只读类 12.4.2 “一成不变”的弊端 12.4.3 不变字串 ...
12.2.8 通过序列化进行深层复制 12.2.9 使克隆具有更大的深度 12.2.10 为什么有这个奇怪的设计 12.3 克隆的控制 12.3.1 副本构建器 12.4 只读类 12.4.1 创建只读类 12.4.2 “一成不变”的弊端 12.4.3 不变字串 ...
9.6.1 Java国际化的思路 346 9.6.2 Java支持的语言和国家 346 9.6.3 完成程序国际化 347 9.6.4 使用MessageFormat处理包含占位符的字符串 349 9.6.5 使用类文件代替资源文件 350 9.6.6 使用NumberFormat格式化...
Java核心技术:如多线程、网络编程、序列化等都有详细的解释和示例。 常用框架:如Spring、MyBatis等框架的使用方法和内部原理都有涉及。 数据库相关:包括关系型数据库和非关系型数据库的使用,以及JDBC、MyBatis等...
《数据结构与算法分析:Java语言描述(第2版)》把算法分析与最有效率的Java程序的开发有机地结合起来,深入分析每种算法,内容全面、缜密严格,并细致讲解精心构造程序的方法。 内容截图 目录: 译者序 前言 ...
数据定义 基本数据操作 数据完整性 表达式 连接 合并 子查询 小结 附录B 序列化 简介 序列化 可串行化的接口 小结 附录C Java和分布式应用程序 分布式系统 分布式...
12.2.8 通过序列化进行深层复制 12.2.9 使克隆具有更大的深度 12.2.10 为什么有这个奇怪的设计 12.3 克隆的控制 12.3.1 副本构建器 12.4 只读类 12.4.1 创建只读类 12.4.2 “一成不变”的弊端 12.4.3 不变字串 ...
12.2.8 通过序列化进行深层复制 12.2.9 使克隆具有更大的深度 12.2.10 为什么有这个奇怪的设计 12.3 克隆的控制 12.3.1 副本构建器 12.4 只读类 12.4.1 创建只读类 12.4.2 “一成不变”的弊端 12.4.3 不变字串 ...