hadoop中典型Writable类详解
本文地址:http://www.cnblogs.com/archimedes/p/hadoop-writable.html,转载请注明源地址。
Hadoop将很多Writable类归入org.apache.hadoop.io包中,在这些类中,比较重要的有Java基本类、Text、Writable集合、ObjectWritable等,重点介绍Java基本类和ObjectWritable的实现。
1. Java基本类型的Writable封装
目前Java基本类型对应的Writable封装如下表所示。所有这些Writable类都继承自WritableComparable。也就是说,它们是可比较的。同时,它们都有get()和set()方法,用于获得和设置封装的值。
Java基本类型对应的Writable封装
Java基本类型 | Writable | 序列化后长度 |
布尔型(boolean) | BooleanWritable | 1 |
字节型(byte) | ByteWritable | 1 |
整型(int) |
IntWritable VIntWritable |
4 1~5 |
浮点型(float) | FloatWritable | 4 |
长整型(long) |
LongWritable VLongWritable |
8 1~9 |
双精度浮点型(double) | DoubleWritable | 8 |
在表中,对整型(int和long)进行编码的时候,有固定长度格式(IntWritable和LongWritable)和可变长度格式(VIntWritable和VLongWritable)两种选择。固定长度格式的整型,序列化后的数据是定长的,而可变长度格式则使用一种比较灵活的编码方式,对于数值比较小的整型,它们往往比较节省空间。同时,由于VIntWritable和VLongWritable的编码规则是一样的,所以VIntWritable的输出可以用VLongWritable读入。下面以VIntWritable为例,说明Writable的Java基本类封装实现。代码如下:
public class VIntWritable implements WritableComparable {
private int value;
……
// 设置VIntWritable的值
public void set(int value) { this.value = value; } // 获取VIntWritable的值
public int get() { return value; } public void readFields(DataInput in) throws IOException {
value = WritableUtils.readVInt(in);
} public void write(DataOutput out) throws IOException {
WritableUtils.writeVInt(out, value);
}
……
}
首先,每个Java基本类型的Writable封装,其类的内部都包含一个对应基本类型的成员变量value,get()和set()方法就是用来对该变量进行取值/赋值操作的。而Writable接口要求的readFields()和write()方法,VIntWritable则是通过调用Writable工具类中提供的readVInt()和writeVInt()读/写数据。方法readVInt()和writeVInt()的实现也只是简单调用了readVLong()和writeVLong(),所以,通过writeVInt()写的数据自然可以通过readVLong()读入。
writeVLong ()方法实现了对整型数值的变长编码,它的编码规则如下:
如果输入的整数大于或等于–112同时小于或等于127,那么编码需要1字节;否则,序列化结果的第一个字节,保存了输入整数的符号和后续编码的字节数。符号和后续字节数依据下面的编码规则(又一个规则):
如果是正数,则编码值范围落在–113和–120间(闭区间),后续字节数可以通过–(v+112)计算。
如果是负数,则编码值范围落在–121和–128间(闭区间),后续字节数可以通过–(v+120)计算。
后续编码将高位在前,写入输入的整数(除去前面全0字节)。代码如下:
public final class WritableUtils {
public stati cvoid writeVInt(DataOutput stream, int i) throws IOException
{
writeVLong(stream, i);
}
/**
* @param stream保存系列化结果输出流
* @param i 被序列化的整数
* @throws java.io.IOException
*/
public static void writeVLong(DataOutput stream, long i) throws……
{
//处于[-112, 127]的整数
if (i >= -112 && i <= 127) {
stream.writeByte((byte)i);
return;
}
//计算情况2的第一个字节
int len = -112;
if (i < 0) {
i ^= -1L;
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));
}
}
}
2. ObjectWritable类的实现
针对Java基本类型、字符串、枚举、Writable、空值、Writable的其他子类,ObjectWritable提供了一个封装,适用于字段需要使用多种类型。ObjectWritable可应用于Hadoop远程过程调用中参数的序列化和反序列化;ObjectWritable的另一个典型应用是在需要序列化不同类型的对象到某一个字段,如在一个SequenceFile的值中保存不同类型的对象(如LongWritable值或Text值)时,可以将该值声明为ObjectWritable。
ObjectWritable的实现比较冗长,需要根据可能被封装在ObjectWritable中的各种对象进行不同的处理。ObjectWritable有三个成员变量,包括被封装的对象实例instance、该对象运行时类的Class对象和Configuration对象。
ObjectWritable的write方法调用的是静态方法ObjectWritable.writeObject(),该方法可以往DataOutput接口中写入各种Java对象。
writeObject()方法先输出对象的类名(通过对象对应的Class 对象的getName()方法获得),然后根据传入对象的类型,分情况序列化对象到输出流中,也就是说,对象通过该方法输出对象的类名,对象序列化结果对到输出流中。在ObjectWritable.writeObject()的逻辑中,需要分别处理null、Java数组、字符串String、Java基本类型、枚举和Writable的子类6种情况,由于类的继承,处理Writable时,序列化的结果包含对象类名,对象实际类名和对象序列化结果三部分。
为什么需要对象实际类名呢?根据Java的单根继承规则,ObjectWritable中传入的declaredClass,可以是传入instance对象对应的类的类对象,也可以是instance对象的父类的类对象。但是,在序列化和反序列化的时候,往往不能使用父类的序列化方法(如write方法)来序列化子类对象,所以,在序列化结果中必须记住对象实际类名。相关代码如下:
public class ObjectWritable implements Writable, Configurable {
private Class declaredClass;//保存于ObjectWritable的对象对应的类对象
private Object instance;//被保留的对象
private Configuration conf; public ObjectWritable() {} public ObjectWritable(Object instance) {
set(instance);
} public ObjectWritable(Class declaredClass, Object instance) {
this.declaredClass = declaredClass;
this.instance = instance;
}
……
public void readFields(DataInput in) throws IOException {
readObject(in, this, this.conf);
} public void write(DataOutput out) throws IOException {
writeObject(out, instance, declaredClass, conf);
}
……
public static void writeObject(DataOutput out, Object instance,
Class declaredClass,Configuration conf) throws……{ if (instance == null) {//空
instance = new NullInstance(declaredClass, conf);
declaredClass = Writable.class;
} // 写出declaredClass的规范名
UTF8.writeString(out, declaredClass.getName()); if (declaredClass.isArray()) {//数组
……
} else if (declaredClass == String.class) {//字符串
……
} else if (declaredClass.isPrimitive()) {//基本类型
if (declaredClass == Boolean.TYPE) { //boolean
out.writeBoolean(((Boolean)instance).booleanValue());
} else if (declaredClass == Character.TYPE) { //char
……
}
} else if (declaredClass.isEnum()) {//枚举类型
……
} else if (Writable.class.isAssignableFrom(declaredClass)) {
//Writable的子类
UTF8.writeString(out, instance.getClass().getName());
((Writable)instance).write(out);
} else {
……
} public static Object readObject(DataInput in,
ObjectWritable objectWritable, Configuration conf){
……
Class instanceClass = null;
……
Writable writable = WritableFactories.newInstance(instanceClass,
conf);
writable.readFields(in);
instance = writable;
……
}
}
和输出对应,ObjectWritable的readFields()方法调用的是静态方法ObjectWritable.readObject(),该方法的实现和writeObject()类似,唯一值得研究的是Writable对象处理部分,readObject()方法依赖于WritableFactories类。WritableFactories类允许非公有的Writable子类定义一个对象工厂,由该工厂创建Writable对象,如在上面的readObject()代码中,通过WritableFactories的静态方法newInstance(),可以创建类型为instanceClass的Writable子对象。相关代码如下:
public class WritableFactories {
//保存了类型和WritableFactory工厂的对应关系
private static final HashMap<Class, WritableFactory>CLASS_TO_FACTORY
= new HashMap<Class, WritableFactory>();
……
public static Writable newInstance(Class<? extends Writable> c,
Configuration conf) {
WritableFactory factory = WritableFactories.getFactory(c);
if (factory != null) {
Writable result = factory.newInstance();
if (result instanceof Configurable) {
((Configurable) result).setConf(conf);
}
return result;
} else {
//采用传统的反射工具ReflectionUtils,创建对象
return ReflectionUtils.newInstance(c, conf);
}
}
}
WritableFactories.newInstance()方法根据输入的类型查找对应的WritableFactory工厂对象,然后调用该对象的newInstance()创建对象,如果该对象是可配置的,newInstance()还会通过对象的setConf()方法配置对象。
WritableFactories提供注册机制,使得这些Writable子类可以将该工厂登记到WritableFactories的静态成员变量CLASS_TO_FACTORY中。下面是一个典型的WritableFactory工厂实现,来自于HDFS的数据块Block。其中,WritableFactories.setFactory()需要两个参数,分别是注册类对应的类对象和能够构造注册类的WritableFactory接口的实现,在下面的代码里,WritableFactory的实现是一个匿名类,其newInstance()方法会创建一个新的Block对象。
public class Block implements Writable, Comparable<Block> {
static {
WritableFactories.setFactory
(Block.class,//类对象
new WritableFactory() {//对应类的WritableFactory实现
public Writable newInstance() { return new Block(); }
});
}
……
}
ObjectWritable作为一种通用机制,相当浪费资源,它需要为每一个输出写入封装类型的名字。如果类型的数量不是很多,而且可以事先知道,则可以使用一个静态类型数组来提高效率,并使用数组索引作为类型的序列化引用。GenericWritable就是因为这个目的被引入org.apache.hadoop.io包中。
hadoop中典型Writable类详解的更多相关文章
- Php-SPL库中的迭代器类详解(转)
SPL提供了多个迭代器类,分别提供了迭代访问.过滤数据.缓存结果.控制分页等功能.,因为php总是在不断壮大,我尽可能列出SPL中所有的迭代类.下面其中一些迭代器类是需要php5.4,另外一些如Sea ...
- MFC中CString.Format类详解
在MFC程序中,使用CString来处理字符串是一个很不错的选择.CString既可以处理Unicode标准的字符串,也可以处理ANSI标准的字符串.CString的Format方法给我们进行字符串的 ...
- Spring框架spring-web模块中的RestTemplate类详解
RestTemplate类是spring-web模块中进行HTTP访问的REST客户端核心类.RestTemplate请求使用阻塞式IO,适合低并发的应用场景. 1. RestTemplate类提供了 ...
- Delphi 中的 XMLDocument 类详解(10) - 判断节点类型: 支节点、叶节点、文本节点、空节点
unit Unit1; interface uses Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, For ...
- Delphi 中的 XMLDocument 类详解(9) - 关于 HasChildNodes 与 IsTextElement
unit Unit1; interface uses Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, For ...
- (14)javaWeb中的HttpServletResponse类详解
如果希望了解请求和响应的详细内容,可以看我的“HTTP协议”系列文章 响应体的简单概述: a,响应报文结构: b,常见的状态码,返回服务器处理的结果: c,常见的响应头: HttpServletRes ...
- JDK中Unsafe类详解
Java中Unsafe类详解 在openjdk8下看Unsafe源码 浅析Java中的原子操作 Java并发编程之LockSupport http://hg.openjdk.java.net/jdk7 ...
- Java中dimension类详解
Java中dimension类详解 https://blog.csdn.net/hrw1234567890/article/details/81217788
- java中的io系统详解 - ilibaba的专栏 - 博客频道 - CSDN.NET
java中的io系统详解 - ilibaba的专栏 - 博客频道 - CSDN.NET 亲,“社区之星”已经一周岁了! 社区福利快来领取免费参加MDCC大会机会哦 Tag功能介绍—我们 ...
随机推荐
- 久邦数码(3G门户)面试
久邦数码(3G门户)面试 1.数组和链表的区别(为什么数组带有索引) 2.数据库(手写选出一个公司年龄最大的100个员工) 3.一百个数查找一个数 利用二分查找一个数在最差的情况下至少比较多少次 4. ...
- 【转载】okhttp源码解析
转自:http://www.open-open.com/lib/view/open1472216742720.html https://blog.piasy.com/2016/07/11/Unders ...
- spring中使用@Value设置全局变量默认值
前几天在开发过程中遇到一个使用 spring 的 @Value 给类的全局变量设置默认值不成功的问题,最后通过查资料也是轻松解决,但是发现使用@Value也是有多种多样的方式,今天总算是将开发任务结束 ...
- input限制中文字数
我们知道input控件有一个maxlength属性可以控制输入字符的长度,但是,它并不会识别是汉字还是其他符号,所以输入maxlength个汉字显然是不符合要求的. 为了实现对带有汉字的输入框长度控制 ...
- [BZOJ3583]杰杰的女性朋友(矩阵快速幂)
杰杰的女性朋友 时间限制:10s 空间限制:256MB 题目描述 杰杰是魔法界的一名传奇人物.他对魔法具有深刻的洞察力,惊人的领悟力,以及令人叹为观止的创造力.自从他从事魔法竞赛以来,短短几 ...
- 【SDOI2017】树点染色【线段树+LCT】
本来只是想练练LCT,没想到是个线段树 对于操作1:诶新的颜色?这不是access吗? 也就是说,我们用一棵splay来表示一种颜色 操作2直接在LCT上乱搞-- 不对啊,操作3要查子树 诶好像是静态 ...
- Week Five
2018.12.25 1.[BZOJ 4310] 2.[BZOJ 3879] 3.[BZOJ 2754] 4.[BZOJ 4698] 5.[Codeforces 914E] 6.[Codeforces ...
- php上传文件常见问题(基础)
既然上一篇文章<php上传中文文件文件名乱码问题>遇到了文件上传的问题,干脆把php上传文件时经常碰到的几个问题总结一下吧,以后用到时不用再去找了. 1.先做个最简单的上传文件 <h ...
- Mac下配置Idea的Maven
环境版本: Mac OS: 10.13.4 JDK: 1.8 Idea: 2018.3 Maven: 3.6.0 Maven 相关配置: Maven 下载: http://maven.apache.o ...
- Linux6.9用RPM方式安装MySQL5.7.21
1.下载安装包 wget https://dev.mysql.com/get/Downloads/MySQL-5.7/mysql-5.7.21-1.el6.x86_64.rpm-bundle.tar ...