摘要:需求問(wèn)題需要對(duì)序列化以后的對(duì)象中的在中進(jìn)行存取由于聲稱(chēng)只支持作為暴露出來(lái)的最基本的數(shù)據(jù)類(lèi)型形式的存取所以需要在存取前后將與互相轉(zhuǎn)換發(fā)現(xiàn)從出來(lái)的跟之前的不一樣即使強(qiáng)制指定了一致的編碼解碼方式結(jié)果仍不符合預(yù)期猜測(cè)嘗試懷疑是系統(tǒng)的默認(rèn)編碼方式與解
需求&問(wèn)題
需要對(duì)序列化以后的對(duì)象 (java中的byte[]) 在redis中進(jìn)行存取
由于redis聲稱(chēng)只支持String(作為redis暴露出來(lái)的最基本的數(shù)據(jù)類(lèi)型)形式的存取 (ref: https://redis.io/topics/internals, https://redis.io/topics/internals )
所以需要在存取前后將byte[]與String互相轉(zhuǎn)換
發(fā)現(xiàn)從string decode出來(lái)的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猜測(cè)&嘗試
懷疑是系統(tǒng)的默認(rèn)編碼方式與解碼時(shí)指定的不同, 如上所示 強(qiáng)制指定后未果
照理說(shuō)編碼解碼的算法是對(duì)稱(chēng)的, 對(duì)一個(gè)byte[]編碼解碼后的到byte[]理應(yīng)也是一樣的. 嘗試使用apache的StringUtils編碼解碼, 結(jié)果徒然
原因&解釋經(jīng)搜索試驗(yàn)后發(fā)現(xiàn)原因既與這個(gè)byte[]本身有關(guān)又與編碼方式有關(guān):
該場(chǎng)景中event結(jié)構(gòu)中包含一個(gè)UUID, 未序列化前在java中以一個(gè)長(zhǎng)度為32個(gè)字符的字符串表示, 例子“ce4326f3694b479dad472f250b975ee7”, 序列化后在java中為一個(gè)長(zhǎng)度16個(gè)字節(jié)的字節(jié)數(shù)組
為了節(jié)省空間, UUID序列化的規(guī)則為: 依次將每2個(gè)字符視為一個(gè)16進(jìn)制數(shù), 將其轉(zhuǎn)成對(duì)應(yīng)的10進(jìn)制數(shù), 并寫(xiě)入一個(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 對(duì)應(yīng) 128 ~ 255
這就導(dǎo)致了將序列化成byte[]以后的event encode成String的時(shí)候出現(xiàn)問(wèn)題, 因?yàn)槌S玫?ASCII, UTF-8等字符集中均沒(méi)有負(fù)數(shù)對(duì)應(yīng)的字符. 這意味著event中UUID部分中 80 ~ FF 的值都會(huì)被無(wú)效encode
比如ASCII中這些值會(huì)默認(rèn)被encode成’?’ (字符), decode成java的byte的時(shí)候就變成了63(10進(jìn)制) ; 在UTF-8中更常見(jiàn)的情況是byte[]中的 byte序列不合法 (Invalid byte sequences) 也就是說(shuō)該序列所代表的值不在UTF-8字符集支持的index范圍之內(nèi). 導(dǎo)致了原始的byte[]和經(jīng)過(guò)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í)對(duì)于UTF-8不兼容
直接傳輸二進(jìn)制數(shù)據(jù)(java中的byte[]), 具體方式為使用jedis中的BinaryClient類(lèi), 其中的方法支持 byte[] 類(lèi)型的參數(shù)
For anyone who’s curious enough:
顯然方案3是比較理想的. 看到這里記性好的人不免發(fā)出疑問(wèn): 開(kāi)頭不是說(shuō)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), 原來(lái)redis所說(shuō)的”String”指的是它的實(shí)現(xiàn)語(yǔ)言C中的char (8bit), 對(duì)應(yīng)java中的byte (8bit), 而不是java中的String或char (16bit). Redis只按8位8位地去裸讀數(shù)據(jù), 而不去解析(所謂的”二進(jìn)制安全”). 所以, 從java的角度看redis, byte[]類(lèi)型才是”原生”的
Redis實(shí)現(xiàn)中“String”的源碼:
struct sdshdr { long len; long free; char buf[]; };
后來(lái)想了下, 從傳輸層面/角度來(lái)講, 根本就沒(méi)有什么類(lèi)型, 都是1 0. 應(yīng)時(shí)時(shí)提醒自己跳出問(wèn)題之外, 從源頭思考, 避免陷入本本主義
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://www.ezyhdfw.cn/yun/66534.html
摘要:編碼就是為了解決編碼的問(wèn)題而生的,它擴(kuò)展自基本多文種平面中,與編碼完全一致,使用兩個(gè)字節(jié)表示到范圍使用個(gè)字節(jié)表示編碼的市場(chǎng)份額和比很小,在頁(yè)面中只占。 引言 一直以來(lái)總是對(duì) unicode, UTF-8 等編碼知識(shí)懵懵懂懂的,尤其是在做項(xiàng)目過(guò)程中只要涉及到幾個(gè)編碼之間的轉(zhuǎn)換,都得到網(wǎng)上搜索一番,根據(jù)別人的經(jīng)驗(yàn)照葫蘆畫(huà)瓢,才能解決問(wèn)題,但是私底下卻完全不懂在做什么。 我再也不愿意重復(fù)這種...
摘要:編碼就是為了解決編碼的問(wèn)題而生的,它擴(kuò)展自基本多文種平面中,與編碼完全一致,使用兩個(gè)字節(jié)表示到范圍使用個(gè)字節(jié)表示編碼的市場(chǎng)份額和比很小,在頁(yè)面中只占。 引言 一直以來(lái)總是對(duì) unicode, UTF-8 等編碼知識(shí)懵懵懂懂的,尤其是在做項(xiàng)目過(guò)程中只要涉及到幾個(gè)編碼之間的轉(zhuǎn)換,都得到網(wǎng)上搜索一番,根據(jù)別人的經(jīng)驗(yàn)照葫蘆畫(huà)瓢,才能解決問(wèn)題,但是私底下卻完全不懂在做什么。 我再也不愿意重復(fù)這種...
摘要:編碼就是為了解決編碼的問(wèn)題而生的,它擴(kuò)展自基本多文種平面中,與編碼完全一致,使用兩個(gè)字節(jié)表示到范圍使用個(gè)字節(jié)表示編碼的市場(chǎng)份額和比很小,在頁(yè)面中只占。 引言 一直以來(lái)總是對(duì) unicode, UTF-8 等編碼知識(shí)懵懵懂懂的,尤其是在做項(xiàng)目過(guò)程中只要涉及到幾個(gè)編碼之間的轉(zhuǎn)換,都得到網(wǎng)上搜索一番,根據(jù)別人的經(jīng)驗(yàn)照葫蘆畫(huà)瓢,才能解決問(wèn)題,但是私底下卻完全不懂在做什么。 我再也不愿意重復(fù)這種...
閱讀 1419·2021-11-16 11:45
閱讀 2381·2021-11-02 14:40
閱讀 4077·2021-09-24 10:25
閱讀 3142·2019-08-30 12:45
閱讀 1413·2019-08-29 18:39
閱讀 2579·2019-08-29 12:32
閱讀 1885·2019-08-26 10:45
閱讀 2030·2019-08-23 17:01