序列化与Writable实现

简介

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

Java原来类型

除char类型以外,所有的原生类型都有对应的Writable类,并且大部分通过get和set方法可以操作他们的值。

IntWritable和LongWritable还有对应的变长VIntWritable和VLongWritable类

固定长度还是变长的选用类似于数据库中的char或者vchar,在这里就不再赘述了。

Text类型

Text类型使用变长int型存储长度,所以Text类型的最大存储为2G

Text类型采用标准的UTF-8编码,所以与其他文本工具可以非常好的交互,但要注意的是,这样的话和java的String类型差别就很大了。

    检索方式的不同 

Text的CharAt返回的是一个整形,即UTF-8编码后的数字,而不是像String那样的unicode编码的char类型。

[java] view plain copy

  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方法,下标从0开始

[java] view plain copy

  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的不同

当utf-8编码后的字节大于两个时,Text和String的区别就会更清晰,因为String是按照Unicode的char计算,而Text是按照字节计算

我们来看下1到4个字节的不同的Unicode字符

4个Unicode分别占用1到4个字节,u+10400在java的Unicode字符中占用两个char,前三个字符分别占用1个char,我们通过代码来看下String和Text的不同。

[java] view plain copy

  1. @Test
  2. public void string() throws UnsupportedEncodingException {
  3. String str = "\u0041\u00DF\u6771\uD801\uDC00";
  4. Assert.assertEquals(str.length(), 5);
  5. Assert.assertEquals(str.getBytes("UTF-8").length, 10);
  6. Assert.assertEquals(str.indexOf("\u0041"), 0);
  7. Assert.assertEquals(str.indexOf("\u00DF"), 1);
  8. Assert.assertEquals(str.indexOf("\u6771"), 2);
  9. Assert.assertEquals(str.indexOf("\uD801\uDC00"), 3);
  10. Assert.assertEquals(str.charAt(0), ‘\u0041‘);
  11. Assert.assertEquals(str.charAt(1), ‘\u00DF‘);
  12. Assert.assertEquals(str.charAt(2), ‘\u6771‘);
  13. Assert.assertEquals(str.charAt(3), ‘\uD801‘);
  14. Assert.assertEquals(str.charAt(4), ‘\uDC00‘);
  15. Assert.assertEquals(str.codePointAt(0), 0x0041);
  16. Assert.assertEquals(str.codePointAt(1), 0x00DF);
  17. Assert.assertEquals(str.codePointAt(2), 0x6771);
  18. Assert.assertEquals(str.codePointAt(3), 0x10400);
  19. }
  20. @Test
  21. public void text() {
  22. Text text = new Text("\u0041\u00DF\u6771\uD801\uDC00");
  23. Assert.assertEquals(text.getLength(), 10);
  24. Assert.assertEquals(text.find("\u0041"), 0);
  25. Assert.assertEquals(text.find("\u00DF"), 1);
  26. Assert.assertEquals(text.find("\u6771"), 3);
  27. Assert.assertEquals(text.find("\uD801\uDC00"), 6);
  28. Assert.assertEquals(text.charAt(0), 0x0041);
  29. Assert.assertEquals(text.charAt(1), 0x00DF);
  30. Assert.assertEquals(text.charAt(3), 0x6771);
  31. Assert.assertEquals(text.charAt(6), 0x10400);
  32. }

这样一比较久很明显了。

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。看一下示例代码:

[java] view plain copy

  1. package com.sweetop.styhadoop;
  2. import org.apache.hadoop.io.Text;
  3. import java.nio.ByteBuffer;
  4. /**
  5. * Created with IntelliJ IDEA.
  6. * User: lastsweetop
  7. * Date: 13-7-9
  8. * Time: 下午5:00
  9. * To change this template use File | Settings | File Templates.
  10. */
  11. public class TextIterator {
  12. public static void main(String[] args) {
  13. Text text = new Text("\u0041\u00DF\u6771\uD801\udc00");
  14. ByteBuffer buffer = ByteBuffer.wrap(text.getBytes(), 0, text.getLength());
  15. int cp;
  16. while (buffer.hasRemaining() && (cp = Text.bytesToCodePoint(buffer)) != -1) {
  17. System.out.println(Integer.toHexString(cp));
  18. }
  19. }
  20. }

