hadoop深入研究:(十一)——序列化与Writable实现

原创 2013年07月17日 12:03:30

转载请写明来源地址:http://blog.csdn.net/lastsweetop/article/details/9249411

所有源码在github上,https://github.com/lastsweetop/styhadoop


简介

在hadoop中,Writable的实现类是个庞大的家族,我们在这里简单的介绍一下常用来做序列化的一部分。


java原生类型

除char类型以外,所有的原生类型都有对应的Writable类,并且通过get和set方法可以他们的值。
IntWritable和LongWritable还有对应的变长VIntWritable和VLongWritable类。
固定长度还是变长的选用类似与数据库中的char或者vchar,在这里就不赘述了。


Text类型

Text类型使用变长int型存储长度,所以Text类型的最大存储为2G.
Text类型采用标准的utf-8编码,所以与其他文本工具可以非常好的交互,但要注意的是,这样的话就和java的String类型差别就很多了。

检索的不同

Text的chatAt返回的是一个整型,及utf-8编码后的数字,而不是象String那样的unicode编码的char类型。
    @Test
    public void testTextIndex(){
        Text text=new Text("hadoop");
        Assert.assertEquals(text.getLength(), 6);
        Assert.assertEquals(text.getBytes().length, 6);
        Assert.assertEquals(text.charAt(2),(int)'d');
        Assert.assertEquals("Out of bounds",text.charAt(100),-1);
    }
Text还有个find方法,类似String里indexOf方法
    @Test
    public void testTextFind() {
        Text text = new Text("hadoop");
        Assert.assertEquals("find a substring",text.find("do"),2);
        Assert.assertEquals("Find first 'o'",text.find("o"),3);
        Assert.assertEquals("Find 'o' from position 4 or later",text.find("o",4),4);
        Assert.assertEquals("No match",text.find("pig"),-1);
    }

Unicode的不同

当uft-8编码后的字节大于两个时,Text和String的区别就会更清晰,因为String是按照unicode的char计算,而Text是按照字节计算。
我们来看下1到4个字节的不同的unicode字符


4个unicode分别占用1到4个字节,u+10400在java的unicode字符重占用两个char,前三个字符分别占用1个char
我们通过代码来看下String和Text的不同
 @Test
    public void string() throws UnsupportedEncodingException {
        String str = "\u0041\u00DF\u6771\uD801\uDC00";
        Assert.assertEquals(str.length(), 5);
        Assert.assertEquals(str.getBytes("UTF-8").length, 10);

        Assert.assertEquals(str.indexOf("\u0041"), 0);
        Assert.assertEquals(str.indexOf("\u00DF"), 1);
        Assert.assertEquals(str.indexOf("\u6771"), 2);
        Assert.assertEquals(str.indexOf("\uD801\uDC00"), 3);

        Assert.assertEquals(str.charAt(0), '\u0041');
        Assert.assertEquals(str.charAt(1), '\u00DF');
        Assert.assertEquals(str.charAt(2), '\u6771');
        Assert.assertEquals(str.charAt(3), '\uD801');
        Assert.assertEquals(str.charAt(4), '\uDC00');

        Assert.assertEquals(str.codePointAt(0), 0x0041);
        Assert.assertEquals(str.codePointAt(1), 0x00DF);
        Assert.assertEquals(str.codePointAt(2), 0x6771);
        Assert.assertEquals(str.codePointAt(3), 0x10400);
    }

    @Test
    public void text() {
        Text text = new Text("\u0041\u00DF\u6771\uD801\uDC00");
        Assert.assertEquals(text.getLength(), 10);

        Assert.assertEquals(text.find("\u0041"), 0);
        Assert.assertEquals(text.find("\u00DF"), 1);
        Assert.assertEquals(text.find("\u6771"), 3);
        Assert.assertEquals(text.find("\uD801\uDC00"), 6);

        Assert.assertEquals(text.charAt(0), 0x0041);
        Assert.assertEquals(text.charAt(1), 0x00DF);
        Assert.assertEquals(text.charAt(3), 0x6771);
        Assert.assertEquals(text.charAt(6), 0x10400);
    }
