溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

Java字符串編碼的知識點有哪些

發布時間:2022-02-28 10:22:44 來源:億速云 閱讀:169 作者:iii 欄目:開發技術

這篇文章主要介紹“Java字符串編碼的知識點有哪些”的相關知識,小編通過實際案例向大家展示操作過程,操作方法簡單快捷,實用性強,希望這篇“Java字符串編碼的知識點有哪些”文章能幫助大家解決問題。

    一、為什么要編碼

    不知道大家有沒有想過一個問題,那就是為什么要編碼?我們能不能不編碼?要回答這個問題必須要回到計算機是如何表示我們人類能夠理解的符號的,這些符號也就是我們人類使用的語言。由于人類的語言有太多,因而表示這些語言的符號太多,無法用計算機中一個基本的存儲單元—— byte 來表示,因而必須要經過拆分或一些翻譯工作,才能讓計算機能理解。我們可以把計算機能夠理解的語言假定為英語,其它語言要能夠在計算機中使用必須經過一次翻譯,把它翻譯成英語。這個翻譯的過程就是編碼。所以可以想象只要不是說英語的國家要能夠使用計算機就必須要經過編碼。這看起來有些霸道,但是這就是現狀,這也和我們國家現在在大力推廣漢語一樣,希望其它國家都會說漢語,以后其它的語言都翻譯成漢語,我們可以把計算機中存儲信息的最小單位改成漢字,這樣我們就不存在編碼問題了。

    所以總的來說,編碼的原因可以總結為:

    • 計算機中存儲信息的最小單元是一個字節即 8 個 bit,所以能表示的字符范圍是 0~255 個

    • 人類要表示的符號太多,無法用一個字節來完全表示,要解決這個矛盾必須需要一個新的數據結構 char,從 char 到 byte 必須編碼

    二、如何“翻譯”

    明白了各種語言需要交流,經過翻譯是必要的,那又如何來翻譯呢?計算中提拱了多種翻譯方式,常見的有 ASCII、ISO-8859-1、GB2312、GBK、UTF-8、UTF-16 等。它們都可以被看作為字典,它們規定了轉化的規則,按照這個規則就可以讓計算機正確的表示我們的字符。目前的編碼格式很多,例如 GB2312、GBK、UTF-8、UTF-16 這幾種格式都可以表示一個漢字,那我們到底選擇哪種編碼格式來存儲漢字呢?這就要考慮到其它因素了,是存儲空間重要還是編碼的效率重要。根據這些因素來正確選擇編碼格式,下面簡要介紹一下這幾種編碼格式。

    • ASCII 碼

    學過計算機的人都知道 ASCII 碼,總共有 128 個(0-127),用一個字節的低 7 位表示,0~31 是控制字符如換行回車刪除等;32~126 是打印字符,可以通過鍵盤輸入并且能夠顯示出來。

    其中48~57為0到9十個阿拉伯數字
    65~90為26個大寫英文字母
    97~122號為26個小寫英文字母

    • ISO-8859-1

    128 個字符顯然是不夠用的,于是 ISO 組織在 ASCII 碼基礎上又制定了一些列標準用來擴展 ASCII 編碼,它們是 ISO-8859-1~ISO-8859-15,其中 ISO-8859-1 涵蓋了大多數西歐語言字符,所有應用的最廣泛。ISO-8859-1 仍然是單字節編碼,它總共能表示 256 個字符。

    • GB2312

    它的全稱是《信息交換用漢字編碼字符集 基本集》,它是雙字節編碼,總的編碼范圍是 A1-F7,其中從 A1-A9 是符號區,總共包含 682 個符號,從 B0-F7 是漢字區,包含 6763 個漢字。

    • GBK

    全稱叫《漢字內碼擴展規范》,是國家技術監督局為 windows95 所制定的新的漢字內碼規范,它的出現是為了擴展 GB2312,加入更多的漢字,它的編碼范圍是 8140~FEFE(去掉 XX7F)總共有 23940 個碼位,它能表示 21003 個漢字,它的編碼是和 GB2312 兼容的,也就是說用 GB2312 編碼的漢字可以用 GBK 來解碼,并且不會有亂碼。

    • GB18030

    全稱是《信息交換用漢字編碼字符集》,是我國的強制標準,它可能是單字節、雙字節或者四字節編碼,它的編碼與 GB2312 編碼兼容,這個雖然是國家標準,但是實際應用系統中使用的并不廣泛。

    • UTF-16

    說到 UTF 必須要提到 Unicode(Universal Code 統一碼),ISO 試圖想創建一個全新的超語言字典,世界上所有的語言都可以通過這本字典來相互翻譯??上攵@個字典是多么的復雜,關于 Unicode 的詳細規范可以參考相應文檔。Unicode 是 Java 和 XML 的基礎,下面詳細介紹 Unicode 在計算機中的存儲形式。

    UTF-16 具體定義了 Unicode 字符在計算機中存取方法。UTF-16 用兩個字節來表示 Unicode 轉化格式,這個是定長的表示方法,不論什么字符都可以用兩個字節表示,兩個字節是 16 個 bit,所以叫 UTF-16。UTF-16 表示字符非常方便,每兩個字節表示一個字符,這個在字符串操作時就大大簡化了操作,這也是 Java 以 UTF-16 作為內存的字符存儲格式的一個很重要的原因。

    • UTF-8

    UTF-16 統一采用兩個字節表示一個字符,雖然在表示上非常簡單方便,但是也有其缺點,有很大一部分字符用一個字節就可以表示的現在要兩個字節表示,存儲空間放大了一倍,在現在的網絡帶寬還非常有限的今天,這樣會增大網絡傳輸的流量,而且也沒必要。而 UTF-8 采用了一種變長技術,每個編碼區域有不同的字碼長度。不同類型的字符可以是由 1~6 個字節組成。
    UTF-8 有以下編碼規則:

    1、如果一個字節,最高位(第 8 位)為 0,表示這是一個 ASCII 字符(00 - 7F)??梢?,所有 ASCII 編碼已經是 UTF-8 了。
    2、如果一個字節,以 11 開頭,連續的 1 的個數暗示這個字符的字節數,例如:110xxxxx 代表它是雙字節 UTF-8 字符的首字節。
    3、如果一個字節,以 10 開始,表示它不是首字節,需要向前查找才能得到當前字符的首字節

    三、Java 中需要編碼的場景

    前面描述了常見的幾種編碼格式,下面將介紹 Java 中如何處理對編碼的支持,什么場合中需要編碼。

    3.1 I/O 操作中存在的編碼

    我們知道涉及到編碼的地方一般都在字符到字節或者字節到字符的轉換上,而需要這種轉換的場景主要是在 I/O 的時候,這個 I/O 包括磁盤 I/O 和網絡 I/O,關于網絡 I/O 部分在后面將主要以 Web 應用為例介紹。

    Reader 類是 Java 的 I/O 中讀字符的父類,而 InputStream 類是讀字節的父類,InputStreamReader 類就是關聯字節到字符的橋梁,它負責在 I/O 過程中處理讀取字節到字符的轉換,而具體字節到字符的解碼實現它由 StreamDecoder 去實現,在 StreamDecoder 解碼過程中必須由用戶指定 Charset 編碼格式。值得注意的是如果你沒有指定 Charset,將使用本地環境中的默認字符集,例如在中文環境中將使用 GBK 編碼。

    寫的情況也是類似,字符的父類是 Writer,字節的父類是 OutputStream,通過 OutputStreamWriter 轉換字符到字節。

    同樣 StreamEncoder 類負責將字符編碼成字節,編碼格式和默認編碼規則與解碼是一致的。

    如下面一段代碼,實現了文件的讀寫功能:

    String file = "c:/stream.txt"; 
    String charset = "UTF-8"; 
    // 寫字符換轉成字節流
    FileOutputStream outputStream = new FileOutputStream(file); 
    OutputStreamWriter writer = new OutputStreamWriter( 
    outputStream, charset); 
    try { 
       writer.write("這是要保存的中文字符"); 
    } finally { 
       writer.close(); 
    } 
    // 讀取字節轉換成字符
    FileInputStream inputStream = new FileInputStream(file); 
    InputStreamReader reader = new InputStreamReader( 
    inputStream, charset); 
    StringBuffer buffer = new StringBuffer(); 
    char[] buf = new char[64]; 
    int count = 0; 
    try { 
       while ((count = reader.read(buf)) != -1) { 
           buffer.append(buffer, 0, count); 
       } 
    } finally { 
       reader.close(); 
    }

    在我們的應用程序中涉及到 I/O 操作時只要注意指定統一的編解碼 Charset 字符集,一般不會出現亂碼問題,有些應用程序如果不注意指定字符編碼,中文環境中取操作系統默認編碼,如果編解碼都在中文環境中,通常也沒問題,但是還是強烈的不建議使用操作系統的默認編碼,因為這樣,你的應用程序的編碼格式就和運行環境綁定起來了,在跨環境下很可能出現亂碼問題。

    3.2 內存中操作中的編碼

    在 Java 開發中除了 I/O 涉及到編碼外,最常用的應該就是在內存中進行字符到字節的數據類型的轉換,Java 中用 String 表示字符串,所以 String 類就提供轉換到字節的方法,也支持將字節轉換為字符串的構造函數。如下代碼示例:

    String s = "這是一段中文字符串"; 
    byte[] b = s.getBytes("UTF-8"); 
    String n = new String(b,"UTF-8");

    另外一個是已經被被廢棄的 ByteToCharConverter 和 CharToByteConverter 類,它們分別提供了 convertAll 方法可以實現 byte[] 和 char[] 的互轉。如下代碼所示:

    ByteToCharConverter charConverter = ByteToCharConverter.getConverter("UTF-8"); 
    char c[] = charConverter.convertAll(byteArray); 
    CharToByteConverter byteConverter = CharToByteConverter.getConverter("UTF-8"); 
    byte[] b = byteConverter.convertAll(c);

    這兩個類已經被 Charset 類取代,Charset 提供 encode 與 decode 分別對應 char[] 到 byte[] 的編碼和 byte[] 到 char[] 的解碼。如下代碼所示:

    Charset charset = Charset.forName("UTF-8"); 
    ByteBuffer byteBuffer = charset.encode(string); 
    CharBuffer charBuffer = charset.decode(byteBuffer);

    編碼與解碼都在一個類中完成,通過 forName 設置編解碼字符集,這樣更容易統一編碼格式,比 ByteToCharConverter 和 CharToByteConverter 類更方便。

    Java 中還有一個 ByteBuffer 類,它提供一種 char 和 byte 之間的軟轉換,它們之間轉換不需要編碼與解碼,只是把一個 16bit 的 char 格式,拆分成為 2 個 8bit 的 byte 表示,它們的實際值并沒有被修改,僅僅是數據的類型做了轉換。如下代碼所以:

    ByteBuffer heapByteBuffer = ByteBuffer.allocate(1024); 
    ByteBuffer byteBuffer = heapByteBuffer.putChar(c);

    以上這些提供字符和字節之間的相互轉換只要我們設置編解碼格式統一一般都不會出現問題。

    四、Java 中如何編解碼

    前面介紹了幾種常見的編碼格式,這里將以實際例子介紹 Java 中如何實現編碼及解碼,下面我們以“I am 君山”這個字符串為例介紹 Java 中如何把它以 ISO-8859-1、GB2312、GBK、UTF-16、UTF-8 編碼格式進行編碼的。

    public static void encode() { 
           String name = "I am 君山"; 
           toHex(name.toCharArray()); 
           try { 
               byte[] iso8859 = name.getBytes("ISO-8859-1"); 
               toHex(iso8859); 
               byte[] gb2312 = name.getBytes("GB2312"); 
               toHex(gb2312); 
               byte[] gbk = name.getBytes("GBK"); 
               toHex(gbk); 
               byte[] utf16 = name.getBytes("UTF-16"); 
               toHex(utf16); 
               byte[] utf8 = name.getBytes("UTF-8"); 
               toHex(utf8); 
           } catch (UnsupportedEncodingException e) { 
               e.printStackTrace(); 
           } 
    }

    我們把 name 字符串按照前面說的幾種編碼格式進行編碼轉化成 byte 數組,然后以 16 進制輸出,我們先看一下 Java 是如何進行編碼的。

    首先根據指定的 charsetName 通過 Charset.forName(charsetName) 設置 Charset 類,然后根據 Charset 創建 CharsetEncoder 對象,再調用 CharsetEncoder.encode 對字符串進行編碼,不同的編碼類型都會對應到一個類中,實際的編碼過程是在這些類中完成的。

    如字符串“I am 君山”的 char 數組為 49 20 61 6d 20 541b 5c71,下面把它按照不同的編碼格式轉化成相應的字節。

    4.1 按照 ISO-8859-1 編碼

    字符串“I am 君山”用 ISO-8859-1 編碼

    從上圖看出 7 個 char 字符經過 ISO-8859-1 編碼轉變成 7 個 byte 數組,ISO-8859-1 是單字節編碼,中文“君山”被轉化成值是 3f 的 byte。3f 也就是“?”字符,所以經常會出現中文變成“?”很可能就是錯誤的使用了 ISO-8859-1 這個編碼導致的。中文字符經過 ISO-8859-1 編碼會丟失信息,通常我們稱之為“黑洞”,它會把不認識的字符吸收掉。由于現在大部分基礎的 Java 框架或系統默認的字符集編碼都是 ISO-8859-1,所以很容易出現亂碼問題,后面將會分析不同的亂碼形式是怎么出現的。

    4.2 按照 GB2312 編碼

    字符串“I am 君山”用 GB2312 編碼

    GB2312 對應的 Charset 是 sun.nio.cs.ext. EUC_CN 而對應的 CharsetDecoder 編碼類是 sun.nio.cs.ext. DoubleByte,

    GB2312 字符集有一個 char 到 byte 的碼表,不同的字符編碼就是查這個碼表找到與每個字符的對應的字節,然后拼裝成 byte 數組。查表的規則如下:

    c2b[c2bIndex[char >> 8] + (char & 0xff)]

    如果查到的碼位值大于 oxff 則是雙字節,否則是單字節。雙字節高 8 位作為第一個字節,低 8 位作為第二個字節,如下代碼所示:

    if (bb > 0xff) {    // DoubleByte 
               if (dl - dp < 2) 
                   return CoderResult.OVERFLOW; 
               da[dp++] = (byte) (bb >> 8); 
               da[dp++] = (byte) bb; 
    } else {                      // SingleByte 
               if (dl - dp < 1) 
                   return CoderResult.OVERFLOW; 
               da[dp++] = (byte) bb; 
    }

    從上圖可以看出前 5 個字符經過編碼后仍然是 5 個字節,而漢字被編碼成雙字節,在第一節中介紹到 GB2312 只支持 6763 個漢字,所以并不是所有漢字都能夠用 GB2312 編碼。

    4.3 按照 GBK 編碼

    字符串“I am 君山”用 GBK 編碼

    你可能已經發現上圖與 GB2312 編碼的結果是一樣的,沒錯 GBK 與 GB2312 編碼結果是一樣的,由此可以得出 GBK 編碼是兼容 GB2312 編碼的,它們的編碼算法也是一樣的。不同的是它們的碼表長度不一樣,GBK 包含的漢字字符更多。所以只要是經過 GB2312 編碼的漢字都可以用 GBK 進行解碼,反過來則不然。

    4.4 按照 UTF-16 編碼

    字符串“I am 君山”用 UTF-16 編碼

    用 UTF-16 編碼將 char 數組放大了一倍,單字節范圍內的字符,在高位補 0 變成兩個字節,中文字符也變成兩個字節。從 UTF-16 編碼規則來看,僅僅將字符的高位和地位進行拆分變成兩個字節。特點是編碼效率非常高,規則很簡單,由于不同處理器對 2 字節處理方式不同,Big-endian(高位字節在前,低位字節在后)或 Little-endian(低位字節在前,高位字節在后)編碼,所以在對一串字符串進行編碼是需要指明到底是 Big-endian 還是 Little-endian,所以前面有兩個字節用來保存 BYTE_ORDER_MARK 值,UTF-16 是用定長 16 位(2 字節)來表示的 UCS-2 或 Unicode 轉換格式,通過代理對來訪問 BMP 之外的字符編碼。

    4.5 按照 UTF-8 編碼

    字符串“I am 君山”用 UTF-8 編碼,下面是編碼結果:

    Java字符串編碼的知識點有哪些

    UTF-16 雖然編碼效率很高,但是對單字節范圍內字符也放大了一倍,這無形也浪費了存儲空間,另外 UTF-16 采用順序編碼,不能對單個字符的編碼值進行校驗,如果中間的一個字符碼值損壞,后面的所有碼值都將受影響。而 UTF-8 這些問題都不存在,UTF-8 對單字節范圍內字符仍然用一個字節表示,對漢字采用三個字節表示。它的編碼規則如下:

    private CoderResult encodeArrayLoop(CharBuffer src, 
    ByteBuffer dst){ 
               char[] sa = src.array(); 
               int sp = src.arrayOffset() + src.position(); 
               int sl = src.arrayOffset() + src.limit(); 
               byte[] da = dst.array(); 
               int dp = dst.arrayOffset() + dst.position(); 
               int dl = dst.arrayOffset() + dst.limit(); 
               int dlASCII = dp + Math.min(sl - sp, dl - dp); 
               // ASCII only loop 
               while (dp < dlASCII && sa[sp] < 'u0080') 
                   da[dp++] = (byte) sa[sp++]; 
               while (sp < sl) { 
                   char c = sa[sp]; 
                   if (c < 0x80) { 
                       // Have at most seven bits 
                       if (dp >= dl) 
                           return overflow(src, sp, dst, dp); 
                       da[dp++] = (byte)c; 
                   } else if (c < 0x800) { 
                       // 2 bytes, 11 bits 
                       if (dl - dp < 2) 
                           return overflow(src, sp, dst, dp); 
                       da[dp++] = (byte)(0xc0 | (c >> 6)); 
                       da[dp++] = (byte)(0x80 | (c & 0x3f)); 
                   } else if (Character.isSurrogate(c)) { 
                       // Have a surrogate pair 
                       if (sgp == null) 
                           sgp = new Surrogate.Parser(); 
                       int uc = sgp.parse(c, sa, sp, sl); 
                       if (uc < 0) { 
                           updatePositions(src, sp, dst, dp); 
                           return sgp.error(); 
                       } 
                       if (dl - dp < 4) 
                           return overflow(src, sp, dst, dp); 
                       da[dp++] = (byte)(0xf0 | ((uc >> 18))); 
                       da[dp++] = (byte)(0x80 | ((uc >> 12) & 0x3f)); 
                       da[dp++] = (byte)(0x80 | ((uc >>  6) & 0x3f)); 
                       da[dp++] = (byte)(0x80 | (uc & 0x3f)); 
                       sp++;  // 2 chars 
                   } else { 
                       // 3 bytes, 16 bits 
                       if (dl - dp < 3) 
                           return overflow(src, sp, dst, dp); 
                       da[dp++] = (byte)(0xe0 | ((c >> 12))); 
                       da[dp++] = (byte)(0x80 | ((c >>  6) & 0x3f)); 
                       da[dp++] = (byte)(0x80 | (c & 0x3f)); 
                   } 
                   sp++; 
               } 
               updatePositions(src, sp, dst, dp); 
               return CoderResult.UNDERFLOW;

    UTF-8 編碼與 GBK 和 GB2312 不同,不用查碼表,所以在編碼效率上 UTF-8 的效率會更好,所以在存儲中文字符時 UTF-8 編碼比較理想。

    五、幾種編碼格式的比較

    對中文字符后面四種編碼格式都能處理,GB2312 與 GBK 編碼規則類似,但是 GBK 范圍更大,它能處理所有漢字字符,所以 GB2312 與 GBK 比較應該選擇 GBK。UTF-16 與 UTF-8 都是處理 Unicode 編碼,它們的編碼規則不太相同,相對來說 UTF-16 編碼效率最高,字符到字節相互轉換更簡單,進行字符串操作也更好。它適合在本地磁盤和內存之間使用,可以進行字符和字節之間快速切換,如 Java 的內存編碼就是采用 UTF-16 編碼。但是它不適合在網絡之間傳輸,因為網絡傳輸容易損壞字節流,一旦字節流損壞將很難恢復,想比較而言 UTF-8 更適合網絡傳輸,對 ASCII 字符采用單字節存儲,另外單個字符損壞也不會影響后面其它字符,在編碼效率上介于 GBK 和 UTF-16 之間,所以 UTF-8 在編碼效率上和編碼安全性上做了平衡,是理想的中文編碼方式。

    關于“Java字符串編碼的知識點有哪些”的內容就介紹到這里了,感謝大家的閱讀。如果想了解更多行業相關的知識,可以關注億速云行業資訊頻道,小編每天都會為大家更新不同的知識點。

    向AI問一下細節

    免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。

    AI

    亚洲午夜精品一区二区_中文无码日韩欧免_久久香蕉精品视频_欧美主播一区二区三区美女