回想:
回想序列化,事實上原書的結構非常清晰,我截圖給出書中的章節結構:
序列化最基本的,最底層的是實現writable接口,wiritable規定讀和寫的游戲規則?(void?write(DataOutput?out)?throws?IOException;??void?readFields(DataInput?in)?throws?IOException;)。為了適應hadoop的mapreduce的運算特性,也就是map 和reduce對key的比較,排序的功能,就要實現Comparable接口,這個接口規定?public?int?compareTo(T?o);這種方法。為了增強處理大數據集的能力。我們不能老是先序列化,傳輸,反序列化。然后進行比較compare,太消耗時間和性能了。我們有了增強的RawComparator,RawComparator是Comparator的增強版,能夠比較沒有被反序列化的數據。
hadoop須要處理的數據五花八門,java具有的基本數據類型都有可能在hadoop中出現,hadoop因此包裝了java的基本數據類型使他們實現以上的接口而且給予實現細節。這些類都實現了WritableComparable接口。插上飛翔的翅膀,能夠在不同的hadoop節點之間毫無障礙的傳輸了。如入無人之境。
既然Text拿出來單獨討論。
自然就要好好研究一下Text的實現細節,對于我們對hadoop的設計細節和思想太重要太重要。
Text是UTF-8字符串的Writable實現。被看做是java String類型的替換。Text?類取代了UTF8?類,?UTF8?類不支持編碼大于32767?個字節的字符.使用了Java?改進過的UTF-8.Text?使用int?型(使用一個可變長度的編碼方案)在字符感編碼中存儲字節數. 最大值是2?GB?。此外。?Text?使用標準的UTF芯,使其更易于與理解U?T?F-8?的其它工具協同工作.
為什么是2GB,我預計非常少人會思考這個問題,我們簡單計算一下:
利用int存儲字節長度,int最大是2^31-1,那么字節最大長度就是2^31-1
Text可以容納的大小R=(2^31-1)/1024/1024/1024=1.99999999=2GB
因此我們使用他的時候要知道他的大小是有限制的。
因為強調使用標準的UTF8,所以Text?和Java?的String?類之間還是有一些差別的。Text?類的索引位于編碼后的字節系列中,而不是字符串中的Unicode?字符.或Java?的char?編碼單元{如同String?一樣)。舉比例如以下:
這方面的差異用中文就非常好的說明這個問題。
?String?line?=?"滾滾長江東逝水";
????System.out.println(line.length());
????Text?text?=?new?Text(line);
????System.out.println(text.getLength());
????System.out.println(line.charAt(2));
????System.out.println(text.charAt(2));
輸出:
7
21
長
-1
? ? String?line?=?"merry?christmas";
????System.out.println(line.length());
????Text?text?=?new?Text(line);
????System.out.println(text.getLength());
????System.out.println(line.charAt(2));
????System.out.println(text.charAt(2));
輸出:
15
15
r
114
能夠看出來,他們的索引(Index)是真的不一樣。同一個索引值取出來的并非同一個東西。
注意,?charAt?(?)返回了一個int?類型來表示Unicode?代碼點,?而不是像String?變量那樣返回一個char?類型。在開始使用一個以上字節進行編碼的字符(比如中文。!
),?Text?和String?之間的
差別是非常明顯的。下表展示了Unicode的代碼點。
U+0041 代碼點相應大寫字母A 一直到U+00DFUTF-8都是一個字節編碼。剩下的都是兩個字節以上。而對于java,最后一行,僅僅有最后一個代碼點是兩個。其它的都是一個字節的。這點區別非常大。
怕非常多人不懂代碼點,我再解釋一下:
Unicode 是通用字符編碼標準。用于表示文本以供計算機處理。Unicode 提供了一種對多語種文本進行一致編碼的方法,便于國際文本文件的交換。每一個 Unicode 字符均映射到一個代碼點,代碼點是一個介于 0 和 1,114,111 之間的整數。Unicode 代碼點使用 U+nnnn 形式的表示法來表示(當中 nnnn 是代碼點的十六進制數),或使用描寫敘述代碼點的文本字符串來表示。比如,小寫字母 “a” 能夠用 U+0061 或文本字符串 "LATIN SMALL LETTER A" 來表示。?代碼點能夠使用不同的字符編碼方案進行編碼。
在 Oracle Solaris Unicode 語言環境中,使用的是 UTF-8 形式。UTF-8 是 Unicode 的一種可變長度編碼形式,它透明地保留了 ASCII 字符代碼值(請參見UTF-8 概述)。 代碼點就是一個字符在Unicode中相應的編碼。
String?的長度是它包含的字符個數?。但Text?對象的長度是其UTF?-8?編碼的字節數.?相同。?indexOf?()?方泣返回一個char?類型的編碼單元的索引。find?()?方格是字節偏移量.請看樣例:
@Test
public?void?string()?throws?UnsupportedEncodingException?{
String?s?=?"\u0041\u00DF\u6771\uD801\uDC00";
assertThat(s.length(),?is(5));
assertThat(s.getBytes("UTF-8").length,?is(10));
assertThat(s.indexOf("\u0041"),?is(0));
assertThat(s.indexOf("\u00DF"),?is(1));
assertThat(s.indexOf("\u6771"),?is(2));
assertThat(s.indexOf("\uD801\uDC00"),?is(3));
assertThat(s.charAt(0),?is('\u0041'));
assertThat(s.charAt(1),?is('\u00DF'));
assertThat(s.charAt(2),?is('\u6771'));
assertThat(s.charAt(3),?is('\uD801'));
assertThat(s.charAt(4),?is('\uDC00'));
assertThat(s.codePointAt(0),?is(0x0041));
assertThat(s.codePointAt(1),?is(0x00DF));
assertThat(s.codePointAt(2),?is(0x6771));
assertThat(s.codePointAt(3),?is(0x10400));
}
@Test
public?void?text()?{
Text?t?=?new?Text("\u0041\u00DF\u6771\uD801\uDC00");
assertThat(t.getLength(),?is(10)); ? //10?=?1+2+3+4?是其UTF?-8?編碼的字節數 ?
assertThat(t.find("\u0041"),?is(0));
assertThat(t.find("\u00DF"),?is(1));
assertThat(t.find("\u6771"),?is(3));
assertThat(t.find("\uD801\uDC00"),?is(6));
assertThat(t.charAt(0),?is(0x0041));
assertThat(t.charAt(1),?is(0x00DF));
assertThat(t.charAt(3),?is(0x6771));
assertThat(t.charAt(6),?is(0x10400));
}
遍歷Text,迭代
迭代使用索引的字節偏移對Text?中的Unicode?字符進行途代是非常復雜的,由于你不能僅僅添加索引。迭代的定義有點模糊(見例4-6?) 將Text?對象變成java.nio.ByteBuffer然后對緩沖的Text?重復調用bytesToCodePoint()?靜態方法.這個方泣提取下一個代碼點作為int?然后更新緩沖中的位置。當bytesToCodePoint()?返回-?1?時,檢測到字符結束。意思就是說,我們取字符的時候。是一整個一整個字符的取,我們不可以依照索引來取,我們依照代碼點整個整個的取。
public?class?TextIterator?{
public?static?void?main(String[]?args)?{
? ? Text?t?=?new?Text("\u0041\u00DF\u6771\uD801\uDC00");
? ? ByteBuffer?buf?=?ByteBuffer.wrap(t.getBytes(),?0,?t.getLength());
? ? int?cp;
? ? while?(buf.hasRemaining()?&&?(cp?=?Text.bytesToCodePoint(buf))?!=?-1)?{
? ? System.out.println(Integer.toHexString(cp));
}
}
}
輸出:
41
df
6771
10400
可改動性
String?和Text?的還有一個差別在于可改動性(像Hadoop?中的全部Writable?實視一樣。但NullWritable?除外,后者是單實例對象)。
我們能夠通過
對它調用set()?函數來重用Text?實例。示比例如以下:
Text?t?=?new?Text("hadoop");
t.set("pig");
assertThat(t.getLength(),?is(3));
assertThat(t.getBytes().length,?is(3));
轉為字符串
Text?不像java.?l?ang.String?一樣有一個能夠處理字符串的API?,所以在很多情況下,須要將Text?對象轉化為String?對象。這通經常使用toString()方法來完畢。
assertThat(new?Text("hadoop?")?.?toString()?,?is(?"hadoop"));
BytesWritable
BytesWritable?是一個二進制數據數組封裝。
它的序列化格式是一個int?字段(4字節)?,指定的是字節數及字節本身。
比如。?一個長度為2?,值為3?和5?的字節數
組序列化為一個4?字節的整數(00000002)加上兩個來自數組的字節(03?和05)?。BytesWritable?b?=?new?BytesWritable(new?byte[]?{?3,?5?});
byte[]?bytes?=?serialize(b);
assertThat(StringUtils.byteToHexString(bytes),?is("000000020305"));
BytesWritab1e?是可變的。其值可通過調用set?(?)方撞來改變。和Text一樣?。從getBytes?(?)方法返回的字節數組大小可能并沒有反映出存儲在BytesWritable?的數據的實際大小.能夠通過調用getLength?()?方法來確定BytesWritable?的長度。比如:
b.setCapacity(11);
assertThat(b.getLength(),?is(2));
assertThat(b.getBytes().length,?is(11));
NullWritable
NullWritable?是一種特殊的Writable?類型,由于它的序列化是零長度的。
沒有字節被寫入流或從流中讀出.它被用作占位符.比如,在MapReduce?中,在不需要這個位置的時候,鍵或值能夠被聲明為NullWritable,他有效存儲了一個不變的空值。NullWritable?也能夠非常實用,在打算存儲一系列值的時候,作為SequenceFile?的一個鍵,而不是鍵/值對。
它是一個不變的單實例,事實上例能夠通
過調用NullWritable.get()?方法來檢索。今天就到這里。
Charles 2015-12-24晚于P.P
版權說明:
本文由Charles Dong原創,本人支持開源以及免費故意的傳播。反對商業化謀利。
CSDN博客:http://blog.csdn.net/mrcharles
個人站:http://blog.xingbod.cn
EMAIL:charles@xingbod.cn