1. 程式人生 > 實用技巧 >計算機如何儲存數字和字元

計算機如何儲存數字和字元

前言

最近在學習中涉及到計算機儲存、傳輸數字和字元等操作,由於對位元組、2進位制、10進位制、16進位制、ASCII碼的概念以及它們之間的關係和轉換理解的不夠透徹,導致在通訊、MD5訊息摘要演算法等時候出現問題,是因為資料轉成計算機認識的01的這個環節出現問題。由於這個問題並不是那麼容易發現,所以我也算是花了挺多時間才解決了這個問題,記錄下解決過程,順便也當複習一下計算機組成原理。

ASCII碼

在計算機中,所有的資料在儲存和運算時都要使用二進位制數表示(因為計算機用高電平和低電平分別表示1和0),例如,像a、b、c、d這樣的52個字母(包括大寫)以及0、1等數字還有一些常用的符號(例如*、#、@等)在計算機中儲存時也要使用二進位制數來表示,而具體用哪些二進位制數字表示哪個符號,當然每個人都可以約定自己的一套(這就叫編碼),而大家如果要想互相通訊而不造成混亂,那麼大家就必須使用相同的編碼規則,於是美國有關的標準化組織就出臺了ASCII編碼,統一規定了上述常用符號用哪些二進位制數來表示。
ASCII 碼一共規定了128個字元(0000 0000-0111 1111)的編碼,比如空格SPACE是32(二進位制0010 0000),大寫的字母A是65(二進位制0100 0001 )。這128個符號(包括32個不能打印出來的控制符號),只佔用了一個位元組的後面7位(低7位),最前面的一位(高1位)統一規定為0(不要和數字的符號位搞混)。
當然除了ASCII碼,還有UTF-8、GBK等。

位元組

位元組(Byte)普通計算機系統能讀取和定位到最小資訊單位,即我們通過計算機儲存和傳輸資料的時候都是先把資料轉成位元組。
位元組即Byte,一個位元組代表8個位元(Bit),位元組通常縮寫為B,位元通常縮寫為b。位元組的大小是8Bit,即位元組的範圍是0000 0000 - 1111 1111,對於無符號型,它表示的十進位制範圍是[0,255],對於有符號型,高一位表示符號位,它表示的十進位制範圍是[-128,127]。

計算機若何儲存資料

計算機只認識0和1(因為計算機只有高低電平兩個狀態),資料要想通過計算機儲存或者傳輸,首先是要把資料轉成計算機能認識的格式即01資料。
我們舉個例子,以儲存十進位制數字28和-28為例,首先將十進位制數轉成二進位制。

需要注意的是: 數字在計算機中儲存的是補碼,而字元是在計算機中儲存的是字元對應的編碼(不要和數字的補碼搞混)。
數字

儲存十進位制數字28和-28為例,首先將十進位制數轉成二進位制,高1位為0代表正數,為1代表負數

28(10) = 0001 1100(2)(原碼)            
-28(10) = 1001 1100(2)(原碼)

然後計算機將二進位制數字進行補碼運算,運算結果如下

28(10) = 0001 1100(2)(原碼) =  0001 1100(2)(補碼)       
-28(10) = 1001 1100(2)(原碼) = 1110 0100(2)(補碼)

然後計算機儲存的就是補碼,當要取出資料的時候,就將補碼逆運算一下,即可求出原碼,再將原碼轉換一下就可以得到真實的資料了。
下面以Java語言演示這個過程,首先我們要清楚Java的byte、short、int、long都是有符號的(signed)

public class test {
    public static void main(String[] args) throws NoSuchAlgorithmException {
        int a1 = 28;
        int a2 = -28;

//        轉成二進位制表示
        String b1 = Integer.toBinaryString(a1);
        String b2 = Integer.toBinaryString(a2);
//        轉成無無符號表示
        String b3 = Integer.toUnsignedString(a1);
        String b4 = Integer.toUnsignedString(a2);

        System.out.println("28儲存到計算機後為:" + b1);
        System.out.println("-28儲存到計算機後為:" + b2);
        System.out.println("取出儲存的28 以無符號表示:" + b3);
        System.out.println("取出儲存的-28 以無符號表示:" + b4);
    }
}

執行輸出:

28儲存到計算機後為:11100
-28儲存到計算機後為:11111111111111111111111111100100
取出儲存的28 以無符號表示:28
取出儲存的-28 以無符號表示:4294967268

我們驗證一下結果,驗證了計算機確實是以補碼的方式儲存數字。這裡有個小問題,就是我們知道int型有4個位元組即32個位元,但是28卻輸出了111005個位元而已,是因為toBinaryString()方法把11100前面的0給忽略了。
取出的時候,我們以無符號的標準去處理,導致取出存入的-28結果是4294967268和我們存入的不一樣,這是因為-28是負數,負數的補碼和原碼不一樣,而用無符號處理的話就是直接將11111111111111111111111111100100轉成結果了。而為什麼28用有無符號處理結果都一樣是因為正數的原碼和補碼一樣,這樣驗證了Java的資料型別都是有符號的。

至於計算機為什麼用補碼來儲存數字,而不是原碼,原因是:
拿單位元組整數來說,無符號型,其表示範圍是[0,255],總共表示了256個數據。有符號型,其表示範圍是[-128,127]。
先看無符號,原碼和補碼都一樣,0表示為0000 0000,255表示為1111 1111,剛好滿足了要求,可以表示256個數據。
再看有符號的,若是用原碼錶示,0表示為0000 000。因為咱們有符號,所以應該也有個負0(雖然它還是0)1000 0000。這樣的話那就有2個0,也就是隻能表示255個數據,不能夠滿足我們的要求。而用補碼則很好的解決了這個問題。

字元

在計算機中,對非數值的字元進行處理時,要對字元進行數字化,即用二進位制編碼來表示字元。其中西文字元最常用到的編碼方案有ASCII編碼和EBCDIC編碼。對於漢字,我國也制定的相應的編碼方案,比如 GBK,GB2312等。
比如字元a的ASCII碼十進位制值為97,在計算機中用二進位制表示就是 01100001。下面同樣用Java來演示計算機是如何儲存字元的。

採用UTF-8和GBK兩種編碼儲存漢字

public class test {
    public static void main(String[] args) throws NoSuchAlgorithmException {
        String a1 = "中";
//        採用兩種不同的編碼儲存"中"這個漢字 比較兩種編碼
        byte[] b1 = a1.getBytes("GBK");
        byte[] b2 = a1.getBytes("UTF-8");
        String c1 = binary(b1,2);
        String c2= binary(b2,2);
        System.out.println("GBK儲存對應的二進位制:" + c1);
        System.out.println("UTF-8儲存對應的二進位制:" +c2);
    }
    
    /**
     * 將byte[]轉為各種進位制的字串
     * @param bytes byte[]
     * @param radix 基數可以轉換進位制的範圍,從Character.MIN_RADIX到Character.MAX_RADIX,超出範圍後變為10進位制
     * @return 轉換後的字串
     */
    public static String binary(byte[] bytes, int radix){
        return new BigInteger(1, bytes).toString(radix);// 這裡的1代表正數
    }    
}

我們除錯看看,發現GBK編碼採用2個位元組儲存,儲存的資料分別是10進位制的-42和-48對應的二進位制分別是11010110和11010000(補碼),即漢字中對應的二進位制為1101011011010000,即16進位制的D6D0,檢視GBK對照表,發現16進位制編碼D6D0對應的漢字確實是中

而UTF-8編碼採用3個位元組儲存,同理將對應的二進位制111001001011100010101101轉成16進位制,為E4B8AD,通過UTF-8編碼查詢,發現漢字中對應的16進位制編碼確實是E4B8AD

參考連結:
https://ddnd.cn/2019/02/16/byte-hex-ascii/#more