这样一比较就很明显了。
1.String的length()方法返回的是char的数量,Text的getLength()方法返回的是字节的数量。
2.String的indexOf()方法返回的是以char为单元的偏移量,Text的find()方法返回的是以字节为单位的偏移量。
3.String的charAt()方法不是返回的整个unicode字符,而是返回的是java中的char字符
4.String的codePointAt()和Text的charAt方法比较类似,不过要注意,前者是按char的偏移量,后者是字节的偏移量

Text的迭代

在Text中对unicode字符的迭代是相当复杂的,因为与unicode所占的字节数有关,不能简单的使用index的增长来确定。首先要把Text对象使用ByteBuffer进行封装,然后再调用Text的静态方法bytesToCodePoint对ByteBuffer进行轮询返回unicode字符的code point。看一下示例代码:
package com.sweetop.styhadoop;

import org.apache.hadoop.io.Text;

import java.nio.ByteBuffer;

/**
 * Created with IntelliJ IDEA.
 * User: lastsweetop
 * Date: 13-7-9
 * Time: 下午5:00
 * To change this template use File | Settings | File Templates.
 */
public class TextIterator {
    public static void main(String[] args) {
        Text text = new Text("\u0041\u00DF\u6771\uD801\udc00");
        ByteBuffer buffer = ByteBuffer.wrap(text.getBytes(), 0, text.getLength());
        int cp;
        while (buffer.hasRemaining() && (cp = Text.bytesToCodePoint(buffer)) != -1) {
            System.out.println(Integer.toHexString(cp));
        }
    }
}

Text的修改

除了NullWritable是不可更改外,其他类型的Writable都是可以修改的。你可以通过Text的set方法去修改去修改重用这个实例。
    @Test
    public void testTextMutability() {
        Text text = new Text("hadoop");
        text.set("pig");
        Assert.assertEquals(text.getLength(), 3);
        Assert.assertEquals(text.getBytes().length, 3);
    }

但要注意的就是,在某些情况下Text的getBytes方法返回的字节数组的长度和Text的getLength方法返回的长度不一致。因此,在调用getBytes()方法的同时最好也调用一下getLength方法,这样你就知道在字节数组里有多少有效的字符。
    @Test
    public void testTextMutability2() {
        Text text = new Text("hadoop");
        text.set(new Text("pig"));
        Assert.assertEquals(text.getLength(),3);
        Assert.assertEquals(text.getBytes().length,6);
    }

BytesWritable类型

ByteWritable类型是一个二进制数组的封装类型,序列化格式是以一个4字节的整数(这点与Text不同,Text是以变长int开头)开始表明字节数组的长度,然后接下来就是数组本身。看下示例:
    @Test
    public void testByteWritableSerilizedFromat() throws IOException {
        BytesWritable bytesWritable=new BytesWritable(new byte[]{3,5});
        byte[] bytes=SerializeUtils.serialize(bytesWritable);
        Assert.assertEquals(StringUtils.byteToHexString(bytes),"000000020305");
    }
和Text一样,ByteWritable也可以通过set方法修改,getLength返回的大小是真实大小,而getBytes返回的大小确不是。
	bytesWritable.setCapacity(11);
        bytesWritable.setSize(4);
        Assert.assertEquals(4,bytesWritable.getLength());
        Assert.assertEquals(11,bytesWritable.getBytes().length);

NullWritable类型

NullWritable是一个非常特殊的Writable类型,序列化不包含任何字符,仅仅相当于个占位符。你在使用mapreduce时,key或者value在无需使用时,可以定义为NullWritable。
package com.sweetop.styhadoop;

import org.apache.hadoop.io.NullWritable;
import org.apache.hadoop.util.StringUtils;

import java.io.IOException;

/**
 * Created with IntelliJ IDEA.
 * User: lastsweetop
 * Date: 13-7-16
 * Time: 下午9:23
 * To change this template use File | Settings | File Templates.
 */
public class TestNullWritable {
    public static void main(String[] args) throws IOException {
        NullWritable nullWritable=NullWritable.get();
        System.out.println(StringUtils.byteToHexString(SerializeUtils.serialize(nullWritable)));
    }
}

ObjectWritable类型

ObjectWritable是其他类型的封装类,包括java原生类型,String,enum,Writable,null等,或者这些类型构成的数组。当你的一个field有多种类型时,ObjectWritable类型的用处就发挥出来了,不过有个不好的地方就是占用的空间太大,即使你存一个字母,因为它需要保存封装前的类型,我们来看瞎示例:
package com.sweetop.styhadoop;

