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型数据结构:分别是ArrayWritable,TwoDArrayWritable,MapWritable和SortedMapWritable
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接口),这样,能够以统一的方式进行数据的读写。方便后面阶段的数据写入和读出操作。

你可能感兴趣的:(hadoop)