本文共 7891 字,大约阅读时间需要 26 分钟。
官方用一句话总结:
It’s like JSON.but fast and small.简单来讲,它的数据格式与json类似,但是在存储时对数字、多字节字符、数组等都做了很多优化,减少了无用的字符,二进制格式,也保证不用字符化带来额外的存储空间的增加。以下是官网给出的简单示例图:核心压缩方式可参看官方说明
概括来讲就是:我们看一下官方给出的stringformat示意图
{ "error_no":0, "message":"", "result":{ "data":[ { "datatype":1, "itemdata": {//共有字段45个 "sname":"\u5fae\u533b", "packageid":"330611", … "tabs":[ { "type":1, "f":"abc" }, … ] } }, … ], "hasNextPage":true, "dirtag":"soft" }}
怎么把tabs中的子数据作为一个整体写入itemdata这个结构中呢?itemdata又怎么写入它的上层数据结构data中?这时Ext出马了。我们可以自定义一种数据类型,指定它的Type值,当解析遇到这个type时就按我们自定义的结构去解析。具体怎么实现后面我们在代码示例的时候会讲到。
首先需要在app的gradle脚本中添加依赖
compile 'org.msgpack:msgpack-core:0.8.11'
java版本用法的可以在源码的/msgpack-java/msgpack-core/src/test/java/org/msgpack/core/example/MessagePackExample.java中看到。
值得一提的是官方的说明文档还停留在1.x版本,建议大家直接去看最新demo。通过MessagePack这个facade获取用户可用的对象packer和unpacker。主要有两种用法:
通过 MessageBufferPacker将数据打包到内存buffer中
MessageBufferPacker packer = MessagePack.newDefaultBufferPacker(); packer .packInt(1) .packString("leo") // pack arrays int[] arr = new int[] {3, 5, 1, 0, -1, 255}; packer.packArrayHeader(arr.length); for (int v : arr) { packer.packInt(v); } // pack map (key -> value) elements packer.packMapHeader(2); // the number of (key, value) pairs // Put "apple" -> 1 packer.packString("apple"); packer.packInt(1); // Put "banana" -> 2 packer.packString("banana"); packer.packInt(2); // pack binary data byte[] ba = new byte[] {1, 2, 3, 4}; packer.packBinaryHeader(ba.length); packer.writePayload(ba); packer.close();
以上分别展示了对基本数据类型、array数组、map、二进制数据的打包用法。
File tempFile = File.createTempFile("target/tmp", ".txt");tempFile.deleteOnExit();// Write packed data to a file. No need exists to wrap the file stream with BufferedOutputStream, since MessagePacker has its own bufferMessagePacker packer = MessagePack.newDefaultPacker(new FileOutputStream(tempFile));// 以下是对自定义数据类型的打包byte[] extData = "custom data type".getBytes(MessagePack.UTF8);packer.packExtensionTypeHeader((byte) 1, extData.length()); // type number [0, 127], data byte lengthpacker.writePayload(extData);packer.close();首先通过packExtensionTypeHeader将自定义数据类型的type值和它的长度写入,这里指定这段数据的type=1,长度就是转为二进制数据后的长度,这里官方demo里有个错误,写了固定长度10,其实是有问题的,这里进行了修正写入extData的实际长度。然后用writePayload方法将byte[]数据写入。结束。可能这个Demo的展示还有点不太好理解,我们就上面的json样式进行进一步说明:假设我要将tabs下的数据样式定义为一个扩展类型,怎么去写呢?首先定义一个这样的数据结构:
public class TabsJson { public int type; public String f = "";}然后指定TabsJson对象的type ExtType.TYPE_TAB=2,官方对自定义数据类型的限制是0~127。然后对TabsJson对象进行初始化和赋值:
TabsJson tabsjson = new TabsJson();tabsjson.type = 199;tabsjson.f = "abc";然后构造MessagePacker进行写入
private static void packTabJson(TabsJson tabsJson, MessagePacker packer) throws IOException { MessageBufferPacker packer1 = MessagePack.newDefaultBufferPacker(); packer1.packInt(tabsJson.type); packer1.packString(tabsJson.f); int l = packer1.toByteArray().length; packer.packExtensionTypeHeader(ExtType.TYPE_TAB,l); packer.writePayload(packer1.toByteArray()); packer1.close(); }packer1的作用就是将tabsjson对象打包成二进制数据,然后我们将这个二进制数据写到packer中。搞定。那解包的时候怎么做呢,后面我们会讲到。这样通过自定义数据结构层层打包就完美解决了上面关于怎么将数据打包为复杂json样式的问题了。必须注意打包结束后必须进行close,以结束此次buffer操作或者关闭输出流。
两种用法与上面打包是对应的:
MessageUnpacker unpacker = MessagePack.newDefaultUnpacker(bytes); int id = unpacker.unpackInt(); // 1 String name = unpacker.unpackString(); // "leo" int numPhones = unpacker.unpackArrayHeader(); // 2 String[] phones = new String[numPhones]; for (int i = 0; i < numPhones; ++i) { phones[i] = unpacker.unpackString(); // phones = {"xxx-xxxx", "yyy-yyyy"} } int maplen = unpacker.unpackMapHeader(); for (int j = 0; j < mapen; j++) { unpacker.unpackString(); unpacker.unpackInt(); } unpacker.close();需要注意的是解包顺序必须与打包顺序一致,否则会出错。也就是说协议格式的维护要靠两端手写代码进行保证,而这是很不安全的。
FileInputStream fileInputStream = new FileInputStream(new File(filepath));MessageUnpacker unpacker = MessagePack.newDefaultUnpacker(fileInputStream);//先将自定义数据的消息头读出ExtensionTypeHeader et = unpacker.unpackExtensionTypeHeader();//判断消息类型if (et.getType() == (ExtType.TYPE_TAB)) { int lenth = et.getLength(); //按长度读取二进制数据 byte[] bytes = new byte[lenth]; unpacker.readPayload(bytes); //构造tabsjson对象 TabsJson tab = new TabsJson(); //构造unpacker将二进制数据解包到java对象中 MessageUnpacker unpacker1 = MessagePack.newDefaultUnpacker(bytes); tab.type = unpacker1.unpackInt(); tab.f = unpacker1.unpackString(); unpacker1.close();}unpacker.close();以上例子展示了对自定义数据类型的完整解包过程,最后不要忘记关闭unpacker。除此之外用户还可以自定义packconfig和unpackconfig,指定打包和解包时的配置,比如内存缓存byte[]数据大小等等。
public class TabsJson implements Parcelable { public int type; public String f = ""; public TabsJson () { } protected TabsJson(Parcel in) { this.type = in.readInt(); this.f = in.readString(); } @Override public void writeToParcel(Parcel dest, int flags) { dest.writeInt(this.type); dest.writeString(this.f); } @Override public int describeContents() { return 0; } public static final Creator打包和解包过程是这样的CREATOR = new Creator () { @Override public TabsJson createFromParcel(Parcel in) { return new TabsJson(in); } @Override public TabsJson[] newArray(int size) { return new TabsJson[size]; } };}
MessageBufferPacker packer = MessagePack.newDefaultBufferPacker();Parcel pc = Parcel.obtain();tabsjson.writeToParcel(pc, Parcelable.PARCELABLE_WRITE_RETURN_VALUE);byte[] bytes = pc.marshall();//先写入数据长度packer.packInt(bytes.length);//写入二进制数据packer.writePayload(bytes);packer.close();pc.recycle();//解包MessageUnpacker unpacker = MessagePack.newDefaultUnpacker(packer.toByteArray());byte[] bytes1 = new byte[unpacker.unpackInt()];unpacker.readPayload(bytes1);Parcel pp = Parcel.obtain();pp.unmarshall(bytes1,0,bytes1.length);pp.setDataPosition(0);TabsJson ij = TabsJson.CREATOR.createFromParcel(pp);pp.recycle();unpacker.close();
这种方式虽然省去了自己手写打包和解包的过程,但是不推荐使用。
笔者对第一部分示例的json数据,同一个itemdata数据段两种方式打包后文件大小对比如下:parcel方式 | 直接操作 | Json数据 | |
---|---|---|---|
数据大小(byte) | 3619 | 2644 | 4090 |
可见parcel方式在压缩效率上比原始的json数据格式并无较大提升,因此不建议使用。
一句话总结一下Messagepack,简单好用,掌握原理后可以想怎么用怎么用。是比Json更轻便更灵活的一种数据协议。转载地址:http://nkvmi.baihongyu.com/