Hadoop序列化與Writable接口(二)

上一篇文章Hadoop序列化與Writable接口(一)介紹了Hadoop序列化,Hadoop Writable接口以及如何定製本身的Writable類,在本文中咱們繼續Hadoop Writable類的介紹,這一次咱們關注的是Writable實例序列化以後佔用的字節長度,以及Writable實例序列化以後的字節序列的構成。 html

爲何要考慮Writable類的字節長度

大數據程序還須要考慮序列化對象佔用磁盤空間的大小嗎?也許你會認爲大數據不是就是數據量很大嗎,那磁盤空間必定是足夠足夠的大,一個序列化對象僅僅佔用幾個到幾十個字節的空間,相對磁盤空間來講,固然是不須要考慮太多;若是你的磁盤空間不夠大,仍是不要玩大數據的好。 java

上面的觀點沒有什麼問題,大數據應用天然須要足夠的磁盤空間,可是可以儘可能的考慮到不一樣Writable類佔用磁盤空間的大小,高效的利用磁盤空間也未必就是沒有必要的,選擇適當的Writable類的另外一個做用是經過減小Writable實例的字節數,可加快數據的讀取和減小網絡的數據傳輸。 git

Writable類佔用的字節長度

下面的表格顯示的是Hadoop對Java基本類型包裝後相應的Writable類佔用的字節長度: github

Java基本類型 Writable實現 序列化後字節數 (bytes)
boolean BooleanWritable 1
byte ByteWritable 1
short ShortWritable 2
int IntWritable 4
  VIntWritable 1–5
float FloatWritable 4
long LongWritable 8
  VLongWritable 1–9
double DoubleWritable 8

不一樣的Writable類序列化後佔用的字數長度是不同的,須要綜合考慮應用中數據特徵選擇合適的類型。對於整數類型有兩種Writable類型能夠選擇,一種是定長(fixed-length)Writable類型,IntWritable和LongWritable;另外一種是變長(variable-length)Writable類型,VIntWritable和VLongWritable。定長類型顧名思義使用固定長度的字節數表示,好比一個IntWritable類型使用4個長度的字節表示一個int;變長類型則根據數值的大小使用相應的字節長度表示,當數值在-112~127之間時使用1個字節表示,在-112~127範圍以外的數值使用頭一個字節表示該數值的正負符號以及字節長度(zero-compressed encoded integer)。 apache

定長的Writable類型適合數值均勻分佈的情形,而變長的Writable類型適合數值分佈不均勻的情形,通常狀況下變長的Writable類型更節省空間,由於大多數狀況下數值是不均勻的,對於整數類型的Writable選擇,我建議: api

1. 除非對數據的均勻分佈頗有把握,不然使用變長Writable類型 數組

2. 除非數據的取值區間肯定在int範圍以內,不然爲了程序的可擴展性,請選擇VLongWritable類型 網絡

整型Writable的字節序列

下面將以實例的方式演示Hadoop整型Writable對象佔用的字節長度以及Writable對象序列化以後字節序列的結構,特別是變長整型Writable實例,請看下面的代碼和程序輸出: oracle


package com.yoyzhou.example;

import java.io.*;
import org.apache.hadoop.io.*;
import org.apache.hadoop.util.StringUtils;

/**
 * Demos per how many bytes per each built-in Writable type takes and what does
 * their bytes sequences look like
 * 
 * @author yoyzhou
 * 
 */

public class WritableBytesLengthDemo {

	public static void main(String[] args) throws IOException {

		// one billion representations by different Writable object
		IntWritable int_b = new IntWritable(1000000000);
		LongWritable long_b = new LongWritable(1000000000);
		VIntWritable vint_b = new VIntWritable(1000000000);
		VLongWritable vlong_b = new VLongWritable(1000000000);

		// serialize writable object to byte array
		byte[] bs_int_b = serialize(int_b);
		byte[] bs_long_b = serialize(long_b);
		byte[] bs_vint_b = serialize(vint_b);
		byte[] bs_vlong_b = serialize(vlong_b);

		// print byte array in hex string and their length
		String hex = StringUtils.byteToHexString(bs_int_b);
		formatPrint("IntWritable", "1,000,000,000",hex, bs_int_b.length);

		hex = StringUtils.byteToHexString(bs_long_b);
		formatPrint("LongWritable", "1,000,000,000",hex, bs_long_b.length);

		hex = StringUtils.byteToHexString(bs_vint_b);
		formatPrint("VIntWritable", "1,000,000,000",hex, bs_vint_b.length);

		hex = StringUtils.byteToHexString(bs_vlong_b);
		formatPrint("VLongWritable", "1,000,000,000", hex, bs_vlong_b.length);
		
		
	}

	private static void formatPrint(String type, String param, String hex, int length) {

		String format = "%1$-50s %2$-16s with length: %3$2d%n";
		System.out.format(format, "Byte array per " + type
				+ "("+ param +") is:", hex, length);

	}

	/**
	 * Utility method to serialize Writable object, return byte array
	 * representing the Writable object
	 * 
	 * */
	public static byte[] serialize(Writable writable) throws IOException {

		ByteArrayOutputStream out = new ByteArrayOutputStream();
		DataOutputStream dataOut = new DataOutputStream(out);
		writable.write(dataOut);
		dataOut.close();

		return out.toByteArray();

	}

