当要在进程间传递对象或持久化对象的时候,就需要序列化对象成字节流,反之当要将接收到或从磁盘读取的字节流转换为对象,就要进行反序列化。Writable是Hadoop的序列化格式,Hadoop定义了这样一个Writable接口。

  1. public interface Writable {
  2. void write(DataOutput out) throws IOException;
  3. void readFields(DataInput in) throws IOException;
  4. }
public interface Writable {
void write(DataOutput out) throws IOException;
void readFields(DataInput in) throws IOException;
}

一个类要支持可序列化只需实现这个接口即可。下面是Writable类得层次结构,借用了<<Hadoop:The Definitive Guide>>的图。

下面我们一点一点来看,先是IntWritable和LongWritable。

WritableComparable接口扩展了Writable和Comparable接口,以支持比较。正如层次图中看到,IntWritable、LongWritable、ByteWritable等基本类型都实现了这个接口。IntWritable和LongWritable的readFields()都直接从实现了DataInput接口的输入流中读取二进制数据并分别重构成int型和long型,而write()则直接将int型数据和long型数据直接转换成二进制流。IntWritable和LongWritable都含有相应的Comparator内部类,这是用来支持对在不反序列化为对象的情况下对数据流中的数据单位进行直接的,这是一个优化,因为无需创建对象。看下面IntWritable的代码片段:

  1. public class IntWritable implements WritableComparable {
  2. private int value;
  3. //…… other methods
  4. public static class Comparator extends WritableComparator {
  5. public Comparator() {
  6. super(IntWritable.class);
  7. }
  8. public int compare(byte[] b1, int s1, int l1,
  9. byte[] b2, int s2, int l2) {
  10. int thisValue = readInt(b1, s1);
  11. int thatValue = readInt(b2, s2);
  12. return (thisValue<thatValue ? -1 : (thisValue==thatValue ? 0 : 1));
  13. }
  14. }
  15. static {                                        // register this comparator
  16. WritableComparator.define(IntWritable.class, new Comparator());
  17. }
  18. }
public class IntWritable implements WritableComparable {
private int value; //…… other methods
public static class Comparator extends WritableComparator {
public Comparator() {
super(IntWritable.class);
} public int compare(byte[] b1, int s1, int l1,
byte[] b2, int s2, int l2) {
int thisValue = readInt(b1, s1);
int thatValue = readInt(b2, s2);
return (thisValue<thatValue ? -1 : (thisValue==thatValue ? 0 : 1));
}
} static { // register this comparator
WritableComparator.define(IntWritable.class, new Comparator());
}
}

代码中的static块调用WritableComparator的static方法define()用来注册上面这个Comparator,就是将其加入WritableComparator的comparators成员中,comparators是HashMap类型且是static的。这样,就告诉WritableComparator,当我使用WritableComparator.get(IntWritable.class)方法的时候,你返回我注册的这个Comparator给我[对IntWritable来说就是IntWritable.Comparator],然后我就可以使用comparator.compare(byte[] b1, int s1, int l1,byte[] b2, int s2, int l2)来比较b1和b2,而不需要将它们反序列化成对象[像下面代码中]。comparator.compare(byte[] b1, int s1, int l1,byte[] b2, int s2, int l2)中的readInt()是从WritableComparator继承来的,它将IntWritable的value从byte数组中通过移位转换出来。

  1. //params byte[] b1, byte[] b2
  2. RawComparator<IntWritable> comparator = WritableComparator.get(IntWritable.class);
  3. comparator.compare(b1,0,b1.length,b2,0,b2.length);
//params byte[] b1, byte[] b2
RawComparator<IntWritable> comparator = WritableComparator.get(IntWritable.class);
comparator.compare(b1,0,b1.length,b2,0,b2.length);

注意,当comparators中没有注册要比较的类的Comparator,则会返回一个默认的Comparator,然后使用这个默认Comparator的compare(byte[] b1, int s1, int l1,byte[] b2, int s2, int l2)方法比较b1、b2的时候还是要序列化成对象的,详见后面细讲WritableComparator。

LongWritable的方法基本和IntWritable一样,区别就是LongWritable的值是long型,且多了一个额外的LongWritable.DecresingComparator,它继承于LongWritable.Comparator,只是它的比较方法返回值与使用LongWritable.Comparator比较相反[取负],这个应当是为降序排序准备的。

  1. public class LongWritable implements WritableComparable {
  2. private long value;
  3. //……others
  4. /** A decreasing Comparator optimized for LongWritable. */
  5. public static class DecreasingComparator extends Comparator {
  6. public int compare(WritableComparable a, WritableComparable b) {
  7. return -super.compare(a, b);
  8. }
  9. public int compare(byte[] b1, int s1, int l1, byte[] b2, int s2, int l2) {
  10. return -super.compare(b1, s1, l1, b2, s2, l2);
  11. }
  12. }
  13. static {                                       // register default comparator
  14. WritableComparator.define(LongWritable.class, new Comparator());
  15. }
  16. }
