NOTE

protobuf

1. 是什么 高效的序列化、反序列化方式 1.1. 普通序列化 Teacher Test 结果 可以看出字节数挺大的 1.2. protobuf序列化 TearchSerializer.proto 生成Java Test 结果 可以看出生成的字节数小 1.3. 对比 protobuf序列化的时候减少了一些额外的信息 比如int占用4个字节,而protobuf则是根据长度占用1 5个字节 1.3.1. 普通的序列化反序列化 1.3.2.

Go约 1 分钟读完historical

这是历史学习笔记,可能存在过时或不完整的理解。

1. 是什么

高效的序列化、反序列化方式

1.1. 普通序列化

  • Teacher
public class Teacher implements Serializable
{
    private Long teacherId;
    private Integer age;
    private String name;
    private List<String> courses = new ArrayList<>();

    public Long getTeacherId()
    {
        return teacherId;
    }

    public void setTeacherId(Long teacherId)
    {
        this.teacherId = teacherId;
    }

    public Integer getAge()
    {
        return age;
    }

    public void setAge(Integer age)
    {
        this.age = age;
    }

    public String getName()
    {
        return name;
    }

    public void setName(String name)
    {
        this.name = name;
    }

    public List<String> getCourses()
    {
        return courses;
    }

    public void setCourses(List<String> courses)
    {
        this.courses = courses;
    }

    @Override
    public String toString()
    {
        return "Teacher{" + "teacherId=" + teacherId + ", age=" + age + ", name='" + name + '\'' + ", courses=" + courses + '}';
    }
}
  • Test
public class Test
{
    public static void main(String[] args) throws IOException, ClassNotFoundException
    {
        Teacher teacher = new Teacher();
        teacher.setTeacherId(1L);
        teacher.setAge(32);
        teacher.setName("张飞");
        teacher.getCourses().add("java");


        byte[] bytes = serialize(teacher);
        System.out.println(Arrays.toString(bytes));

        Teacher teacher1 = deserialize(bytes);

        System.out.println(teacher1);

    }

    private static Teacher deserialize(byte[] bytes) throws IOException, ClassNotFoundException
    {
        ObjectInputStream objectInputStream = new ObjectInputStream(new ByteArrayInputStream(bytes));
        return (Teacher) objectInputStream.readObject();
    }

    private static byte[] serialize(Teacher teacher) throws IOException
    {
        ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
        ObjectOutputStream outputStream = new ObjectOutputStream(byteArrayOutputStream);
        outputStream.writeObject(teacher);
        return byteArrayOutputStream.toByteArray();
    }


}
  • 结果 可以看出字节数挺大的
[-84, -19, 0, 5, 115, 114, 0, 29, 99, 111, 109, 46, 122, 115, 107, 46, 116, 101, 115, 116, 46, 112, 114, 111, 116, 111, 98, 117, 102, 46, 84, 101, 97, 99, 104, 101, 114, 94, 99, 107, -78, -117, -101, -64, 60, 2, 0, 4, 76, 0, 3, 97, 103, 101, 116, 0, 19, 76, 106, 97, 118, 97, 47, 108, 97, 110, 103, 47, 73, 110, 116, 101, 103, 101, 114, 59, 76, 0, 7, 99, 111, 117, 114, 115, 101, 115, 116, 0, 16, 76, 106, 97, 118, 97, 47, 117, 116, 105, 108, 47, 76, 105, 115, 116, 59, 76, 0, 4, 110, 97, 109, 101, 116, 0, 18, 76, 106, 97, 118, 97, 47, 108, 97, 110, 103, 47, 83, 116, 114, 105, 110, 103, 59, 76, 0, 9, 116, 101, 97, 99, 104, 101, 114, 73, 100, 116, 0, 16, 76, 106, 97, 118, 97, 47, 108, 97, 110, 103, 47, 76, 111, 110, 103, 59, 120, 112, 115, 114, 0, 17, 106, 97, 118, 97, 46, 108, 97, 110, 103, 46, 73, 110, 116, 101, 103, 101, 114, 18, -30, -96, -92, -9, -127, -121, 56, 2, 0, 1, 73, 0, 5, 118, 97, 108, 117, 101, 120, 114, 0, 16, 106, 97, 118, 97, 46, 108, 97, 110, 103, 46, 78, 117, 109, 98, 101, 114, -122, -84, -107, 29, 11, -108, -32, -117, 2, 0, 0, 120, 112, 0, 0, 0, 32, 115, 114, 0, 19, 106, 97, 118, 97, 46, 117, 116, 105, 108, 46, 65, 114, 114, 97, 121, 76, 105, 115, 116, 120, -127, -46, 29, -103, -57, 97, -99, 3, 0, 1, 73, 0, 4, 115, 105, 122, 101, 120, 112, 0, 0, 0, 1, 119, 4, 0, 0, 0, 1, 116, 0, 4, 106, 97, 118, 97, 120, 116, 0, 6, -27, -68, -96, -23, -93, -98, 115, 114, 0, 14, 106, 97, 118, 97, 46, 108, 97, 110, 103, 46, 76, 111, 110, 103, 59, -117, -28, -112, -52, -113, 35, -33, 2, 0, 1, 74, 0, 5, 118, 97, 108, 117, 101, 120, 113, 0, 126, 0, 7, 0, 0, 0, 0, 0, 0, 0, 1]
Teacher{teacherId=1, age=32, name='张飞', courses=[java]}