Text的修改

除了NullWritable是不可以更改之外,其他类型的Writable都是可以修改的,你可以通过Text的set方法进行修改重用这个实例。

[java] view plain copy

  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的取值比较特殊,使用XXX.toString()方法,其他大部分都提供了set和get方法。

BytesWritable类型

BytesWritable类型是一个二进制数组的封装类型,序列化格式是以一个4字节的整数(这点与Text不同,Text是以变长int开头)开始表明字节数组的长度,然后接下来才是数组本身,看下面的示例:

[java] view plain copy

  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返回的大小却不是

[java] view plain copy

  1. 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。

[java] view plain copy

  1. package com.sweetop.styhadoop;
  2. import org.apache.hadoop.io.NullWritable;
  3. import org.apache.hadoop.util.StringUtils;
  4. import java.io.IOException;
  5. /**
  6. * Created with IntelliJ IDEA.
  7. * User: lastsweetop
  8. * Date: 13-7-16
  9. * Time: 下午9:23
  10. * To change this template use File | Settings | File Templates.
  11. */
  12. public class TestNullWritable {
  13. public static void main(String[] args) throws IOException {
  14. NullWritable nullWritable=NullWritable.get();
  15. System.out.println(StringUtils.byteToHexString(SerializeUtils.serialize(nullWritable)));
  16. }
  17. }

注:NullWritable是通过NullWritable.get()方法获取的。

ObjectWritable类型

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

[java] view plain copy

  1. package com.sweetop.styhadoop;
  2. import org.apache.hadoop.io.ObjectWritable;
  3. import org.apache.hadoop.io.Text;
  4. import org.apache.hadoop.util.StringUtils;
  5. import java.io.IOException;
  6. /**
  7. * Created with IntelliJ IDEA.
  8. * User: lastsweetop
  9. * Date: 13-7-17
  10. * Time: 上午9:14
  11. * To change this template use File | Settings | File Templates.
  12. */
  13. public class TestObjectWritable {
  14. public static void main(String[] args) throws IOException {
  15. Text text=new Text("\u0041");
  16. ObjectWritable objectWritable=new ObjectWritable(text);
  17. System.out.println(StringUtils.byteToHexString(SerializeUtils.serialize(objectWritable)));
  18. }
  19. }

我们仅仅保存了一个字母,但是序列化之后的结果居然是:

[java] view plain copy

  1. 00196f72672e6170616368652e6861646f6f702e696f2e5465787400196f72672e6170616368652e6861646f6f702e696f2e546578740141

太浪费空间了。不建议使用,建议使用GenericWritable类型

GenericWritable类型

使用GenericWritable时,只需要继承于它,并通过重写getTypes方法制定哪些类型需要支持即可,我们看下方法:

[java] view plain copy

  1. package com.sweetop.styhadoop;
  2. import org.apache.hadoop.io.GenericWritable;
  3. import org.apache.hadoop.io.Text;
  4. import org.apache.hadoop.io.Writable;
  5. class MyWritable extends GenericWritable {
  6. MyWritable(Writable writable) {
  7. set(writable);
  8. }
  9. public static Class<? extends Writable>[] CLASSES=null;
  10. static {
  11. CLASSES=  (Class<? extends Writable>[])new Class[]{
  12. Text.class
  13. };
  14. }
  15. @Override
  16. protected Class<? extends Writable>[] getTypes() {
  17. return CLASSES;  //To change body of implemented methods use File | Settings | File Templates.
  18. }
  19. }

测试类:

