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;
-
-
-
-
-
-
-
-
- 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开头)开始表明字节数组的长度,然后接下来就是数组本身。看下示例:
- <span style="white-space:pre"> </span>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;
-
-
-
-
-
-
-
-
- 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;
-
-
-
-
-
-
-
-
- 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;
- }
- }
然后输出序列化后的结果
- 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;
-
-
-
-
-
-
-
-
- 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)));
-
- }
- }
结果是:
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;
-
-
-
-
-
-
-
-
- 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)));
- }
- }
看下输出:
可知,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里的元素。