	/**
	 * Utility method to deserialize input byte array, return Writable object
	 * 
	 * */
	public static Writable deserialize(Writable writable, byte[] bytes)
			throws IOException {

		ByteArrayInputStream in = new ByteArrayInputStream(bytes);
		DataInputStream dataIn = new DataInputStream(in);
		writable.readFields(dataIn);

		dataIn.close();
		return writable;

	}
}





程序輸出:

Byte array per IntWritable(1,000,000,000) is: \ ide

3b9aca00 with length: 4 Byte array per LongWritable(1,000,000,000) is: \ 000000003b9aca00 with length: 8 Byte array per VIntWritable(1,000,000,000) is: \ 8c3b9aca00 with length: 5 Byte array per VLongWritable(1,000,000,000) is:\ 8c3b9aca00 with length: 5



從上面的輸出咱們能夠看出:

+ 對1,000,000,000的表示不一樣的Writable佔用了不一樣字節長度

+ 變長Writable類型並不老是比定長類型更加節省空間,當IntWritable佔用4個字節、LongWritable佔用8個字節時,相應的變長Writable須要一個額外的字節來存放正負信息和字節長度。因此回到前面的整數類型選擇的問題上,選擇出最合適的整數Writable類型,咱們應該對數值的整體分佈有必定的認識

Text的字節序列

能夠簡單的認爲Text類是java.lang.String的Writable類型,可是要注意的是Text類對於Unicode字符采用的是UTF-8編碼,而不是使用Java Character類的UTF-16編碼。

Java Character類採用遵循Unicode Standard version 4的UTF-16編碼[1],每一個字符采用定長的16位(兩個字節)進行編碼,對於代碼點高於Basic Multilingual Plane(BMP,代碼點U+0000~U+FFFF)的增補字符,採用兩個代理字符進行表示。

Text類採用的UTF-8編碼,使用變長的1~4個字節對字符進行編碼。對於ASCII字符只使用1個字節,而對於High ASCII和多字節字符使用2~4個字節表示,我想Hadoop在設計時選擇使用UTF-8而不是String的UTF-16就是基於上面的緣由,爲了節省字節長度/空間的考慮。

因爲Text採用的是UTF-8編碼,因此Text類沒有提供String那樣多的操做,而且在操做Text對象時,好比Indexing和Iteration,必定要注意這個區別,不過咱們建議在進行Text操做時,若是可能能夠將Text對象先轉換成String,再進行操做。

Text類的字節序列表示爲一個VIntWritable + UTF-8字節流,VIntWritable爲整個Text的字符長度,UTF-8字節數組爲真正的Text字節流。具體請看下面的代碼片斷:


...//omitted per conciseness
Text myText = new Text("my text");
byte[] text_bs = serialize(myText);
hex = StringUtils.byteToHexString(text_bs);
formatPrint("Text", "\"my text\"", hex, text_bs.length);
		
Text myText2 = new Text("個人文本");
byte[] text2_bs = serialize(myText2);
hex = StringUtils.byteToHexString(text2_bs);
formatPrint("Text", "\"個人文本\"", hex, text2_bs.length);
...



Byte array per Text("my text") is: \
 	076d792074657874 with length:  8

Byte array per Text("個人文本") is: \
0ce68891e79a84e69687e69cac with length: 13


在上面的輸出中,首個字節表明的該段Text/文本的長度,在UTF-8編碼下「my text」佔用的字節長度爲7個字節(07),而中文「個人文本」的字節長度是12個字節(0c)。

定製Writable類的字節序列

本節中咱們將使用上篇文章中的MyWritable類進行說明,回顧一下,MyWritable是一個由兩個VLongWritable類構成的定製化Writable類型。


程序輸出:

Byte array per MyWritable(1000, 1000000000) is: \
8e03e88c3b9aca00 with length:  8
從輸出咱們能夠很清楚的看到,定製的Writable類的字節序列實際上就是基本Writable類型的組合,輸出「8e03e88c3b9aca00」的前三個字節是1000的VLongWritable的字節序列,「8c3b9aca00」是1000000000VLongWritable的字節序列,這一點能夠從咱們編寫的MyWritable類的write方法中找到答案:



...//omitted per conciseness
@Override
public void write(DataOutput out) throws IOException {
	field1.write(out);
	field2.write(out);
}
...



總結

本文經過實例介紹了Hadoop Writable類序列化時佔用的字節長度,並分析了Writable類序列化後的字節序列的結構。須要注意的是Text類爲了節省空間的目的採用了UTF-8的編碼,而不是Java Character的UTF-16編碼,自定義的Writable的字節序列與該Writable類的write()方法有關。

最後指出,Writable是Hadoop序列化的核心,理解Hadoop Writable的字節長度和字節序列對於選擇合適的Writable對象以及在字節層面操做Writable對象相當重要。

參考資料

Tom White, Hadoop: The Definitive Guide, 3rd Edition

相關文章
相關標籤/搜索