Java IO(02) 編碼問題(二)

Java IO(02) 編碼問題(二)

作為一個程式設計師可能在日常工作中對於編碼的瞭解可能不會造成什麼大的問題,但是作為計算機技術的基礎知識,我認為還是有必要進行一下了解的,在本篇文章之前,還有一篇姊妹篇《Java IO(01)編碼問題(一)》,其中用一些程式碼演示了編碼的一些內容。那麼本文章將會詳細的進行說明編碼的原理和演進,這作為第一部分進行介紹,另一部分呢,和本文或許關聯性不是非常的密切,就是位運算的內容了,而且位運算這種計算方式,通常在面嚮物件語言中很少使用,大多出現在底層程式碼中,但是對於演算法研究和閱讀JDK原始碼來說還是很重要的,位運算的威力可以說很強大,記得上學的時候學習微控制器和彙編課程的時候,位運算是不可能缺少的。

(一)編碼

1、ASCII編碼

就先從計算機行業人員最為熟知的ASCII編碼說起吧。上一篇文章當中的程式中,英文字元都是一個位元組表示的,比如說A是十六進位制的41(二進位制01000001),B是十六進位制的42(二進位制01000011)。我們來看一個圖。

圖1

一個位元組(byte)需要8位(bit)來表示,所以一個位元組最多有256種表示(0-255),但是從ASCII表中,我們可以看到,實際上我們並沒有把256種編碼全部使用,只使用了0-127這部分的二進位制編碼,也就是所以首位為”1“的八位二進位制都沒有進行編碼。這裡說的沒有編碼只是相對而言的,計算機使用一個位元組來儲存以上的字元,但是在後來的發展過程中,剩下的可利用編碼被編為了歐洲語言中的一些符號,暫且不表。這就是ASCII編碼了,每一個計算機專業的學生最先接觸的編碼。

2、GB xxxx編碼

從以上我們知道了編碼的意義,那麼很明顯ASCII碼並無法表示世界上所有的字元,所以後面的128種編碼被使用,但是即使這樣,一個位元組最多也只能表示256個字元,對比我泱泱中華的漢字來說,簡直是車水杯薪。但是這難不倒我們,一個位元組不行,再來一個嘛,兩個位元組就是16位(bit),可以表示2^16個字元。先不管這到底夠不夠,先來看看GB2312這種編碼方式的原理,為了與ASCII編碼相容,0-127與ASCII編碼一致,這些字元仍舊用一個位元組表示。而規定兩個碼點大於127的字元連在一起表示一個漢字,其中第一個位元組在0xA1-0xF7(10100001-11110111),第二個位元組在0xA1-0XFE(10100001-11111110)之間,這樣就有6763個漢字得到了編碼表示,但是對於漢字來講仍舊遠遠不夠,所以就出現了更進一步的拓展”GBK”編碼,在《Java
IO(01)編碼問題(一)》
中,介紹了一種編碼叫做”gbk”,也就是接下來要介紹的”GBK”,這種編碼收錄了21886個字元,其中21003個漢字,基本相容”GB2312″,它的兩個位元組的範圍:第一個位元組在0x81-0xFE(10000001-11111110),第二個位元組在0x40-0xFE(1000000-11111110)。其實”GBK”是國標擴充套件的意思,^-^!

3、UNICODE

到這裡是不是已經知道編碼的基本原理,就是編嘛,但是,如果有一種編碼格式,將世界上所有的符號都編一個獨一無二的程式碼,那豈不是最好,這樣就再也不用擔心亂碼的問題了。就這樣,UNICODE就是這個世界上超級大的字符集,需要強調的是它只是一個字符集,它已經給世界上超過一百萬種字元進行了編碼。它的基本格式是U [XX]XXXX,每個X代表一個十六進位制的數,目前範圍是U 0000–U 10FFFF。這樣的超級字符集確實很好,但是也帶了一個問題,那就是任何一個字元都要用4個位元組,包括那些只需要7位(bit)的ASCII字元,現在都要用32位(bit)來表示了,這有點太浪費了。但是聰明的電腦科學家可不會直接把UNICODE編碼拿到計算機上使用,而是用各種不同的實現方式應用到計算機上。

4、UTF-XXX

