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.