编码技术
基于Java提供的对象输入输出流,可以直接把Java对象作为可存储的字节数组写入文件,也可以传输到网络上去。
基于jdk默认的序列化机制可以避免操作底层的字节数组,从而提升开发效率。
Java序列化的两个目的
【1】网络传输
当进行远程跨进程服务调用时,需要把被传输的Java对象编码为字节数组或者bytebuffer对象。二当远程服务读取到bytebuffer对象或者字节数组时,需要将其解码为发送时的Java对象,这种技术被称为Java对象编解码技术。
【2】对象持久化
Java序列化的缺点
【1】无法跨语言
Java序列化最为致命的问题。
Java序列化技术是Java语言内部的私有协议,其他语言并不支持,对于用户来说完全是黑盒。对于Java序列化后的字节数组,别的语言无法进行反序列化。严重阻碍了它的应用。
扫描二维码关注公众号,回复:
2412770 查看本文章
【2】序列化以后的码流太大
package com.nio.seria;
import java.io.Serializable;
import java.nio.ByteBuffer;
public class UserInfo implements Serializable {
private String userName;
private Integer userId;
public UserInfo buildUserName(String userName){
this.userName = userName;
return this;
}
public UserInfo buildUserId(int userId){
this.userId = userId;
return this;
}
public String getUserName() {
return userName;
}
public void setUserName(String userName) {
this.userName = userName;
}
public Integer getUserId() {
return userId;
}
public void setUserId(Integer userId) {
this.userId = userId;
}
@Override
public String toString() {
return "UserInfo{" +
"userName='" + userName + '\'' +
", userId=" + userId +
'}';
}
public byte[] codeC(){
ByteBuffer buffer = ByteBuffer.allocate(1024);
byte[] value = this.userName.getBytes();
buffer.putInt(value.length);
buffer.put(value);
buffer.putInt(this.userId);
buffer.flip();
value = null;
byte[] result = new byte[buffer.remaining()];
buffer.get(result);
return result;
}
}
package com.nio.seria;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;
public class SeriaTest {
public static void main(String[] args)throws IOException {
UserInfo user = new UserInfo();
user.buildUserId(100).buildUserName("mqs");
ByteArrayOutputStream bos = new ByteArrayOutputStream();
ObjectOutputStream os = new ObjectOutputStream(bos);
os.writeObject(user);
os.flush();
os.close();
byte[] b = bos.toByteArray();
System.out.println("采用jdk序列化机制: " + b.length);
System.out.println("采用二进制编码: " + user.codeC().length);
}
}
以上结果发现采用jdk序列化机制编码后的二进制数组的大小是二进制编码的好多倍。
怎么评判一个编解码框架的优劣要考虑的因素:
-
是否支持跨语言
-
编码后的码流大小
-
编解码的性能
-
类库是否小巧,api使用是否方便
-
使用者需要手工开发的工作量和难度
在同样的情况下,编码后的字节数组越大,存储的时候就越占空间,存储的硬件成本就越高,并且在网络传输时更占带宽,导致系统的吞吐量降低。
【3】序列化性能太低
package com.nio.seria;
import java.io.Serializable;
import java.nio.ByteBuffer;
public class UserInfo implements Serializable {
private String userName;
private Integer userId;
public UserInfo buildUserName(String userName){
this.userName = userName;
return this;
}
public UserInfo buildUserId(int userId){
this.userId = userId;
return this;
}
public String getUserName() {
return userName;
}
public void setUserName(String userName) {
this.userName = userName;
}
public Integer getUserId() {
return userId;
}
public void setUserId(Integer userId) {
this.userId = userId;
}
@Override
public String toString() {
return "UserInfo{" +
"userName='" + userName + '\'' +
", userId=" + userId +
'}';
}
public byte[] codeC(ByteBuffer buffer){
buffer.clear();
byte[] value = this.userName.getBytes();
buffer.putInt(value.length);
buffer.put(value);
buffer.putInt(this.userId);
buffer.flip();
value = null;
byte[] result = new byte[buffer.remaining()];
buffer.get(result);
return result;
}
}
package com.nio.seria;
import java.io.ByteArrayOutputStream;
import java.io.ObjectOutputStream;
import java.nio.ByteBuffer;
public class SeriaTestXN {
public static void main(String[] args)throws Exception {
UserInfo info = new UserInfo();
info.buildUserId(200).buildUserName("welcome to china study netty");
System.out.println(info);
int loop = 1000000;
ByteArrayOutputStream bos = null;
ObjectOutputStream os = null;
long startTime = System.currentTimeMillis();
for (int i = 0; i < loop; i++) {
bos = new ByteArrayOutputStream();
os =new ObjectOutputStream(bos);
os.writeObject(info);
os.flush();
os.close();
byte[] b = bos.toByteArray();
bos.close();
}
long endTime = System.currentTimeMillis();
System.out.println("jdk序列化编码耗时:" + (endTime - startTime) + "ms");
System.out.println("---------------------------------------------------------");
ByteBuffer buffer = ByteBuffer.allocate(1024);
startTime = System.currentTimeMillis();
for (int i = 0; i < loop; i++) {
byte[] b = info.codeC(buffer);
}
endTime = System.currentTimeMillis();
System.out.println("二进制编解码耗时: " + (endTime - startTime) + "ms");
}
}
总结: 无论是序列化后的码流大小,还是序列化的性能,jdk默认的序列化机制表现都非常的差。因此,我们通常不会选择Java序列化作为远程跨节点调用的编解码框架。
业界主流的编解码框架
【1】Google的protobuf
【2】Facebook的thritf
【3】jboss的marshalling