1. 程式人生 > >java梳理-一個漢字占多大空間

java梳理-一個漢字占多大空間

創建 tex otto new lov lis develop eas XML

面試題:一個漢字占多大空間。事實上這個問題我了解不深的,知道結論不知道為什麽。借此梳理下認識。


先回想下java基本類型
一基本類型 :
簡稱四類八種,聲明變量的同一時候分配了空間。舉比例如以下: Int a =1;
一、4種整型
byte 1字節 -128——127
short 2 字節 -32,768 —— 32,767
int 4 字節 -2,147,483,648 ——2,147,483,647(超過20億)
long 8 字節 -9,223,372,036,854,775,808——9,223,372,036854,775,807
凝視:java中全部的數據類所占領的字節數量與平臺無關,java也沒有任何無符號類型
二、 2種浮點類型
float 4 字節 32位IEEE 754單精度(有效位數 6 – 7位)
double 8 字節 64位IEEE 754雙精度(有效位數15位)
三、1種Unicode編碼的字符單元
char 2 字節 整個Unicode字符集
四、1種真值類型

boolean 1 位 True或者false 二 引用類型:除了四類八種基本類型外,全部的類型都稱為引用類型:類class 、接口interface 、數組array
基本類型是值傳遞。引用類型是引用傳遞。


MyDate a,b。 //在內存開辟兩個引用空間

a = new MyDate(); //開辟MyDate對象的數據空間,並把該空間的首地址賦給a

b = a。 //將a存儲空間中的地址寫到b的存儲空間中

一個具有值類型(value type)的數據存放在棧內的一個變量中。即是在棧中分配內存空間。直接存儲所包括的值,其值就代表數據本身。值類型的數據具有較快的存取速度。

一個具有引用類型(reference type)的數據並不駐留在棧中,而是存儲於堆中。即是在堆中分配內存空間,不直接存儲所包括的值,而是指向所要存儲的值,其值代表的是所指向的地址。當訪問一個具有引用類型的數據時。須要到棧中檢查變量的內容,該變量引用堆中的一個實際數據。引用類型的數據比值類型的數據具有更大的存儲規模和較低的訪問速度。

************************背景結束。正文開始************************

事實上關於上面引用類型如:String,還有常見集合有非常多細化知識點,本篇寫不完。

看下問題的簡單回答:

簡單漢字:utf-8 的漢字占3個字節  gbk 是兩個字節。 
  先了解下主要的單位:
1、比特(bit)即一個二進制位。比如100011就是6比特。
2、字節(byte)。這是計算機中數據類型最主要的單位了,8bit組成1byte。 為什麽要編碼?原因就是: 1)一個字節一共能夠用來表示256種不同的狀態,每個狀態相應一個符號,就是256個符號,從0000000到11111111。 2)表示的符號太多,無法用一個字節來全然表示。要解決這個矛盾必須須要一個新的數據結構 char,從 char 到 byte 必須編碼。


以下介紹下常見的編碼規則,所謂的規則就是為轉換成計算機所能理解的語言。

  • ASCII 碼

學過計算機的人都知道 ASCII 碼。總共同擁有 128 個,用一個字節的低 7 位表示,0~31 是控制字符如換行回車刪除等。32~126 是打印字符。能夠通過鍵盤輸入並且能夠顯示出來。這128個符號(包括32個不能打印出來的控制符號),僅僅占用了一個字節的後面7位,最前面的1位統一規定為0。

  • 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 來解碼,並且不會有亂碼。

  • UTF-16

說到 UTF 必須要提到 Unicode(Universal Code 統一碼),ISO 試圖想創建一個全新的超語言字典。世界上全部的語言都能夠通過這本字典來相互翻譯。

可想而知這個字典是多麽的復雜,關於 Unicode 的具體規範能夠參考相應文檔。

Unicode 是 Java 和 XML 的基礎,以下具體介紹 Unicode 在計算機中的存儲形式。須要註意的是,Unicode僅僅是一個符號集,它僅僅規定了符號的二進制代碼,卻沒有規定這個二進制代碼應該怎樣存儲。

比方,漢字"嚴"的unicode是十六進制數4E25,轉換成二進制數足足有15位(100111000100101)。也就是說這個符號的表示至少須要2個字節。表示其它更大的符號,可能須要3個字節或者4個字節。甚至很多其它。

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

  • UTF-8