import org.apache.hadoop.io.ObjectWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.util.StringUtils;

import java.io.IOException;

/**
 * Created with IntelliJ IDEA.
 * User: lastsweetop
 * Date: 13-7-17
 * Time: 上午9:14
 * To change this template use File | Settings | File Templates.
 */
public class TestObjectWritable {
    public static void main(String[] args) throws IOException {
        Text text=new Text("\u0041");
        ObjectWritable objectWritable=new ObjectWritable(text);
        System.out.println(StringUtils.byteToHexString(SerializeUtils.serialize(objectWritable)));

    }
}
仅仅是保存一个字母,那么看下它序列化后的结果是什么:
00196f72672e6170616368652e6861646f6f702e696f2e5465787400196f72672e6170616368652e6861646f6f702e696f2e546578740141
太浪费空间了,而且类型一般是已知的,也就那么几个,那么它的代替方法出现,看下一小节

GenericWritable类型

使用GenericWritable时,只需继承于他,并通过重写getTypes方法指定哪些类型需要支持即可,我们看下用法:
package com.sweetop.styhadoop;

import org.apache.hadoop.io.GenericWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.io.Writable;

class MyWritable extends GenericWritable {

    MyWritable(Writable writable) {
        set(writable);
    }

    public static Class<? extends Writable>[] CLASSES=null;

    static {
        CLASSES=  (Class<? extends Writable>[])new Class[]{
                Text.class
        };
    }

    @Override
    protected Class<? extends Writable>[] getTypes() {
        return CLASSES;  //To change body of implemented methods use File | Settings | File Templates.
    }
}
然后输出序列化后的结果
package com.sweetop.styhadoop;

import org.apache.hadoop.io.IntWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.io.VIntWritable;
import org.apache.hadoop.util.StringUtils;

import java.io.IOException;

/**
 * Created with IntelliJ IDEA.
 * User: lastsweetop
 * Date: 13-7-17
 * Time: 上午9:51
 * To change this template use File | Settings | File Templates.
 */
public class TestGenericWritable {

    public static void main(String[] args) throws IOException {
        Text text=new Text("\u0041\u0071");
        MyWritable myWritable=new MyWritable(text);
        System.out.println(StringUtils.byteToHexString(SerializeUtils.serialize(text)));
        System.out.println(StringUtils.byteToHexString(SerializeUtils.serialize(myWritable)));

    }
}
结果是:
024171
00024171
GenericWritable的序列化只是把类型在type数组里的索引放在了前面,这样就比ObjectWritable节省了很多空间,所以推荐大家使用GenericWritable

集合类型的Writable

ArrayWritable和TwoDArrayWritable

ArrayWritable和TwoDArrayWritable分别表示数组和二维数组的Writable类型,指定数组的类型有两种方法,构造方法里设置,或者继承于ArrayWritable,TwoDArrayWritable也是一样。
package com.sweetop.styhadoop;

import org.apache.hadoop.io.ArrayWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.io.Writable;
import org.apache.hadoop.util.StringUtils;

import java.io.IOException;

/**
 * Created with IntelliJ IDEA.
 * User: lastsweetop
 * Date: 13-7-17
 * Time: 上午11:14
 * To change this template use File | Settings | File Templates.
 */
public class TestArrayWritable {
    public static void main(String[] args) throws IOException {
        ArrayWritable arrayWritable=new ArrayWritable(Text.class);
        arrayWritable.set(new Writable[]{new Text("\u0071"),new Text("\u0041")});
        System.out.println(StringUtils.byteToHexString(SerializeUtils.serialize(arrayWritable)));
    }
}
看下输出:
0000000201710141
可知,ArrayWritable以一个整型开始表示数组长度,然后数组里的元素一一排开。
ArrayPrimitiveWritable和上面类似,只是不需要用子类去继承ArrayWritable而已。

MapWritable和SortedMapWritable

MapWritable对应Map,SortedMapWritable对应SortedMap,以4个字节开头,存储集合大小,然后每个元素以一个字节开头存储类型的索引(类似GenericWritable,所以总共的类型总数只能倒127),接着是元素本身,先key后value,这样一对对排开。
这两个Writable以后会用很多,贯穿整个hadoop,这里就不写示例了。

