作為2019的程序員,你還在為編碼問題困惑?



如果你是一個生活在2019年的程序員,卻不了解字符、字符集、編碼和Unicode這些基礎知識。那你可要小心了,要是被我抓到你,我會讓你在潛水艇里剝六個月洋蔥來懲罰你。 --來源網絡


基本概念科普

為了讓下面的內容能看得更明白,在這邊先科普幾個和編碼相關的概念。

1. 字符

字符(Character):在計算機和電信技術中,一個字符是一個單位的字形、類字形單位或符號的基本信息。說的簡單點字符是各種文字和符號的總稱。一個字符可以是一個中文漢字、一個英文字母、一個阿拉伯數字、一個標點符號、一個圖形符號或者控制符號等。

2. 字符集

字符集(Character Set):是指多個字符的集合。不同的字符集包含的字符個數不一樣、包含的字符不一樣、對字符的編碼方式也不一樣。例如GB2312是中國國家標准的簡體中文字符集,GB2312收錄簡化漢字(6763個)及一般符號、序號、數字、拉丁字母、日文假名、希臘字母、俄文字母、漢語拼音符號、漢語注音字母,共 7445 個圖形字符。而ASCII字符集只包含了128字符,這個字符集收錄的主要字符是英文字母、阿拉伯字母和一些簡單的控制字符。

另外,還有其他常用的字符集有 GBK字符集、GB18030字符集、Big5字符集、Unicode字符集等。

3. 字符編碼
字符編碼(Character Encoding):字符編碼是指一種映射規則,根據這個映射規則可以將某個字符映射成其他形式的數據以便在計算機中存儲和傳輸。例如ASCII字符編碼規定使用單字節中低位的7個比特去編碼所有的字符,在這個編碼規則下字母A的編號是65(ASCII碼),用單字節表示就是0x41,因此寫入存儲設備的時候就是二進制的 01000001。每種字符集都有自己的字符編碼規則,常用的字符集編碼規則還有 UTF-8編碼、GBK編碼、Big5編碼等。

4. 碼點
碼點(Code Point):是指在某個字符集中,根據某種編碼規則將字符編碼后得到的值。比如在ASCII字符集中,字母A經過ASCII編碼得到的值是65,那么65就是字符A在ASCII字符集中的碼點。

通過上面介紹,我們發現字符和碼點的概念比較好理解。字符集和字符編碼的概念可能會有點繞。

其實仔細看下上面的概念的我們會發現這是兩個完全不同的概念。字符集的話是一個字符的集合,而字符編碼則是一種規則(某個字符在計算機中怎么表示,具體占用幾個字節等),根據這個規則可以將某個字符映射成相應的碼值。

ASCII字符集

上個世紀60年代,美國制定了一套字符編碼規則,對英語字符與二進制位之間的關系做了統一規定,這編碼規則被稱為ASCII編碼,一直沿用至今。

ASCII編碼一共規定了128個字符的編碼規則,這128個字符形成的集合就叫做ASCII字符集。在ASCII編碼中,每個字符占用一個字節的后面7位,最前面的1位統一規定為0。在ASCII編碼中,0~31 是控制字符如換行回車刪除等,32~126 是可打印字符,可以通過鍵盤輸入並且能夠顯示出來。(下圖是ASCII字符集中字符和碼值的對應關系)

英語用128個符號編碼就夠了,但是用來表示其他語言,128個符號是不夠的。所以當ASCII碼到歐洲的時候,一些歐洲國家就決定對ASCII編碼進行適當的“改造”:利用字節中閑置的最高位編入新的符號。比如,法語中的é的編碼為130(二進制10000010)。這樣一來,這些歐洲國家使用的編碼體系,可以表示最多256個符號。這個編碼統稱為EASCII(Extended ASCII)。

但是歐洲的語言體系有個特點:小國家特別多,每個國家可能都有自己的語言體系,語言環境十分復雜。因此即使EASCII可以表示256個字符,也不能統一歐洲的語言環境。

