目錄
由於公司需求,需要讀取遊戲Redis資料做內外網資料遷移,沒有與遊戲組過多的溝通。 使用的資料型別是Hash, key是string,value是byte[]。以前對於編碼的理解是:計算機底層儲存的永遠是01的二進位制資料,編碼是一種對於計算機二進位制資料的字元對映,也就是約定好哪個值對應哪個字元。是為了便於在顯示器上展示。
那麼基於這個理解,我就以為 不需要關心儲存的資料型別,因為我不需要用到資料,我只是負責做資料的搬運。於是我用的是HGetAsync方法去讀的字串。然後HSetAsync把資料存到另一個Redis。結果發現資料發生了編碼。基於我上邊對於編碼的理解,也就是按照不同的編碼讀取字串,只是顯示器上會亂碼,但是底層的01二進位制沒有發生變化,這次問題打破了我的認知。
當一個byte[]在計算機中儲存時,它就是以二進位制形式儲存的。如果這個byte[]中的每一個位元組代表的是ASCII碼(一個位元組表示一個字元),那麼它在不同的編碼下讀取應該沒有問題。但是,如果它代表的是Unicode字元集(UTF-8和UTF-16等),那麼在不同的編碼下讀取就會發生問題。因為不同的編碼方式對儲存方式和位元組長度都有不同的要求。
以UTF-8為例,它對不同字元分配的位數不同。對於ASCII字元,UTF-8使用一個位元組表示,而對於其他字元,它需要兩個位元組、三個位元組或四個位元組來表示。因此,在按照UTF-8格式讀取一個byte[]時,如果它的編碼確實是UTF-8,那麼就可以讀取正確的字元。但是,如果重新以UTF-8的格式儲存它時,就會按照UTF-8的編碼方式重新把這個字元轉換成二進位制。如果這個字元之前的編碼不是UTF-8,那麼它在轉換為UTF-8的二進位制時,就會變成不同的值,因此資料也就變了。
var data = Encoding.UTF32.GetBytes("愛"); var word = Encoding.UTF8.GetString(data); var word1 = Encoding.UTF32.GetString(data); File.WriteAllText($@"{AppDomain.CurrentDomain.BaseDirectory}/code.txt", word); File.WriteAllText($@"{AppDomain.CurrentDomain.BaseDirectory}/code1.txt", word1); foreach (var d in File.ReadAllBytes($@"{AppDomain.CurrentDomain.BaseDirectory}/code.txt")) { Console.WriteLine(d); } Console.WriteLine("------------"); foreach (var d in File.ReadAllBytes($@"{AppDomain.CurrentDomain.BaseDirectory}/code1.txt")) { Console.WriteLine(d); }
例如,我們有一個Unicode字元「愛」,其二進位制表示為:0000 0100 1110 0111。按照UTF-8編碼的規則,在儲存這個字元時,我們需要使用3個位元組的二進位制資料:1110XXXX 10XXXXXX 10XXXXXX(X表示對應字元的二進位制資料的高位)
我們將其儲存到一個byte[]中,再將其儲存到檔案中。然後按照UTF-8的格式讀取,解析出Unicode字元「愛」,再將其按照UTF-8的格式儲存回檔案。這時,由於使用了UTF-8編碼,我們需要將Unicode字元「愛」轉換為UTF-8編碼的二進位制資料,即,使用3個位元組的二進位制資料:11100100 10101110 10011111。
通過執行程式碼,可以看到,由於儲存使用了UTF-8編碼,而讀取和重新儲存又使用了UTF-8編碼,因此二進位制資料發生了變化。
當一個byte[]在計算機中儲存時,它就是以二進位制形式儲存的。如果這個byte[]中的每一個位元組代表的是ASCII碼(一個位元組表示一個字元),那麼它在不同的編碼下讀取應該沒有問題。但是,如果它代表的是Unicode字元集(UTF-8和UTF-16等),那麼在不同的編碼下讀取就會發生問題。