UTF(Unicode Transformation Format),是將UNICODE轉換成最終編碼的一種方式,在《Java IO(01)編碼問題(一)》中明顯我們已經用過了”utf-8″和”utf-16″。其中”utf-32″就是直接4個位元組(32bit)來表示一個字元,可以說就是把UNICODE直接拿來用了,但是UNICODE中大部分的字元都不會完全佔用四個位元組,只需要兩個位元組就可以,這樣就是”utf-16″,只有當需要用到更多位元組表示的字元時,才會選擇四個位元組來表示,其它均採用兩個字元來表示,當然,只需要一個位元組的字元也需要兩個位元組來表示,雙位元組編碼嘛。同理,”utf-8″編碼能用一個位元組表示就用一個位元組,不行的話再用兩個,三個或四個,可以說”utf-8″最為靈活,是一種變長的,自適應編碼(這是一個通訊領域的編碼技術,這裡強行用一下這個次來描述)。

那麼”utf-8″的具體方式是什麼樣的呢?兩條規則:

  1. 對於單位元組的符號,位元組的第一位設為0,後面7位是這個符號的UNICODE碼,所以對於英文字母,”utf-8″等同於ASCII碼。
  2. 對於n(>1)個位元組的符號,第一個位元組的前n位都設為1,第n 1位設為0,後面位元組的前兩位一律設為10。剩下的二進位制位,全部為這個符號的UNICODE碼。
根據上表,就很好理解這兩條規則了。如果一個位元組的第一位是0,那麼這個位元組就是一個單獨的字元;如果第一位是1,那麼看有多少個連續的1,有幾個就表示該字元佔有幾個位元組。

5、大小端

(1) 什麼是大小端呢?

所謂的大端模式,是指資料的低位儲存在記憶體的高地址中,而資料的高位,儲存在記憶體的低地址中。
所謂的小端模式,是指資料的低位儲存在記憶體的低地址中,而資料的高位儲存在記憶體的高地址中。

(2) 為什麼會有大小端模式之分呢?

這是因為在計算機系統中,我們是以位元組為單位的,每個地址單元都對應著一個位元組,一個位元組為8bit。但是在C語言中除了8bit的char之外,還有16bit的short型,32bit的long型(要看具體的編譯器),另外,對於位數大於8位的處理器,例如16位或者32位的處理器,由於暫存器寬度大於一個位元組,那麼必然存在著一個如果將多個位元組安排的問題。因此就導致了大端儲存模式和小端儲存模式。例如一個16bit的short型x,在記憶體中的地址為0x0010,x的值為0x1122,那麼0x11為高位元組,0x22為低位元組。對於大端模式,就將0x11放在低地址中,即0x0010中,0x22放在高地址中,即0x0011中。小端模式,剛好相反。我們常用的X86結構是小端模式,而KEIL
C51則為大端模式。很多的ARM,DSP都為小端模式。有些ARM處理器還可以由硬體來選擇是大端模式還是小端模式。
那麼IDEA的儲存是大端還是小端呢?
 short x = 0x1122;
System.out.println(Integer.toHexString(x&0xff));

執行結果:

22
所以是大端模式。

(二)位運算的威力

這裡直接摘取http://blog.csdn.net/iukey/article/details/7195265的部分,感謝這位作者的文章。

1、基礎概念

位運算包括:&(與)、|(或)、^(異或)、~(取反)、>>(右移)、<<(左移)
環境預設:32位機下面,int佔2個位元組,有符號(括號外數字代表進位制)
int a = 11;
int b = 1000;
(a)2 = (00000000 00001011 )2                                      //a的二進位制表示
(b)2 = (00000011 11101000 )2                                       //b的二進位制表示
a&b =(00000000 00001000 )2 =(8)10                            //一一為一,其它為0
a|b =  (00000011 11101011 )2 =(1003)10                      //有一為一,零零為0
a^b = (00000011 11100011 )2 =(995)10                        //相同為0,不相同為1
~b =   (11111100 00010111 )2 =(-31767)10                   //按位取反
b>>3 =  (00000000 01111101 )2 =(125)10                    // 去掉低3位,高位補0
或      =  (11100000 01111101 )2 =(-24701)10               //去掉低3位,高位補1    補0還是1具體情況視編譯環境決定
a<<3 =  (00000000 01011000 )2 =(88)10                     //去掉高3位,低位補0
看了上面的例子,相信你已經明白具體規則了,不明白自己去google。下面講具體作用。
位運算應用口訣
清零取數要用與,某位置一可用或
若要取反和交換,輕輕鬆鬆用異或