UTF-16 統一採用兩個字節表示一個字符,盡管在表示上非常easy方便,可是也有其缺點,有非常大一部分字符用一個字節就能夠表示的如今要兩個字節表示,存儲空間放大了一倍,在如今的網絡帶寬還非常有限的今天。這樣會增大網絡傳輸的流量,並且也不是必需。而 UTF-8 採用了一種變長技術,每個編碼區域有不同的字碼長度。

不同類型的字符能夠是由 1~6 個字節組成。

UTF-8 有以下編碼規則:

假設一個字節,最高位(第 8 位)為 0,表示這是一個 ASCII 字符(00 - 7F)。

可見。全部 ASCII 編碼已經是 UTF-8 了。

假設一個字節。以 11 開頭,連續的 1 的個數暗示這個字符的字節數,比如:110xxxxx 代表它是雙字節 UTF-8 字符的首字節。

反復一遍,這裏的關系是,UTF-8是Unicode的實現方式之中的一個。

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

Unicode符號範圍 | UTF-8編碼方式

(十六進制) | (二進制)
--------------------+---------------------------------------------
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

在 Java 開發中除了 I/O 涉及到編碼外。最經常使用的應該就是在內存中進行字符到字節的數據類型的轉換。

上面介紹了常見的編碼格式,以下結合樣例看看java中編解碼方式:

我們一漢字“嚴”為例進行測試。

public class LengthTest {

public static void main(String[] args) throws Exception{
String name = "嚴";
byte[] utf8 = name.getBytes("UTF-8");
System.out.println(utf8.length);
System.out.print("UTF8:");toHex(utf8);
byte[] gbk = name.getBytes("GBK");
System.out.print("GBK:");toHex(gbk);
String unicodeStr =Integer.toHexString("嚴".charAt(0) );
System.out.println("unicode:"+unicodeStr); //
//unicode轉漢字
StringBuffer sb = new StringBuffer();
String str[] = unicodeStr.toUpperCase().split("U");
for(int i=0;i<str.length;i++){
if(str[i].equals("")) continue;
char c = (char)Integer.parseInt(str[i].trim(),16);
sb.append(c);
}
System.out.println(sb.toString());
}
public static void toHex(byte[] b) {
for (int i = 0; i < b.length; i++) {
System.out.printf("%x " , b[i]);
}
System.out.println();
}
}

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

首先依據指定的 charsetName 通過 Charset.forName(charsetName) 設置 Charset 類,然後依據 Charset 創建 CharsetEncoder 對象。再調用 CharsetEncoder.encode 對字符串進行編碼,不同的編碼類型都會相應到一個類中,實際的編碼過程是在這些類中完畢的。以下是 String. getBytes(charsetName) 編碼過程的時序圖技術分享以下是相應的StringCoding的代碼:

static byte[] encode(String charsetName, char[] ca, int off, int len)
throws UnsupportedEncodingException
{
StringEncoder se = deref(encoder);
String csn = (charsetName == null) ? "ISO-8859-1" : charsetName;
if ((se == null) || !(csn.equals(se.requestedCharsetName())
|| csn.equals(se.charsetName()))) {
se = null;
try {
Charset cs = lookupCharset(csn);
if (cs != null)
se = new StringEncoder(cs, csn);
} catch (IllegalCharsetNameException x) {}
if (se == null)
throw new UnsupportedEncodingException (csn);
set(encoder, se);
}
return se.encode(ca, off, len);
}

從上圖能夠看出依據 charsetName 找到 Charset 類,然後依據這個字符集編碼生成 CharsetEncoder。這個類是全部字符編碼的父類。針對不同的字符編碼集在其子類中定義了怎樣實現編碼,有了 CharsetEncoder 對象後就能夠調用 encode 方法去實現編碼了。這個是 String.getBytes 編碼方法。其它的如 StreamEncoder 中也是相似的方式。以下看看不同的字符集是怎樣將前面的字符串編碼成 byte 數組的?依照GBK方式: 技術分享

以下是大神 君山寫的,我在jdk源代碼找不到相應查表規則,僅僅有c2b接口。