[java] view plain copy

  1. package com.sweetop.styhadoop;
  2. import org.apache.hadoop.io.IntWritable;
  3. import org.apache.hadoop.io.Text;
  4. import org.apache.hadoop.io.VIntWritable;
  5. import org.apache.hadoop.util.StringUtils;
  6. import java.io.IOException;
  7. /**
  8. * Created with IntelliJ IDEA.
  9. * User: lastsweetop
  10. * Date: 13-7-17
  11. * Time: 上午9:51
  12. * To change this template use File | Settings | File Templates.
  13. */
  14. public class TestGenericWritable {
  15. public static void main(String[] args) throws IOException {
  16. Text text=new Text("\u0041\u0071");
  17. MyWritable myWritable=new MyWritable(text);
  18. System.out.println(StringUtils.byteToHexString(SerializeUtils.serialize(text)));
  19. System.out.println(StringUtils.byteToHexString(SerializeUtils.serialize(myWritable)));
  20. }
  21. }

结果是:

[html] view plain copy

  1. 024171
  2. 00024171

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

集合类型的Writable

ArrayWritable和TwoDArrayWritable

ArrayWritable和TwoDArrayWritable分别表示数组和二维数组的Writable类型,指定数组的类型有两种方式:通过构造方法设置;继承于ArrayWritable,TwoDArrayWritable也是一样的。

[java] view plain copy

  1. package com.sweetop.styhadoop;
  2. import org.apache.hadoop.io.ArrayWritable;
  3. import org.apache.hadoop.io.Text;
  4. import org.apache.hadoop.io.Writable;
  5. import org.apache.hadoop.util.StringUtils;
  6. import java.io.IOException;
  7. /**
  8. * Created with IntelliJ IDEA.
  9. * User: lastsweetop
  10. * Date: 13-7-17
  11. * Time: 上午11:14
  12. * To change this template use File | Settings | File Templates.
  13. */
  14. public class TestArrayWritable {
  15. public static void main(String[] args) throws IOException {
  16. ArrayWritable arrayWritable=new ArrayWritable(Text.class);
  17. arrayWritable.set(new Writable[]{new Text("\u0071"),new Text("\u0041")});
  18. System.out.println(StringUtils.byteToHexString(SerializeUtils.serialize(arrayWritable)));
  19. }
  20. }

看下输出:

[html] view plain copy

  1. 0000000201710141

可知,ArrayWritable以一个整数开始表示数组长度,然后数组里的元素一一排开。

ArrayPrimitiveWritable和上面类似,只是不需要用子类去继承ArrayWritable而已。

MapWritable和SortMapWritable

MapWritable对应Map,SortedMapWritable对应SortedMap,以4个字节开头,存储集合大小,然后每个元素以一个字节开头存储类型的索引。

这里没有看到Set和List集合,这个是可以代替实现的,用MapWritable代替Set,SortMapWritable代替SortedMap,只需要将他们的values设置成NullWritable即可,NullWritable不占用空间。相同类型的List,可以用ArrayWritable代替,不同类型的List可以用GenericWritable类型代替,然后再使用ArrayWritable封装。当然MapWritable一样可以实现List,把Key设置为索引,values做成list里的元素。

注:还有一些类型比如:DoubleWritable等比较简单,就不再赘述。

文章来源:http://blog.csdn.net/lastsweetop/article/details/9249411
代码下载:https://github.com/lastsweetop/styhadoop

时间: 2025-01-05 20:25:07

序列化与Writable实现的相关文章

Hadoop阅读笔记(六)——洞悉Hadoop序列化机制Writable

酒,是个好东西,前提要适量.今天参加了公司的年会,主题就是吃.喝.吹,除了那些天生话唠外,大部分人需要加点酒来作催化剂,让一个平时沉默寡言的码农也能成为一个喷子!在大家推杯换盏之际,难免一些画面浮现脑海,有郁闷抓狂的,有出成果喜极而涕的,有不知前途在哪儿的迷茫与不安……总的来说,近一年来,不白活,不虚度,感触良多,不是一言两语能说得清道的明的,有时间可以做个总结,下面还是言归正传谈技术吧. 上篇在了解了Hadoop的目录和源码结构后,说好的要啃源码的,那就得啃.也感谢一直以来关注我.支持我的网友

Hadoop序列化与Writable接口(一)

