java序列化和反序列话总结
序列化:将java对象转换为字节序列的过程叫做序列化
反序列化:将字节对象转换为java对象的过程叫做反序列化
通常情况下,序列化有两种用途:、
1) 把对象的字节序列永久的保存在硬盘中
2)在网络上传输对象的字节序列
相应的API
java.io.ObjectOutputStream
writeObject(Object obj)
java.io.ObjectInputStream
readObject()
只有实现了Serializable或者Externalizable接口的类的对象才能够被序列化。否则当调用writeObject方法的时候会出现IOException。
需要注意的是Externalizable接口继承自Serializable接口。两者的区别如下:
仅仅实现Serializable接口的类可应采用默认的序列化方式。比如String类。
假设有一个Customer类的对象需要序列化,如果这个类仅仅实现了这个接口,那么序列化和反序列化的方式如下:ObjectOutputStream采用默认的序列化方式,对于这个类的非static,非transient的实例变量进行序列化。ObjectInputStream采用默认的反序列化方式,对于这个类的非static,非transient的实例变量进行反序列化。
如果这个类不仅实现了Serializable接口,而且定义了readObject(ObjectInputStream in)和 writeObject(ObjectOutputStream out)方法,那么将按照如下的方式进行序列化和反序列化:ObjectOutputStream会调用这个类的writeObject方法进行序列化,ObjectInputStream会调用相应的readObject方法进行反序列化。
实现Externalizable接口的类完全由自身来控制序列化的行为。而且必须实现writeExternal(ObjectOutput out)和readExternal(ObjectInput in)。那么将按照如下的方式进行序列化和反序列化:ObjectOutputStream会调用这个类的writeExternal方法进行序列化,ObjectInputStream会调用相应的readExternal方法进行反序列化。
下面来看一个最简单的例子:
package com.java; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.io.Serializable; public class simpleSerializableTest { public static void main(String[] args) throws Exception { ObjectOutputStream out=new ObjectOutputStream(new FileOutputStream("d:\\objectFile.obj")); String strObj="name"; Customer customer=new Customer("rollen"); //序列化,此处故意将同一对象序列化2次 out.writeObject(strObj); out.writeObject(customer); out.writeObject(customer); out.close(); //反序列化 ObjectInputStream in=new ObjectInputStream(new FileInputStream("d:\\objectFile.obj")); String strobj1=(String)in.readObject(); Customer cus1=(Customer)in.readObject(); Customer cus2=(Customer)in.readObject();<br> in.close(); System.out.println(strobj1+": "+cus1); System.out.println(strObj==strobj1); System.out.println(cus1==customer); System.out.println(cus1==cus2); } } class Customer implements Serializable { private static final long serialVersionUID = 1L; private String name; public Customer() { System.out.println("无参构造方法"); } public Customer(String name) { System.out.println("有参构造方法"); this.name = name; } public String toString() { return "[ "+name+" ]"; } }
输出结果为:
有参构造方法
name: [ rollen ]
false
false
true
可以看出,在进行反序列话的时候,并没有调用类的构造方法。而是直接根据他们的序列化数据在内存中创建新的对象。另外需要注意的是,如果由一个ObjectOutputStream对象多次序列化同一个对象,那么右一个objectInputStream对象反序列化后的也是同一个对象。(cus1==cus2结果为true可以看出)
看一段代码,证明static是不会被序列化的:
package com.java; import java.io.IOException; import java.io.ObjectOutputStream; import java.io.Serializable; import java.net.ServerSocket; import java.net.Socket; public class SerializableServer { public void send(Object obj) throws IOException { ServerSocket serverSocket = new ServerSocket(8000); while (true) { Socket socket = serverSocket.accept(); ObjectOutputStream out = new ObjectOutputStream( socket.getOutputStream()); out.writeObject(obj); out.writeObject(obj); out.close(); socket.close(); } } public static void main(String[] args) throws Exception { Customer customer = new Customer("rollen", "male"); new SerializableServer().send(customer); } } class Customer implements Serializable { private static final long serialVersionUID = 1L; private String name; private static int count; private transient String sex; static { System.out.println("调用静态代码块"); } public Customer() { System.out.println("无参构造方法"); } public Customer(String name, String sex) { System.out.println("有参构造方法"); this.name = name; this.sex = sex; count++; } public String toString() { return "[ " + count + " " + name + " " + sex + " ]"; } }
package com.java; import java.io.ObjectInputStream; import java.net.Socket; public class SerializableClient { public void recive() throws Exception { Socket socket = new Socket("localhost", 8000); ObjectInputStream in = new ObjectInputStream(socket.getInputStream()); Object obj1 = in.readObject(); Object obj2 = in.readObject(); System.out.println(obj1); System.out.println(obj1==obj2); } public static void main(String[] args) { try { new SerializableClient().recive(); } catch (Exception e) { e.printStackTrace(); } } }
运行结果中,count的值为0.
我们来看另外一种情况:
class A implements Serializable{ B b; //... } class B implements Serializable{ //... }
当我们在序列化A的对象的时候,也会自动序列化和他相关联的B的对象。也就是说在默认的情况下,对象输出流会对整个对象图进行序列化。因此会导致出现下面的问题,看代码(例子中是使用双向列表作为内部结构的,只是给出了demo,并没有完整的实现,只是为了说明情况):
package com.java; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.io.Serializable; public class SeriListTest implements Serializable { private static final long serialVersionUID = 1L; private int size; private Node head = null; private Node end = null; private static class Node implements Serializable { private static final long serialVersionUID = 1L; String data; Node next; Node previous; } // 列表末尾添加一个字符串 public void add(String data) { Node node = new Node(); node.data = data; node.next = null; node.previous = end; if (null != end) { end.next = node; } size++; end = node; if (size == 1) { head = end; } } public int getSize() { return size; } // other methods... public static void main(String[] args) throws Exception { SeriListTest list = new SeriListTest(); for (int i = 0; i < 10000; ++i) { list.add("rollen" + i); } ByteArrayOutputStream buf = new ByteArrayOutputStream(); ObjectOutputStream out = new ObjectOutputStream(buf); out.writeObject(list); ObjectInputStream in = new ObjectInputStream(new ByteArrayInputStream( buf.toByteArray())); list = (SeriListTest) in.readObject(); System.out.println("size is :" + list.getSize()); } }
这段代码会出现如下错误:
Exception in thread "main" java.lang.StackOverflowError
at java.lang.ref.ReferenceQueue.poll(ReferenceQueue.java:82)
at java.io.ObjectStreamClass.processQueue(ObjectStreamClass.java:2234)
....
整个就是因为序列化的时候,对整个对象图进行序列化引起的问题。在这种情况下啊,我们需要自定义序列化的过程:
package com.java; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.io.Serializable; public class SeriListTest implements Serializable { private static final long serialVersionUID = 1L; transient private int size; transient private Node head = null; transient private Node end = null; private static class Node implements Serializable { private static final long serialVersionUID = 1L; String data; Node next; Node previous; } // 列表末尾添加一个字符串 public void add(String data) { Node node = new Node(); node.data = data; node.next = null; node.previous = end; if (null != end) { end.next = node; } size++; end = node; if (size == 1) { head = end; } } public int getSize() { return size; } // other methods... private void writeObject(ObjectOutputStream outStream) throws IOException { outStream.defaultWriteObject(); outStream.writeInt(size); for (Node node = head; node != null; node = node.next) { outStream.writeObject(node.data); } } private void readObject(ObjectInputStream inStream) throws IOException, ClassNotFoundException { inStream.defaultReadObject(); int count = inStream.readInt(); for (int i = 0; i < count; ++i) { add((String) inStream.readObject()); } } public static void main(String[] args) throws Exception { SeriListTest list = new SeriListTest(); for (int i = 0; i < 10000; ++i) { list.add("rollen" + i); } ByteArrayOutputStream buf = new ByteArrayOutputStream(); ObjectOutputStream out = new ObjectOutputStream(buf); out.writeObject(list); ObjectInputStream in = new ObjectInputStream(new ByteArrayInputStream( buf.toByteArray())); list = (SeriListTest) in.readObject(); System.out.println("size is :" + list.getSize()); } }
运行结果为:10000
现在我们总结一下,在什么情况下我们需要自定义序列化的方式:
1)为了确保序列化的安全性,对于一些敏感信息加密
2)确保对象的成员变量符合正确的约束条件
3)优化序列化的性能(之前的那个例子已经解释了这种情况)
下面我们来用例子解释一下这些:
先来看看:为了确保序列化的安全性,对于一些敏感信息加密
package com.java; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.io.Serializable; public class SeriDemo1 implements Serializable { private String name; transient private String password; // 注意此处的transient public SeriDemo1() { } public SeriDemo1(String name, String password) { this.name = name; this.password = password; } // 此处模拟对密码进行加密,进行了简化 private String change(String password) { return password + "rollen"; } private void writeObject(ObjectOutputStream outStream) throws IOException { outStream.defaultWriteObject(); outStream.writeObject(change(password)); } private void readObject(ObjectInputStream inStream) throws IOException, ClassNotFoundException { inStream.defaultReadObject(); String strPassowrd = (String) inStream.readObject(); //此处模拟对密码解密 password = strPassowrd.substring(0, strPassowrd.length() - 6); } @Override public String toString() { return "SeriDemo1 [name=" + name + ", password=" + password + "]"; } public static void main(String[] args) throws Exception { SeriDemo1 demo = new SeriDemo1("hello", "1234"); ByteArrayOutputStream buf = new ByteArrayOutputStream(); ObjectOutputStream out = new ObjectOutputStream(buf); out.writeObject(demo); ObjectInputStream in = new ObjectInputStream(new ByteArrayInputStream( buf.toByteArray())); demo = (SeriDemo1) in.readObject(); System.out.println(demo); } }
然后我们看看:确保对象的成员变量符合正确的约束条件。比如一般情况下我们会在构造函数中对于参数进行合法性检查,但是默认的序列化并不会调用类的构造函数,直接由对象的序列化数据来构造出一个对象,这个我们就有可能提供遗传非法的序列化数据,来构造一个不满足约束条件的对象。
为了避免这种情况,我们可以自定义反序列话的方式。比如在readObject方法中,进行检查。当数据不满足约束的时候(比如年龄小于0等等不满足约束的情况),可以抛出异常之类的。
接下来我们看看readResolve()方法在单例模式中的使用:
单例模式大家应该都清楚,我就不多说了,看看下面的代码:
package com.java; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.io.Serializable; public class ReadResolveDemo implements Serializable { private static final long serialVersionUID = 1L; private ReadResolveDemo() { } public static ReadResolveDemo getInstance() { return new ReadResolveDemo(); } public static void main(String[] args) throws Exception { ReadResolveDemo demo=ReadResolveDemo.getInstance(); ByteArrayOutputStream buf = new ByteArrayOutputStream(); ObjectOutputStream out = new ObjectOutputStream(buf); out.writeObject(demo); ObjectInputStream in = new ObjectInputStream(new ByteArrayInputStream( buf.toByteArray())); ReadResolveDemo demo1 = (ReadResolveDemo) in.readObject(); System.out.println(demo==demo1); //false } }
本来单例模式中,只有一个实例,但是在序列化的时候,无论采用默认的方式,还是自定义的方式,在反序列化的时候都会产生一个新的对象,所以上面的程序运行输出false。
因此可以看出反序列化打破了单例模式只有一个实例的约定,为了避免这种情况,我们可以使用readReslove:
package com.java; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.io.Serializable; public class ReadResolveDemo implements Serializable { private static final long serialVersionUID = 1L; private static final ReadResolveDemo INSTANCE = new ReadResolveDemo(); private ReadResolveDemo() { } public static ReadResolveDemo getInstance() { return INSTANCE; } private Object readResolve() { return INSTANCE; } public static void main(String[] args) throws Exception { ReadResolveDemo demo = ReadResolveDemo.getInstance(); ByteArrayOutputStream buf = new ByteArrayOutputStream(); ObjectOutputStream out = new ObjectOutputStream(buf); out.writeObject(demo); ObjectInputStream in = new ObjectInputStream(new ByteArrayInputStream( buf.toByteArray())); ReadResolveDemo demo1 = (ReadResolveDemo) in.readObject(); System.out.println(demo == demo1); // true } }
最后我们简单的说一下实现Externalizable接口。 实现Externalizable接口的类完全由自身来控制序列化的行为。而且必须实现writeExternal(ObjectOutput out)和readExternal(ObjectInput in)。
注意在对实现了这个接口的对象进行反序列化的时候,会先调用类的不带参数的构造函数,这个和之前的默认反序列化方式是不一样的。
例子如下:
package com.java; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.Externalizable; import java.io.IOException; import java.io.ObjectInput; import java.io.ObjectInputStream; import java.io.ObjectOutput; import java.io.ObjectOutputStream; public class ExternalizableDemo implements Externalizable { private String name; static { System.out.println("调用静态代码块"); } public ExternalizableDemo() { System.out.println("调用默认无参构造函数"); } public ExternalizableDemo(String name) { this.name = name; System.out.println("调用有参构造函数"); } @Override public void writeExternal(ObjectOutput out) throws IOException { out.writeObject(name); } @Override public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException { name = (String) in.readObject(); } @Override public String toString() { return "[" + name + "]"; } public static void main(String[] args) throws Exception { ExternalizableDemo demo = new ExternalizableDemo("rollen"); ByteArrayOutputStream buf = new ByteArrayOutputStream(); ObjectOutputStream out = new ObjectOutputStream(buf); out.writeObject(demo); ObjectInputStream in = new ObjectInputStream(new ByteArrayInputStream( buf.toByteArray())); demo = (ExternalizableDemo) in.readObject(); System.out.println(demo); } }
输出:
调用静态代码块
调用有参构造函数
调用默认无参构造函数
[rollen]
参考资料:
1.java序列化高级认识
相关推荐
java 序列化和反序列化的方法 Java 序列化和反序列化是 Java 语言中的一种机制,用于将对象转换为字节流,以便在网络上传输或存储。序列化是将对象转换为字节流的过程,而反序列化是将字节流转换回对象的过程。 在...
Java反序列化是一种将已序列化的对象状态转换回对象的过程,它是Java平台中持久化数据的一种常见方式。在Java应用程序中,序列化用于保存对象的状态以便稍后恢复,或者在网络间传输对象。然而,这个过程也可能引入...
Xson是一个Java对象序列化和反序列化程序。支持Java对象到字节数组的序列化,和从字节数组到Java对象的反序列化。 Maven: <groupId>com.github.xsonorg</groupId> <artifactId>xson-core <version>1.0.1 ...
### Java序列化与反序列化详解 #### 一、Java序列化概述 Java序列化(Serialization)是一项重要的功能,它可以将对象的状态转化为一系列字节,从而实现对象的持久化存储或在网络上传输。序列化机制使得Java对象...
Java对象的序列化和反序列化是Java编程中一项...总结,Java对象的序列化和反序列化是Java编程中的基础概念,它涉及到数据持久化、网络通信等多个方面。理解并熟练运用这一技术,能够帮助开发者更有效地管理和传递数据。
总结来说,C#和Java的序列化和反序列化机制都是各自语言中不可或缺的一部分,它们使得数据能够在不同环境之间自由流动。理解和掌握这些技术对于任何软件开发者来说都是非常重要的,特别是涉及到数据持久化、网络通信...
**一、Java序列化** 1. **什么是序列化**:序列化是将对象的状态(属性和成员变量)转换为可以存储或传输的数据格式的过程。在Java中,通常是将对象转换为字节数组,以便写入磁盘或通过网络发送。 2. **为什么需要...
### Java序列化(Serializable)的作用与反序列化详解 #### 一、序列化的概念 序列化是指将程序中的对象转换为一系列字节流的过程,主要用于保存对象的状态或在网络之间传输对象。序列化的主要目的是为了能够持久化...
### Java序列化(Serializable)的作用与反序列化详解 #### 一、序列化是什么? 序列化是指将程序中的对象转换为字节流的过程,从而方便存储或传输这些对象。通常,序列化用于将对象的状态(即其实例变量的值,而非...
综上,Java对象序列化和反序列化是Java开发中的基础技能,它们在数据持久化、网络通信等方面发挥着关键作用。了解并掌握这些知识,能够帮助开发者更好地设计和实现各种功能。在实际应用中,需要注意安全性和版本兼容...
Java序列化与反序列化 Java序列化与反序列化 Java序列化与反序列化 Java序列化与反序列化 Java序列化与反序列化
总结,Java中的JSON序列化与反序列化是数据交互的重要环节,Jackson和Gson是两个常用的库,它们提供了丰富的功能和良好的API设计,使得处理JSON数据变得简单高效。通过理解和掌握这些知识,开发者可以更好地在Java...
3. **数据格式**:Java序列化生成的字节流是平台和版本相关的,不适用于跨平台或跨语言通信。 4. **替代方案**:Java序列化并不是唯一的选择。例如,JSON、XML、protobuf等轻量级序列化库提供了更高效、更安全的...
在IT领域,序列化和反序列化是两个关键的概念,特别是在网络通信、数据持久化以及对象存储中。本文将深入探讨Hessian框架的基础知识,它是一个高效的二进制序列化协议,广泛应用于Java和.NET之间跨语言通信。通过...
用户可以编写.proto文件,定义消息类型,然后编译成各种目标语言(如C++、Java或Python)的类,这些类可以用于序列化和反序列化数据。 在处理Proto文件时,序列化意味着将根据.proto文件定义的消息实例转换为二进制...
Java序列化和反序列化是Java平台中的核心特性,允许对象在各种上下文中持久化和传输。虽然它提供了许多便利,但同时也需要注意安全性问题和性能优化。在实际开发中,根据需求选择合适的序列化策略和工具是至关重要的...
Java序列化是Java平台中的一种标准机制,允许将对象的状态转换为字节流,以便存储在磁盘上、通过网络进行传输或者在某些时候恢复原来的对象状态。这一过程包括两个主要步骤:对象的序列化(将对象转换为字节流)和反...
在Java编程语言中,对象的序列化和反...总结,Java对象的序列化和反序列化是Java开发中的重要技能,它们可以帮助我们处理数据持久化、网络通信等问题。了解并熟练掌握这些技术,将有助于提升软件的稳定性和可维护性。
- Java序列化:Java序列化虽然方便,但生成的数据量较大,且序列化和反序列化速度相对较慢。 2. 数据结构: - Protocol Buffer:PB允许定义自己的数据结构(消息类型),通过.proto文件进行描述,支持基本数据...
总结来说,Java中的序列化和反序列化是处理对象和文件之间转换的关键技术。它们允许我们保存和恢复对象状态,方便数据存储和传输。同时,我们也应该了解如何处理文件操作,以及在面对类结构变动和安全性问题时如何...