GB2312 相應的 Charset 是 sun.nio.cs.ext. EUC_CN 而相應的 CharsetDecoder 編碼類是 sun.nio.cs.ext. DoubleByte,GB2312 字符集有一個 char 到 byte 的碼表,不同的字符編碼就是查這個碼表找到與每個字符的相應的字節,然後拼裝成 byte 數組。查表的規則例如以下:

 c2b[c2bIndex[char >> 8] + (char & 0xff)]
GBK 編碼是兼容 GB2312 編碼的。它們的編碼算法也是一樣的。

不同的是它們的碼表長度不一樣。GBK 包括的漢字字符很多其它。

所以僅僅要是經過 GB2312 編碼的漢字都能夠用 GBK 進行解碼,反過來則不然。技術分享依照utf-8格式編碼:技術分享 為加深理解:對照上面utf-8編碼表。已知"嚴"的unicode是4E25,相應二進制(100111000100101),依據上表,能夠發現4E25處在第三行的範圍內(0000 0800-0000 FFFF),因此"嚴"的UTF-8編碼須要三個字節,即格式是"1110xxxx 10xxxxxx 10xxxxxx"。

然後。從"嚴"的最後一個二進制位開始。依次從後向前填入格式中的x。多出的位補0。這樣就得到了。"嚴"的UTF-8編碼是"11100100 10111000 10100101",轉換成十六進制就是E4B8A5。 以下結合代碼執行結果,能夠更好的理解技術分享 至此。能夠理解題目的答案了。

事實上Windows平臺還有一個簡單的辦法,就是記事本能夠輔助實現不同編碼轉換。

打開文件後。點擊"文件"菜單中的"另存為"命令,會跳出一個對話框。在最底部有一個"編碼"的下拉條。

技術分享

裏面有四個選項:ANSI。Unicode,Unicode big endian 和 UTF-8。

1)ANSI是默認的編碼方式。對於英文文件是ASCII編碼。對於中文簡體文件是GB2312編碼(僅僅針對Windows中文簡體版,假設是繁體中文版會採用Big5碼)。

2)Unicode編碼指的是UCS-2編碼方式,即直接用兩個字節存入字符的Unicode碼。

這個選項用的little endian格式。

3)Unicode big endian編碼與上一個選項相相應。我在下一節會解釋little endian和big endian的涵義。

4)UTF-8編碼,也就是上一節談到的編碼方法。

選擇完"編碼方式"後,點擊"保存"button,文件的編碼方式就立馬轉換好了。

7. Little endian和Big endian

上一節已經提到,Unicode碼能夠採用UCS-2格式直接存儲。

以漢字"嚴"為例,Unicode碼是4E25。須要用兩個字節存儲,一個字節是4E。還有一個字節是25。

存儲的時候。4E在前,25在後,就是Big endian方式;25在前,4E在後,就是Little endian方式。

這兩個古怪的名稱來自英國作家斯威夫特的《格列佛遊記》。在該書中,小人國裏爆發了內戰。戰爭起因是人們爭論。吃雞蛋時究竟是從大頭(Big-Endian)敲開還是從小頭(Little-Endian)敲開。為了這件事情。前後爆發了六次戰爭,一個皇帝送了命。還有一個皇帝丟了王位。

因此,第一個字節在前,就是"大頭方式"(Big endian)。第二個字節在前就是"小頭方式"(Little endian)。

那麽非常自然的,就會出現一個問題:計算機怎麽知道某一個文件究竟採用哪一種方式編碼?

Unicode規範中定義,每個文件的最前面分別增加一個表示編碼順序的字符,這個字符的名字叫做"零寬度非換行空格"(ZERO WIDTH NO-BREAK SPACE)。用FEFF表示。這正好是兩個字節。並且FF比FE大1。

假設一個文本文件的頭兩個字節是FE FF,就表示該文件採用大頭方式;假設頭兩個字節是FF FE,就表示該文件採用小頭方式。

*****************************

幾種編碼格式的比較

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

有些扯遠了。理解了這些,也就不easy出現亂碼問題。

另外知乎上大神給出了“內碼”“外碼”的概念,更加easy理解.

https://www.zhihu.com/question/27562173/answer/37188642

參考:
http://www.cnblogs.com/bluestorm/archive/2012/07/30/2615034.html
http://www.ibm.com/developerworks/cn/java/j-lo-chinesecoding/
http://blog.csdn.net/fancylovejava/article/details/10142391

java梳理-一個漢字占多大空間