為了解決上面這個問題,人們想出了一個折中的方案:在EASCII中表示的256個字符中,前128字符和ASCII編碼表示的字符完全一樣,后128個字符每個國家或地區都有自己的編碼標准。比如,130在法語編碼中代表了é,在希伯來語編碼中卻代表了字母Gimel (ג),在俄語編碼中又會代表另一個符號。但是不管怎樣,所有這些編碼方式中,0—127表示的符號是一樣的,不一樣的只是128—255的這一段。

根據這個規則,就形成了很多子標准:ISO-8859-1、ISO-8859-2、ISO-8859-3、……、ISO-8859-16。這些子標准適用於歐洲不同的國家地區。具體關於ISO-8859的標准請參考這個鏈接。這邊我摘錄了部分介紹。

ISO8859-1 字符集,也就是 Latin-1,是西歐常用字符,包括德法兩國的字母。
ISO8859-2 字符集,也稱為 Latin-2,收集了東歐字符。
ISO8859-3 字符集,也稱為 Latin-3,收集了南歐字符。

至於亞洲國家的文字,使用的符號就更多了,漢字就多達10萬左右。一個字節最多只能表示256種符號,肯定是不夠的,必須使用多個字節表達一個符號,因此才出現了后面的Unicode字符集和GB2312等字符集。比如,簡體中文常見的編碼方式是GB2312,使用兩個字節表示一個漢字,所以理論上最多可以表示65536個符號。

Unicode字符集

可以想象,如果有一種字符集,能將世界上所有的字符都納入其中,每一個字符都給予一個獨一無二的碼值,那么就不會出現上面ASCII編碼的問題了。Unicode就是這樣一種字符集。

Unicode字符集是一個很大的字符集合,現在的規模可以容納100多萬個符號。每個符號的編碼都不一樣,比如,U+0639表示阿拉伯字母Ain,U+0041表示英語的大寫字母A,U+4E25表示漢字“嚴”。

需要注意的是,Unicode只是一個字符集,它只規定了符號的二進制代碼,卻沒有規定這個二進制代碼應該如何編碼如何存儲。這就造成了兩個問題:

  • 第一個問題是,如何才能區別Unicode和ASCII?計算機怎么知道三個字節表示一個符號,而不是分別表示三個符號呢?
  • 第二個問題是,我們已經知道,英文字母只用一個字節表示就夠了,如果unicode統一規定,每個符號用三個或四個字節表示,那么每個英文字母前都必然有二到三個字節是0,這對於存儲來說是極大的浪費,文本文件的大小會因此大出二三倍,這是無法接受的。

為了解決Unicode字符集存在的問題,就出現了UTF(Unicode Transformation Formats)系列的編碼規則。UTF編碼規則具體規定了Unicode字符集中的字符是如何編碼的。

簡單總結下就是:Unicode是一個很大的字符集,這個字符集只規定了這個字符集中每個字符對應的碼值是多少,但是這個字符集並沒有規定具體的編碼規則,具體的編碼規則有UTF系列的編碼規則實現。

下面我們就來看看UTF系列編碼的具體實現。

UTF-8編碼

互聯網的普及,強烈要求出現一種統一的編碼方式。UTF-8就是在互聯網上使用最廣的一種Unicode的實現方式。其他實現方式還包括UTF-16和UTF-32,不過在互聯網上基本不用。重復一遍,這里的關系是:UTF-8編碼是Unicode的實現方式之一。

UTF-8(8-bit Unicode Transformation Format)是一種針對Unicode的可變長度字符編碼規則,又稱萬國碼。由Ken Thompson於1992年創建。現在已經標准化為RFC 3629。UTF-8用1到4個字節編碼Unicode字符。用在網頁上可以統一頁面顯示中文簡體繁體及其它語言(如英文,日文,韓文)。

UTF-8最大的一個特點,就是它是一種變長的編碼方式。它可以使用1~4個字節表示一個符號,根據不同的符號而變化字節長度(UTF-8編碼可以容納2^21個字符,總共200多萬個字符)

