對於一些應用,需要特殊的數據結構來存儲數據。比如運行基於MapReduce的進程,當存儲數據時,將每個二進制數據塊放入它自己的文件,這樣做使得後期不容易擴展。爲此,hadoop開發了一系列高級容器。
一、SequenceFile類
包爲:org.apache.hadoop.io.SequenceFile
Hadoop的SequenceFile類爲二進制鍵值對提供了一個持續化的數據結構。它提供了 Writer, Reader and SequenceFile.Sorter 類能獨立執行讀、寫以及排序操作。
如果想應用於日誌文件格式,需要選擇一個鍵(如LongWritable表示時間戳)和一個值(如Writable表示日誌記錄的數量)。
用SequenceFile類作爲小型文件的容器也不錯。HDFS和MapReduce是大型文件的利器,當我們把文件打包到一個SequenceFile類中,我們能夠高效對小型文件進程存儲和處理。
(1)創建一個SequenceFile類
該類提供了一種靜態方法創建SequenceFile.Writer實例。而且有幾個重載方法。hadoop推薦使用靜態構造方法如下:
public static org.apache.hadoop.io.SequenceFile.Writer createWriter(FileContext fc, //文件上下文
Configuration conf, //配置信息
Path name, //文件path
Class keyClass, //鍵類
Class valClass, //值類
CompressionType compressionType, //壓縮類型
CompressionCodec codec, //壓縮器
Metadata metadata, //文件頭部metadata
EnumSet<CreateFlag> createFlag, //給出創建的語義如overwrite
org.apache.hadoop.fs.Options.CreateOpts... opts) //可選項
throws IOException
存儲在SequenceFile類中的鍵和值不一定必須是Writable。可以被SequenceFile類序列化和反序列的任何類型都可以使用。
在SequencdFile.Writer之後,就用append()方法寫入鍵/值對。然後在結束的時候調用close()方法。(SequenceFile.Write實現了java.io.Closeable)。
下面就是一個SequenceFile類的程序例子:
public static void main(String[] args) throws IOException(
String uri = args[0]; //創建FileSystem的步驟已經在學習小結一中學過,這裏就不再做介紹
Configuration conf = new Configuration();
FileSystem fs = FileSystem.gei(URI.create(uri), conf);
Paht path = new Path(uri);
IntWritable key = new IntWritable(); //創建鍵
Text value = new Text(); //創建值
SequencdFile.Writer writer = null;
try{
writer = SequencdFile.createWriter(fs,conf,path,key.getClass(),value.getClass() ); //創建Writer
key.set(data);
value.set(data);
writer.append(key,value);
}finally{
IOUtils.closeStream(writer);
}
}
(2)讀取SequenceFile類
從頭到尾讀取序列文件,需要創建一個SequenceFile.Reader實例。反覆調用next()方法之一遍歷記錄。使用哪一方法取決於所使用的序列化框架。比如Writable類型,可以爲:
public boolean next(Writable key, Writable value); //使用鍵值作爲參數,如果讀取是一個鍵值對,返回true。如果讀取到文件末尾,返回false。
下面就是一個讀取序列文件的例子:
public static void main(String[] args){
String uri = args[0];
Configuration conf = new Configuration();
FileSystem fs = FileSystem.get(URI.create(uri),conf);
Path path = new Path(uri);
SequenceFile.Reader reader = null;
try{
reader = new SequenceFile.Reader(fs,path,conf);
Writable key = (Writable) ReflectionUtils.newInstance(reader.getKeyClass(), conf); //使用Reflection工具類創建key的一個實例
Writable key = (Writable) ReflectionUtils.newInstance(reader.getValueClass(), conf);
long position = reader.getPosition(); //取得reader當前位置
while(reader.next(key,value)){ //不斷往下讀取
String syncSeen = reader.syncSeen() ? " * " : " "; //插入同步點
System.out.printf(position,syncSeen,key,value);
position = reader.getPosition();
}
}finally{
IOUtils.closeStream(reader);
}
}
此程序能顯示序列文件中同步點的位置。同步點是流中的一個點,如果reader失去對位置的判斷,同步點就可用於重新同步記錄邊界,例如在查找流中任意一個位置之後。同步點由SequenceFile.Reader來記錄,當序列文件被寫入的時候,它會每隔幾個記錄就插入一個特殊的項來標記此同步點。插入的開銷非常小。上面我就使用星號*標記同步點。
有兩種方法查找序列文件中指定的位置。第一種是seek()方法,第二種是sync(long position)方法。
三、序列文件的格式
序列文件由一個頭部和一個或多個記錄組成。
序列文件有三種類型,分別爲1無壓縮類型,2有壓縮類型,3和塊壓縮類型。它們的記錄組成不同,但序列文件頭都相同。
SequenceFile 頭格式:
- version - 3 字節SEQ,後接1字節版本號
- keyClassName -鍵類名
- valueClassName - 值類名
- compression - 布爾類型,標識是否對鍵值對啓用壓縮
- blockCompression - 布爾類型,標識是否對鍵值對啓用塊壓縮
- compression codec - 編碼解碼器類型
- metadata - SequenceFile的Metadata
- sync - 一個同步標記記錄頭結尾
四、MapFile類
MapFile類是經過排序的帶索引的SequenceFile,可以根據鍵進行查找。MapFile可以被認爲是java.util.Map的一種持久化形式。它會無限增長,直至超過map在內存中佔有的大小。
創建MapFile類的步驟和創建SequenceFile類差不多,這裏我們就不再介紹。
(1)它有兩個靜態成員變量:
static String DATA_FILE_NAME; //數據文件名
static String INDEX_FILE_NAME; //索引文件名
(2)常用方法:
void rename(FileSystem fs, String oldName, String newName)
//對已存在的map目錄重命名
long fix(FileSystem fs, Path dir, Class<?
extends Writable> keyClass, Class<?
extends Writable> valueClass, boolean dryrun, Configuration conf)
//該方法通過重新建立索引來定位出故障的MapFile,返回MapFile中無效的實體數