我们注意到没看到set集合和list集合,这个可以代替实现。用MapWritable代替set,SortedMapWritable代替sortedmap,只需将他们的values设置成NullWritable即可,NullWritable不占空间。相同类型构成的list,可以用ArrayWritable代替,不同类型的list可以用GenericWritable实现类型,然后再使用ArrayWritable封装。当然MapWritable一样可以实现list,把key设置为索引,values做list里的元素。

如果我的文章对您有帮助,请用支付宝打赏:



版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/lastsweetop/article/details/9249411

Hadoop序列化与Writable源码分析

序列化的概念      1.序列化(Serialization)是指把结构化对象转化为字节流。      2.反序列化(Deserialization)是序列化的逆过程,即把字节流转回结构化对象 ...
  • lzm1340458776
  • lzm1340458776
  • 2015-01-07 11:24:33
  • 881

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

昨天出去玩了,今天继续。   前面讲了InputFormat,就顺便讲一下Writable的东西吧,本来应当是放在HDFS中的。   当要在进程间传递对象或持久化对象的时候,就需要序列化对象成字节...
  • posa88
  • posa88
  • 2012-08-26 12:12:55
  • 11092

Hadoop Serialization -- hadoop序列化详解 (3)【ObjectWritable,集合Writable以及自定义的Writable】

前瞻:本文介绍ObjectWritable,集合Writable以及自定义的Writable TextPair 回顾: 前面了解到hadoop本身支持java的基本类型的序列化,并且提供相应的包装实...
  • MrCharles
  • MrCharles
  • 2015-12-26 18:40:48
  • 1997

hadoop深入研究:(十二)——自定义Writable

hadoop虽然已经实现了一些非常有用的Writable,而且你可以使用他们的组合做很多事情,但是如果你想构造一些更加复杂的结果,你可以自定义Writable来达到你的目的,我们以注释的方式对自定义W...
  • lastsweetop
  • lastsweetop
  • 2013-07-18 19:19:32
  • 15842

Hadoop 实现Writable接口

MapReduce  里的键值对必须实现可序列化,以支持在网络中传输。 值必须实现 Writable接口,而键因为在Reduce阶段有排序任务,所以必须实现WritableComparable 接口。...
  • OOC_ZC
  • OOC_ZC
  • 2016-01-10 22:30:54
  • 772

[hadoop2.7.1]I/O之Writable的实现类及子接口

Hadoop引入org.apache.hadoop.io.Writable接口,是所有可序列化对象必须实现的接口。 在hadoop2.71.中,它共有6个子接口。 WritableCompar...
  • s060403072
  • s060403072
  • 2015-10-20 14:21:58
  • 1556

Hadoop-2.4.1学习之Writable及其实现

本文学习了Writable及WritableComparable接口,并结合IntWritable和Text类介绍了具体的实现,最后给出了一个自定义的WritableComparable类...
  • sky_walker85
  • sky_walker85
  • 2014-12-23 09:49:18
  • 2928

Hadoop自定义可序列化的类

在hadoop框架中实现自定义类可以被序列化。 [java] view plaincopy package com.rpc.nefu;      ...
  • qingmu0803
  • qingmu0803
  • 2014-09-29 09:31:57
  • 753

Hadoop使用ByteWritable时的一个注意事项

今天使用ByteWritable时候遇到了问题,浪费了好多时间,最后通过查看ByteWritable的源代码才解决这个问题。分享一下,希望能帮助别人节约点时间。 自己写了一个类继承了RecordRea...
  • luoleicn
  • luoleicn
  • 2010-07-05 19:17:00
  • 5838

MapReduce中使用BytesWritable类型时要注意

转载地址:http://blog.csdn.net/xcytec/article/details/20909151 在MR程序中出现错误,数据读取的内容和原文件不一致,经过定位是因为使用By...
  • gyflyx
  • gyflyx
  • 2014-09-19 13:54:12
  • 2849
收藏助手
不良信息举报
您举报文章:hadoop深入研究:(十一)——序列化与Writable实现
举报原因:
原因补充:

(最多只允许输入30个字)