UTF-8的編碼規則很簡單,只有二條:

  1. 對於單字節的符號,字節的第一位設為0,后面7位為這個符號的unicode碼。因此對於英語字母,UTF-8編碼和ASCII碼是相同的。

  2. 對於n字節的符號(n>1),第一個字節的前n位都設為1,第n+1位設為0,后面字節的前兩位一律設為10。剩下的沒有提及的二進制位,全部為這個符號的unicode碼。

下表總結了編碼規則,字母x表示可用編碼的位。

Unicode符號范圍 | UTF-8編碼方式
UTF字節數   (十六進制) | (二進制)
一個字節 0000 0000-0000 007F | 0xxxxxxx
兩個字節 0000 0080-0000 07FF | 110xxxxx 10xxxxxx
三個字節 0000 0800-0000 FFFF | 1110xxxx 10xxxxxx 10xxxxxx
四個字節 0001 0000-0010 FFFF | 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx

下面, 還是以漢字“嚴”為例,演示如何實現UTF-8編碼。

已知“嚴”的unicode是\u4E25(100111000100101),根據上表,可以發現4E25處在第三行的范圍內(0000 0800-0000 FFFF),因此“嚴”的UTF-8編碼需要三個字節,即格式是“1110xxxx 10xxxxxx 10xxxxxx”。然后,從“嚴”的最后一個二進制位開始,依次從后向前填入格式中的x,多出的位補0。這樣就得到了,“嚴”的UTF-8編碼是“11100100 10111000 10100101”,轉換成十六進制就是E4B8A5。

UTF8、UTF16和UTF32之間的區別

再次強調一個概念:就是Unicode是一個字符集,這個字符集世界上所有的字符定義了一個唯一編碼。其僅僅規定了每個符號的二進制代碼,沒有制定細化的存儲規則。UTF-8、UTF-16、UTF-32才是Unicode的存儲格式定義。上面已經簡單介紹了UTF8編碼規則,那么這個規則和UTF16、UTF32等規則有什么區別呢?

UCS-2和UCS-4

在將UTF8和UTF16、UTF32的區別之前,再先科普兩個名詞:UCS-2和UCS-4。

Unicode是為整合全世界的所有語言文字而誕生的。任何文字在Unicode中都對應一個值, 這個值稱為代碼點(code point,也稱碼值)。代碼點的值通常寫成 U+ABCD 的格式。而文字和代碼點之間的對應關系就是UCS-2(Universal Character Set coded in 2 octets)。顧名思義,UCS-2是用兩個字節來表示代碼點,其取值范圍為 U+0000~U+FFFF。

為了能表示更多的文字,人們又提出了UCS-4,即用四個字節表示代碼點。它的范圍為 U+00000000~U+7FFFFFFF,其中 U+00000000~U+0000FFFF和UCS-2是一樣的。

要注意,UCS-2和UCS-4只規定了代碼點和文字之間的對應關系,並沒有規定代碼點在計算機中如何存儲。規定存儲方式的稱為UTF(Unicode Transformation Format),也就是我們上面提到的UTF8格式和下面將要提到的UTF16、UTF32格式。

UTF-16編碼格式

UTF-16由RFC2781規定,它使用兩個字節來表示一個代碼點。不難猜到,UTF-16是完全對應於UCS-2的,即把UCS-2規定的代碼點通過Big Endian或Little Endian方式直接保存下來。UTF-16包括三種:UTF-16,UTF-16BE(Big Endian)和UTF-16LE(Little Endian)。UTF-16BE和UTF-16LE不難理解,而UTF-16就需要通過在文件開頭以名為BOM(Byte Order Mark)的字符來表明文件是Big Endian還是Little Endian。BOM為U+FEFF這個字符。其實BOM是個小聰明的想法。由於UCS-2沒有定義U+FEFF,因此只要出現 FF FE 或者 FE FF 這樣的字節序列,就可以認為它是U+FEFF,並且可以判斷出是Big Endian還是Little Endian。

