通过Zoopkeeper-BinaryOutputArchive类学习utf-8的实现
2019獨角獸企業重金招聘Python工程師標準>>>
BinaryOutputArchive類位于org.apache.jute包中,是序列化組件中的一個類。從字面意思理解就是輸出類。這里類實現了OutputArchive接口。并且在構造函數中需要傳遞一個DataOutput接口的實現類。
在這個代碼中有一段代碼引起了我的注意:
?/***?create?our?own?char?encoder?to?utf8.?This?is?faster?*?then?string.getbytes(UTF8).*?@param?s?the?string?to?encode?into?utf8*?@return?utf8?byte?sequence.*/final?private?ByteBuffer?stringToByteBuffer(CharSequence?s)?{bb.clear();final?int?len?=?s.length();for?(int?i?=?0;?i?<?len;?i++)?{if?(bb.remaining()?<?3)?{ByteBuffer?n?=?ByteBuffer.allocate(bb.capacity()?<<?1);bb.flip();n.put(bb);bb?=?n;}char?c?=?s.charAt(i);//一個字節if?(c?<?0x80)?{bb.put((byte)?c);}else?if?(c?<?0x800)?{bb.put((byte)?(0xc0?|?(c?>>?6)));bb.put((byte)?(0x80?|?(c?&?0x3f)));}?else?{bb.put((byte)?(0xe0?|?(c?>>?12)));bb.put((byte)?(0x80?|?((c?>>?6)?&?0x3f)));bb.put((byte)?(0x80?|?(c?&?0x3f)));}}bb.flip();return?bb;}根據代碼的注釋說這里的實現比java自身的要高效。那么究竟是怎么樣呢?值得對比進行學習一番。
????首先在對比之前要先弄清除他們在做什么。這里的方式實現了一個字符串轉化正一個utf-8編碼格式的字節數組。那么utf-8編碼格式是規定怎么進行轉換的呢?大體的規則如下:
? 1.對于單字節的符號,字節的第一位設為0,后面7位為這個符號的unicode碼。因此對于英語字母,UTF-8編碼和ASCII碼是相同的。
? ? ? ? ?2.對于n字節的符號(n>1),第一個字節的前n位都設為1,第n+1位設為0,后面字節的前兩位一律設為10。剩下的沒有提及的二進制位,全部為這個符號的unicode碼。
????????實例如下:
? ? ? ? ? ? ? ? ?一個字節 0000 0000-0000 007F | 0xxxxxxx
? ? ? ? ? ? ? ? ?兩個字節 0000 0080-0000 07FF | 110xxxxx 10xxxxxx
? ? ? ? ? ? ? ? ?三個字節 0000 0800-0000 FFFF | 1110xxxx 10xxxxxx 10xxxxxx
? ? ? ? ? ? ? ? ?四個字節 0001 0000-0010 FFFF | 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx
根據上面的規則再去理解代碼就方便一些了。stringToByteBuffer方法的邏輯大致是:
循環字符的長度。如果緩沖字節類ByteBuffer 的剩余空間不足則擴充1倍。(這里使用了位運算)
如果是1個字節直接保存
如果是2個字節,先把字符的前一部分根據規則放入第一個字節中,然后把后面一部分放入第二個字節中。0xc0-->11000000。用它或一個字符剛好滿足上面規則中110xxxxx的要求。代碼 c >> 6 讓我理解了好半天(可能是我比較笨的原因吧)。最后我發現根據規則除了第一個字節,其他字節都是前兩位為10.那么就只剩下6位了。所以移位計算的數字是以6的倍數來進行的。0x80-->10000000、0x3f-->00111111。代碼 0x80 | (c & 0x3f) 剛好滿足了第二個字節的條件
3字節的代碼和2字節的代碼比較類似
理解了stringToByteBuffer這個方法之后,我們再來看看java中的實現:
?public?byte[]?getBytes(String?charsetName)throws?UnsupportedEncodingException?{if?(charsetName?==?null)?throw?new?NullPointerException();return?StringCoding.encode(charsetName,?value,?0,?value.length);}沒什么好說的,調用了一個StringCoding的一個方法。繼續跟蹤代碼:
static?byte[]?encode(String?charsetName,?char[]?ca,?int?off,?int?len)throws?UnsupportedEncodingException{StringEncoder?se?=?deref(encoder);String?csn?=?(charsetName?==?null)???"ISO-8859-1"?:?charsetName;if?((se?==?null)?||?!(csn.equals(se.requestedCharsetName())||?csn.equals(se.charsetName())))?{se?=?null;try?{Charset?cs?=?lookupCharset(csn);if?(cs?!=?null)se?=?new?StringEncoder(cs,?csn);}?catch?(IllegalCharsetNameException?x)?{}if?(se?==?null)throw?new?UnsupportedEncodingException?(csn);set(encoder,?se);}return?se.encode(ca,?off,?len);}這段代碼的大概的意思猜測是,先根據傳遞的字符集名稱去查找字符集,然后根據字符集創建一個StringEncoder類型的對象。然后調用對象的encode方法。繼續跟蹤代碼:
byte[]?encode(char[]?ca,?int?off,?int?len)?{int?en?=?scale(len,?ce.maxBytesPerChar());byte[]?ba?=?new?byte[en];if?(len?==?0)return?ba;if?(ce?instanceof?ArrayEncoder)?{int?blen?=?((ArrayEncoder)ce).encode(ca,?off,?len,?ba);return?safeTrim(ba,?blen,?cs,?isTrusted);}?else?{ce.reset();ByteBuffer?bb?=?ByteBuffer.wrap(ba);CharBuffer?cb?=?CharBuffer.wrap(ca,?off,?len);try?{CoderResult?cr?=?ce.encode(cb,?bb,?true);if?(!cr.isUnderflow())cr.throwException();cr?=?ce.flush(bb);if?(!cr.isUnderflow())cr.throwException();}?catch?(CharacterCodingException?x)?{//?Substitution?is?always?enabled,//?so?this?shouldn't?happenthrow?new?Error(x);}return?safeTrim(ba,?bb.position(),?cs,?isTrusted);}}這段代碼判斷StringEncoder的類型。我寫個測試用例byte[] x="a".getbytes("utf-8"); 跟蹤代碼到這里發現走的是?ArrayEncoder這里邏輯。繼續跟蹤發現ArrayEncoder有多個實現。其中有一個UTF_8類。這個類在nio包中。這里的代碼只能通過反編譯來看,沒有找到源碼包:
/*?????*/?????public?int?encode(char[]?paramArrayOfChar,?int?paramInt1,?int?paramInt2,?byte[]?paramArrayOfByte) /*?????*/?????{ /*?627?*/???????int?i?=?paramInt1?+?paramInt2; /*?628?*/???????int?j?=?0; /*?629?*/???????int?k?=?j?+?Math.min(paramInt2,?paramArrayOfByte.length); /*?632?*/???????while?((j?<?k)?&&?(paramArrayOfChar[paramInt1]?<?''))?{ /*?633?*/?????????paramArrayOfByte[(j++)]?=?((byte)paramArrayOfChar[(paramInt1++)]); /*?????*/???????} /*?635?*/???????while?(paramInt1?<?i) /*?????*/???????{ /*?636?*/?????????char?c?=?paramArrayOfChar[(paramInt1++)]; /*?637?*/?????????if?(c?<?'-') /*?????*/?????????{ /*?639?*/???????????paramArrayOfByte[(j++)]?=?((byte)c); /*?????*/?????????} /*?640?*/?????????else?if?(c?<?'?') /*?????*/?????????{ /*?642?*/???????????paramArrayOfByte[(j++)]?=?((byte)(0xC0?|?c?>>?'\006')); /*?643?*/???????????paramArrayOfByte[(j++)]?=?((byte)(0x80?|?c?&?0x3F)); /*?????*/?????????} /*?644?*/?????????else?if?(Character.isSurrogate(c)) /*?????*/?????????{ /*?645?*/???????????if?(this.sgp?==?null)?{ /*?646?*/?????????????this.sgp?=?new?Surrogate.Parser(); /*?????*/???????????} /*?647?*/???????????int?m?=?this.sgp.parse(c,?paramArrayOfChar,?paramInt1?-?1,?i); /*?648?*/???????????if?(m?<?0) /*?????*/???????????{ /*?649?*/?????????????if?(malformedInputAction()?!=?CodingErrorAction.REPLACE)?{ /*?650?*/???????????????return?-1; /*?????*/?????????????} /*?651?*/?????????????paramArrayOfByte[(j++)]?=?replacement()[0]; /*?????*/???????????} /*?????*/???????????else /*?????*/???????????{ /*?653?*/?????????????paramArrayOfByte[(j++)]?=?((byte)(0xF0?|?m?>>?18)); /*?654?*/?????????????paramArrayOfByte[(j++)]?=?((byte)(0x80?|?m?>>?12?&?0x3F)); /*?655?*/?????????????paramArrayOfByte[(j++)]?=?((byte)(0x80?|?m?>>?6?&?0x3F)); /*?656?*/?????????????paramArrayOfByte[(j++)]?=?((byte)(0x80?|?m?&?0x3F)); /*?657?*/?????????????paramInt1++; /*?????*/???????????} /*?????*/?????????} /*?????*/?????????else /*?????*/?????????{ /*?661?*/???????????paramArrayOfByte[(j++)]?=?((byte)(0xE0?|?c?>>?'\f')); /*?662?*/???????????paramArrayOfByte[(j++)]?=?((byte)(0x80?|?c?>>?'\006'?&?0x3F)); /*?663?*/???????????paramArrayOfByte[(j++)]?=?((byte)(0x80?|?c?&?0x3F)); /*?????*/?????????} /*?????*/???????} /*?666?*/???????return?j; /*?????*/?????}和zookeeper中的差不多,都是在用位運算來實現utf-8的規則。
對比代碼的理解如下:
zookeeper中實現了3字節的轉換,而java的類實現了4字節的轉換。
代碼復雜度上java的比zookeeper要高
直觀上我沒有發現具體的實現上zookeeper會比較高。
????到這里utf-8的實現就完畢了,但這里引起了我的思考:
zookeeper為什么需要自己去實現序列化、實現utf-8的轉換
如果自己去開發一個分布式的系統是否也需要實現這些呢
也許等我在不斷的學習zookeeper的源碼中會有新的體會和領悟!樹立目標,堅持去做,不斷的提升自己的理解和領悟,才能超越過去的自己!
轉載于:https://my.oschina.net/u/2456287/blog/645180
總結
以上是生活随笔為你收集整理的通过Zoopkeeper-BinaryOutputArchive类学习utf-8的实现的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 4. 测试设计技术
- 下一篇: 优秀系列文章(持续)