当要在进程间传递对象或持久化对象的时候,就需要序列化对象成字节流,反之当要将接收到或从磁盘读取的字节流转换为对象,就要进行反序列化。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. Magento添加一个下拉登陆菜单Create Magento Dropdown Login in a few minutes

    Dropdown login forms are not a feature many online stores use, but in some cases they could be quite ...

  2. Introduction to Project Management(II)

    Introduction The purpose of this paper is to gain an understanding of project management and to give ...

  3. JS-面向对象-封装

    --参考文献: --http://www.ruanyifeng.com/blog/2010/05/object-oriented_javascript_encapsulation.html --js面 ...

  4. http://blog.csdn.net/yaerfeng/article/details/27683813

    http://blog.csdn.net/yaerfeng/article/details/27683813

  5. LeetCode Game of Life

    原题链接在这里:https://leetcode.com/problems/game-of-life/ 题目: According to the Wikipedia's article: " ...

  6. 诊断一句SQL不走索引的原因

    from http://www.itpub.net/thread-1852897-1-1.html 有论坛朋友在上面的帖子里问SQL为什么不走索引,正好这两天我也刚刚在看SQL优化,于是试着回答了一下 ...

  7. event.pageY和event.pageX

    event.pageY 属性返回鼠标指针的位置,相对于文档的上边缘. 提示:该事件属性通常与 event.pageX属性一起使用.(简写:e.pageY(或者e.pageX)) 实例: drag 首先 ...

  8. Oracle导入中文乱码解决办法

    Oracle导入中文乱码解决办法 一.确保各个客户端字符集的编码同服务器字符集编码一致 1-       确定sqlplus字符集编码,如果是windows设置环境变量. 2-       确保Sec ...

  9. 第七篇 Replication:合并复制-订阅

    本篇文章是SQL Server Replication系列的第七篇,详细内容请参考原文. 订阅服务器就是复制发布项目的所有变更将传送到的服务器.每一个发布需要至少一个订阅,但是一个发布可以有多个订阅. ...

  10. 第二篇 Integration Services:SSIS数据泵

    本篇文章是Integration Services系列的第二篇,详细内容请参考原文. 简介SSIS用于移动数据.数据流任务提供此功能.因为这个原因,当介绍SSIS时我喜欢从数据流任务开始.数据流任务的 ...