摘要:需求問題需要對序列化以后的對象中的在中進(jìn)行存取由于聲稱只支持作為暴露出來的最基本的數(shù)據(jù)類型形式的存取所以需要在存取前后將與互相轉(zhuǎn)換發(fā)現(xiàn)從出來的跟之前的不一樣即使強(qiáng)制指定了一致的編碼解碼方式結(jié)果仍不符合預(yù)期猜測嘗試懷疑是系統(tǒng)的默認(rèn)編碼方式與解
需求&問題
需要對序列化以后的對象 (java中的byte[]) 在redis中進(jìn)行存取
由于redis聲稱只支持String(作為redis暴露出來的最基本的數(shù)據(jù)類型)形式的存取 (ref: https://redis.io/topics/internals, https://redis.io/topics/internals )
所以需要在存取前后將byte[]與String互相轉(zhuǎn)換
發(fā)現(xiàn)從string decode出來的byte[]跟encode之前的byte[]不一樣
即使強(qiáng)制指定了一致的編碼解碼方式, 結(jié)果仍不符合預(yù)期
byte[] origin = eh.toBytes(event); // serialized event String str1 = new String(origin); byte[] new1 = str1.getBytes(); System.out.println(Arrays.equals(origin, new1)); // output: false String str2 = new String(origin, StandardCharsets.US_ASCII); byte[] new2 = str2.getBytes(StandardCharsets.US_ASCII); System.out.println(Arrays.equals(origin, new2)); // output: false String str3 = new String(origin, StandardCharsets.UTF_8); byte[] new3 = str3.getBytes(StandardCharsets.UTF_8); System.out.println(Arrays.equals(origin, new3)); // output: false猜測&嘗試
懷疑是系統(tǒng)的默認(rèn)編碼方式與解碼時(shí)指定的不同, 如上所示 強(qiáng)制指定后未果
照理說編碼解碼的算法是對稱的, 對一個(gè)byte[]編碼解碼后的到byte[]理應(yīng)也是一樣的. 嘗試使用apache的StringUtils編碼解碼, 結(jié)果徒然
原因&解釋經(jīng)搜索試驗(yàn)后發(fā)現(xiàn)原因既與這個(gè)byte[]本身有關(guān)又與編碼方式有關(guān):
該場景中event結(jié)構(gòu)中包含一個(gè)UUID, 未序列化前在java中以一個(gè)長度為32個(gè)字符的字符串表示, 例子“ce4326f3694b479dad472f250b975ee7”, 序列化后在java中為一個(gè)長度16個(gè)字節(jié)的字節(jié)數(shù)組
為了節(jié)省空間, UUID序列化的規(guī)則為: 依次將每2個(gè)字符視為一個(gè)16進(jìn)制數(shù), 將其轉(zhuǎn)成對應(yīng)的10進(jìn)制數(shù), 并寫入一個(gè)字節(jié)空間中. 總共占16字節(jié)
一個(gè)字節(jié)占8個(gè)位, 范圍為 0000 0000 ~ 1111 1111 (2進(jìn)制), 00 ~ FF (16進(jìn)制), 0 ~ 255 (10進(jìn)制). java里的一個(gè)byte變量也能表示256種狀態(tài) (剛好相當(dāng)于16進(jìn)制數(shù)) 然而它的值(10進(jìn)制)的范圍是 -128 ~ 127, 而不是 0 ~ 255. 其中 -128 ~ -1 對應(yīng) 128 ~ 255
這就導(dǎo)致了將序列化成byte[]以后的event encode成String的時(shí)候出現(xiàn)問題, 因?yàn)槌S玫?ASCII, UTF-8等字符集中均沒有負(fù)數(shù)對應(yīng)的字符. 這意味著event中UUID部分中 80 ~ FF 的值都會(huì)被無效encode
比如ASCII中這些值會(huì)默認(rèn)被encode成’?’ (字符), decode成java的byte的時(shí)候就變成了63(10進(jìn)制) ; 在UTF-8中更常見的情況是byte[]中的 byte序列不合法 (Invalid byte sequences) 也就是說該序列所代表的值不在UTF-8字符集支持的index范圍之內(nèi). 導(dǎo)致了原始的byte[]和經(jīng)過encode decode后的byte[]不同
Reference:
java - Encoding and decoding UTF-8 byte arrays from and to strings - Stack Overflow
java - Why are the lengths different when converting a byte array to a String and then back to a byte array? - Stack Overflow
使用Base64安全的轉(zhuǎn)換二進(jìn)制與字符串, 但會(huì)使payload增加33%, 原因點(diǎn)此
使用 Latin-1 編碼, 最大缺點(diǎn)是解碼時(shí)對于UTF-8不兼容
直接傳輸二進(jìn)制數(shù)據(jù)(java中的byte[]), 具體方式為使用jedis中的BinaryClient類, 其中的方法支持 byte[] 類型的參數(shù)
For anyone who’s curious enough:
顯然方案3是比較理想的. 看到這里記性好的人不免發(fā)出疑問: 開頭不是說redis只支持String形式的存取嗎?
這里引用一段jedis的文檔:
A note about String and Binary - what is native?
Redis/Jedis talks a lot about Strings. And here http://redis.io/topics/internals it says Strings are the basic building block of Redis. However, this stress on strings may be misleading. Redis" "String" refer to the C char type (8 bit), which is incompatible with Java Strings (16-bit). Redis sees only 8-bit blocks of data of predefined length, so normally it doesn"t interpret the data (it"s "binary safe"). Therefore in Java, byte[] data is "native", whereas Strings have to be encoded before being sent, and decoded after being retrieved by the SafeEncoder. This has some minor performance impact. In short: if you have binary data, don"t encode it into String, but use the binary versions.
上文提到其實(shí)redis官方文檔中多次提到的string是一種誤導(dǎo), 原來redis所說的”String”指的是它的實(shí)現(xiàn)語言C中的char (8bit), 對應(yīng)java中的byte (8bit), 而不是java中的String或char (16bit). Redis只按8位8位地去裸讀數(shù)據(jù), 而不去解析(所謂的”二進(jìn)制安全”). 所以, 從java的角度看redis, byte[]類型才是”原生”的
Redis實(shí)現(xiàn)中“String”的源碼:
struct sdshdr { long len; long free; char buf[]; };
后來想了下, 從傳輸層面/角度來講, 根本就沒有什么類型, 都是1 0. 應(yīng)時(shí)時(shí)提醒自己跳出問題之外, 從源頭思考, 避免陷入本本主義
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://m.hztianpu.com/yun/66534.html
摘要:編碼就是為了解決編碼的問題而生的,它擴(kuò)展自基本多文種平面中,與編碼完全一致,使用兩個(gè)字節(jié)表示到范圍使用個(gè)字節(jié)表示編碼的市場份額和比很小,在頁面中只占。 引言 一直以來總是對 unicode, UTF-8 等編碼知識懵懵懂懂的,尤其是在做項(xiàng)目過程中只要涉及到幾個(gè)編碼之間的轉(zhuǎn)換,都得到網(wǎng)上搜索一番,根據(jù)別人的經(jīng)驗(yàn)照葫蘆畫瓢,才能解決問題,但是私底下卻完全不懂在做什么。 我再也不愿意重復(fù)這種...
摘要:編碼就是為了解決編碼的問題而生的,它擴(kuò)展自基本多文種平面中,與編碼完全一致,使用兩個(gè)字節(jié)表示到范圍使用個(gè)字節(jié)表示編碼的市場份額和比很小,在頁面中只占。 引言 一直以來總是對 unicode, UTF-8 等編碼知識懵懵懂懂的,尤其是在做項(xiàng)目過程中只要涉及到幾個(gè)編碼之間的轉(zhuǎn)換,都得到網(wǎng)上搜索一番,根據(jù)別人的經(jīng)驗(yàn)照葫蘆畫瓢,才能解決問題,但是私底下卻完全不懂在做什么。 我再也不愿意重復(fù)這種...
摘要:編碼就是為了解決編碼的問題而生的,它擴(kuò)展自基本多文種平面中,與編碼完全一致,使用兩個(gè)字節(jié)表示到范圍使用個(gè)字節(jié)表示編碼的市場份額和比很小,在頁面中只占。 引言 一直以來總是對 unicode, UTF-8 等編碼知識懵懵懂懂的,尤其是在做項(xiàng)目過程中只要涉及到幾個(gè)編碼之間的轉(zhuǎn)換,都得到網(wǎng)上搜索一番,根據(jù)別人的經(jīng)驗(yàn)照葫蘆畫瓢,才能解決問題,但是私底下卻完全不懂在做什么。 我再也不愿意重復(fù)這種...
閱讀 1374·2021-11-16 11:45
閱讀 2316·2021-11-02 14:40
閱讀 3969·2021-09-24 10:25
閱讀 3080·2019-08-30 12:45
閱讀 1362·2019-08-29 18:39
閱讀 2534·2019-08-29 12:32
閱讀 1742·2019-08-26 10:45
閱讀 1982·2019-08-23 17:01