我的上一篇博客,介绍了通讯的原理,写了几个关于Socket的几个Demo,那么我们思考下,如何基于Socket进行对象的传输呢?
之前又同学说建议我写博客代码的部分不要截图,直接贴代码,我今天就把代码贴上来吧。来看下我写的Demo吧(代码只是demo,连接啥的我没都没有关闭哦)。
public class User {
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
} }
import java.io.IOException;
import java.io.ObjectOutputStream;
import java.net.Socket;
public class SocketClientConsumer {
public static void main(String[] args) throws IOException {
Socket socket = new Socket("localhost", 8888);
ObjectOutputStream objectOutputStream = new ObjectOutputStream(socket.getOutputStream());
objectOutputStream.writeObject(new User("carry"));
}
}
import java.io.IOException;
import java.io.ObjectInputStream;
import java.net.ServerSocket;
import java.net.Socket;
public class SocketServerProvider {
public static void main(String[] args) throws IOException {
ServerSocket serverSocket = new ServerSocket(8888);
Socket socket = serverSocket.accept();
ObjectInputStream inputStream = new ObjectInputStream(socket.getInputStream());
try {
User user = (User) inputStream.readObject();
System.out.println(user);
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
}
看下运行的结果:
直接报错了,告诉我们没有序列化的异常,我们只需要在User实体上实现 Serializable接口就好了
我们发现对 User 这个类增加一个 Serializable,就可以解决 Java 对象的网络传输问题。这就是今天想给大家讲解的序列化这块的意义简单来说Java 平台允许我们在内存中创建可复用的 Java 对象,但一般情况下,只有当 JVM 处于运行时,这些对象才可能存在,即,这些对象的生命周期不会比 JVM 的生命周期更长。但在现实应用中,就可能要求在 JVM 停止运行之后能够保存(持久化)指定的对象,并在将来重新读取被保存的对象。Java 对象序列化就能够帮助我们实现该功能序列化是把对象的状态信息转化为可存储或传输的形式过程,也就是把对象转化为字节序列的过程称为对象的序列化反序列化是序列化的逆向过程,把字节数组反序列化为对象,把字节序列恢复为对象的过程成为对象的反序列化
常见的序列化和反序列化的协议:
XML、JSON、Protobuf、Thrift和Avro
我们常见的就是JSON形式的序列化吧,在之前webservice流行的时候,XML和SOAP协议,我对这几个协议也不是很清楚,
但是我觉得,我们想要序列化技术肯定要满足跨平台,性能高,传输小等特性吧。我们可以去看下一直知名的框架用的序列化协议吧,我们去github上看下Dubbo有啥协议吧
我们在github上搜索Dubbo,看下他的序列化协议有几种。
这几种主流的协议都有实现,大框架就是不一样。怀着好奇的心情,我也打算实现几种序列化技术,来简单的分析一下这些序列化技术的性能吧。
我们来定义一个序列化接口
public interface ISerializer {
// 序列化
byte[] serialize(T obj);
// 反序列化
T deserialize(byte[] data, Class clazz);
}
我们先来看下Java提供的序列化技术:
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
/**
* java方式实现序列化和反序列化
*/
public class JavaSerializer implements ISerializer {
@Override
public byte[] serialize(T obj) {
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
try {
ObjectOutputStream objectOutputStream = new ObjectOutputStream(byteArrayOutputStream);
objectOutputStream.writeObject(obj);
return byteArrayOutputStream.toByteArray();
} catch (IOException e) {
e.printStackTrace();
}
return new byte[0];
}
@Override
public T deserialize(byte[] data, Class clazz) {
ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(data);
try {
ObjectInputStream objectInputStream = new ObjectInputStream(byteArrayInputStream);
return (T) objectInputStream.readObject();
} catch (IOException | ClassNotFoundException e) {
e.printStackTrace();
}
return null;
}
}
可以找一个main方法,测试一下这个java序列化的:
public static void xStreamSerial() {
ISerializer javaSerializer = new XStreamSerializer();
User javaUser = new User("carry");
byte[] serialize = javaSerializer.serialize(javaUser);
System.out.println("xStreamSerial序列化的长度:" + serialize.length);
// 反序列化
User user = javaSerializer.deserialize(serialize, User.class);
System.out.println(user);
}
这就java原生实现序列化与反序列化的实现
2.我们来看下把对象序列化成xml的格式,我们来利用XStream来实现:
com.thoughtworks.xstream
xstream
1.4.12
import com.thoughtworks.xstream.XStream;
import com.thoughtworks.xstream.io.xml.DomDriver;
public class XStreamSerializer implements ISerializer {
XStream xStream = new XStream(new DomDriver());
@Override
public byte[] serialize(T obj) {
xStream.allowTypesByWildcard(new String[] {
"com.carry.**" //你的包路径
});
byte[] bytes = xStream.toXML(obj).getBytes();
return bytes;
}
@Override
public T deserialize(byte[] data, Class clazz) {
xStream.allowTypesByWildcard(new String[] {
"com.carry.**" //你的包路径
});
return (T) xStream.fromXML(new String(data));
}
}
public static void xStreamSerial() {
ISerializer javaSerializer = new XStreamSerializer();
User javaUser = new User("carry");
byte[] serialize = javaSerializer.serialize(javaUser);
System.out.println("xStreamSerial序列化的长度:" + serialize.length);
// 反序列化
User user = javaSerializer.deserialize(serialize, User.class);
System.out.println(user);
}
我们再看下利用fastJson来实现对象的序列化:
import com.alibaba.fastjson.JSON;
public class FastJsonSeriliazer implements ISerializer {
@Override
public byte[] serialize(T obj) {
return JSON.toJSONString(obj).getBytes();
}
@Override
public T deserialize(byte[] data, Class clazz) {
return JSON.parseObject(new String(data), clazz);
}
}
public static void fastJsonSerial() {
ISerializer javaSerializer = new FastJsonSeriliazer();
User javaUser = new User("carry");
byte[] serialize = javaSerializer.serialize(javaUser);
System.out.println("fastJsonSerial序列化的长度:" + serialize.length);
// 反序列化
User user = javaSerializer.deserialize(serialize, User.class);
System.out.println(user);
}
注意:这个fastJson实现一个序列化一个大坑就是,如果你的User实体的路径有中文,他会报错,我可以给你们演示一下:
其实还有几种技术实现这个序列化技术,我们都可以去实现的,我就玩这三种就好了。
我再想讲一下一个和序列化相关的知识点:
transient,不知道大家知不知道这个关键字是啥含义:
我们创建的类的有些属性需要序列化,而其他属性不需要被序列化,打个比方,如果一个用户有一些敏感信息(如密码,银行卡号等),为了安全起见,不希望在网络操作(主要涉及到序列化操作,本地序列化缓存也适用)中被传输,这些信息对应的变量就可以加上transient关键字。换句话说,这个字段的生命周期仅存于调用者的内存中而不会写到磁盘里持久化。
我们如果在User实体的name属性,加上这个transient ,可以在看下这个执行结果:
如果我们再User实体里面写上readObject 和 writeObject这两个方法,效果就不一样了:
private void writeObject(java.io.ObjectOutputStream s) throws IOException {
s.defaultWriteObject();
s.writeObject(name);
}
private void readObject(java.io.ObjectInputStream s) throws IOException, ClassNotFoundException {
s.defaultReadObject();
name=(String)s.readObject();
}
我们上面已经分析过了,这个会在java.io.ObjectInputStream 里面进行反射调用的,只要方法名称是这两个,注意不要乱起方法名称哦。
其实这样的使用在Java集合里面大量使用,我们可以看下ArrayList里面的使用:
这个存放list集合的数组都不让他参与序列化的,但是这些属性我们肯定是想要传输的,就可以利用我们上面提到的来绕过这个。我们看下ArrayList是不是这样实现的。
哈哈,确实是这样的。我们可以思考下为啥这样做:
我们知道集合有一个扩容机制,你的elementData数组肯定有很多空闲的空间,如果一股脑交给自带的序列化机制去实现,肯定有一些无用的序列化次数,性能比较差,如果你根据你数组的数据量来序列化,次数肯定少很多。