确定自定义RecordReader中gzip压缩文件的长度

时间:2013-01-25 09:11:47

标签: hadoop mapreduce

我编写了一个自定义记录阅读器来读取Hadoop中的文本和gzip文件,因为我特别要求将完整的文件数据作为密钥的值和文件名。来源如下:

public class WholeFileRecordReader extends RecordReader<Text, BytesWritable> {

    private CompressionCodecFactory compressionCodecs = null;
    private FileSplit fileSplit;
    private Configuration conf;
    private InputStream in;
    private Text key = new Text("");
    private BytesWritable value = new BytesWritable();
    private boolean processed = false;

    @Override
    public void initialize(InputSplit split, TaskAttemptContext context)
            throws IOException, InterruptedException {

        this.fileSplit = (FileSplit) split;
        this.conf = context.getConfiguration();

        final Path file = fileSplit.getPath();
        compressionCodecs = new CompressionCodecFactory(conf);

        final CompressionCodec codec = compressionCodecs.getCodec(file);
        System.out.println(codec);
        FileSystem fs = file.getFileSystem(conf);
        in = fs.open(file);

        if (codec != null) {
            in = codec.createInputStream(in);
        }
    }

    @Override
    public boolean nextKeyValue() throws IOException, InterruptedException {
        if (!processed) {
            byte[] contents = new byte[(int) fileSplit.getLength()];
            Path file = fileSplit.getPath();
            key.set(file.getName());

            try {
                IOUtils.readFully(in, contents, 0, contents.length);
                value.set(contents, 0, contents.length);
            } finally {
                IOUtils.closeStream(in);
            }

            processed = true;
            return true;
        }

        return false;
    }

    @Override
    public Text getCurrentKey() throws IOException, InterruptedException {
        return key;
    }

    @Override
    public BytesWritable getCurrentValue() throws IOException, InterruptedException {
        return value;
    }

    @Override
    public float getProgress() throws IOException {
        return processed ? 1.0f : 0.0f;
    }

    @Override
    public void close() throws IOException {
        // Do nothing
    }

}

问题是我的代码正在读取不完整的文件数据。这可能是因为我使用fileSplit(指向压缩文件)来确定内容的长度,因此我得到一个较小的值。因此,这会导致将不完整的数据传递给Mapper。

有人可以指出我如何获取gizip文件数据的实际长度或修改RecordReader以便它读取完整的数据。

2 个答案:

答案 0 :(得分:1)

延伸@Chris White的答案,我不得不对他给出的代码做出某些语法上的修改。它如下:

fileLength = (int) fileSplit.getLength();
compressionCodecs = new CompressionCodecFactory(conf);

final CompressionCodec codec = compressionCodecs.getCodec(file);
FileSystem fs = file.getFileSystem(conf);
in = fs.open(file);

if (codec != null) {
    if (codec instanceof GzipCodec) {
        byte[] len = new byte[4];
        try {
            in.skip(fileLength - 4);
            IOUtils.readFully(in, len, 0, len.length);
            fileLength = (len[3] << 24) | (len[2] << 16) + (len[1] << 8) + len[0];
        } finally {
            in.close();
        }
    }

    in = fs.open(file);
    in = codec.createInputStream(in);
}

非常感谢@Chris White的投入。没有你就不可能做到:)。

答案 1 :(得分:0)

对于GZip文件,您可以跳到最后4个字节(根据规范),应该返回原始的未压缩文件大小)。请注意,该值的模数为2 ^ 32,因此如果您希望原始文件大于此值,请务必小心。

所以你的初始化方法可以修改为类似的东西(未经测试!):

final CompressionCodec codec = compressionCodecs.getCodec(file);
System.out.println(codec);
FileSystem fs = file.getFileSystem(conf);
in = fs.open(file);

length = fileSplit.getLength();
if (codec instanceof GZipCodec) {
  // skip to last 4 bytes
  in.seek(length-4);

  // read size
  length = in.readInt();

  // reset stream position
  in.seek(0);
}

现在您应该拥有实际文件长度(对于未压缩和压缩的Gzip),您可以在nextKeyValue()方法中使用它。