1.2. protobuf序列化

  • TearchSerializer.proto
syntax = "proto3";

option java_package = "com.example.test.protobuf.proto";
option java_outer_classname = "TearcherSerializer";

message Teacher {
    int64 tearcherId = 1;
    int32 age = 2;
    string name = 3;
    repeated string courses = 4;
}
  • 生成Java
protoc .\TeacherSerializer.proto --java_out=.\
  • Test
public class TestSerializer
{
    public static void main(String[] args) throws InvalidProtocolBufferException
    {
        byte[] bytes = serialize();

        System.out.println(Arrays.toString(bytes));

        TearcherSerializer.Teacher teacher = deserialize(bytes);

        System.out.println(teacher.getTearcherId());
        System.out.println(teacher.getName());
        System.out.println(teacher.getAge());
        System.out.println(teacher.getCoursesList());


    }

    private static TearcherSerializer.Teacher deserialize(byte[] bytes) throws InvalidProtocolBufferException
    {
        return TearcherSerializer.Teacher.parseFrom(bytes);
    }

    private static byte[] serialize()
    {
        TearcherSerializer.Teacher.Builder builder = TearcherSerializer.Teacher.newBuilder();
        builder.setTearcherId(1L).setAge(22).setName("张飞").addCourses("java");
        TearcherSerializer.Teacher build = builder.build();

        return build.toByteArray();
    }
}
  • 结果 可以看出生成的字节数小
[8, 1, 16, 22, 26, 6, -27, -68, -96, -23, -93, -98, 34, 4, 106, 97, 118, 97]
1
张飞
22
[java]

1.3. 对比

protobuf序列化的时候减少了一些额外的信息 比如int占用4个字节,而protobuf则是根据长度占用1-5个字节

1.3.1. 普通的序列化反序列化

public class TestInteger
{
    public static void main(String[] args)
    {
        byte[] bytes = intToBytes(11);
        System.out.println(Arrays.toString(bytes));

        int value = bytesToInt(bytes);
        System.out.println(value);
    }

    private static int bytesToInt(byte[] bytes)
    {
        return (bytes[0] & 0xFF) |
                ((bytes[1] << 8) & 0xFF00) |
                ((bytes[2] << 16) & 0xFF0000) |
                ((bytes[3] << 24) & 0xFF000000);
    }

    //大端:高字节位在低地址。小端:低字节位在低地址
    private static byte[] intToBytes(int value)
    {
        byte[] bytes = new byte[4];

        //与上0xFF000000表示只保留最高字节,同时右移3*8表示把这个字节移到最后,强转成byte才能取出这个字节
        bytes[3] = (byte)((value & 0xFF000000) >> 3*8);
        bytes[2] = (byte)((value & 0x00FF0000) >> 2*8);
        bytes[1] = (byte)((value & 0x0000FF00) >> 1*8);
        bytes[0] = (byte)((value & 0x000000FF)); //这里采用小端法

        return bytes;
    }
}

1.3.2. protobuf源码

  • com.google.protobuf.AbstractMessageLite#toByteArray->com.example.test.protobuf.proto.TearcherSerializer.Teacher#writeTo
public void writeTo(com.google.protobuf.CodedOutputStream output)
                    throws java.io.IOException {
  if (tearcherId_ != 0L) {
    output.writeInt64(1, tearcherId_);//可以看出按照顺序写的,这里是1
  }
  if (age_ != 0) {
    //我们追这个源码:com.google.protobuf.CodedOutputStream.ArrayEncoder#writeInt32
    output.writeInt32(2, age_);//可以看出按照顺序写的,这里是2
  }
  if (!getNameBytes().isEmpty()) {
    com.google.protobuf.GeneratedMessageV3.writeString(output, 3, name_);//可以看出按照顺序写的,这里是3
  }
  for (int i = 0; i < courses_.size(); i++) {
    com.google.protobuf.GeneratedMessageV3.writeString(output, 4, courses_.getRaw(i));//可以看出按照顺序写的,这里是4
  }
  unknownFields.writeTo(output);
}
  • com.google.protobuf.CodedOutputStream.ArrayEncoder#writeInt32
