ここで言う可変長バイトコードは int(4バイト)を long(8バイト)整数を 4、8 バイトといった 固定長サイズで表現するのではなく、その値により 1~ 5 / 10 バイトで表現するものである。 数値自体は7ビットで表し、先頭の1ビットは後ろにバイトが続くか、それともこれで終わりかを 表すフラグビットとして使う。非常に小さい数値は 1 バイトで表現できるが、 正味32ビット必要な数値では 5 バイト、正味64ビット必要な数値では 10 バイト必要となるため、 固定整数の int や long よりもより多くのメモリを必要とする。 小さな数値が多い時には、int や long よりも少ないメモリで表現できる。 このため、OpenStreetMap の圧縮データ形式である *.pbf形式[1] や *.o5m 形式[2]などで使われている。 例えば、3バイトで表される数値は以下の通りである。符号ありの 0 は二通りある。
1xxxxxxx 1xxxxxxx 0xxxxxxx 符号なし 3x7 = 21ビット. 0 ~ 221 1xxxxxx0 1xxxxxxx 0xxxxxxx 符号あり正 20ビット. 0 ~ 220 1xxxxxx1 1xxxxxxx 0xxxxxxx 符号あり負 20ビット. 0 ~ -220
ByteBufferを使ってもよいが、byte配列の方がオーバヘッドは少ないであろう。
static int ConvertULong(long v, byte[] buf, int ix) { byte frac = (byte)(v & 0x7f); while (frac != v) { buf[ix++] = (byte)(frac | 0x80); v >>= 7; frac = (byte)(v & 0x7f); } buf[ix++] = frac; return ix; } static int ConvertSLong(long v, byte[] buf, int ix) { long u = v<0 ? ((-v)<<1) - 1 : v<<1; return ConvertULong(u, buf, ix); }
C++ や C# などでは、参照型の int が使えるが、Java にはない。 メソッドで簡単に値(long 型)とインデックス(int 型)の二つを戻す方法がないため、 ByteBuffer を使う。
static long ParseULong(ByteBuffer buffer) { long value = 0; long b = 0x80; for (int i = 0; (b & 0x80) != 0; i += 7) { b = buffer.get(); long part = (b & 0x7F) << i; value |= part; } return value; } static long ParseSLong(ByteBuffer buffer) { long value = ParseULong(buffer); if ((value & 1) != 0) { // negative return -(value >> 1) - 1; } else { // positive return (value >> 1); } }