Hadoop的 hdfs 和 mapreduce 子框架主要是針對大數據文件設計的,在小文件的處理上不但效率低下,而且十分消耗磁盤空間(每一個小文件佔用一個Block , hdfs默認block大小爲128M)。因此,hadoop提供給我們SequenceFile和MapFile兩種容器處理小文件,將這些小文件組織起來統一存儲。
【SequenceFile】
1、SequenceFile概述
(1)SequenceFile文件是Hadoop用來存儲二進制形式的 <key, value> 而設計的一種平面文件(Flat File)
(2)SequenceFile是一種容器,把所有文件打包到SequenceFile類中可以高效的對小文件進行存儲和處理
(3)SequenceFile文件不按照其存儲的Key進行排序,其提供的內部類Writer提供了append功能
(4)SequenceFile中的Key 和 Value 可以使任意類型的Writable或自定義Writable
(5)在存儲結構上,SequenceFile由一個Header 後跟多條Record組成,Header主要包含Key classname,value classname,存儲壓縮算法,用戶自定義元數據等信息,此外,還包含一些同步標識,用於快速定位到記錄的邊界。每條Record以鍵值對的方式進行存儲,用來表示它的字符數組可以一次解析成:記錄的長度、Key的長度、Key值和value值,並且Value值的結構取決於該記錄是否被壓縮。
2、SequenceFile壓縮
(1)SequenceFIle的內部格式取決於是否啓用壓縮,如果是壓縮,則又可以分爲記錄壓縮和塊壓縮。
(2)三種類型的壓縮:
a.無壓縮類型:如果沒有啓用壓縮(默認設置)那麼每個記錄就由它的記錄長度(字節數)、鍵的長度,鍵和值組成。長度字段爲4字節。
b.記錄壓縮類型:記錄壓縮格式與無壓縮格式基本相同,不同的是值字節是用定義在頭部的編碼器來壓縮。注意:鍵是不壓縮的。下圖爲記錄壓縮:
c.塊壓縮類型:塊壓縮一次壓縮多個記錄,因此它比記錄壓縮更緊湊,而且一般優先選擇。當記錄的字節數達到最小大小,纔會添加到塊。該最小值由io.seqfile.compress.blocksize中的屬性定義。默認值是1000000字節。格式爲記錄數、鍵長度、鍵、值長度、值。下圖爲塊壓縮:
3、SequenceFile實例
工具類:
package com.yc.hadoop42_003_mapreduce;
import java.io.IOException;
import java.net.URI;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.io.IOUtils;
import org.apache.hadoop.io.IntWritable;
import org.apache.hadoop.io.SequenceFile;
import org.apache.hadoop.io.SequenceFile.Reader;
import org.apache.hadoop.io.SequenceFile.Writer;
import org.apache.hadoop.io.Text;
public class SequenceFileUtil {
private static Configuration conf = new Configuration();//創建配置文件
private static URI uri = URI.create("hdfs://master:9000/");
public static void doWrite(String[] texts, String strPath) {
try {
Writer writer = SequenceFile.createWriter(conf, Writer.file(new Path(strPath)),
Writer.keyClass(IntWritable.class), Writer.valueClass(Text.class));
StringBuffer str = new StringBuffer();
for (String text : texts) {
writer.append(new IntWritable(str.length()), new Text(text));
str.append(text);
}
IOUtils.closeStream(writer); //關閉流
} catch (IllegalArgumentException | IOException e) {
e.printStackTrace();
}
}
public static void doRead(String strPath) {
try {
Reader reader = new Reader(conf, Reader.file(new Path(strPath)));
IntWritable key = new IntWritable();
Text value = new Text();
while (reader.next(key, value)) {
System.out.println("key:" + key);
System.out.println("value:" + value);
System.out.println("position:" + reader.getPosition()); //返回輸入文件中的當前字節位置
}
IOUtils.closeStream(reader);
} catch (IllegalArgumentException | IOException e) {
e.printStackTrace();
}
}
}
測試類:
package com.yc.hadoop42_003_mapreduce;
import org.junit.Test;
public class SequenceFileUtilTest {
@Test
public void testDoWrite() {
String[] texts = {"人生若只如初見", "何事秋風悲畫扇",
"等閒變卻故人心", "卻道故人心易變",
"驪山語罷清宵半", "淚雨零鈴終不怨",
"何如薄倖錦衣郎", "比翼連枝當日願"};
SequenceFileUtil.doWrite(texts, "hdfs://master:9000/data/test.txt");
}
@Test
public void testDoRead() {
SequenceFileUtil.doRead("hdfs://master:9000/data/test.txt");
}
}
測試結果:key:0
value:人生若只如初見
position:173
key:7
value:何事秋風悲畫扇
position:218
key:14
value:等閒變卻故人心
position:263
key:21
value:卻道故人心易變
position:308
key:28
value:驪山語罷清宵半
position:353
key:35
value:淚雨零鈴終不怨
position:398
key:42
value:何如薄倖錦衣郎
position:443
key:49
value:比翼連枝當日願
position:488
4、SequenceFile總結
【優點】
a、支持基於記錄(Record)或塊(Block)的數據壓縮。
b、支持splitable,能夠作爲MapReduce的輸入分片。
c、修改簡單:主要負責修改相應的業務邏輯,而不用考慮具體的存儲格式。
【缺點】
a、需要一個合併文件的過程,且合併後的文件不方便查看。
【MapFile】
1、MapFile概述
(1)MapFile是排序後的SequenceFile,通過觀察其目錄結構可以看到MapFile由兩部分組成分別是data和index。
(2)index作爲文件的數據索引,主要記錄了每個Record的Key值,以及該Record在文件中的偏移位置。在MapFile被訪問的時候,索引文件會被加載到內存,通過索引映射關係可以迅速定位到指定Record所在文件位置,因此,相對SequenceFile而言,MapFile的檢索效率是最高的,缺點是會消耗一部分內存來存儲index數據。
(3)需要注意的是,MapFile並不不會把所有的Record都記錄到index中去,默認情況下每隔128條記錄會存儲一個索引映射。當然,記錄間隔可認爲修改,通過MapFile.Writer的setIndexInterval()方法,或修改io.map.index.interval屬性。
(4)與SequenceFile不同的是,MapFile的KeyClass一定要實現WritableComparable接口,即Key值是可比較的。
2、MapFile實例
工具類:
package com.yc.hadoop42_003_mapreduce;
import java.io.IOException;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.io.IOUtils;
import org.apache.hadoop.io.LongWritable;
import org.apache.hadoop.io.MapFile;
import org.apache.hadoop.io.Text;
public class MapFileUtil {
private static Configuration conf = new Configuration();
public static void doWriter(String[] texts, String dirPath) {
try {
MapFile.Writer writer = new MapFile.Writer(conf, new Path(dirPath),
MapFile.Writer.keyClass(LongWritable.class), MapFile.Writer.valueClass(Text.class));
LongWritable key = new LongWritable();
for (String text : texts) {
writer.append(key, new Text(text));
key.set(key.get()+text.getBytes().length);
}
IOUtils.closeStream(writer);
} catch (IllegalArgumentException | IOException e) {
e.printStackTrace();
}
}
public static void doReader(String dirPath) {
try {
MapFile.Reader reader = new MapFile.Reader(new Path(dirPath), conf);
LongWritable key = new LongWritable();
Text value = new Text();
while (reader.next(key, value)) {
System.out.println("key:" + key + ", value:" + value);
}
IOUtils.closeStream(reader);
} catch (IllegalArgumentException | IOException e) {
e.printStackTrace();
}
}
}
測試類:
package com.yc.hadoop42_003_mapreduce;
import org.junit.Test;
public class MapFileUtilTest {
@Test
public void testDoWriter() {
String[] texts = { "人生若只如初見", "何事秋風悲畫扇", "等閒變卻故人心", "卻道故人心易變", "驪山語罷清宵半", "淚雨零鈴終不怨", "何如薄倖錦衣郎", "比翼連枝當日願" };
MapFileUtil.doWriter(texts, "hdfs://master:9000/data/test01.txt");
}
@Test
public void testDoReader() {
MapFileUtil.doReader("hdfs://master:9000/data/test01.txt");
}
}
測試結果:
key:0, value:人生若只如初見
key:21, value:何事秋風悲畫扇
key:42, value:等閒變卻故人心
key:63, value:卻道故人心易變
key:84, value:驪山語罷清宵半
key:105, value:淚雨零鈴終不怨
key:126, value:何如薄倖錦衣郎
key:147, value:比翼連枝當日願