zookeeper源码解析系列一序列化与反序列化

1. 序列化与反序列化概述

  • 序列化:将对象的状态信息转换为可以存储或传输的形式的过程
  • 反序列化:字节序列的或XML编码格式等还原为完全相等的对象

序列化的主要使用场景:
(1) 将序列化的对象存储到某个存储媒介中
(2) 用于网络传输

2. java序列化和反序列化

2.1 基本概念

使用java自带的序列化功能,一定要实现Serializable和Externalizable接口,然后配合ObjectInputStream 和 ObjectOutputStream进行对象的读写。

2.2 实例

// 需要序列化的对象()
    @Data // lombok注解
    public class Vote implements Serializable {
        private Long id;
        private Integer version;
    }
// 测试序列化
    public class TestSer {
        public static final String FILE_PATH = "/tmp/vote";
    
        public static void main(String[] args) throws IOException, ClassNotFoundException {
            serialize();
            Vote vote = deserialize();
            System.out.println(vote);
        }
    
    
        public static void serialize() throws IOException {
            ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(FILE_PATH));
            Vote vote = new Vote();
            vote.setId(1L);
            vote.setVersion(10);
            oos.writeObject(vote);
            oos.close();
        }
    
        public static Vote deserialize() throws IOException, ClassNotFoundException {
            ObjectInputStream ois = new ObjectInputStream(new FileInputStream(FILE_PATH));
            Vote vote = (Vote)ois.readObject();
            return vote;
        }
    }

2.3 缺点

我们可以看到短短的几行代码就可以实现序列化功能,但是zookeeper并没有使用java自身的序列化。主要是因为,java自身的序列化功能存在以下几个缺点:

  • 无法跨语言:只能使用java语言进行序列化操作,在网络传输过程中我们很有可能会使用其他语言来处理,这个限制极大的影响了多语言的配合。
  • 码流太大::磁盘IO和网络IO很容易成为性能瓶颈,码流过大会操作性能下降;
  • 性能差:由于Java序列化采用同步阻塞IO效率非常差。

考虑到java序列化功能的局限制,zookeeper使用jute序列化和反序列化。虽然现在已经存在比jute更好的序列化组件,比如:Avro,Protobuf等,但是新老版本序列化的兼容性问题将变得比较棘手,而且jute还没有成为zookeeper的性能瓶颈。

3. jute介绍

3.1 核心类

  • InputArchive : 所有反序列化器都需要实现的接口
  • OutputArchive: 所有序列化器都需要实现的接口
  • Index : 用于迭代反序列化器的迭代器
  • Record : 所有用于网络传输或者本地存储的类型都实现该接口

3.2 InputArchive

3.2.1 接口介绍

// 读取各种类型变量的接口
public interface InputArchive {
    public byte readByte(String tag) throws IOException;
    public boolean readBool(String tag) throws IOException;
    public int readInt(String tag) throws IOException;
......

3.2.2 子类结构

InputArchive核心子类

  • BinaryInputArchive: 封装DataInput接口,用于从二进制流中读取字节
  • CsvInputArchive : 用于读取csv格式流的数据
  • XmlInputArchive : 用于读取xml格式流的数据

由于zookeeper中主要使用BinaryInputArchive,所以只对这部分源码进行介绍

public class BinaryInputArchive implements InputArchive {
    static public final String UNREASONBLE_LENGTH= "Unreasonable length = ";
    // DataInput 接口提供用于读取二进制流字节并重建为任何java原始数据类型,也提供了一个转换为UTF-8编码的字符串类型方法;
    private DataInput in;
    
    // 将流对象转换为本类对象
    static public BinaryInputArchive getArchive(InputStream strm) {
        return new BinaryInputArchive(new DataInputStream(strm));
    }
    // 迭代器类
    static private class BinaryIndex implements Index {
        // 元素个数
        private int nelems;
        BinaryIndex(int nelems) {
            this.nelems = nelems;
        }
        public boolean done() {
            return (nelems <= 0);
        }
        public void incr() {
            nelems--;
        }
    }
    /** Creates a new instance of BinaryInputArchive */
    public BinaryInputArchive(DataInput in) {
        this.in = in;
    }
    
    /*
     读取各种基本数据类型
    */
    public byte readByte(String tag) throws IOException {
        return in.readByte();
    }
    
    public boolean readBool(String tag) throws IOException {
        return in.readBoolean();
    }
    
    public int readInt(String tag) throws IOException {
        return in.readInt();
    }
    
    public long readLong(String tag) throws IOException {
        return in.readLong();
    }
    
    public float readFloat(String tag) throws IOException {
        return in.readFloat();
    }
    
    public double readDouble(String tag) throws IOException {
        return in.readDouble();
    }
    
    /*
    读取一个字符串
    */
    public String readString(String tag) throws IOException {
        // 确定长度
    	int len = in.readInt();
    	if (len == -1) return null;
    	// 校验长度范围
        checkLength(len);
    	byte b[] = new byte[len];
    	// 从输入流中读取一些字节,并将它们存储在缓冲区数组b中
    	in.readFully(b);
    	return new String(b, "UTF8");
    }
    