public class LongWritable implements WritableComparable {
private long value;
//……others
/** A decreasing Comparator optimized for LongWritable. */
public static class DecreasingComparator extends Comparator {
public int compare(WritableComparable a, WritableComparable b) {
return -super.compare(a, b);
}
public int compare(byte[] b1, int s1, int l1, byte[] b2, int s2, int l2) {
return -super.compare(b1, s1, l1, b2, s2, l2);
}
}
static { // register default comparator
WritableComparator.define(LongWritable.class, new Comparator());
}
}

另外,ByteWritable、BooleanWritable、FloatWritable、DoubleWritable都基本一样。

然后我们看VIntWritable和VLongWritable,这两个类基本一样而且VIntWritable[反]的value编码的时候也是使用VLongWritable的value编解码时的方法,主要区别是VIntWritable对象使用int型value成员,而VLongWritable使用long型value成员,这是由它们的取值范围决定的。它们都没有Comparator,不像上面的类。

我们只看VLongWritable即可,先看看其源码长什么样。

  1. public class VLongWritable implements WritableComparable {
  2. private long value;
  3. public VLongWritable() {}
  4. public VLongWritable(long value) { set(value); }
  5. /** Set the value of this LongWritable. */
  6. public void set(long value) { this.value = value; }
  7. /** Return the value of this LongWritable. */
  8. public long get() { return value; }
  9. public void readFields(DataInput in) throws IOException {
  10. value = WritableUtils.readVLong(in);
  11. }
  12. public void write(DataOutput out) throws IOException {
  13. WritableUtils.writeVLong(out, value);
  14. }
  15. /** Returns true iff <code>o</code> is a VLongWritable with the same value. */
  16. public boolean equals(Object o) {
  17. if (!(o instanceof VLongWritable))
  18. return false;
  19. VLongWritable other = (VLongWritable)o;
  20. return this.value == other.value;
  21. }
  22. public int hashCode() {
  23. return (int)value;
  24. }
  25. /** Compares two VLongWritables. */
  26. public int compareTo(Object o) {
  27. long thisValue = this.value;
  28. long thatValue = ((VLongWritable)o).value;
  29. return (thisValue < thatValue ? -1 : (thisValue == thatValue ? 0 : 1));
  30. }
  31. public String toString() {
  32. return Long.toString(value);
  33. }
  34. }
public class VLongWritable implements WritableComparable {
private long value; public VLongWritable() {} public VLongWritable(long value) { set(value); } /** Set the value of this LongWritable. */
public void set(long value) { this.value = value; } /** Return the value of this LongWritable. */
public long get() { return value; } public void readFields(DataInput in) throws IOException {
value = WritableUtils.readVLong(in);
} public void write(DataOutput out) throws IOException {
WritableUtils.writeVLong(out, value);
} /** Returns true iff <code>o</code> is a VLongWritable with the same value. */
public boolean equals(Object o) {
if (!(o instanceof VLongWritable))
return false;
VLongWritable other = (VLongWritable)o;
return this.value == other.value;
} public int hashCode() {
return (int)value;
} /** Compares two VLongWritables. */
public int compareTo(Object o) {
long thisValue = this.value;
long thatValue = ((VLongWritable)o).value;
return (thisValue < thatValue ? -1 : (thisValue == thatValue ? 0 : 1));
} public String toString() {
return Long.toString(value);
} }

在上面可以看到它编码时使用WritableUtils.writeVLong()方法。WritableUtils是关于编解码等的,暂时只看关于VIntWritable和VLongWritable的。

VIntWritable的value的编码实际也是使用writeVLong():

  1. public static void writeVInt(DataOutput stream, int i) throws IOException {
  2. writeVLong(stream, i);
  3. }
  public static void writeVInt(DataOutput stream, int i) throws IOException {
writeVLong(stream, i);
}

首先VIntWritable的长度是[1-5],VLonWritable长度是[1-9],如果数值在[-112,127]时,使用1Byte表示,即编码后的1Byte存储的就是这个数值。{中文版权威指南上p91我看见说范围是[-127,127],我猜可能是编码方法进行更新了}。如果不是在这个范围内,则需要更多的Byte,而第一个Byte将被用作存储长度,其它Byte存储数值。

