hadoop 源码阅读之io篇


从基础的IO包开始阅读。

IO:表示层,将各种数据编码/解码,方便于在网络上传输。

下图是一个大致的结构图,可以看出,大部分的类都是以Writable结

尾的数据结构和基本数据类型。


一、基本数据类型

Hadoop 中,并没有使用java自带的基本类型类(Integer、Float等)而是使用了自己开发的类IntWritable、FloatWritable、BooleanWritable、LongWritable、ByteWritable、BytesWritable、DoubleWritable,他们都实现了接口WritableComparable,接口定义如下:

public interface WritableComparable<T> extends Writable, Comparable<T> {}

Comparable是java.lang包的接口。

Writable接口定义如下

public interface Writable {

  void write(DataOutput out) throws IOException;

  void readFields(DataInput in) throws IOException;

Writable接口是一个序列化对象的接口,能够将数据写入流或者从流中读出。实现了之后,能够进行特定类型数据的异地传输。

除了这些基本类型的定义,还添加了VLongWritable和VIntWritable,V指的是可变长度,例如long型的1实际只需要一个字节的空间,由于是long型的,所以会占用8字节的空间,而VLongWritable中,会根据数值的大小,分配适当的空间(仅分配一个字节),达到节省空间的作用。在基本数据类型的Writable类中,readFields(DataInput in)方法是直接调用in.readLong()(以LongWritable为例),而在VLongWritable与VIntegerWritable中,readFields(DataInputin)方法是使用了静态类WritableUtils中的readVLong(in)方法。WritableUtils是一个工具类,用于提供io中的Writable类的一些静态方法。

下面分析下VLongWritable中的write和readFields方法的实现。

Write方法:

将long型的数根据所占用的字节数写入DataOutput 中,例如-112~ 127,只需要一个字节存储(-128~-113用于做标识了)。其他的数,则需要先指明该数的正负与占用的长度(在第一个字节表示),然后再按照长度存储。

第一个字符中, -113(11110001)到-120(11111000)表示正数,-121(11111000)到-128(10000000)表示负数。

后续的N个字节,表示该数字的N字节。

代码如下:

 

  public static void writeVLong(DataOutput stream, long i) throws IOException {

    if (i >= -112 && i <= 127) {

      stream.writeByte((byte)i);

      return;

    }

     

    int len = -112;

    if (i < 0) {

      i ^= -1L; // take one's complement'

      len = -120;

    }

     

    long tmp = i;

    while (tmp != 0) {

      tmp = tmp >> 8;

      len--;

    }

     

    stream.writeByte((byte)len);

     

    len = (len < -120) ? -(len + 120) : -(len + 112);

     

    for (int idx = len; idx != 0; idx--) {

      int shiftbits = (idx - 1) * 8;

      long mask = 0xFFL << shiftbits;

      stream.writeByte((byte)((i & mask) >> shiftbits));

    }

  }

 

readVLong方法,若第一个字节大于-112,则仅为一位数,直接返回该值;否则,则判断后面字节的位数,并读出返回。

由于对整数的操作都是以字节方式进行,故都是使用位操作进行。如

i= i << 8;

i= i | (b & 0xFF);

将i左移8位,并将其与新读入的8位数进行或运算,这样,就完成了一个字节数据的写入。

 

  public static long readVLong(DataInput stream) throws IOException {

    byte firstByte = stream.readByte();

    int len = decodeVIntSize(firstByte);

    if (len == 1) {

      return firstByte;

    }

    long i = 0;

    for (int idx = 0; idx < len-1; idx++) {

      byte b = stream.readByte();

      i = i << 8;

      i = i | (b & 0xFF);

    }

    return (isNegativeVInt(firstByte) ? (i ^ -1L) : i);

  }

 

  public static int decodeVIntSize(byte value) {

    if (value >= -112) {

      return 1;

    } else if (value < -120) {

      return -119 - value;

    }

    return -111 - value;

  }

 

在上述每个类初始化的时候,都会将自定义的比较器(Comparator)注册进WritableComparator的HashMap中,以供调用。

static {                                        // register this comparator

    WritableComparator.define(FloatWritable.class, new Comparator());

  }

下图是这些基本数据类型的类图

 

二、数据结构

几个实现了Writable接口的数据结构如下


主要有4种Writable型数据结构:分别是ArrayWritableTwoDArrayWritableMapWritableSortedMapWritable

1、  ArrayWritable

看到ArrayWritable的构造函数有个形式如下:

private Class<? extends Writable> valueClass;

private Writable[] values;

  public ArrayWritable(Class<? extends Writable> valueClass) {

    if (valueClass == null) {

      throw new IllegalArgumentException("null valueClass");

    }   

    this.valueClass = valueClass;

  }

 

  public ArrayWritable(Class<? extends Writable> valueClass, Writable[] values) {

    this(valueClass);

    this.values = values;

  }

 

  public ArrayWritable(String[] strings) {

    this(UTF8.class, new Writable[strings.length]);

    for (int i = 0; i < strings.length; i++) {

      values[i] = new UTF8(strings[i]);

    }

    第三个构造函数中,传入的是一个字符串数组,则自动将其进行打包,使用UTF8这个类进行封装,该类实现了WritableComparable 接口。这样,能够方便的处理字符串数组。因此,除了String类型的数据,该数据结构不能够存储其他未实现Writable接口的数据。

2、  TwoDArrayWritable 是二维数组。实现不复杂,主要还是实现了Writable接口,

 

public Object toArray() {

    int dimensions[] = {values.length, 0};

    Object result = Array.newInstance(valueClass, dimensions);

    for (int i = 0; i < values.length; i++) {

      Object resultRow = Array.newInstance(valueClass, values[i].length);

      Array.set(result, i, resultRow);

      for (int j = 0; j < values[i].length; j++) {

        Array.set(resultRow, j, values[i][j]);

      }

    }

    return result;

  }

该toArray方法将二维数组以对象的形式返回,使用了自带的Array里面的newInstance和set方法,而不是传统的

Array arr = newArrayList();

Arr.add(newArrayList())

的方式。自带的Array这种方式能够创建任意维度的数组(<255),并且似乎看上去更加优美,他不会绑定于具有的Array实现类上,更灵活。

 

3、MapWritable 和SortedMapWritable 都继承了抽象类AbstractMapWritable,这两个类像是设计模式中的适配器,大部分的函数都是直接调用类成员变量的相应方法,并实现了Writable接口(通过继承AbstractMapWritable间接实现了Writable接口),使其满足接口约束。

        

         从以上可以看出,由于java的数据类型及数据结构不便于hadoop进行数据流的写入和读出。因此,hadoop将一些常用的数据类型及数据结构进行封装,在外面加了层壳(让他们都实现Writable接口),这样,能够以统一的方式进行数据的读写。方便后面阶段的数据写入和读出操作。

 

参考

1、Hadoop源代码分析【IO专题】

http://www.cnblogs.com/qlee/archive/2011/05/18/2049864.html

2、[Hadoop源码解读](五)MapReduce篇之Writable相关类

http://blog.csdn.net/posa88/article/details/7906426

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值