public final void writeInt32(final int fieldNumber, final int value) throws IOException {
  writeTag(fieldNumber, WireFormat.WIRETYPE_VARINT);
  //这个com.google.protobuf.CodedOutputStream.ArrayEncoder#writeInt32NoTag
  writeInt32NoTag(value);
}
  • com.google.protobuf.CodedOutputStream.ArrayEncoder#writeInt32NoTag
public final void writeInt32NoTag(int value) throws IOException {
  if (value >= 0) {
    //这个com.google.protobuf.CodedOutputStream.ArrayEncoder#writeUInt32NoTag
    writeUInt32NoTag(value);
  } else {
    // Must sign-extend.
    writeUInt64NoTag(value);
  }
}
  • com.google.protobuf.CodedOutputStream.ArrayEncoder#writeUInt32NoTag
public final void writeUInt32NoTag(int value) throws IOException {
  if (HAS_UNSAFE_ARRAY_OPERATIONS
      && !Android.isOnAndroidDevice()
      && spaceLeft() >= MAX_VARINT32_SIZE) {
    if ((value & ~0x7F) == 0) {
      UnsafeUtil.putByte(buffer, position++, (byte) value);
      return;
    }
    UnsafeUtil.putByte(buffer, position++, (byte) (value | 0x80));
    value >>>= 7;
    if ((value & ~0x7F) == 0) {
      UnsafeUtil.putByte(buffer, position++, (byte) value);
      return;
    }
    UnsafeUtil.putByte(buffer, position++, (byte) (value | 0x80));
    value >>>= 7;
    if ((value & ~0x7F) == 0) {
      UnsafeUtil.putByte(buffer, position++, (byte) value);
      return;
    }
    UnsafeUtil.putByte(buffer, position++, (byte) (value | 0x80));
    value >>>= 7;
    if ((value & ~0x7F) == 0) {
      UnsafeUtil.putByte(buffer, position++, (byte) value);
      return;
    }
    UnsafeUtil.putByte(buffer, position++, (byte) (value | 0x80));
    value >>>= 7;
    UnsafeUtil.putByte(buffer, position++, (byte) value);
  } else {
    try {
    //关键代码在这里,使用小端法
    //每个循环都是处理一个字节
      while (true) {
         //0x7F取反与上value,得到的结果为高(32-7)=25位
         //如果为0那么没有高字节位需要处理了
         //或者说数字的范围在一个字节内,即<=127
        if ((value & ~0x7F) == 0) {
          buffer[position++] = (byte) value;
          return;
        } else {
           //0x7F与上value,取出的结果是低7bit,或上0x80得到的结果是第8位为1
           //凑够一个字节存入buffer
          buffer[position++] = (byte) ((value & 0x7F) | 0x80);
          //继续下一个7bit
          value >>>= 7;
        }
      }
    } catch (IndexOutOfBoundsException e) {
      throw new OutOfSpaceException(
          String.format("Pos: %d, limit: %d, len: %d", position, limit, 1), e);
    }
  }
}

2. 安装

2.1. 安装protobuf

2.2. 安装protoc-gen-go

  • 前提是配置好GOBIN环境变量

  • 下载源码

go get -v github.com/golang/protobuf/{proto,protoc-gen-go}
  • 把可执行文件放在protoc的安装目录下
  • 也可以安装到GOBIN中
cd $GOPATH/src/github.com/golang/protobuf/protoc-gen-go
go install
cd $GOBIN
ls

3. 使用

  • proto1.proto
syntax = "proto3"; //指定版本信息,不指定会报错
option go_package = ".;pb"; //后期生成go文件的包名
//message为关键字,作用为定义一种消息类型
message Person {
    //    名字
    string name = 1;
    //    年龄
    int32 age = 2;
    //    邮箱
    repeated string emalis = 3;
    //    手机
    repeated string phones = 4;
    // repeated为关键字,作用为重复使用 一般在go语言中用切片表示
}
  • 生成go文件
protoc --go_out=./ *.proto
  • 测试生成的go文件
import (
	"fmt"
	"github.com/golang/protobuf/proto"
	pb "my_demo/proto_test"
)

func main() {
	p := &pb.Person{
		Name:   "example",
		Age:    25,
		Emalis: []string{"<EMAIL>"},
		Phones: []string{"<PHONE>"},
	}
	fmt.Println(p)
	//json序列化为字节
	data, err := proto.Marshal(p)
	if err != nil {
		fmt.Println("Marshal error")
	}
	fmt.Println(data)

    //json反序列化为对象
	p2 := &pb.Person{}
	proto.Unmarshal(data, p2)
	fmt.Println(p2)
}

4. 参考

讨论

使用 GitHub 账号参与讨论,评论会保存在 GitHub Issues 中。在 GitHub 查看