2、例項

3、應用例項

例1.子網掩碼

子網掩碼是個啥東東我也就不講了,電腦科學技術本身就是個非常龐大系統,一個人不可能面面俱到,但是一些基本的嘗試還是要懂的,不懂的可以自己去google,也可以等我的相關網路方面的文章。這裡只講與本問有關的應用部分。
假如我是一個網管,公司內部使用C類地址,現在我要把公司網路劃分成5個子網,網路號為192.168.1.0的前三段,那麼子掩碼怎麼填呢?
我現在先告訴你子網的子網掩碼分別怎麼填:192.168.1.224。(當然這裡還有其他答案,我取的是在子網擴充不超過8個的情況下的每個子網所容納主機最多的最佳方案)。
這個怎麼來的呢?ip本身是個二進位制的東東,為了方便人們設定,我們採用了點分十進位制的轉換,把32位的ip地址轉換成了4個位元組的十進位制萊表示。比如 192.168.1.213 這個ip地址的二進位制表示為:11000000 10101000 00000001 11010101 。對於C類地址預設的前三個位元組表示網路號,那麼這個網路號就是:11000000 10101000 00000001   ,最後一個位元組11010101表示主機號,可以知道這個網路可以容納的最多主機數為2^8-2,為什麼減2自己去查。現在要劃分子網,那麼我們就要從表示主機的那個位元組也就是8個位裡面拿出幾個位來表示子網號,
幾位比較合適呢?這就要看你需要劃分多少個子網咯。比如我們現在要劃分5個子網,(5)10 = (101)2 ,那麼至少就需要3位了,而且最多可以劃分2^3 = 8個子網。現在你把224換成二進位制看看吧(224)10 = (11100000)2  ,明白了吧,我們可以推斷出子網掩碼幹了什麼勾當?不錯子網掩碼與ip地址做了按位與運算,他的作用就是遮蔽了主機號獲取網路號與子網號。如果你明白了這點,你就知道自己在192.168.1.64子網的ip該怎麼填了,不會錯誤滴填成192.168.1.10了。 
竟然扯到一邊去了,講了半天才講了一個與運算的應用。

例2. 防止int型變數溢位

int x = 32760;int y = 32762; 要求求x、y的平均值,要求空間複雜度位O(0)。
你能用常規方法去解決嗎?可以。我不會講,這裡只講位運算的 方法。

int ave(int x, int y)   //返回X、Y的平均值
{   
return (x & y)   ( (x^y)>>1 );
}

知識點:

>>n 相當於除於2^n ,<<n 相當於乘於2^n .
x,y對應位均為1,相加後再除以2還是原來的數,如兩個00001000相加後除以2仍得00001000,那麼我們把x與y分別分成兩個部分來看,兩者相同的位分別拿出來則 :
x = (111111111111000)2 =  (111111111111000)2   (000000000000000)2
y =  (111111111111010)2 = (111111111111000)2   (000000000000010)2
相同部分我們叫做x1,y1,不同部分我們叫做x2,y2.那麼現在(x y)/2 =(x1 y1)/2 (x2 y2)/2 ,因為x1 == y1 ,所以(x1 y1)/2==x1 ==y1,相同部分我們用與運算求出來 x1 = x&y ,不同部分的和我們用^求出來,然後除於2是不是我們想要的結果了呢?言至於此,無需再言!
這個例子有點難於理解.但是經過我的分解應該還算好理解了,弄懂這個例子相信你的位運算已經登入大門了。
參考文章:
http://www.ruanyifeng.com/blog/2007/10/ascii_unicode_and_utf-8.html
https://zhuanlan.zhihu.com/p/26261762
https://zhuanlan.zhihu.com/p/21388517
http://blog.csdn.net/zhaoshuzhaoshu/article/details/37600857
http://blog.csdn.net/iukey/article/details/7195265