zoukankan      html  css  js  c++  java
  • 【转】hadoop深入研究:(十一)——序列化与Writable实现

    原文链接 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类型。
    1. @Test  
    2. public void testTextIndex(){  
    3.     Text text=new Text("hadoop");  
    4.     Assert.assertEquals(text.getLength(), 6);  
    5.     Assert.assertEquals(text.getBytes().length, 6);  
    6.     Assert.assertEquals(text.charAt(2),(int)'d');  
    7.     Assert.assertEquals("Out of bounds",text.charAt(100),-1);  
    8. }  
    Text还有个find方法,类似String里indexOf方法
    1. @Test  
    2. public void testTextFind() {  
    3.     Text text = new Text("hadoop");  
    4.     Assert.assertEquals("find a substring",text.find("do"),2);  
    5.     Assert.assertEquals("Find first 'o'",text.find("o"),3);  
    6.     Assert.assertEquals("Find 'o' from position 4 or later",text.find("o",4),4);  
    7.     Assert.assertEquals("No match",text.find("pig"),-1);  
    8. }  

    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的不同
    1. @Test  
    2.    public void string() throws UnsupportedEncodingException {  
    3.        String str = "u0041u00DFu6771uD801uDC00";  
    4.        Assert.assertEquals(str.length(), 5);  
    5.        Assert.assertEquals(str.getBytes("UTF-8").length, 10);  
    6.   
    7.        Assert.assertEquals(str.indexOf("u0041"), 0);  
    8.        Assert.assertEquals(str.indexOf("u00DF"), 1);  
    9.        Assert.assertEquals(str.indexOf("u6771"), 2);  
    10.        Assert.assertEquals(str.indexOf("uD801uDC00"), 3);  
    11.   
    12.        Assert.assertEquals(str.charAt(0), 'u0041');  
    13.        Assert.assertEquals(str.charAt(1), 'u00DF');  
    14.        Assert.assertEquals(str.charAt(2), 'u6771');  
    15.        Assert.assertEquals(str.charAt(3), 'uD801');  
    16.        Assert.assertEquals(str.charAt(4), 'uDC00');  
    17.   
    18.        Assert.assertEquals(str.codePointAt(0), 0x0041);  
    19.        Assert.assertEquals(str.codePointAt(1), 0x00DF);  
    20.        Assert.assertEquals(str.codePointAt(2), 0x6771);  
    21.        Assert.assertEquals(str.codePointAt(3), 0x10400);  
    22.    }  
    23.   
    24.    @Test  
    25.    public void text() {  
    26.        Text text = new Text("u0041u00DFu6771uD801uDC00");  
    27.        Assert.assertEquals(text.getLength(), 10);  
    28.   
    29.        Assert.assertEquals(text.find("u0041"), 0);  
    30.        Assert.assertEquals(text.find("u00DF"), 1);  
    31.        Assert.assertEquals(text.find("u6771"), 3);  
    32.        Assert.assertEquals(text.find("uD801uDC00"), 6);  
    33.   
    34.        Assert.assertEquals(text.charAt(0), 0x0041);  
    35.        Assert.assertEquals(text.charAt(1), 0x00DF);  
    36.        Assert.assertEquals(text.charAt(3), 0x6771);  
    37.        Assert.assertEquals(text.charAt(6), 0x10400);  
    38.    }  
    这样一比较就很明显了。
    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。看一下示例代码:
    1. package com.sweetop.styhadoop;  
    2.   
    3. import org.apache.hadoop.io.Text;  
    4.   
    5. import java.nio.ByteBuffer;  
    6.   
    7. /** 
    8.  * Created with IntelliJ IDEA. 
    9.  * User: lastsweetop 
    10.  * Date: 13-7-9 
    11.  * Time: 下午5:00 
    12.  * To change this template use File | Settings | File Templates. 
    13.  */  
    14. public class TextIterator {  
    15.     public static void main(String[] args) {  
    16.         Text text = new Text("u0041u00DFu6771uD801udc00");  
    17.         ByteBuffer buffer = ByteBuffer.wrap(text.getBytes(), 0, text.getLength());  
    18.         int cp;  
    19.         while (buffer.hasRemaining() && (cp = Text.bytesToCodePoint(buffer)) != -1) {  
    20.             System.out.println(Integer.toHexString(cp));  
    21.         }  
    22.     }  
    23. }  

    Text的修改

    除了NullWritable是不可更改外,其他类型的Writable都是可以修改的。你可以通过Text的set方法去修改去修改重用这个实例。
    1. @Test  
    2. public void testTextMutability() {  
    3.     Text text = new Text("hadoop");  
    4.     text.set("pig");  
    5.     Assert.assertEquals(text.getLength(), 3);  
    6.     Assert.assertEquals(text.getBytes().length, 3);  
    7. }  
    但要注意的就是,在某些情况下Text的getBytes方法返回的字节数组的长度和Text的getLength方法返回的长度不一致。因此,在调用getBytes()方法的同时最好也调用一下getLength方法,这样你就知道在字节数组里有多少有效的字符。
    1. @Test  
    2. public void testTextMutability2() {  
    3.     Text text = new Text("hadoop");  
    4.     text.set(new Text("pig"));  
    5.     Assert.assertEquals(text.getLength(),3);  
    6.     Assert.assertEquals(text.getBytes().length,6);  
    7. }  

    BytesWritable类型

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

    NullWritable类型

    NullWritable是一个非常特殊的Writable类型,序列化不包含任何字符,仅仅相当于个占位符。你在使用mapreduce时,key或者value在无需使用时,可以定义为NullWritable。
    1. package com.sweetop.styhadoop;  
    2.   
    3. import org.apache.hadoop.io.NullWritable;  
    4. import org.apache.hadoop.util.StringUtils;  
    5.   
    6. import java.io.IOException;  
    7.   
    8. /** 
    9.  * Created with IntelliJ IDEA. 
    10.  * User: lastsweetop 
    11.  * Date: 13-7-16 
    12.  * Time: 下午9:23 
    13.  * To change this template use File | Settings | File Templates. 
    14.  */  
    15. public class TestNullWritable {  
    16.     public static void main(String[] args) throws IOException {  
    17.         NullWritable nullWritable=NullWritable.get();  
    18.         System.out.println(StringUtils.byteToHexString(SerializeUtils.serialize(nullWritable)));  
    19.     }  
    20. }  

    ObjectWritable类型

    ObjectWritable是其他类型的封装类,包括java原生类型,String,enum,Writable,null等,或者这些类型构成的数组。当你的一个field有多种类型时,ObjectWritable类型的用处就发挥出来了,不过有个不好的地方就是占用的空间太大,即使你存一个字母,因为它需要保存封装前的类型,我们来看瞎示例:
    1. package com.sweetop.styhadoop;  
    2.   
    3. import org.apache.hadoop.io.ObjectWritable;  
    4. import org.apache.hadoop.io.Text;  
    5. import org.apache.hadoop.util.StringUtils;  
    6.   
    7. import java.io.IOException;  
    8.   
    9. /** 
    10.  * Created with IntelliJ IDEA. 
    11.  * User: lastsweetop 
    12.  * Date: 13-7-17 
    13.  * Time: 上午9:14 
    14.  * To change this template use File | Settings | File Templates. 
    15.  */  
    16. public class TestObjectWritable {  
    17.     public static void main(String[] args) throws IOException {  
    18.         Text text=new Text("u0041");  
    19.         ObjectWritable objectWritable=new ObjectWritable(text);  
    20.         System.out.println(StringUtils.byteToHexString(SerializeUtils.serialize(objectWritable)));  
    21.   
    22.     }  
    23. }  
    仅仅是保存一个字母,那么看下它序列化后的结果是什么:
    1. 00196f72672e6170616368652e6861646f6f702e696f2e5465787400196f72672e6170616368652e6861646f6f702e696f2e546578740141  
    太浪费空间了,而且类型一般是已知的,也就那么几个,那么它的代替方法出现,看下一小节

    GenericWritable类型

    使用GenericWritable时,只需继承于他,并通过重写getTypes方法指定哪些类型需要支持即可,我们看下用法:
    1. package com.sweetop.styhadoop;  
    2.   
    3. import org.apache.hadoop.io.GenericWritable;  
    4. import org.apache.hadoop.io.Text;  
    5. import org.apache.hadoop.io.Writable;  
    6.   
    7. class MyWritable extends GenericWritable {  
    8.   
    9.     MyWritable(Writable writable) {  
    10.         set(writable);  
    11.     }  
    12.   
    13.     public static Class<? extends Writable>[] CLASSES=null;  
    14.   
    15.     static {  
    16.         CLASSES=  (Class<? extends Writable>[])new Class[]{  
    17.                 Text.class  
    18.         };  
    19.     }  
    20.   
    21.     @Override  
    22.     protected Class<? extends Writable>[] getTypes() {  
    23.         return CLASSES;  //To change body of implemented methods use File | Settings | File Templates.  
    24.     }  
    25. }  
    然后输出序列化后的结果
    1. package com.sweetop.styhadoop;  
    2.   
    3. import org.apache.hadoop.io.IntWritable;  
    4. import org.apache.hadoop.io.Text;  
    5. import org.apache.hadoop.io.VIntWritable;  
    6. import org.apache.hadoop.util.StringUtils;  
    7.   
    8. import java.io.IOException;  
    9.   
    10. /** 
    11.  * Created with IntelliJ IDEA. 
    12.  * User: lastsweetop 
    13.  * Date: 13-7-17 
    14.  * Time: 上午9:51 
    15.  * To change this template use File | Settings | File Templates. 
    16.  */  
    17. public class TestGenericWritable {  
    18.   
    19.     public static void main(String[] args) throws IOException {  
    20.         Text text=new Text("u0041u0071");  
    21.         MyWritable myWritable=new MyWritable(text);  
    22.         System.out.println(StringUtils.byteToHexString(SerializeUtils.serialize(text)));  
    23.         System.out.println(StringUtils.byteToHexString(SerializeUtils.serialize(myWritable)));  
    24.   
    25.     }  
    26. }  
    结果是:
    1. 024171  
    2. 00024171  
    GenericWritable的序列化只是把类型在type数组里的索引放在了前面,这样就比ObjectWritable节省了很多空间,所以推荐大家使用GenericWritable

    集合类型的Writable

    ArrayWritable和TwoDArrayWritable

    ArrayWritable和TwoDArrayWritable分别表示数组和二维数组的Writable类型,指定数组的类型有两种方法,构造方法里设置,或者继承于ArrayWritable,TwoDArrayWritable也是一样。
    1. package com.sweetop.styhadoop;  
    2.   
    3. import org.apache.hadoop.io.ArrayWritable;  
    4. import org.apache.hadoop.io.Text;  
    5. import org.apache.hadoop.io.Writable;  
    6. import org.apache.hadoop.util.StringUtils;  
    7.   
    8. import java.io.IOException;  
    9.   
    10. /** 
    11.  * Created with IntelliJ IDEA. 
    12.  * User: lastsweetop 
    13.  * Date: 13-7-17 
    14.  * Time: 上午11:14 
    15.  * To change this template use File | Settings | File Templates. 
    16.  */  
    17. public class TestArrayWritable {  
    18.     public static void main(String[] args) throws IOException {  
    19.         ArrayWritable arrayWritable=new ArrayWritable(Text.class);  
    20.         arrayWritable.set(new Writable[]{new Text("u0071"),new Text("u0041")});  
    21.         System.out.println(StringUtils.byteToHexString(SerializeUtils.serialize(arrayWritable)));  
    22.     }  
    23. }  
    看下输出:
    1. 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里的元素。
  • 相关阅读:
    SVN日常使用
    zabbix安装
    shell日常脚本(centos6)
    mysql故障记录
    PHP商品秒杀功能实现思路分析
    Redis
    PHP 实现实时通信一般有两种方式
    FTP DNS SMTP POP3 HTTP HTTPS DHCP DNS SNMP Telnet 端口号
    TCP/UDP/HTTP的区别和联系
    TCP 和 UDP 的区别
  • 原文地址:https://www.cnblogs.com/ihongyan/p/5137275.html
Copyright © 2011-2022 走看看