这是Hadoop的I/O操作的最后一部分了。
这一部分的主要内容是用来存储文件数据的数据结构。
关于SequenceFile
Hadoop 的SequenceFile
类为二进制键值对提供了一个持久数据结构,对于不适合用纯文本记录的二进制类型日志文件非常合适。
HDFS 和 MapReduce 是针对大文件优化的,所以通过SequenceFIle
类将小文件包装起来,可以获得更高效率的存储和处理。
SequenceFile的写操作
通过createWriter()
方法可以创建SequenceFile
对象,并返回SequenceFIle.Writer
实例。该静态方法需要指定待写入的数据流(FSDataOutputStream
或FileSystem
对象和Path
对象),Configuration
对象,以及键和值的类型。另外,可选参数包括压缩类型以及相应的codec
,Progressable
回调函数用于通知写入的进度,以及在SequenceFile
头文件中存储的Metadata
实例。存储在SequenceFile
中的键和值不一定必须是Writable
类型,只要能被Serialization
序列化和反序列化,任何类型都可以。
IntWritable key = ...
Text value = ...
SequenceFile.Writer writer = null;
try{
writer = SequenceFile.createWriter(fs, conf, path, key.getClass(), value.getClass());
writer.append(key, value);
}else{
IOUtils.closeStream(writer);
}
一旦拥有了SequenceFIle.Writer
实例,就可以通过append()
方法在文件末尾附加键值对,写完后,可以调用close()
方法。
SequenceFile的读操作
从头到尾读取顺序文件的方法是创建SequenceFile.Reader
实例后反复调用next()
方法迭代读取记录。读取哪条记录与使用的序列化框架相关。若用的是Writable类型,那么通过键和值作为参数的next()
方法可以将数据流中的下一条键值对读入变量中:
public boolean next(Writable key, Writable val)
若键值对成功读取,则返回true
,若已到文件末尾,则返回false
。
对于其他非Writable
类的序列化框架,则应用下面两个方法:
public Object next(Object key) throws IOException
public Object getCurrentValue(Object val) throws IOException
这种情况下,要将io.serializations
属性设置为想使用的序列化框架。
若next()方法返回的是非null对象,则可以从数据流中读取键值对并可以通过getCurrentValue()方法读取该值。若next()返回null对象,则说明已读到文件尾。
IntWritable key = ...
Text value = ...
SequenceFile.Reader reader = null;
long position = reader.getPosition();
try{
reader = new SequenceFile.Reader(fs, path, conf);
Writable key = (Writable)ReflectioonUtils.newInstance(reader.getKeyClass(), conf);
Writable value = (Writable)ReflectioonUtils.newInstance(reader.getValueClass(), conf);
while(reader.nect(key, value){
String syncSeen = reader.syncSeen() ? "*" : "";
......
position = reader.getPosition();//找到下一个记录的位置
}
}else{
IOUtils.closeStream(reader);
}
程序中的syncSeen
用来表示当前记录是否包括文件中的同步点的位置信息。
所谓同步点,是指数据读取迷路后可以再一次与记录边界同步的数据流中的某个位置。同步点是由SequenceFile.Writer
记录的,后者在顺序文件写入过程中插入一个特殊项以便每隔几个记录就有一个同步标识。
如果想要在顺序文件中上搜索给定位置的记录,有两种方法:
①调用seek()
方法,该方法将读指针指向文件中指定的位置:
reader.seek(...)
但若给定位置不是记录边界,则会抛出IOException
异常。
②通过同步点查找记录边界。SequenceFile.Reader
对象的sync(long position)
方法可以将读取位置定位到 position 后的下一个同步点。
命令行接口显示SequenceFile
hadoop fs
命令有一个-text
选项可以以文本形式显示顺序文件。该选项可以查看文件代码,由此检测出文件类型并将其转化成相应文本,该选项可以识别 gzip 压缩文件、顺序文件和 Avro 数据文件。
对于顺序文件,如果键和值是由具体含义的字符串,那么这个命令就很有用。
命令如下:
hadoop fs -text numbers.seq | head
SequenceFile的排序和合并
①通过 MapReduce 实现排序归并,MapReduce 是对多个顺序文件进行排序或合并的最有效的方法。
②使用SequenceFile.Sorter
类中的sort()
方法和merge()
方法,但它比 MapReduce 更底层(如,实现并行需要手动对数据进行分区)。
所以 MapReduce 是更好的选择。
SequenceFile的格式
顺序文件由文件头和随后的一条或多条记录组成,其中同步标识位于顺序文件中的记录与记录之间:
同步标识的额外存储开销要求小于1%,所以没有必要在每条记录末尾都添加。
记录的内部结构取决于是否启用压缩,若已启用压缩,则结构取决于是记录压缩还是数据块压缩。
①没有启用压缩(默认情况)。每条记录由记录长度(字节数)、键长度、键和值组成,记录长度字段为四字节长的正数,遵循java.io.DataOutput
类中writeInt()
方法的协定。为写入顺序文件的类定义Serialization
类,通过它来实现键和值的序列化。
②记录压缩。与无压缩情况基本相同,只不过值是用文件头中定义的codec
压缩。
③块压缩。块压缩是指一次性压缩多条记录,因为它可以利用记录间的相似性进行压缩,所以相较于单条记录压缩方法,该方法压缩效率更高。
关于MapFile
MapFile
是已经排过序的SequenceFile
,它有索引,所以可以按键查找。索引自身就是一个SequenceFile
,包含了 map 中的一小部分键。由于索引能加载进内存,因此可以提供对主数据文件的快速查找。主数据文件则是另一个SequenceFile
,包含了所有的map 条目,这些条目按键顺序进行了排序。
MapFile
提供了一个用于读写的、与SequenceFile
类似的接口。需要注意的是,当使用MapFile.Writer
进行写操作时,map 条目顺序添加,否则会抛出IOException
异常。
MapFile的变种
①SetFile
。SetFile
是一个特殊的MapFile
,用于存储Writable
键的集合。键必须按排好的顺序添加。
②ArrayFile
。该变种中的键是一个整型,用于表示数组中元素的索引,而值是一个Writable
值。
③BloomMapFile
。该变种提供了get()
方法的一个高性能实现,对稀疏文件特别有用。该实现使用一个动态的布隆过滤器来检测某个给定的键是否在 map 文件中。虽然速度很快,但这个测试会有出现假阳性的可能。所以仅当测试通过时(键存在),常规的get()
方法才会被调用。