编码器和解码器用以执行压缩解压算法。在Hadoop里,编码/解码器是通过一个压缩解码器接口实现的。因此,例如,GzipCodec封装了gzip压缩的压缩和解压算法。下表列出了Hadoop可用的编码/解码器。
压缩格式 | Hadoop压缩编码/解码器 |
DEFLATE | org.apache.hadoop.io.compress.DefaultCodec |
gzip | org.apache.hadoop.io.compress.GzipCodec |
bzip2 | org.apache.hadoop.io.compress.BZip2Codec |
LZO | com.hadoop.compression.lzo.LzopCodec |
LZO格式是基于GPL许可的,不能通过Apache来分发许可,基于此,它的hadoop}编码/解码器必须单独下载,地址是http: //code.google.com/p/hadoop-gpl-compression/。lzop编码/解码器兼容干lzop工具,它其实就是LZO 格式,但额外还有头部,它正是我们想要的。还有一个纯LZO格式的编码/解码器LzoCodec,它使用.lzo_deflate作为扩展名(根据 DEFLATE类推,是没有头部的gzip格式)。
CompressionCodec对流进行压缩和解压缩
CompressionCodec有两个方法可以用于轻松地压缩或解压缩数据。要想对正在被写入一个输出流的数据进行压缩,我们可以使用 createOutputStream(OutputStreamout)方法创建一个CompressionOutputStream(未压缩的数据将 被写到此),将其以压缩格式写入底层的流。相反,要想对从输入流读取而来的数据进行解压缩,则调用 createInputStream(InputStreamin)函数,从而获得一个CompressionInputStream,,从而从底层的流 读取未压缩的数据。CompressionOutputStream和CompressionInputStream类似干 java.util.zip.DeflaterOutputStream和java.util.zip.DeflaterInputStream,前两者 还可以提供重置其底层压缩和解压缩功能,当把数据流中的section压缩为单独的块时,这比较重要。比如SequenceFile。
下例中说明了如何使用API来压缩从标谁输入读取的数据及如何将它写到标准输出:
public
class
StreamCompressor {
public
static
void
main(String[] args)
throws
Exception { String codecClassname
=
args[
0
]; Class
<?>
codecClass
=
Class.forName(codecClassname); Configuration conf
=
new
Configuration(); CompressionCodec codec
=
(CompressionCodec) ReflectionUtils.newInstance(codecClass, conf); CompressionOutputStream out
=
codec.createOutputStream(System.out); IOUtils.copyBytes(System.in, out,
4096
,
false
); out.finish(); } }
此 应用需要压缩CompressionCodec的合法全名来作为命令行的第一个参数。我们使用ReflectionUtils来建立一个新的实例,然后获 得一个压缩好的System.out。然后我们调用IOUtils上的公共方法copyBytes()将输入复制到经过 CompressionOutputStream压缩的输出。最后,调用CompressionOutputStream.的finish()方法,从而 向压缩程序表明结束向压缩流写入数据,但不关闭流。我们可以试试以下命令行,使用StreamCompressor程序与GzipCodec压缩字符串 “Text”,然后使用gunzip从标准输入对它进行解压缩操作:
|
% echo "Text" | hadoop StreamCompressor org.apache.hadoop.io.compress.GzipCodec
| gunzip -
Text
|
用CompressionCodecFactory方法来推断CompressionCodecs
在阅读一个压缩文件时,我们通常可以从其扩展名来推断出它的编码/解码器。以.gz结尾的文件可以用GzipCodec来阅读,如此类推。每个压缩格式的扩展名均以下表所示:
压缩格式 | 工具 | 算法 | 文件扩展名 | 多文件 | 可分割性 |
DEFLATEa | 无 | DEFLATE | .deflate | 不 | 不 |
gzip | gzip | DEFLATE | .gz | 不 | 不 |
ZIP | zip | DEFLATE | .zip | 是 | 是,在文件范围内 |
bzip2 | bzip2 | bzip2 | .bz2 | 不 | 是 |
LZO | lzop | LZO | .lzo | 不 | 是 |
CompressionCodecFactory提供了getCodec()方法,从而将文件扩展名映射到相应的CompressionCodec。此方法接受一个Path对象。下面的例子显示了一个应用程序,此程序便使用这个功能来解压缩文件。
public
class
FileDecompressor {
public
static
void
main(String[] args)
throws
Exception { String uri
=
args[
0
]; Configuration conf
=
new
Configuration(); FileSystem fs
=
FileSystem.get(URI.create(uri), conf); Path inputPath
=
new
Path(uri); CompressionCodecFactory factory
=
new
CompressionCodecFactory(conf); CompressionCodec codec
=
factory.getCodec(inputPath);
if
(codec
==
null
) { System.err.println(
"
No codec found for
"
+
uri); System.exit(
1
); } String outputUri
=
CompressionCodecFactory.removeSuffix(uri, codec.getDefaultExtension()); InputStream in
=
null
; OutputStream out
=
null
;
try
{ in
=
codec.createInputStream(fs.open(inputPath)); out
=
fs.create(
new
Path(outputUri)); IOUtils.copyBytes(in, out, conf); }
finally
{ IOUtils.closeStream(in); IOUtils.closeStream(out); } } }
编码/解码器一旦找到,就会被用来去掉文件名后缀生成输出文件名(通过CompressionCodecFactory的静态方法removeSuffix()来实现)。这样,如下调用程序便把一个名为file.gz的文件解压缩为file文件:
% hadoop FileDecompressor
file
.gz
CompressionCodecFactory 从io.compression.codecs配置属性定义的列表中找到编码/解码器。默认情况下,这个列表列出了Hadoop提供的所有编码/解码器 (见表4-3),如果你有一个希望要注册的编码/解码器(如外部托管的LZO编码/解码器)你可以改变这个列表。每个编码/解码器知道它的默认文件扩展 名,从而使CompressionCodecFactory可以通过搜索这个列表来找到一个给定的扩展名相匹配的编码/解码器(如果有的话)。
属性名 | 类型 | 默认值 | 描述 |
io.compression.codecs | 逗号分隔的类名 | org.apache.hadoop.io. compress.DefaultCodec, org.apache.hadoop.io. compress.GzipCodec, org.apache.hadoop.io. compress.Bzip2Codec | 用于压缩/解压的CompressionCodec列表 |
表4-3
本地库
考虑到性能,最好使用一个本地库(native library)来压缩和解压。例如,在一个测试中,使用本地gzip压缩库减少了解压时间50%,压缩时间大约减少了10%(与内置的Java实现相比 较)。表4-4展示了Java和本地提供的每个压缩格式的实现。井不是所有的格式都有本地实现(例如bzip2压缩),而另一些则仅有本地实现(例如 LZO)。
压缩格式 | Java实现 | 本地实现 |
DEFLATE | 是 | 是 |
gzip | 是 | 是 |
bzip2 | 是 | 否 |
LZO | 否 | 是 |
Hadoop带有预置的32位和64位Linux的本地压缩库,位于库/本地目录。对于其他平台,需要自己编译库,具体请参见Hadoop的维基百科http://wiki.apache.org/hadoop/NativeHadoop。
本地库通过Java系统属性java.library.path来使用。Hadoop的脚本在bin目录中已经设置好这个属性,但如果不使用该脚本,则需要在应用中设置属性。
默认情况下,Hadoop会在它运行的平台上查找本地库,如果发现就自动加载。这意味着不必更改任何配置设置就可以使用本地库。在某些情况下,可能 希望禁用本地库,比如在调试压缩相关问题的时候。为此,将属性hadoop.native.lib设置为false,即可确保内置的Java等同内置实现 被使用(如果它们可用的话)。
CodecPool(压缩解码池)
如果要用本地库在应用中大量执行压缩解压任务,可以考虑使用CodecPool,从而重用压缩程序和解压缩程序,节约创建这些对象的开销。
下例所用的API只创建了一个很简单的压缩程序,因此不必使用这个池。此应用程序使用一个压缩池程序来压缩从标准输入读入然后将其写入标准愉出的数据:
public
class
PooledStreamCompressor {
public
static
void
main(String[] args)
throws
Exception { String codecClassname
=
args[
0
]; Class
<?>
codecClass
=
Class.forName(codecClassname); Configuration conf
=
new
Configuration(); CompressionCodec codec
=
(CompressionCodec) ReflectionUtils.newInstance(codecClass, conf); Compressor compressor
=
null
;
try
{ compressor
=
CodecPool.getCompressor(codec); CompressionOutputStream out
=
codec.createOutputStream(System.out, compressor); IOUtils.copyBytes(System.in, out,
4096
,
false
); out.finish(); }
finally
{ CodecPool.returnCompressor(compressor); } } }
我 们从缓冲池中为指定的CompressionCodec检索到一个Compressor实例,codec的重载方法 createOutputStream()中使用的便是它。通过使用finally块,我们便可确保此压缩程序会被返回缓冲池,即使在复制数据流之间的字 节期间抛出了一个IOException。
http://www.itivy.com/arch/archive/2011/12/10/hadoop-codec-usage.html