    static public final int maxBuffer = Integer.getInteger("jute.maxbuffer", 0xfffff);

    /*
    读取缓冲区和字符串方式一样
    */
    public byte[] readBuffer(String tag) throws IOException {
        int len = readInt(tag);
        if (len == -1) return null;
        checkLength(len);
        byte[] arr = new byte[len];
        in.readFully(arr);
        return arr;
    }
    /**
    * 读取记录
    */
    public void readRecord(Record r, String tag) throws IOException {
        r.deserialize(this, tag);
    }
    
    public void startRecord(String tag) throws IOException {}
    
    public void endRecord(String tag) throws IOException {}
    
    /**
     * 创建迭代器
     */
    public Index startVector(String tag) throws IOException {
        int len = readInt(tag);
        if (len == -1) {
        	return null;
        }
		return new BinaryIndex(len);
    }
    
    public void endVector(String tag) throws IOException {}
    
    public Index startMap(String tag) throws IOException {
        return new BinaryIndex(readInt(tag));
    }
    
    public void endMap(String tag) throws IOException {}

    /**
    * 只是一个粗略的检查
    */
    private void checkLength(int len) throws IOException {
        if (len < 0 || len > maxBuffer + 1024) {
            throw new IOException(UNREASONBLE_LENGTH + len);
        }
    }
}

3.3 OutputArchive

3.3.1 接口介绍

// 写入各种类型变量的接口
public interface OutputArchive {
    public void writeByte(byte b, String tag) throws IOException;
    public void writeBool(boolean b, String tag) throws IOException;
    public void writeInt(int i, String tag) throws IOException;

3.3.2 子类结构

OutputArchive子类结构
同上只介绍BinaryOutputArchive:

public class BinaryOutputArchive implements OutputArchive {
    // 缓冲
    private ByteBuffer bb = ByteBuffer.allocate(1024);
    // 封装DataOutput对象,DataOutput 接口用于将任意 Java 基本类型转换为一系列字节,并将这些字节写入二进制流。同时还提供了一个将 String 转换成 UTF-8 修改版格式并写入所得到的系列字节的工具。
    private DataOutput out;
    
    // 包装OutputStream
    public static BinaryOutputArchive getArchive(OutputStream strm) {
        return new BinaryOutputArchive(new DataOutputStream(strm));
    }
    
    /** Creates a new instance of BinaryOutputArchive */
    public BinaryOutputArchive(DataOutput out) {
        this.out = out;
    }
    
    /*
      写入基础数据类型
    */
    public void writeByte(byte b, String tag) throws IOException {
        out.writeByte(b);
    }
    
    public void writeBool(boolean b, String tag) throws IOException {
        out.writeBoolean(b);
    }
    
    public void writeInt(int i, String tag) throws IOException {
        out.writeInt(i);
    }
    
    public void writeLong(long l, String tag) throws IOException {
        out.writeLong(l);
    }
    
    public void writeFloat(float f, String tag) throws IOException {
        out.writeFloat(f);
    }
    
    public void writeDouble(double d, String tag) throws IOException {
        out.writeDouble(d);
    }
    
    /**
     * create our own char encoder to utf8. This is faster 
     * then string.getbytes(UTF8).
     * 
     * 字符串类型转换为ByteBuffer
     * @param s the string to encode into utf8
     * @return utf8 byte sequence.
     */
    final private ByteBuffer stringToByteBuffer(CharSequence s) {
        bb.clear();
        final int len = s.length();
        for (int i = 0; i < len; i++) {
            if (bb.remaining() < 3) {// 剩余空间小于3
                // 扩容一倍
                ByteBuffer n = ByteBuffer.allocate(bb.capacity() << 1);
                bb.flip();
                n.put(bb);
                // 重新赋值到缓冲
                bb = n;
            }
            char c = s.charAt(i);
            if (c < 0x80) {// 0 -128 
                // 1个字节
                bb.put((byte) c);
            } else if (c < 0x800) {// 128 - 2048
                // 2个字节
                bb.put((byte) (0xc0 | (c >> 6)));
                bb.put((byte) (0x80 | (c & 0x3f)));
            } else { // 2048 -
                bb.put((byte) (0xe0 | (c >> 12)));
                bb.put((byte) (0x80 | ((c >> 6) & 0x3f)));
                bb.put((byte) (0x80 | (c & 0x3f)));
            }
        }
        bb.flip();
        return bb;
    }

    /**
     *  写入字符串类型
     */
    public void writeString(String s, String tag) throws IOException {
        // 长度-1表示null
        if (s == null) {
            writeInt(-1, "len");
            return;
        }
        ByteBuffer bb = stringToByteBuffer(s);
        // 写入长度
        writeInt(bb.remaining(), "len");
        // 写入数据
        out.write(bb.array(), bb.position(), bb.limit());
    }

