計算機如何儲存數字和字元
前言
最近在學習中涉及到計算機儲存、傳輸數字和字元等操作,由於對位元組、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