avro是一种二进制数据编码格式,将一个类对象或者在说一种格式的数据转化二进制编码,可以将其写为文件,也可以存在于内存中,我们分析一下它在内存中的存储格式。
如图,在文件中存储时首先分为 header和datablock,文件由一个header和多个datablock组成,avro是行存顺序存储数据的,且它没有索引,所以它采用多个datablock的方式来存储数据,header和datablock之间都是使用一个Sync Marker分开标识的,这个Sync Marker一般为16字节,在写入时avro提供的api会自动在写入固定长度的数据后结束调当前的block,并写入Sync Marker,也可以手动的调用方法结束当前写入的block并将数据刷入磁盘。
在读取时,显示提供的api基本只允许你顺序读取数据,但是他会提供一个方法允许你使用一个sync方法来找到一个文件中一个偏移量之后最近的一个同步点
/**
* Move to the next synchronization point after a position. To process a range
* of file entires, call this with the starting position, then check
* {@link #pastSync(long)} with the end point before each call to
* {@link #next()}.
*/
@Override
public void sync(long position) throws IOException {
seek(position);
// work around an issue where 1.5.4 C stored sync in metadata
if ((position == 0) && (getMeta("avro.sync") != null)) {
initialize(sin); // re-init to skip header
return;
}
try {
//这里i为syncBuffer中起始位置的偏移量
//这里采用了循环存储的方式
int i = 0, b;
InputStream in = vin.inputStream();
//读取固定长度到syncBuffer中去
vin.readFixed(syncBuffer);
do {
int j = 0;
//遍历验证syncBuffer是否与Header中的sync相同
for (; j < SYNC_SIZE; j++) {
if (getHeader().sync[j] != syncBuffer[(i + j) % SYNC_SIZE])
break;
}
//如果遍历的长度等于SYNC_SIZE,这说明匹配到一个完整的sync,将blockStart置为sync后的位置
if (j == SYNC_SIZE) { // matched a complete sync
blockStart = position + i + SYNC_SIZE;
return;
}
//否则读取一个字节到b中
b = in.read();
//将b设置到syncBuffer中最前面需要丢弃的字节的位置上,然后i++
syncBuffer[i++ % SYNC_SIZE] = (byte) b;
} while (b != -1);
} catch (EOFException e) {
// fall through
}
// if no match or EOF set start to the end position
blockStart = sin.tell();
// System.out.println("block start location after EOF: " + blockStart );
}
总之这里通过一个暴力搜索的方式 匹配到一个sync 然后将偏移量设置到这里
/**
* Move to a specific, known synchronization point, one returned from
* {@link DataFileWriter#sync()} while writing. If synchronization points were
* not saved while writing a file, use {@link #sync(long)} instead.
*/
public void seek(long position) throws IOException {
sin.seek(position);
vin = DecoderFactory.get().binaryDecoder(this.sin, vin);
datumIn = null;
blockRemaining = 0;
blockStart = position;
}
其中seek方法会将sin流偏移量置为position,然后这里是使用sin生成一个vin流?
所以说这里我们要注意 比如一个文件中在1000偏移量开启一个新的block,我们希望从这里开始读取,那么我们必须sync(1000-16),因为从1000-16的位置开始,才能匹配到这个block之前的sync点,然后将偏移量移动到1000,如果我们调用sync(1000),则只能找到下一个block
/** Return true if past the next synchronization point after a position. */
@Override
public boolean pastSync(long position) throws IOException {
return ((blockStart >= position + SYNC_SIZE) || (blockStart >= sin.length()));
}
pastSync用来判断当前读取的位置是否已经超过给定的偏移量,比如0到984结束一个block,然后985到1000是sync,那么我们只要读取到984之前的这一个block,那么我们就需要将position设置为984,并在每次读取时调用pastSync判断一下,如果返回true则表示已经超过需要读取的位置了,这里position设置成大于984的值比如1000,则会造成多读数据的问题。
header分为 magic number:固定为"Obj1" metaData:一些信息入schema信息,压缩格式等 Sync Marker
block包括 记录数 数据长度 数据 Sync Marker