   /**
    * 写缓冲
    */
    public void writeBuffer(byte barr[], String tag)
    throws IOException {
    	if (barr == null) {
    		out.writeInt(-1);
    		return;
    	}
    	out.writeInt(barr.length);
        out.write(barr);
    }
    
    public void writeRecord(Record r, String tag) throws IOException {
        r.serialize(this, tag);
    }
    
    public void startRecord(Record r, String tag) throws IOException {}
    
    public void endRecord(Record r, String tag) throws IOException {}
    
    public void startVector(List v, String tag) throws IOException {
    	if (v == null) {
    		writeInt(-1, tag);
    		return;
    	}
        writeInt(v.size(), tag);
    }
    
    public void endVector(List v, String tag) throws IOException {}
    
    public void startMap(TreeMap v, String tag) throws IOException {
        writeInt(v.size(), tag);
    }
    
    public void endMap(TreeMap v, String tag) throws IOException {}
    
}

3.4 Index

3.4.1 接口介绍

public interface Index {
    // 是否已经完成
    public boolean done();
    // 下一项
    public void incr();
}

BinaryIndex在前面已经介绍了,不做重复

3.5 Record

public interface Record {
    // 序列化
    public void serialize(OutputArchive archive, String tag)
        throws IOException;
    // 反序列化
    public void deserialize(InputArchive archive, String tag)
        throws IOException;
}

4.实例

@Data
public class Vote2 implements Record{
    private Long id;
    private Integer version;

    @Override
    public void serialize(OutputArchive archive, String tag) throws IOException {
        archive.startRecord(this, tag);
        archive.writeLong(id, "id");
        archive.writeInt(version, "version");
        archive.endRecord(this, tag);
    }

    @Override
    public void deserialize(InputArchive archive, String tag) throws IOException {
        archive.startRecord(tag);
        id = archive.readLong("id");
        version = archive.readInt("version");
    }
}


// 测试类
public class TestSer2 {

    public static final String FILE_PATH = "/tmp/vote2";

    public static void main(String[] args) throws IOException {
    // 序列化操作
        OutputStream outputStream = new FileOutputStream(new File(FILE_PATH));
        BinaryOutputArchive binaryOutputArchive = BinaryOutputArchive.getArchive(outputStream);
        // 序列化同步对象
        Vote2 vote2 = new Vote2();
        vote2.setId(30L);
        vote2.setVersion(12);
        binaryOutputArchive.writeRecord(vote2, "vote2");
        // 序列化map
        TreeMap<String, Integer> map = new TreeMap<String, Integer>();
        map.put("age1", 25);
        map.put("age2", 25);
        Set<String> keys = map.keySet();
        // 调用startMap方法
        binaryOutputArchive.startMap(map, "map");
        int i = 0;
        for (String key: keys) {
            // 依次写入
            binaryOutputArchive.writeString(key, "map");
            binaryOutputArchive.writeInt(map.get(key), "map");
            i++;
        }

        binaryOutputArchive.endMap(map, "map");


        // 反序列化
        InputStream inputStream = new FileInputStream(new File(FILE_PATH));
        BinaryInputArchive binaryInputArchive = BinaryInputArchive.getArchive(inputStream);

        Vote2 v2 = new Vote2();
        binaryInputArchive.readRecord(v2, "vote2");
        System.out.println(v2);

        /**
         * 可以看出可以和tag无关,但是和写入顺序有关。tag只是用来标识的,比如标识异常
         */
        Index index = binaryInputArchive.startMap("map2");
        while (!index.done()) {
            System.out.println("key = " + binaryInputArchive.readString("map1")
                    + ", value = " + binaryInputArchive.readInt("map1"));
            index.incr();
        }
        binaryInputArchive.endMap("map2");
 
    }
}

// 打印信息
Vote2(id=30, version=12)
key = age1, value = 25
key = age2, value = 25

5. 数据描述语言

上面的Vote2类我们可以看出,其实实现Record的类只要定义好属性,序列化和反序列化的代码可以相应的写出来。在只有简单的几个需要序列化的类,这样的编写方式还不会让我们觉得很麻烦。但是如果需要序列化的类有几十上百个这就变成了一个大麻烦。为了解决它,可以使用数据描述语言。只要编写少量的代码就可以实现一样的功能,还可以防止不小心写错的问题。

在zookeeper/src下可以找到zookeeper.jute文件,文件的大致内容如下:

// module指定了包名
module org.apache.zookeeper.data { 
// class指定类名
    class Id {
    // 字段定义
        ustring scheme;
        ustring id;
    }
    class ACL {
        int perms;
        Id id;
    }

为了将这个数据描述语言编写的文件转换为不同语言支持的文件,我们还需要执行一些操作
jute数据描述语言核心类
可以执行上图中的RCC类生成想要的类文件:

// 默认生成的就是java的类文件,如果需要生成其他语言的可以查看RCC源码
 Rcc.main(new String[]{"zookeeper.jute"});

参考资料:
http://www.cnblogs.com/leesf456/p/6278853.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值