writeVLong()的操作过程如下图,解析附在代码中[不知道说的够明白不,如果感觉难理解,个人觉得其实也不一定要了解太细节]。

WritableUtils.writeVLong()源码:

  1. public static void writeVLong(DataOutput stream, long i) throws IOException {
  2. if (i >= -112 && i <= 127) {
  3. stream.writeByte((byte)i);
  4. return;  //-112~127 only use one byte
  5. }
  6. int len = -112;
  7. if (i < 0) {
  8. i ^= -1L; // take one's complement' ~1 = (11111111)2  得到这
  9. //个i_2, i_2 + 1 = |i|,可想一下负数的反码如何能得到其正数[连符号一起取反+1]
  10. len = -120;
  11. }
  12. long tmp = i;  //到这里,i一定是正数,这个数介于[0,2^64-1]
  13. //然后用这个循环计算一下长度,i越大,实际长度越大,偏离长度起始值[原来len]越大,len值越小
  14. while (tmp != 0) {
  15. tmp = tmp >> 8;
  16. len--;
  17. }
  18. //现在,我们显然计算出了一个能表示其长度的值len,只要看其偏离长度起始值多少即可
  19. stream.writeByte((byte)len);
  20. len = (len < -120) ? -(len + 120) : -(len + 112); //看吧,计算出了长度,不包含第一个Byte哈[表示长度的Byte]
  21. for (int idx = len; idx != 0; idx--) {  //然后,这里从将i的二进制码从左到右8位8位地拿出来,然后写入流中
  22. int shiftbits = (idx - 1) * 8;
  23. long mask = 0xFFL << shiftbits;
  24. stream.writeByte((byte)((i & mask) >> shiftbits));
  25. }
  26. }
  public static void writeVLong(DataOutput stream, long i) throws IOException {
if (i >= -112 && i <= 127) {
stream.writeByte((byte)i);
return; //-112~127 only use one byte
} int len = -112;
if (i < 0) {
i ^= -1L; // take one's complement' ~1 = (11111111)2 得到这
//个i_2, i_2 + 1 = |i|,可想一下负数的反码如何能得到其正数[连符号一起取反+1]
len = -120;
} long tmp = i; //到这里,i一定是正数,这个数介于[0,2^64-1]
//然后用这个循环计算一下长度,i越大,实际长度越大,偏离长度起始值[原来len]越大,len值越小
while (tmp != 0) {
tmp = tmp >> 8;
len--;
}
//现在,我们显然计算出了一个能表示其长度的值len,只要看其偏离长度起始值多少即可
stream.writeByte((byte)len); len = (len < -120) ? -(len + 120) : -(len + 112); //看吧,计算出了长度,不包含第一个Byte哈[表示长度的Byte] for (int idx = len; idx != 0; idx--) { //然后,这里从将i的二进制码从左到右8位8位地拿出来,然后写入流中
int shiftbits = (idx - 1) * 8;
long mask = 0xFFL << shiftbits;
stream.writeByte((byte)((i & mask) >> shiftbits));
}
}

现在知道它是怎么写出去的了,再看看它是怎么读进来,这显然是个反过程。

WritableUtils.readVLong():

  1. public static long readVLong(DataInput stream) throws IOException {
  2. byte firstByte = stream.readByte();
  3. int len = decodeVIntSize(firstByte);
  4. if (len == 1) {
  5. return firstByte;
  6. }
  7. long i = 0;
  8. for (int idx = 0; idx < len-1; idx++) {
  9. byte b = stream.readByte();
  10. i = i << 8;
  11. i = i | (b & 0xFF);
  12. }
  13. return (isNegativeVInt(firstByte) ? (i ^ -1L) : i);
  14. }
  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);
}

这显然就是读出字节表示长度[包括表示长度],然后从输入流中一个Byte一个Byte读出来,& 0xFF是为了不让系统自动类型转换,然后再^ -1L,也就是连符号一起取反.

WritableUtils.decodeVIntSize()就是获取编码长度:

  1. public static int decodeVIntSize(byte value) {
  2. if (value >= -112) {
  3. return 1;
  4. } else if (value < -120) {
  5. return -119 - value;
  6. }
  7. return -111 - value;
  8. }
  public static int decodeVIntSize(byte value) {
if (value >= -112) {
return 1;
} else if (value < -120) {
return -119 - value;
}
return -111 - value;
}

显然,就是按照上面图中的反过程,使用了-119和-111只是为了获取编码长度而不是实际数值长度[不包含表示长度的第一个Byte]而已。