Hadoop序列化与Writable接口(一) 序列化 序列化(serialization)是指将结构化的对象转化为字节流,以便在网络上传输或者写入到硬盘进行永久存储:相对的反序列化(deserialization)是指将字节流转回到结构化对象的过程. 在分布式系统中进程将对象序列化为字节流,通过网络传输到另一进程,另一进程接收到字节流,通过反序列化转回到结构化对象,以达到进程间通信.在Hadoop中,Mapper,Combiner,Reducer等阶段之间的通信都需要使用序列化与反序列化技术.

Hadoop序列化与Writable源码分析

序列化的概念     1.序列化(Serialization)是指把结构化对象转化为字节流.     2.反序列化(Deserialization)是序列化的逆过程,即把字节流转回结构化对象 Hadoop序列化的特点     1.序列化格式特点         ——紧凑:高效使用 存储空间         ——快速:读写数据的额外开销小         ——可扩展:可透明地读取老格式的数据         ——互操作:支持多语言的交互注:hadoop1.x的序列化仅满足了紧凑和快速的特点. 2.

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

原文链接 http://blog.csdn.net/lastsweetop/article/details/9249411 所有源码在github上,https://github.com/lastsweetop/styhadoop 简介 在hadoop中,Writable的实现类是个庞大的家族,我们在这里简单的介绍一下常用来做序列化的一部分. java原生类型 除char类型以外,所有的原生类型都有对应的Writable类,并且通过get和set方法可以他们的值. IntWritable和Lon

hadoop中的序列化与Writable接口

本文地址:http://www.cnblogs.com/archimedes/p/hadoop-writable-interface.html,转载请注明源地址. 简介 序列化和反序列化就是结构化对象和字节流之间的转换,主要用在内部进程的通讯和持久化存储方面. 通讯格式需求 hadoop在节点间的内部通讯使用的是RPC,RPC协议把消息翻译成二进制字节流发送到远程节点,远程节点再通过反序列化把二进制流转成原始的信息.RPC的序列化需要实现以下几点: 1.压缩,可以起到压缩的效果,占用的宽带资源要

hadoop中的序列化与Writable类

本文地址:http://www.cnblogs.com/archimedes/p/hadoop-writable-class.html,转载请注明源地址. hadoop中自带的org.apache.hadoop.io包中有广泛的writable类可供选择,它们形成下图所示的层次结构: java基本类型的Writable封装器 Writable类对java基本类型提供封装,short和char除外,所有的封装包含get()和set()两个方法用于读取或设置封装的值 java基本类型的Writabl

Hadoop序列化学习笔记(一)

什么是序列化? 序列化(serialization),是指将结构化对象转化为字节流,以便在网络上传输或写入磁盘进行永久存储. 反序列化(deserialization),是指将字节流重新转换为结构化对象. Hadoop使用哪种序列化框架? Hadoop使用自己的序列化格式Writable,除开Writable,Hadoop也支持Avro序列化框架. Writable格式紧凑,速度快.,但很难使用除开Java之外的语言对它进行扩展. Avro支持跨语言使用,意思是,可以用Python写入文件,而用

Hadoop权威指南读书笔记

本书中提到的Hadoop项目简述 Common:一组分布式文件系统和通用I/O的组件与接口(序列化.javaRPC和持久化数据结构). Avro:一种支持高效.跨语言的RPC以及永久存储数据的序列化系统. MapReduce:分布式数据处理模型和执行环境,运行于大型商业集群. HDFS:分布式文件系统,运行于大型商用机集群. Pig:一种数据流语言和运行环境,用以检索非常大的数据集.Pig运行在MapReduce和HDFS的集群上. Hive:一个分布式.按列存储的数据仓库.Hive管理HDFS

大数据时代之hadoop(四):hadoop 分布式文件系统(HDFS)

分布式文件系统即是网络中多台计算机组合在一起提供一个统一存储及管理的系统. Hadoop提供了一个文件系统接口和多个分布式文件系统实现,其中比较重要的就是HDFS(Hadoop Distributed Filesystem)了.Hadoop是一个综合性的文件系统抽象,因此它也可以集成其他文件系统的实现,如本地文件系统和Amazon S3系统及淘宝 TFS等. 1.概念模型 HDFS以流式数据访问模式来存储超大文件,运行于商业硬件集群上. HDFS实现下来,分为两类节点,一个是namenode及s