BOM(Byte Order Mark)用來放在文檔的開頭告訴閱讀器該文檔的字節序。UTF-8不需要BOM來表明字節順序,但可以用BOM來表明編碼方式。字符"ZERO WIDTH NO-BREAK SPACE"的UTF-8編碼是EF BB BF。所以如果接收者收到以EF BB BF開頭的字節流,就知道這是UTF-8編碼了。UTF-16才需要加BOM。因為它是按Unicode 順序編碼,在BMP范圍內是二字節,需要識別是大或小字節序。

這邊順便科普下大小端的概念。

低字節序(Little Endian)和高字節序(Big Endian)
低字節序和高字節序只是一個關於在內存中存儲和讀取一段字節(被稱作words)的約定。這意味着當你讓計算機用UTF-16把字母A(占兩個字節)存在內存中時,使用哪種字節序方案決定了你把第一個字節放在第二個字節的前面還是后面。這么說有點不太容易懂,讓我們來看一個例子:當你使用UTF-16存下某段內容時,在不同的系統中它的后半部分可能是這樣的:
00 68 00 65 00 6C 00 6C 00 6F(高字節序,高位字節被存在前面)
68 00 65 00 6C 00 6C 00 6F 00(低字節序,低位字節被存在前面)

字節序方案只是一個微處理器架構設計者的偏好問題,例如,Intel使用低字節序,Motorola使用高字節序。

舉個例子。“ABC”這三個字符用各種方式編碼后的結果如下:

編碼類型 碼值
UTF-16BE 00 41 00 42 00 43
UTF-16LE 41 00 42 00 43 00
UTF-16(Big Endian) FE FF 00 41 00 42 00 43
UTF-16(Little Endian) FF FE 41 00 42 00 43 00
UTF-16(不帶BOM) 00 41 00 42 00 43

UTF-32

UTF-32用四個字節表示代碼點,這樣就可以完全表示UCS-4的所有代碼點,而無需像UTF-8那樣使用復雜的算法。 與UTF-16類似,UTF-32也包括UTF-32、UTF-32BE、UTF-32LE三種編碼,UTF-32也同樣需要BOM字符。

文本編輯器怎么知道文本的編碼

當一個軟件打開一個文本時,它要做的第一件事是決定這個文本究竟是使用哪種字符集的哪種編碼保存的。軟件一般采用三種方式來決定文本的字符集和編碼:

  1. 檢測文件頭標識(BOM)

    EF   BB   BF   UTF-8      
    FE   FF   UTF-16/UCS-2,   big   endian      
    FF   FE   UTF-16/UCS-2,   little   endian      
    FF   FE   00   00   UTF-32/UCS-4,   little   endian.      
    00   00   FE   FF   UTF-32/UCS-4,   big-endian.

  2. 軟件自己根據編碼規則猜測當前文件的編碼

  3. 提示用戶自己輸入當前文件的編碼

GBK、GB2312和GB18030之間的區別

GB2312編碼是第一個漢字編碼國家標准,是由中國國家標准總局1980年發布,1981年5月1日開始實施的一套國家標准,標准號是GB2312—1980。GB2312編碼適用於漢字處理、漢字通信等系統之間的信息交換,通行於中國大陸;新加坡等地也采用此編碼。中國大陸幾乎所有的中文系統和國際化的軟件都支持GB2312。GB2312編碼共收錄漢字6763個,其中一級漢字3755個,二級漢字3008個。同時,GB2312編碼收錄了包括拉丁字母、希臘字母、日文平假名及片假名字母、俄語西里爾字母在內的682個全角字符。

GB2312是對ASCll碼的擴展,占用兩個字節。具體的編碼規則這邊就不介紹了,感興趣的讀者可以參考這篇博客

GB2312能表示的漢字只有6000多個,但是中國的漢字有10萬之多,所以GB2312字符集還是不夠用,於是GBK出現了。GBK是對GB1212的擴展,也是占用2個字節,GBK不再要求低字節一定是127號之后的內碼,只要第一個字節是大於127就固定表示這是一個漢字的開始,不管后面跟的是不是擴展字符集里的內容。GBK 包括了 GB2312 的所有內容,同時又增加了近20000個新的漢字(包括繁體字)和符號。

