今天来学习下Java序列化和反序列化技术,笔者对《Java编程思想》中的内容,结合网上各位前辈的帖子进行了整理和补充,包括:
笔者将Hession、Json、XML都归为是一种序列化技术(序列化的本质是“轻量级持久化”,而Hession、Json、XML都属于这个范畴,Hessian、Json主要用于数据网络传输,XML用于数据介质的存储)。内容还是比较丰富和完善的,部分内容直接参考了第三方博客,参考文献中有说明,希望大家指教!
对象的序列化是非常有意义的事情,利用它可以实现轻量级持久性(lightweight persistence)。“持久性”表明对象的生存周期不取决于程序是否正在执行,可以生存与程序的调用之间;“轻量级”表明序列化并不像ORM,需要显示的在程序中进行序列化(serialize)和反序列化操作(deserialize)。
Serialize:把对象转换为字节序列的过程称为对象的序列化。
Deserialize:把字节序列恢复为对象的过程称为对象的反序列化。
在很多应用中,需要对某些对象进行序列化,让它们离开内存空间,入住物理硬盘,以便长期保存。比如最常见的是Web服务器中的Session对象,当有 10万用户并发访问,就有可能出现10万个Session对象,内存可能吃不消,于是Web容器就会把一些seesion先序列化到硬盘中,等要用了,再把保存在硬盘中的对象还原到内存中。
当两个进程在进行远程通信时,彼此可以发送各种类型的数据。无论是何种类型的数据,都会以二进制序列的形式在网络上传送。发送方需要把这个Java对象转换为字节序列,才能在网络上传送;接收方则需要把字节序列再恢复为Java对象。
数据介质存储:把对象的字节序列永久地保存到硬盘上,通常存放在一个文件中;
数据网络传输:在网络上传送对象的字节序列。
Java原生的序列化技术包括Serializable
接口和Externalizable
接口。
只要对象实现了Serializable
接口(该接口仅是一个标记接口,不包括任何方法),对象的序列化处理就会非常简单。要序列化一个对象,首先要创建OutputStream
对象,然后将其封装在一个ObjectOutputStream
对象内,再调用writeObject()
方法即可对象序列化。反之,需要将一个InputStream封装在ObjectInputStream
内,然后调用readObject()
方法反序列化。
下面给一个例子:
package c18_Serial;
import java.io.Serializable;
public class Tester implements Serializable{
/**
* 显示地定义serialVersionUID
*/
private static final long serialVersionUID = -730422746011869282L;
private String test;
public String getTest() {
return test;
}
public void setTest(String test) {
this.test = test;
}
}
package c18_Serial;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
public class SerializableDemo {
public static void Serialize() throws IOException {
Tester tester = new Tester();
tester.setTest("序列化测试!");
ObjectOutputStream oo = new ObjectOutputStream(new
FileOutputStream(new File("E:/Person.txt")));
oo.writeObject(tester);
System.out.println("Person对象序列化成功!");
oo.close();
}
public static Tester Deserialize() throws IOException, ClassNotFoundException {
@SuppressWarnings("resource")
ObjectInputStream ois = new ObjectInputStream(new
FileInputStream(new File("E:/Person.txt")));
Tester tester = (Tester) ois.readObject();
System.out.println("Person对象反序列化成功!");
return tester;
}
public static void main(String[] args) throws IOException, ClassNotFoundException {
Serialize();
Tester tester = Deserialize();
}
}
输出结果:
Person对象序列化成功!
Person对象反序列化成功!
serialVersionUID表示序列化的版本号,凡是实现Serializable接口的类都有一个表示序列化版本标识符的静态变量。serialVersionUID的取值是Java运行时环境根据类的内部细节自动生成的。如果对类的源代码作了修改,再重新编译,新生成的类文件的serialVersionUID的取值有可能也会发生变化。
在进行反序列化时,JVM会把传来的字节流中的serialVersionUID与本地相应实体(类)的serialVersionUID进行比较,如果相同就认为是一致的,可以进行反序列化,否则就会出现序列化版本不一致的异常。
serialVersionUID分为两种:
默认的serialVersionUID:
private static final long serialVersionUID = 1L
;
生成的serialVersionUID:
private static final long serialVersionUID = xxxxL;
根据类名、接口名、成员方法及属性等来生成一个64位的哈希字段
类的serialVersionUID的默认值完全依赖于Java编译器的实现,对于同一个类,用不同的Java编译器编译,有可能会导致不同的 serialVersionUID,也有可能相同。为了提高serialVersionUID的独立性和确定性,强烈建议在一个可序列化类中显示的定义serialVersionUID,为它赋予明确的值。
当我们对序列化进行控制时,如果子对象表示的是我们不希望将其序列化的敏感信息。面对这种情况,我们有两种方案:
transient关键字只能和Serializable对象一起使用。举个例子:
package c18_Serial;
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;
import java.util.Date;
import java.util.concurrent.TimeUnit;
public class TransientDemo implements Serializable {
private static final long serialVersionUID = 397689623972578855L;
private Date date = new Date();
private String username;
private transient String password;
public TransientDemo(String name, String pwd) {
username = name;
password = pwd;
}
public String toString() {
return "logon info:\n username:" + username + "\n date:" + date
+ "\n password:" + password;
}
public static void main(String[] args) throws FileNotFoundException,
IOException, InterruptedException, ClassNotFoundException {
TransientDemo t = new TransientDemo("Hulk", "myLittlePony");
System.out.println("TransientDemo t = " + t);
ObjectOutputStream o = new ObjectOutputStream(new FileOutputStream(
"TransientDemo.out"));
o.writeObject(t);
o.close();
TimeUnit.SECONDS.sleep(1);
ObjectInputStream in = new ObjectInputStream(new FileInputStream(
"TransientDemo.out"));
System.out.println("Recovering object t" + new Date());
t = (TransientDemo)in.readObject();
System.out.println("TransientDemo t = " +t);
in.close();
}
}
输出结果:
TransientDemo t = logon info:
username:Hulk
date:Fri Jun 24 17:25:48 CST 2016
password:myLittlePony
Recovering object tFri Jun 24 17:25:49 CST 2016
TransientDemo t = logon info:
username:Hulk
date:Fri Jun 24 17:25:48 CST 2016
password:null
Externalizable接口继承了Serializable接口,并增加了两个方法writeExternal()和readExternal()来控制哪些属性可以序列化,哪些不可以:在writeExternal()方法里定义了哪些属性可以序列化,哪些不可以序列化,所以,对象在经过这里就把规定能被序列化的序列化保存文件,不能序列化的不处理,然后在反序列的时候自动调用readExternal()方法,根据序列顺序挨个读取进行反序列,并自动封装成对象返回。
package c18_Serial;
import java.io.Externalizable;
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;
import java.util.Date;
public class ExternalizableDemo implements Externalizable{
private String username;
private transient String password;
public ExternalizableDemo(String name, String pwd) {
username = name;
password = pwd;
}
public String toString() {
return "logon info:\n username:" + username
+ "\n password:" + password;
}
@Override
public void writeExternal(ObjectOutput out) throws IOException {
System.out.println("执行序列化操作");
//可以在序列化时写非自身的属性
out.writeObject(new Date());
//只序列化username
out.writeObject(username);
}
@Override
public void readExternal(ObjectInput in) throws IOException,
ClassNotFoundException {
System.out.println("执行反序列化操作");
Date d = (Date)in.readObject();
System.out.println("Date: " + d);
username = (String)in.readObject();
}
public static void main(String[] args) throws Exception, IOException {
ExternalizableDemo e = new ExternalizableDemo("Hulk", "myLittlePony");
ObjectOutputStream o = new ObjectOutputStream(new FileOutputStream(
"ExternalizableDemo.out"));
o.writeObject(e);
o.close();
ObjectInputStream in = new ObjectInputStream(new FileInputStream(
"ExternalizableDemo.out"));
System.out.println("Recovering object t" + new Date());
e = (ExternalizableDemo)in.readObject();
System.out.println("ExternalizableDemo t = " + e);
in.close();
}
}
Hessian是一个轻量级的remoting onhttp工具,使用简单的方法提供了RMI(Remote Method Invoke 远程方法调用)的功能,相比WebService,Hessian更简单、快捷。
官方网站(http://hessian.caucho.com/)提供Java、Flash/Flex、Python、C++、.NET C#等实现。Hessian和Axis、XFire都能实现web service方式的远程方法调用,区别是Hessian是二进制协议,Axis、XFire则是SOAP协议,所以从性能上说Hessian远优于后两者,并且Hessian的JAVA使用方法非常简单。Hessian由于没有WSDL这种服务描述文件去对实现进行规定,似乎更适合内部分布式系统之间的交互,对外提供服务优于Axis、XFire。
Hessian的实现包括服务端和客户端。
通过maven引入Hessian的jar包,最新版本是4.0.38。如下所示
com.caucho
hessian
4.0.38
服务端设计一个接口,用来给客户端调用,并实现该接口的动能。
public interface IHello {
String sayHello();
}
public class IHelloImpl extends HessianServlet implements IHello {
@Override
public String sayHello() {
// TODO Auto-generated method stub
return "Hello,I from HessianService";
}
}
配置web.xml,配置相应的servlet
Hello
com.caucho.hessian.server.HessianServlet
home-class
com.kcpt.hessian.service.IHelloImpl
home-api
com.kcpt.hessian.service.IHello
1
Hello
/Hello
java客户端包含Hessian.jar包
具有和服务器端结构一样的接口和实体类。包括命名空间都最好一样。将服务器端打包放到客户端,利用HessianProxyFactory调用远程接口。
public class ClientTest {
public static String url = "http://127.0.0.1:8080/HessianService/Hello";
public static void main(String[] args){
HessianProxyFactory factory = new HessianProxyFactory();
try {
IHello iHello = (IHello) factory.create(IHello.class, url);
System.out.println(iHello.sayHello());
} catch (MalformedURLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
在实际应用中,我们不只是简单的只使用Hessian来进行通信的,如果方法多得话,还不如直接写在客户端来调用,然而:当Hessian与Spring结合后,大大减少了这些操作,将dao层的操作全部放在Hessian服务端,将业务逻辑全部放在Hessian客户端,这样的话我们的Hessian客户端和服务端完全分离,因此我们的业务逻辑和dao层就真正的达到了分离,就可以放在不同的服务器上,当然Hessian的通信的作用不仅仅只有这些。
有关Hessian与Spring结合的具体实现,我们有机会通过具体的篇幅再分享,这里不再深入。
JSON是JavaScript Object Notation的缩写,是一种轻量级的数据交换形式,是一种XML的替代方案,而且比XML更小,更快而且更易于解析。
举个简单的例子
XML格式:
<person>
<name>xiazdongname>
<age>20age>
person>
JSON格式:
{ "name":"xiazdong", "age":20 }
JSON工具类有许多种,这里列出三个比较流行的json工具类:Jackson,Gson,FastJson
Jackson社区相对比较活跃,更新速度也比较快。Jackson对于复杂类型的json转换bean会出现问题,一些集合Map,List的转换出现问题。Jackson对于复杂类型的bean转换Json,转换的json格式不是标准的Json格式。
package serialize.json;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import org.codehaus.jackson.JsonEncoding;
import org.codehaus.jackson.JsonGenerator;
import org.codehaus.jackson.map.ObjectMapper;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import serialize.UserVo;
public class JacksonTest
{
private UserVo user = null;
private JsonGenerator jsonGenerator = null;
private ObjectMapper objectMapper = null;
@Before
public void init()
{
user = new UserVo();
user.setName("zzh");
user.setAge(18);
UserVo f1 = new UserVo();
f1.setName("jj");
f1.setAge(17);
UserVo f2 = new UserVo();
f2.setName("qq");
f2.setAge(19);
List friends = new ArrayList();
friends.add(f1);
friends.add(f2);
user.setFriends(friends);
objectMapper = new ObjectMapper();
try
{
jsonGenerator = objectMapper.getJsonFactory().createJsonGenerator(System.out,JsonEncoding.UTF8);
}
catch (IOException e)
{
e.printStackTrace();
}
}
@After
public void destory()
{
try
{
if(jsonGenerator != null)
{
jsonGenerator.flush();
}
if(!jsonGenerator.isClosed())
{
jsonGenerator.close();
}
jsonGenerator = null;
objectMapper = null;
user = null;
}
catch (IOException e)
{
e.printStackTrace();
}
}
@Test
public void writeJson()
{
try
{
jsonGenerator.writeObject(user);
System.out.println();
System.out.println(objectMapper.writeValueAsBytes(user).length);
// System.out.println(objectMapper.writeValueAsString(user).length());
}
catch (IOException e)
{
e.printStackTrace();
}
}
@Test
public void readJson()
{
String serString = "{\"name\":\"zzh\",\"age\":18,\"friends\":[{\"name\":\"jj\",\"age\":17,\"friends\":null},{\"name\":\"qq\",\"age\":19,\"friends\":null}]}";
UserVo uservo = null;
try
{
uservo = objectMapper.readValue(serString, UserVo.class);
}
catch (IOException e)
{
e.printStackTrace();
}
System.out.println(uservo.getName());
}
}
序列化大小:111
注意到这里Jackson会输出null,在Jackson的2.x版本中可以通过设置而使其不输出null的字段。
Gson是目前功能最全的Json解析神器,Gson当初是为因应Google公司内部需求而由Google自行研发而来,但自从在2008年五月公开发布第一版后已被许多公司或用户应用。Gson的应用主要为toJson与fromJson两个转换函数,无依赖,不需要例外额外的jar,能够直接跑在JDK上。而在使用这种对象转换之前需先创建好对象的类型以及其成员才能成功的将JSON字符串成功转换成相对应的对象。类里面只要有get和set方法,Gson完全可以将复杂类型的json到bean或bean到json的转换,是JSON解析的神器。Gson在功能上面无可挑剔,但是性能上面比FastJson有所差距。
package serialize.json;
import static org.junit.Assert.*;
import java.util.ArrayList;
import java.util.List;
import org.junit.Before;
import org.junit.Test;
import com.google.gson.Gson;
import com.google.gson.JsonSyntaxException;
import serialize.UserVo;
public class GsonTest
{
private UserVo user = null;
@Before
public void init()
{
user = new UserVo();
user.setName("zzh");
user.setAge(18);
UserVo f1 = new UserVo();
f1.setName("jj");
f1.setAge(17);
UserVo f2 = new UserVo();
f2.setName("qq");
f2.setAge(19);
List friends = new ArrayList();
friends.add(f1);
friends.add(f2);
user.setFriends(friends);
}
@Test
public void writeJson()
{
try
{
String str = Gson.class.newInstance().toJson(user);//一行就可以搞定!!!
System.out.println(str);
System.out.println(str.length());
}
catch (InstantiationException | IllegalAccessException e)
{
e.printStackTrace();
}
}
@Test public void readJson()
{
String serString = "{\"name\":\"zzh\",\"age\":18,\"friends\":[{\"name\":\"jj\",\"age\":17},{\"name\":\"qq\",\"age\":19}]}";
try
{
UserVo userVo = Gson.class.newInstance().fromJson(serString, UserVo.class);
System.out.println(userVo.getName());
}
catch (JsonSyntaxException | InstantiationException | IllegalAccessException e)
{
e.printStackTrace();
}
}
}
序列化大小:81
Gson和Jackson的区别是:如果你的应用经常会处理大的JSON文件,那么Jackson应该是你的菜。**GSON在大文件上表现得相当吃力。如果你主要是处理小文件请求,比如某个微服务或者分布式架构的初始化,那么GSON当是首选。**Jackson在小文件上的表现则不如人意。
Fastjson是一个Java语言编写的高性能的JSON处理器,由阿里巴巴公司开发。无依赖,不需要例外额外的jar,能够直接跑在JDK上。
FastJson在复杂类型的Bean转换Json上会出现一些问题,可能会出现引用的类型,导致Json转换出错,需要制定引用。FastJson采用独创的算法,将parse的速度提升到极致,超过所有json库。
package serialize.json;
import static org.junit.Assert.*;
import java.util.ArrayList;
import java.util.List;
import org.junit.Before;
import org.junit.Test;
import com.alibaba.fastjson.JSON;
import serialize.UserVo;
public class FastJsonTest
{
private UserVo user = null;
@Before
public void init()
{
user = new UserVo();
user.setName("zzh");
user.setAge(18);
UserVo f1 = new UserVo();
f1.setName("jj");
f1.setAge(17);
UserVo f2 = new UserVo();
f2.setName("qq");
f2.setAge(19);
List friends = new ArrayList();
friends.add(f1);
friends.add(f2);
user.setFriends(friends);
}
@Test public void writeJson()
{
String str = JSON.toJSONString(user);
System.out.println(str);
System.out.println(str.length());
}
@Test public void readJson()
{
String serString = "{\"name\":\"zzh\",\"age\":18,\"friends\":[{\"name\":\"jj\",\"age\":17},{\"name\":\"qq\",\"age\":19}]}";
UserVo userVo = JSON.parseObject(serString,UserVo.class);
System.out.println(userVo.getName());
}
}
如果只是功能要求,没有性能要求,可以使用google的Gson,如果有性能上面的要求可以使用Gson将bean转换json确保数据的正确,使用FastJson将Json转换Bean。
XML现在已经成为一种通用的数据交换格式,它的平台无关性,语言无关性,系统无关性,给数据集成与交互带来了极大的方便。对于XML本身的语法知识与技术细节,需要阅读相关的技术文献,这里面包括的内容有DOM(Document Object Model), DTD(Document Type Definition), SAX(Simple API for XML),XSD(Xml Schema Definition),XSLT(Extensible Stylesheet Language Transformations),具体可参阅w3c官方网站文档http://www.w3.org获取更多信息。
首先我们定义一个XML文档如下:
<books>
<book id="001">
<title>Harry Pottertitle>
<author>J K. Rowlingauthor>
book>
<book id="002">
<title>Learning XMLtitle>
<author>Erik T. Rayauthor>
book>
books>
Java 中的 DOM 接口简介: JDK 中的 DOM API 遵循 W3C DOM 规范,其中 org.w3c.dom 包提供了 Document、DocumentType、Node、NodeList、Element 等接口, 这些接口均是访问 DOM 文档所必须的。我们可以利用这些接口创建、遍历、修改 DOM 文档。
javax.xml.parsers 包中的 DoumentBuilder 和 DocumentBuilderFactory 用于解析 XML 文档生成对应的 DOM Document 对象。
javax.xml.transform.dom 和 javax.xml.transform.stream 包中 DOMSource 类和 StreamSource 类,用于将更新后的 DOM 文档写入 XML 文件。
import java.io.File;
import java.io.IOException;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;
public class DOMParser {
DocumentBuilderFactory builderFactory = DocumentBuilderFactory
.newInstance();
// Load and parse XML file into DOM
public Document parse(String filePath) {
Document document = null;
try {
// DOM parser instance
DocumentBuilder builder = builderFactory.newDocumentBuilder();
// parse an XML file into a DOM tree
document = builder.parse(new File(filePath));
} catch (ParserConfigurationException e) {
e.printStackTrace();
} catch (SAXException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
return document;
}
public static void main(String[] args) {
DOMParser parser = new DOMParser();
Document document = parser.parse("books.xml");
// get root element
Element rootElement = document.getDocumentElement();
// traverse child elements
NodeList nodes = rootElement.getChildNodes();
for (int i = 0; i < nodes.getLength(); i++) {
Node node = nodes.item(i);
if (node.getNodeType() == Node.ELEMENT_NODE) {
Element child = (Element) node;
// process child element
}
}
NodeList nodeList = rootElement.getElementsByTagName("book");
if (nodeList != null) {
for (int i = 0; i < nodeList.getLength(); i++) {
Element element = (Element) nodeList.item(i);
String id = element.getAttribute("id");
}
}
}
}
与 DOM 建立树形结构的方式不同,SAX 采用事件模型来解析 XML 文档,是解析 XML 文档的一种更快速、更轻量的方法。 利用 SAX 可以对 XML 文档进行有选择的解析和访问,而不必像 DOM 那样加载整个文档,因此它对内存的要求较低。 但 SAX 对 XML 文档的解析为一次性读取,不创建任何文档对象,很难同时访问文档中的多处数据。
import org.xml.sax.Attributes;
import org.xml.sax.SAXException;
import org.xml.sax.XMLReader;
import org.xml.sax.helpers.DefaultHandler;
import org.xml.sax.helpers.XMLReaderFactory;
public class SAXParser {
class BookHandler extends DefaultHandler {
private List nameList;
private boolean title = false;
public List getNameList() {
return nameList;
}
// Called at start of an XML document
@Override
public void startDocument() throws SAXException {
System.out.println("Start parsing document...");
nameList = new ArrayList();
}
// Called at end of an XML document
@Override
public void endDocument() throws SAXException {
System.out.println("End");
}
/**
* Start processing of an element.
*
* @param namespaceURI
* Namespace URI
* @param localName
* The local name, without prefix
* @param qName
* The qualified name, with prefix
* @param atts
* The attributes of the element
*/
@Override
public void startElement(String uri, String localName, String qName,
Attributes atts) throws SAXException {
// Using qualified name because we are not using xmlns prefixes
// here.
if (qName.equals("title")) {
title = true;
}
}
@Override
public void endElement(String namespaceURI, String localName,
String qName) throws SAXException {
// End of processing current element
if (title) {
title = false;
}
}
@Override
public void characters(char[] ch, int start, int length) {
// Processing character data inside an element
if (title) {
String bookTitle = new String(ch, start, length);
System.out.println("Book title: " + bookTitle);
nameList.add(bookTitle);
}
}
}
public static void main(String[] args) throws SAXException, IOException {
XMLReader parser = XMLReaderFactory.createXMLReader();
BookHandler bookHandler = (new SAXParser()).new BookHandler();
parser.setContentHandler(bookHandler);
parser.parse("books.xml");
System.out.println(bookHandler.getNameList());
}
}
dom4j是一个Java的XML API,类似于jdom,用来读写XML文件的。dom4j是一个非常非常优秀的Java XML API,具有性能优异、功能强大和极端易用使用的特点,同时它也是一个开放源代码的软件,可以在SourceForge上找到它.
对主流的Java XML API进行的性能、功能和易用性的评测,dom4j无论在那个方面都是非常出色的。如今你可以看到越来越多的Java软件都在使用dom4j来读写XML,例如Hibernate,包括sun公司自己的JAXM也用了Dom4j。
举个例子,使用Iterator迭代器的方式来解析xml:
import java.io.File;
import java.util.Iterator;
import org.dom4j.Attribute;
import org.dom4j.Document;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;
public class Demo {
public static void main(String[] args) throws Exception {
SAXReader reader = new SAXReader();
Document document = reader.read(new File("books.xml"));
Element root = document.getRootElement();
Iterator it = root.elementIterator();
while (it.hasNext()) {
Element element = (Element) it.next();
// 未知属性名称情况下
/*
* Iterator attrIt = element.attributeIterator(); while
* (attrIt.hasNext()) { Attribute a = (Attribute) attrIt.next();
* System.out.println(a.getValue()); }
*/
// 已知属性名称情况下
System.out.println("id: " + element.attributeValue("id"));
// 未知元素名情况下
/*
* Iterator eleIt = element.elementIterator(); while
* (eleIt.hasNext()) { Element e = (Element) eleIt.next();
* System.out.println(e.getName() + ": " + e.getText()); }
* System.out.println();
*/
// 已知元素名情况下
System.out.println("title: " + element.elementText("title"));
System.out.println("author: " + element.elementText("author"));
System.out.println();
}
}
}