继续说前面的WritableComparator,它是实现了RawComparator接口。RawComparator无非就是一个compare()方法。

  1. public interface RawComparator<T> extends Comparator<T> {
  2. public int compare(byte[] b1, int s1, int l1, byte[] b2, int s2, int l2);
  3. }
public interface RawComparator<T> extends Comparator<T> {
public int compare(byte[] b1, int s1, int l1, byte[] b2, int s2, int l2);
}

WritableComparator是RawComparator实例的工厂[注册了的Writable的实现类],它为这些Writable实现类提供了反序列化用的方法,这些方法都比较简单,比较难的readVInt()和readVLong()也就是上面说到的过程。Writable还提供了compare()的默认实现,它会反序列化才比较。如果WritableComparator.get()没有得到注册的Comparator,则会创建一个新的Comparator[其实是WritableComparator的实例],然后当你使用 public int compare(byte[] b1, int s1, int l1, byte[] b2, int s2, int l2)进行比较,它会去使用你要比较的Writable的实现的readFields()方法读出value来。

比如,VIntWritable没有注册,我们get()时它就构造一个WritableComparator,然后设置key1,key2,buffer,keyClass,当你使用 public int compare(byte[] b1, int s1, int l1, byte[] b2, int s2, int l2) ,则使用VIntWritable.readField从编码后的byte[]中读取value值再进行比较。

然后是ArrayWritable和TwoDArrayWritable,AbstractMapWritable

这两个Writable实现分别是对一位数组和二维数组的封装,不难想象它们都应该提供一个Writable数组和保持关于这个数组的类型,而且序列化和反序列化也将使用封装的Writable实现的readFields()方法和write()方法。

  1. public class TwoDArrayWritable implements Writable {
  2. private Class valueClass;
  3. private Writable[][] values;
  4. //……others
  5. public void readFields(DataInput in) throws IOException {
  6. // construct matrix
  7. values = new Writable[in.readInt()][];
  8. for (int i = 0; i < values.length; i++) {
  9. values[i] = new Writable[in.readInt()];
  10. }
  11. // construct values
  12. for (int i = 0; i < values.length; i++) {
  13. for (int j = 0; j < values[i].length; j++) {
  14. Writable value;                             // construct value
  15. try {
  16. value = (Writable)valueClass.newInstance();
  17. } catch (InstantiationException e) {
  18. throw new RuntimeException(e.toString());
  19. } catch (IllegalAccessException e) {
  20. throw new RuntimeException(e.toString());
  21. }
  22. value.readFields(in);                       // read a value
  23. values[i][j] = value;                       // store it in values
  24. }
  25. }
  26. }
  27. public void write(DataOutput out) throws IOException {
  28. out.writeInt(values.length);                 // write values
  29. for (int i = 0; i < values.length; i++) {
  30. out.writeInt(values[i].length);
  31. }
  32. for (int i = 0; i < values.length; i++) {
  33. for (int j = 0; j < values[i].length; j++) {
  34. values[i][j].write(out);
  35. }
  36. }
  37. }
  38. }
public class TwoDArrayWritable implements Writable {
private Class valueClass;
private Writable[][] values; //……others
public void readFields(DataInput in) throws IOException {
// construct matrix
values = new Writable[in.readInt()][];
for (int i = 0; i < values.length; i++) {
values[i] = new Writable[in.readInt()];
} // construct values
for (int i = 0; i < values.length; i++) {
for (int j = 0; j < values[i].length; j++) {
Writable value; // construct value
try {
value = (Writable)valueClass.newInstance();
} catch (InstantiationException e) {
throw new RuntimeException(e.toString());
} catch (IllegalAccessException e) {
throw new RuntimeException(e.toString());
}
value.readFields(in); // read a value
values[i][j] = value; // store it in values
}
}
} public void write(DataOutput out) throws IOException {
out.writeInt(values.length); // write values
for (int i = 0; i < values.length; i++) {
out.writeInt(values[i].length);
}
for (int i = 0; i < values.length; i++) {
for (int j = 0; j < values[i].length; j++) {
values[i][j].write(out);
}
}
}
}

也就是那样,没什么好讲的了。

另外还有些TupleWritable,AbstractMapWritable->{MapWritable,SortMapWritable},DBWritable,CompressedWritable,VersionedWritable,GenericWritable之类的,有必要时去再谈它们,其实也差不多,功能不一样而已。

参考资料:

      [1]Hadoop权威指南中文版第二版
转载:http://blog.csdn.net/posa88/article/details/7906426