GB18030采用變長編碼,可以是1個字節、2個字節和4個字節。是對GB2312和GBK的擴展,完全兼容兩者。

通過上面介紹,可以發現GBK、GB2312和GB18030字符集主要是對中文漢字的編碼,同時兼顧了一些其他常用符號的編碼。其中:

  • GB2312編碼方案出現最早,占用2個字節,但是能表示的字符較少;
  • GBK也占用2個字節,采用了不同的編碼方式,對GB2312進行了擴展,增加了近20000個新的漢字(包括繁體字)和符號;
  • GB18030采用變長編碼,可以是1個字節、2個字節和4個字節。是對GB2312和GBK的擴展,完全兼容兩者。

Java中的編碼問題

我們知道涉及到編碼問題的地方一般都發生在字符到字節或者字節到字符的轉換上,而需要這種轉換的場景主要是在IO的時候,這個IO包括磁盤IO和網絡IO。而大部分引起亂碼的IO都是網絡IO。

用戶從瀏覽器端發起一個 HTTP 請求,需要編碼的地方是 URL、Cookie、Parameter。服務器端接受到 HTTP 請求后要解析 HTTP 協議,其中 URI、Cookie 和 POST 表單參數需要解碼,服務器端可能還需要讀取數據庫中的數據,本地或網絡中其它地方的文本文件,這些數據都可能存在編碼問題,當 Servlet 處理完所有請求的數據后,需要將這些數據再編碼通過 Socket 發送到用戶請求的瀏覽器里,再經過瀏覽器解碼成為文本。這些過程如下圖所示:

如上圖所示一次 HTTP 請求設計到很多地方需要編解碼,它們編解碼的規則是什么?下面將會重點闡述:

URL的編解碼

用戶提交一個 URL,這個 URL 中可能存在中文,因此需要對URL進行編碼。那么具體如何對這個 URL 進行編碼?根據什么規則來編碼?又如何來解碼?下面就來分析這塊。

如下圖一個 URL:

