RPC属于远程通信,属于网络传输,在分布式掌控的天下,相互独立的项目如何互相访问就成了首要解决的问题,此时,跨进程的数据传输就进而诞生,比较常见的就是基于soap实现的webservice(http+xml)的数据传输形式,再后来就是一直火热到现在的dubbo。在这些传输过程中该怎么实现数据的传输呢,那么java中的流的用处就体现出来了,我们可以形象的理解为将一块由积木垒成的房子拆成积木运送到某个地方,在把积木拼成房子的过程,这个过程就是序列化。现在也有很多序列化工具,比如spring默认使用的jackson,阿里的fastjson,谷歌的Gson等等,今天就整理下Java自带的序列化方式,通过实现Serializable接口进行序列化。
废话不多说,直接上菜:
首先我们先定义个接口,再定义几个序列化和反序列化的方法(其中有两个方法是序列化到文件和从文件反序列化):
public interface IObjSerialize {
/**
* 以流的方式序列化obj对象
* @author xinlizz
* @Date 2018/7/1
* @Param [obj]
* @return byte[]
*/
<T> byte[] serialize(T obj);
/**
* 以流的方式反序列化
* @author xinlizz
* @Date 2018/7/1
* @Param [data, clazz]
* @return T
*/
<T> T deSerialize(byte[] data, Class<T> clazz);
/**
* 序列化到文件中
* @author xinlizz
* @Date 2018/7/1
* @Param [fileName]
* @return void
*/
<T> void serializeToFile(T obj, String fileUrl);
/**
* 从文件中将数据反序列化
* @author xinlizz
* @Date 2018/7/1
* @Param [fileUrl, clazz]
* @return T
*/
<T> T deSerializeByFile(String fileUrl, Class<T> clazz);
}
接着是实现类:
/**
* ObjSerializeImpl java自带序列化实现
*
* @Author xinlizz
* @Date ${DATE}
*/
public class ObjSerializeImpl implements IObjSerialize {
@Override
public <T> byte[] serialize(T obj) {
ByteArrayOutputStream bout = null;
ObjectOutputStream oop = null;
try {
bout = new ByteArrayOutputStream();
oop = new ObjectOutputStream(bout);
oop.writeObject(obj);
return bout.toByteArray();
} catch (Exception e) {
} finally {
if (null != bout) {
try {
bout.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (null != oop) {
try {
oop.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
return null;
}
@Override
public <T> T deSerialize(byte[] data, Class<T> clazz) {
ByteArrayInputStream byteArrayInputStream = null;
ObjectInputStream bis = null;
try {
byteArrayInputStream = new ByteArrayInputStream(data);
bis = new ObjectInputStream(byteArrayInputStream);
return (T) bis.readObject();
} catch (Exception e) {
} finally {
if (null != byteArrayInputStream) {
try {
byteArrayInputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (null != bis) {
try {
bis.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
return null;
}
@Override
public <T> void serializeToFile(T obj, String fileUrl) {
ObjectOutputStream oos = null;
try {
oos = new ObjectOutputStream(new FileOutputStream(new File(fileUrl)));
oos.writeObject(obj);
} catch (IOException e) {
e.printStackTrace();
} finally {
if (null != oos) {
try {
oos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
@Override
public <T> T deSerializeByFile(String fileUrl, Class<T> clazz) {
ObjectInputStream ois = null;
try {
ois = new ObjectInputStream(new FileInputStream(new File(fileUrl)));
return (T) ois.readObject();
} catch (IOException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
} finally {
if (null != ois) {
try {
ois.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
return null;
}
}
然后是定义一个实体类:
/**
* 测试实体类
*
* @Author xinlizz
* @Date 2018/7/1
*/
public class User implements Serializable {
private static final long serialVersionUID = -6019430775011618705L;
public static int num = 5;
private Long id;
private String userName;
private String password;
private transient String sex = "男";
private transient int age = 5;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getUserName() {
return userName;
}
public void setUserName(String userName) {
this.userName = userName;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public String getSex() {
return sex;
}
public void setSex(String sex) {
this.sex = sex;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public String toString() {
return "User{" +
"id=" + id +
", userName='" + userName + '\'' +
", password='" + password + '\'' +
", sex='" + sex + '\'' +
", age=" + age +
'}';
}
}
main方法:
public static void main(String[] args) {
IObjSerialize objSerialize = new ObjSerializeImpl();
User user = new User();
user.setId(100L);
user.setUserName("xinlizz");
user.setSex("保密");
user.setAge(25);
byte[] data = objSerialize.serialize(user);
User u = objSerialize.deSerialize(data, User.class);
System.out.println(u);
}
执行一下:
User{id=100, userName='xinlizz', password='null', sex='null', age=0}
我们可以看到通过objSerialize.serialize()方法进行序列化,再通过deSerialize()进行反序列化会成功的将原来的信息还原,但是,但是,但是,为什么sex会是null?age会是0呢?我们分明给这两个属性赋了默认值了,并且还专门set值了,但怎么值就不对呢?transient!这个关键字很吊,他完全不理会序列化的命令,也就是说,只要是被他修饰的变量,在序列化时均不会被序列化,并且,值不会被保留,反序列化时被他修饰的变量将会是其对应类型的默认值,string的默认值是null,int的默认值是0,这也就是为什么反序列化后会出现sex='null',age=0的原因了。
当然,我们也还是有办法让transient失效的,我们只需要在实体类中重定义下序列化是所需要用到的writeObject()和readObject()就可以:
private transient String sex = "男";
private transient int age = 5;
private void writeObject(ObjectOutputStream oos) throws IOException {
oos.defaultWriteObject();
oos.writeObject(sex);
oos.writeObject(age);
}
private void readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException {
ois.defaultReadObject();
sex = (String) ois.readObject();
age = (int) ois.readObject();
}
这样的话我们就可以将这俩属性的值保留并进行序列化了:
User{id=100, userName='xinlizz', password='null', sex='保密', age=25}
同样的我们也可以将序列化后的byte数组保存到文件中,保存到我们的磁盘上(某些软件会有数据备份,就是通过序列化到本地磁盘上进行保存),然后我们通过反序列化进行读取即可,实现代码也很简单易懂。
tips:有人可能不知道serialVersionUID是干啥使的,这里解释下,这玩意就是用来对比序列化的数据和将要被反序列化的类是否一致的,可以理解为一个萝卜对应一个坑。
---------------------------------------------------------------------------------------------------------------------------------
说到序列化,就不得不说与之相关的很容易被面试问到的问题,关于深克隆和浅克隆的实现和原理了。为什么会有深克隆和浅克隆之分呢,因为Java方法栈中对引用对象是存放的对象的引用地址,浅克隆只会把这个地址复制过去,当根据这个引用地址修改引用对象的数据时,就会把被克隆的对象中这个引用对象也一起修改了,画个图了解下:
这就是浅克隆的示例,只是克隆的引用对象的引用地址,例如上图中的102,那么我们该怎么实现深克隆呢,我们常用的就是利用实现cloneable接口,使每个类都进行重写clone方法来达到深克隆的效果,但这个方法比较笨重,每个类都要写clone方法简直受不了,那么这个时候我们就可以通过序列化的方式来实现深克隆了,代码如下:
/**
* Person
*
* @Author xinlizz
* @Date 2018/7/1
*/
public class Person implements Cloneable, Serializable {
private static final long serialVersionUID = 1380784608285905772L;
private String name;
private Boolean flag;
private Integer age;
private Email email;
public Person(String name, Boolean flag, Integer age, Email email) {
this.name = name;
this.flag = flag;
this.age = age;
this.email = email;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Boolean getFlag() {
return flag;
}
public void setFlag(Boolean flag) {
this.flag = flag;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
public Email getEmail() {
return email;
}
public void setEmail(Email email) {
this.email = email;
}
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", flag=" + flag +
", age=" + age +
", email=" + email +
'}';
}
@Override
protected Person clone() throws CloneNotSupportedException {
return (Person) super.clone();
}
/**
* 深克隆
*
* @return com.xinlizz.base.secondDay_serialization.Person
* @author xinlizz
* @Date 2018/7/1
* @Param []
*/
public Person deepClone() {
ByteArrayOutputStream bos = null;
ByteArrayInputStream bis = null;
ObjectOutputStream oos = null;
ObjectInputStream ois = null;
try {
bos = new ByteArrayOutputStream();
oos = new ObjectOutputStream(bos);
oos.writeObject(this);
bis = new ByteArrayInputStream(bos.toByteArray());
ois = new ObjectInputStream(bis);
return (Person) ois.readObject();
} catch (IOException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
} finally {
if (null != bos) {
try {
bos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (null != bis) {
try {
bis.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (null != oos) {
try {
oos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (null != ois) {
try {
ois.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
return null;
}
}
通过oos.readObject()就可以得到崭新的克隆后的实体对象了,也不用把Person的子类Email进行重写clone方法了。
总结一下:
序列化和反序列化是实现数据传输的基础,Java自带序列化的方式是通过流的形式进行解析,将对象数据转化为byte数组,然后再转化成对象。
序列化实现深克隆会比每个子类都重写clone方法更简单,实用性更加方便。