MapReduce之Writable相关类的更多相关文章

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

    前面讲了InputFormat,就顺便讲一下Writable的东西吧,本来应当是放在HDFS中的. 当要在进程间传递对象或持久化对象的时候,就需要序列化对象成字节流,反之当要将接收到或从磁盘读取的字节 ...

  2. Android随笔之——Android时间、日期相关类和方法

    今天要讲的是Android里关于时间.日期相关类和方法.在Android中,跟时间.日期有关的类主要有Time.Calendar.Date三个类.而与日期格式化输出有关的DateFormat和Simp ...

  3. 21 BasicTaskScheduler基本任务调度器(一)——Live555源码阅读(一)任务调度相关类

    21_BasicTaskScheduler基本任务调度器(一)——Live555源码阅读(一)任务调度相关类 BasicTaskScheduler基本任务调度器 BasicTaskScheduler基 ...

  4. 8 延时队列相关类——Live555源码阅读(一)基本组件类

    这是Live555源码阅读的第一部分,包括了时间类,延时队列类,处理程序描述类,哈希表类这四个大类. 本文由乌合之众 lym瞎编,欢迎转载 http://www.cnblogs.com/oloroso ...

  5. 4 Handler相关类——Live555源码阅读(一)基本组件类

    这是Live555源码阅读的第一部分,包括了时间类,延时队列类,处理程序描述类,哈希表类这四个大类. Handler相关类概述 处理程序相关类一共有三个,其没有派生继承关系,但是其有友元关系和使用关系 ...

  6. MFC编程入门之十三(对话框:属性页对话框及相关类的介绍)

    前面讲了模态对话框和非模态对话框,本节来将一种特殊的对话框--属性页对话框. 属性页对话框的分类 属性页对话框想必大家并不陌生,XP系统中桌面右键点属性,弹出的就是属性页对话框,它通过标签切换各个页面 ...

  7. android 6.0 SDK中删除HttpClient的相关类的解决方法

    一.出现的情况 在eclipse或 android studio开发, 设置android SDK的编译版本为23时,且使用了httpClient相关类的库项目:如android-async-http ...

  8. Android 6.0删除Apache HttpClient相关类的解决方法

    相应的官方文档如下: 上面文档的大致意思是,在Android 6.0(API 23)中,Google已经移除了Apache HttpClient相关的类,推荐使用HttpUrlConnection. ...

  9. List 接口以及实现类和相关类源码分析

    List 接口以及实现类和相关类源码分析 List接口分析 接口描述 用户可以对列表进行随机的读取(get),插入(add),删除(remove),修改(set),也可批量增加(addAll),删除( ...

随机推荐

  1. 【C++】C++求vector中的最大最小值

    利用algorithm库里的max_element和min_element可以得到vector的最大最小值,配合distance函数可以得到最大值的位置 #include<vector> ...

  2. 实现 UISegmentControl 与 UIScrollView的上下级联(分别在相应的方法中加入级联代码)

    实现 UISegmentControl 与 UIScrollView的上下级联,需要在 [segmentCtr addTarget:self action:@selector(segmentedCon ...

  3. maven小记

    编译webx3.0,必须用maven 3.0 git clone https://github.com/webx/citrus.gitmaven clean install -DskipTest(Te ...

  4. 如何在intellj Idea中给新建的项目添加jar包?

    1. 假如我加入joda.jar 2. 找到发布的你想要的jar包,下载! 3. 解压刚下载的jar包,复制 4. 在intellj idea中新建一个java项目,然后创建一个专门用于放jar的li ...

  5. 另一个SqlParameterCollection中已包含SqlParameter

    一般情况下,我们定义的一个SqlParameter参数数组,如: SqlParameter[] parms =             {                new SqlParamete ...

  6. Jenkins定时构建项目

    我们是不是可以自动的来构建项目那么此时我们需要使用Poll SCM和Build periodically,我们在构建触发中选择这两项即可,其实他们两个就是一个自动任务 触发远程构建:触发远程构建Bui ...

  7. Android Error:Execution failed for task ':app:mergeDebugResources'. > Crunching Cruncher bg_btn.9.png

    使用Android Studio来进行图片背景设置,编译时发生了一个mergeDebugResources异常. 异常原因 这个异常的意思是对资源合并发生错误,那就是我使用的图片资源有问题,我使用的图 ...

  8. linux 入门

    php            php -v ------------------ 阿帕奇 apachectl -v httpd -v ----------------------------- mys ...

  9. java 高效批量插入 sqlserver 数据库

    插入1000条:347毫秒 插入1W条:4086毫秒 插入10W条:47953毫秒 同理,批量更新也可以用此方法,只不过没有插入的快, 更新1000条:90秒 更新100条:9秒

  10. ISymbol

    public void Draw (IGeometry Geometry); public void QueryBoundary (     int hDC,     ITransformation ...