Port 對應在 Tomcat 的 <Connector port="8080"/> 中配置,而 Context Path 在 <Context path="/examples"/> 中配置,Servlet Path 在 Web 應用的 web.xml 中的

    <servlet-mapping> 
            <servlet-name>junshanExample</servlet-name> 
            <url-pattern>/servlets/servlet/*</url-pattern> 
     </servlet-mapping> 

中配置,PathInfo 是我們請求的具體的 Servlet,QueryString 是要傳遞的參數,注意這里是在瀏覽器里直接輸入 URL 所以是通過 Get 方法請求的,如果是 POST 方法請求的話,QueryString 將通過表單方式提交到服務器端,這個將在后面再介紹。

上圖中 PathInfo 和 QueryString 出現了中文,當我們在瀏覽器中直接輸入這個 URL 時,在瀏覽器端和服務端會如何編碼和解析這個 URL 呢?為了驗證瀏覽器是怎么編碼 URL 的我們選擇 FireFox 瀏覽器並通過 HTTPFox 插件觀察我們請求的 URL 的實際的內容,以下是 URL:HTTP://localhost:8080/examples/servlets/servlet/君山?author= 君山 在中文 FireFox3.6.12 的測試結果:

君山的編碼結果分別是:e5 90 9b e5 b1 b1,be fd c9 bd,查閱編碼可知,PathInfo 是 UTF-8 編碼而 QueryString 是經過 GBK 編碼,至於為什么會有“%”?查閱 URL 的編碼規范 RFC3986 可知瀏覽器編碼 URL 是將非 ASCII 字符按照某種編碼格式編碼成 16 進制數字然后將每個 16 進制表示的字節前加上“%”,所以最終的 URL 就成了上圖的格式了。

從上面測試結果可知瀏覽器對 PathInfo 和 QueryString 的編碼可能是不一樣的,不同瀏覽器對 PathInfo 也可能不一樣(我另外還做了下測試,發現Chrome瀏覽器對PathInfo 和 QueryString進行URL編碼是默認都是使用的UTF8編碼格式,大家感興趣的可以自己驗證下),這就對服務器的解碼造成很大的困難,下面我們以 Tomcat 為例看一下,Tomcat 接受到這個 URL 是如何解碼的。

protected void convertURI(MessageBytes uri, Request request) 
 throws Exception { 
        ByteChunk bc = uri.getByteChunk(); 
        int length = bc.getLength(); 
        CharChunk cc = uri.getCharChunk(); 
        cc.allocate(length, -1); 
        String enc = connector.getURIEncoding(); 
        if (enc != null) { 
            B2CConverter conv = request.getURIConverter(); 
            try { 
                if (conv == null) { 
                    conv = new B2CConverter(enc); 
                    request.setURIConverter(conv); 
                } 
            } catch (IOException e) {...} 
            if (conv != null) { 
                try { 
                    conv.convert(bc, cc, cc.getBuffer().length - 
 cc.getEnd()); 
                    uri.setChars(cc.getBuffer(), cc.getStart(), 
 cc.getLength()); 
                    return; 
                } catch (IOException e) {...} 
            } 
        } 
        // Default encoding: fast conversion 
        byte[] bbuf = bc.getBuffer(); 
        char[] cbuf = cc.getBuffer(); 
        int start = bc.getStart(); 
        for (int i = 0; i < length; i++) { 
            cbuf[i] = (char) (bbuf[i + start] & 0xff); 
        } 
        uri.setChars(cbuf, 0, length); 
 }

從上面的代碼中可以知道對 URL 的 URI 部分進行解碼的字符集是在connector的 中定義的,如果沒有定義,那么將以默認編碼 ISO-8859-1 解析。所以如果有中文 URL 時最好把 URIEncoding 設置成 UTF-8 編碼。

QueryString 又如何解析? GET 方式 HTTP 請求的 QueryString 與 POST 方式 HTTP 請求的表單參數都是作為 Parameters 保存,都是通過 request.getParameter 獲取參數值。對它們的解碼是在 request.getParameter 方法第一次被調用時進行的。request.getParameter 方法被調用時將會調用 org.apache.catalina.connector.Request 的 parseParameters 方法。這個方法將會對 GET 和 POST 方式傳遞的參數進行解碼,但是它們的解碼字符集有可能不一樣。POST 表單的解碼將在后面介紹,QueryString 的解碼字符集是在哪定義的呢?它本身是通過 HTTP 的 Header 傳到服務端的,並且也在 URL 中,是否和 URI 的解碼字符集一樣呢?從前面瀏覽器對 PathInfo 和 QueryString 的編碼采取不同的編碼格式不同可以猜測到解碼字符集肯定也不會是一致的。的確是這樣 QueryString 的解碼字符集要么是 Header 中 ContentType 中定義的 Charset 要么就是默認的 ISO-8859-1,要使用 ContentType 中定義的編碼就要設置 connector 的 中的 useBodyEncodingForURI 設置為 true。這個配置項的名字有點讓人產生混淆,它並不是對整個 URI 都采用 BodyEncoding 進行解碼而僅僅是對 QueryString 使用 BodyEncoding 解碼,這一點還要特別注意。

從上面的 URL 編碼和解碼過程來看,比較復雜,而且編碼和解碼並不是我們在應用程序中能完全控制的,所以在我們的應用程序中應該盡量避免在 URL 中使用非 ASCII 字符,不然很可能會碰到亂碼問題,當然在我們的服務器端最好設置 中的 URIEncoding 和 useBodyEncodingForURI 兩個參數。

HTTP Header 的編解碼

當客戶端發起一個 HTTP 請求除了上面的 URL 外還可能會在 Header 中傳遞其它參數如 Cookie、redirectPath 等,這些用戶設置的值很可能也會存在編碼問題,Tomcat 對它們又是怎么解碼的呢?

對 Header 中的項進行解碼也是在調用 request.getHeader 是進行的,如果請求的 Header 項沒有解碼則調用 MessageBytes 的 toString 方法,這個方法將從 byte 到 char 的轉化使用的默認編碼也是 ISO-8859-1,而我們也不能設置 Header 的其它解碼格式,所以如果你設置 Header 中有非 ASCII 字符解碼肯定會有亂碼。

我們在添加 Header 時也是同樣的道理,不要在 Header 中傳遞非 ASCII 字符,如果一定要傳遞的話,我們可以先將這些字符用 org.apache.catalina.util.URLEncoder 編碼然后再添加到 Header 中,這樣在瀏覽器到服務器的傳遞過程中就不會丟失信息了,如果我們要訪問這些項時再按照相應的字符集解碼就好了。

POST 表單的編解碼

在前面提到了 POST 表單提交的參數的解碼是在第一次調用 request.getParameter 發生的,POST 表單參數傳遞方式與 QueryString 不同,它是通過 HTTP 的 BODY 傳遞到服務端的。當我們在頁面上點擊 submit 按鈕時瀏覽器首先將根據 ContentType 的 Charset 編碼格式對表單填的參數進行編碼然后提交到服務器端,在服務器端同樣也是用 ContentType 中字符集進行解碼。所以通過 POST 表單提交的參數一般不會出現問題,而且這個字符集編碼是我們自己設置的,可以通過 request.setCharacterEncoding(charset) 來設置。

另外針對 multipart/form-data 類型的參數,也就是上傳的文件編碼同樣也是使用 ContentType 定義的字符集編碼,值得注意的地方是上傳文件是用字節流的方式傳輸到服務器的本地臨時目錄,這個過程並沒有涉及到字符編碼,而真正編碼是在將文件內容添加到 parameters 中,如果用這個編碼不能編碼時將會用默認編碼 ISO-8859-1 來編碼。

HTTP BODY 的編解碼

當用戶請求的資源已經成功獲取后,這些內容將通過 Response 返回給客戶端瀏覽器,這個過程先要經過編碼再到瀏覽器進行解碼。這個過程的編解碼字符集可以通過 response.setCharacterEncoding 來設置,它將會覆蓋 request.getCharacterEncoding 的值,並且通過 Header 的 Content-Type 返回客戶端,瀏覽器接受到返回的 socket 流時將通過 Content-Type 的 charset 來解碼,如果返回的 HTTP Header 中 Content-Type 沒有設置 charset,那么瀏覽器將根據 Html 的 中的 charset 來解碼。如果也沒有定義的話,那么瀏覽器將使用默認的編碼來解碼。

其他需要注意編碼的地方

除了 URL 和參數編碼問題外,在服務端還有很多地方可能存在編碼,如可能需要讀取xml、velocity模版引擎、JSP或者從數據庫讀取數據等。

xml 文件可以通過設置頭來制定編碼格式

    <?xml version="1.0" encoding="UTF-8"?> 

Velocity 模版設置編碼格式:

services.VelocityService.input.encoding=UTF-8 

JSP 設置編碼格式:

<%@page contentType="text/html; charset=UTF-8"%>

訪問數據庫都是通過客戶端 JDBC 驅動來完成,用 JDBC 來存取數據要和數據的內置編碼保持一致,可以通過設置 JDBC URL 來制定如 MySQL:

url="jdbc:mysql://localhost:3306/DB?useUnicode=true&characterEncoding=GBK"

亂碼問題分析

下面看一下,當我們碰到一些亂碼時,應該怎么處理這些問題?出現亂碼問題唯一的原因都是在 char 到 byte 或 byte 到 char 轉換中編碼和解碼的字符集不一致導致的,由於往往一次操作涉及到多次編解碼,所以出現亂碼時很難查找到底是哪個環節出現了問題。根據自己的經驗,往往從最源頭開始一步步查原因是最快的。

Java最多支持65535個字符

目前用於實用的 Unicode 版本對應於 UCS-2,使用16位的編碼空間,因此最大能表示65535個字符。而Java中使用的字符集就是UCS-2,占用兩個字節,所以最多支持65535個字符。

參考

公眾號推薦

歡迎大家關注我的微信公眾號「程序員自由之路」


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM