java梳理-⼀个汉字占多⼤空间
⾯试题:⼀个汉字占多⼤空间。
事实上这个问题我了解不深的,知道结论不知道为什么。借此梳理下认识。
先回想下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编码⽅式
float几个字节多少位(⼗六进制) | (⼆进制)
--------------------+---------------------------------------------
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 = Bytes("UTF-8");
System.out.println(utf8.length);
System.out.print("UTF8:");toHex(utf8);
byte[] gbk = Bytes("GBK");
System.out.print("GBK:");toHex(gbk);
String unicodeStr =HexString("严".charAt(0) );
System.out.println("unicode:"+unicodeStr); //
//unicode转汉字
StringBuffer sb = new StringBuffer();
String str[] = UpperCase().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.String());
}
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 对象。再调⽤de 对字符串进⾏编码,不同的编码类型都会相应到⼀个类中,实际的编码过程是在这些类中完毕的。以下是 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.questedCharsetName())
|| 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);
}
de(ca, off, len);
}
从上图能够看出依据 charsetName 到 Charset 类,然后依据这个字符集编码⽣成 CharsetEncoder。这个类是全部字符编码的⽗类。针对不同的字符编码集在其⼦类中定义了怎样实现编码,有了 CharsetEncoder 对象后就能够调⽤ encode ⽅法去实现编码了。这个是 Bytes 编码⽅法。其它的如 StreamEncoder 中也是相似的⽅式。以下看看不同的字符集是怎样将前⾯的字符串编码成 byte 数组的?
依照GBK⽅式:
以下是⼤神君⼭写的,我在jdk源代码不到相应查表规则,仅仅有c2b接⼝。
GB2312 相应的 Charset 是 sun. EUC_CN ⽽相应的 CharsetDecoder 编码类是 sun. 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 FF FF),因此"严"的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理解.
參考:
wwwblogs/bluestorm/archive/2012/07/30/2615034.html
www.ibm/developerworks/cn/java/j-lo-chinesecoding/
blog.csdn/fancylovejava/article/details/10142391
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论