I wrote two previous articles about Java’s serialization mechanism. One is the default Java serialization mechanism, which is inefficient. The other is Google’s Protobuf, but with that we have to write proto files, and we have to use tools to compile and generate Java files, which is too cumbersome. With Protostuff, however, both of these problems are well addressed. This article will study how to use it and give a simple analysis.

1. Know Protostuff

For example, if a file is under 10 MB, it is better to use Java serialization mechanism, but if the file is large, it is better to use Protostuff. 10 MB is not a strict limit.

Protostuff is also a Google product, it is based on Protobuf and offers more features and easier usage than Protobuf.

Without further ado, let’s take a look at how protoStuff is used.

Two, code implementation

Environment preparation: Add dependencies or JARS

<dependency>
       <groupId>io.protostuff</groupId>
       <artifactId>protostuff-core</artifactId>
       <version>1.6.0</version>
</dependency>
<dependency>
      <groupId>io.protostuff</groupId>
      <artifactId>protostuff-runtime</artifactId>
      <version>1.6.0</version>
</dependency>
Copy the code

I’m using Maven here, so add the dependency directly. If you don’t use Maven, just search baidu for the corresponding JAR package.

1. Define the bean to serialize

First, students

public class Student {
    private  String name;
    private  int    age;
    public Student(String name, int age) {
        this.name = name;
        this.age = age;
    }
    Getters and setters
    / / the toString method
}
Copy the code

Then there is the school category

public class School {
    private  String schoolName;
    private List<Student> students;
    public School(String schoolName, List<Student> students) {
        this.schoolName = schoolName;
        this.students = students;
    }
    Getters and setters
    / / the toString method
}
Copy the code

What we really want to serialize here is School, but to make the example more convincing, we define Student inside School.

2. ProtoStuff serialization utility class

public class ProtostuffUtils {
    // Avoid reclaiming Buffer space for each serialization
    private static LinkedBuffer buffer = LinkedBuffer.allocate(LinkedBuffer.DEFAULT_BUFFER_SIZE);
    / / the cache Schema
    private staticMap<Class<? >, Schema<? >> schemaCache =newConcurrentHashMap<Class<? >, Schema<? > > ();// Serialization method that serializes the specified object into a byte array
    @SuppressWarnings("unchecked")
    public static <T> byte[] serialize(T obj) {
        Class<T> clazz = (Class<T>) obj.getClass();
        Schema<T> schema = getSchema(clazz);
        byte[] data;
        try {
            data = ProtostuffIOUtil.toByteArray(obj, schema, buffer);
        } finally {
            buffer.clear();
        }
        return data;
    }
    // Deserialization method that deserializes a byte array to the specified Class type
    public static <T> T deserialize(byte[] data, Class<T> clazz) {
        Schema<T> schema = getSchema(clazz);
        T obj = schema.newMessage();
        ProtostuffIOUtil.mergeFrom(data, obj, schema);
        return obj;
    }
    @SuppressWarnings("unchecked")
    private static <T> Schema<T> getSchema(Class<T> clazz) {
        Schema<T> schema = (Schema<T>) schemaCache.get(clazz);
        if (schema == null) {
            schema = RuntimeSchema.getSchema(clazz);
            if (schema == null) { schemaCache.put(clazz, schema); }}returnschema; }}Copy the code

This is essentially a utility class where the code can be picked up and used without changing it. But here it is necessary to carry out a description of some field methods and so on.

(1) Field LinkedBuffer

DEFAULT_BUFFER_SIZE indicates that a default size of 512 bytes is requested. We can also use MIN_BUFFER_SIZE, which indicates 256 bytes.

(2) Field schemaCache

This field represents the Schema for the cache. So what is this Schema? An organizational structure, such as tables, views, etc. in a database, represents the structure of a serialized object.

(3) method serialize

public static <T> byte[] serialize(T obj) {
        Class<T> clazz = (Class<T>) obj.getClass();
        Schema<T> schema = getSchema(clazz);
        byte[] data;
        try {
            data = ProtostuffIOUtil.toByteArray(obj, schema, buffer);
        } finally {
            buffer.clear();
        }
        return data;
}
Copy the code

It is the serialization method, and the code in it is easy to understand: first get the class of the object to be serialized, then allocate a cache space for it, and then get the Schema of the class. The last line of code ProtostuffIOUtil. ToByteArray serialization.

(4) method deserialize

// Deserialization method that deserializes a byte array to the specified Class type
    public static <T> T deserialize(byte[] data, Class<T> clazz) {
        Schema<T> schema = getSchema(clazz);
        T obj = schema.newMessage();
        ProtostuffIOUtil.mergeFrom(data, obj, schema);
        return obj;
}
Copy the code

Represents the deserialization, the code inside the deserialization is simpler, first according to the serialized object to obtain its organizational structure Schema. Then mergeFrom directly forms an object according to byte.

(5) Method getSchema

private static <T> Schema<T> getSchema(Class<T> clazz) {
        Schema<T> schema = (Schema<T>) schemaCache.get(clazz);
        if (schema == null) {
            schema = RuntimeSchema.getSchema(clazz);
            if (schema == null) { schemaCache.put(clazz, schema); }}return schema;
}
Copy the code

Gets the organizational structure of the serialized object.

3, test,

public class Test {
    public static void main(String[] args) {
        Student stu1 = new Student("Zhang".20);
        Student stu2 = new Student("Bill".21);
        List<Student> students = new ArrayList<Student>();
        students.add(stu1);
        students.add(stu2);
        School school = new School("West China University of Technology",students);
        // The first is serialization
        byte[] bytes = ProtostuffUtils.serialize(school);
        System.out.println("Serialized:" + bytes.length);
        // Then deserialize
        School group1 = ProtostuffUtils.deserialize(bytes,School.class);
        System.out.println("After deserialization:"+ school.toString()); }}Copy the code

Run it and you get the result. It’s very simple. The ProtostuffUtils above is a utility class that you can keep, copy and paste and use anywhere. The following summary is an analysis of the underlying principle, if you just use it, it is OK. If you want to know more, you can read on

How is protoStuff serialized?

The above is just a basic use, and the Protostuff serialization tool class of the fields and methods for a brief introduction, here we in-depth analysis of the bottom layer how to achieve serialization and deserialization.

In serialization methods above, the core is the last sentence: data = ProtostuffIOUtil. ToByteArray (obj, schema, buffer); How to implement serialization is actually in the toByteArray method. Let’s take a closer look at this method:

public static <T> byte[] toByteArray(T message, Schema<T> schema, LinkedBuffer buffer){
   if(buffer.start ! = buffer.offset)throw new IllegalArgumentException("Buffer previously used and had not been reset.");

   final ProtostuffOutput output = new ProtostuffOutput(buffer);
   try{
     // This is the core of serialization
      schema.writeTo(output, message);
   }
   catch (IOException e){
       throw new RuntimeException("Serializing to a byte array threw an IOException " +
               "(should never happen).", e);
   }
   return output.toByteArray();
}
Copy the code

We can see schema.writeto (output, message); Is the real core, we continue to catch up to see:

public final void writeTo(Output output, T message) throws IOException{
     for (Field<T> f : getFields())
        f.writeTo(output, message);
}
Copy the code

It turns out there’s another layer in there, but it doesn’t matter that the source of the real serialization is about to emerge

@Override
public void writeTo(Output output, T message) throws IOException{
         CharSequence value = (CharSequence)us.getObject(message, offset);
         if(value ! =null)
              output.writeString(number, value, false);
}
Copy the code

As you can see, the serialized object information is saved into a CharSequence and then serialized.

What about deserialization? Core ProtostuffIOUtil. MergeFrom (data, obj, schema); Let’s get in there, too

 public static <T> void mergeFrom(byte[] data, T message, Schema<T> schema){
     IOUtil.mergeFrom(data, 0, data.length, message, schema, true);
 }
Copy the code

To find out, keep going:

static <T> void mergeFrom(byte[] data, int offset, int length, T message,
      Schema<T> schema, boolean decodeNestedMessageAsGroup){
     try{
        final ByteArrayInput input = new ByteArrayInput(data, offset, length,
              decodeNestedMessageAsGroup);
        // Follow up
        schema.mergeFrom(input, message);
        input.checkLastTagWas(0);
     }
     catch (ArrayIndexOutOfBoundsException ae){
        throw new RuntimeException("Truncated.", ProtobufException.truncatedMessage(ae));
     }
     catch (IOException e){
        throw new RuntimeException("Reading from a byte array threw"+
                   "an IOException (should never happen).", e); }}Copy the code

Keep going.

@Override
public final void mergeFrom(Input input, T message) throws IOException{
   // Get the fields in order
   for (int n = input.readFieldNumber(this); n ! =0; n = input.readFieldNumber(this)) {final Field<T> field = getFieldByNumber(n);
       if (field == null){
           input.handleUnknownField(n, this);
       }
       else{ field.mergeFrom(input, message); }}}Copy the code

OK, really out immediately, a little patience to continue to follow in:

public void mergeFrom(Input input, T message)throws IOException{
    // Load to field
    us.putObject(message, offset, input.readString());
}
Copy the code

Now that we’re at this point, I think we’ll get the point.