Java一个汉字占几个字节(详解与原理)(转载)

日期: 2024-10-26 05:03:11|浏览: 8|编号: 105202

友情提醒:信息内容由网友发布,请自鉴内容实用性。

Java一个汉字占几个字节(详解与原理)(转载)

背景:

今天在学习用Netty发送定长消息的时候,发现UTF-8编码的汉字不是两个字节,而是三个字节,omg~,于是看了一篇博客,发现字节长度对应Java汉字就是有好多话要说,好激动,咳咳~

原文如下: 太长了。原文作者辛苦了。如果您有兴趣,请看一下。遇到编码问题的Java新手可以稍后阅读编码问题的解释。应该有所收获吧~

(闲话结束……)

1、先说重点:

不同的编码格式占用的字节数是不同的。 UTF-8编码下汉字占用的字节也是不确定的,可能是2、3、4字节;

2、源码如下:

@Test
    public void test1() throws UnsupportedEncodingException {
        String a = "名";
        System.out.println("UTF-8编码长度:"+a.getBytes("UTF-8").length);
        System.out.println("GBK编码长度:"+a.getBytes("GBK").length);
        System.out.println("GB2312编码长度:"+a.getBytes("GB2312").length);
        System.out.println("==========================================");
        String c = "0x20001";
        System.out.println("UTF-8编码长度:"+c.getBytes("UTF-8").length);
        System.out.println("GBK编码长度:"+c.getBytes("GBK").length);
        System.out.println("GB2312编码长度:"+c.getBytes("GB2312").length);
        System.out.println("==========================================");
        char[] arr = Character.toChars(0x20001);
        String s = new String(arr);
        System.out.println("char array length:" + arr.length);
        System.out.println("content:|  " + s + " |");
        System.out.println("String length:" + s.length());
        System.out.println("UTF-8编码长度:"+s.getBytes("UTF-8").length);
        System.out.println("GBK编码长度:"+s.getBytes("GBK").length);
        System.out.println("GB2312编码长度:"+s.getBytes("GB2312").length);
        System.out.println("==========================================");
    }

三、运行结果

UTF-8编码长度:3
GBK编码长度:2
GB2312编码长度:2
==========================================
UTF-8编码长度:4
GBK编码长度:1
GB2312编码长度:1
==========================================
char array length:2
content:|  ? |
String length:2
UTF-8编码长度:4
GBK编码长度:1
GB2312编码长度:1
==========================================

4.几种编码格式简介

几种编码格式。

学过计算机的人都知道,ASCII码一共有128个,用一个字节的低7位来表示。 0~31为换行、回车、删除等控制字符; 32~126为打印字符,可通过键盘输入并可显示。

128个字符显然是不够的,因此ISO组织在ASCII码的基础上制定了一系列标准来扩展ASCII码。它们是ISO-8859-1~ISO-8859-15,其中ISO-8859-1涵盖了大多数西欧语言的字符,是所有语言中使用最广泛的。 ISO-8859-1仍然是单字节编码,总共可以表示256个字符。

它的全称是《信息交换中文编码字符集基本集》。它是双字节编码。总的编码范围为A1-F7,其中A1-A9为符号区,共包含682个符号,B0-F7为汉字区,包含6763个汉字。

全称是《汉字内码扩展规范》,是国家技术监督局制定的新的汉字内码规范。它似乎扩展并添加了更多的汉字。其编码范围为8140~FEFE(除去XX7F)共有23940个码点,可表示21003个汉字。其编码兼容GBK,也就是说编码后的汉字可以用GBK解码,不会出现乱码。

全称是《信息交换中文编码字符集》,是我国的强制性标准。它可以是单字节、双字节或四字节编码。它的编码与编码兼容。虽然这是国家标准,但其实际应用系统中应用并不广泛。

说起UTF,就必须提到(Code )。 ISO正在尝试创建一个新的超语言词典,通过该词典世界上所有语言都可以相互翻译。你可以想象这本词典有多么复杂。详细规格请参考相应文档。它是Java 和XML 的基础。下面详细介绍其在计算机中的存储形式。

UTF-16 特别定义了计算机中如何访问字符。 UTF-16使用两个字节来表示转换格式。这是一种固定长度的表示方法。任何字符都可以用两个字节来表示。两个字节就是16位,所以称为UTF-16。 UTF-16 非常方便表示字符。每两个字节代表一个字符。这大大简化了字符串操作时的操作。这也是Java使用UTF-16作为内存中字符存储格式的一个很重要的原因。

UTF-16统一使用两个字节来表示一个字符。虽然它的表示非常简单方便,但它也有其缺点。一个字节可以表示大量的字符。现在它们需要用两个字节来表示,这增加了存储空间。带宽加倍。在网络带宽还非常有限的今天,这会增加网络传输流量,是没有必要的。 UTF-8采用变长技术,每个编码区都有不同的字符长度。不同类型的字符可由1~6个字节组成。

UTF-8有以下编码规则:

如果一个字节的最高位(位8)为0,则表示它是一个ASCII字符(00 - 7F)。可以看到所有的ASCII编码都已经是UTF-8了。如果一个字节以11开头,则连续1的个数表示该字符的字节数,例如: 表示它是双字节UTF-8字符的第一个字节。如果一个字节以10开头,则说明它不是第一个字节,需要向前寻找当前字符的第一个字。 5.字符编码的历史故事

很久以前,有一群人决定用8个可以开关的晶体管来形成不同的状态来代表世界上的一切。他们认为 8 个开关状态可以作为原子单位,所以他们称它们为“字节”。

后来,他们建造了一些可以处理这些字节的机器。当机器启动时,它们可以使用字节来组合更多的状态,并且状态开始改变。他们看到这很好,所以他们称这台机器为“计算机”。

最初,该计算机仅在美国销售。八位字节总共可以组合256种(2的8次方)种不同的状态。

他们指定了 32 个状态,编号从 0 开始,用于特殊目的。一旦终端设备或打印机遇到这些约定的字节,就必须执行一些约定的操作。当遇到00x10时,终端会换行。当遇到0x07时,终端会发出蜂鸣声。例如,当遇到0x1b时,打印机将反白打印该字符,终端将彩色显示该字母。他们看到这很好,所以他们将这些低于 0x20(十进制 32)的字节状态称为“控制代码”。

它们还将所有空格、标点符号、数字和大小写字母都以连续的字节状态表示,一直到数字127,以便计算机可以使用不同的字节来存储英文文本。大家看到这个都感觉不错,于是大家把这个方案称为ANSI的“Ascii”编码(Code for,美国信息交换标准码)。当时,世界上所有计算机都使用相同的 ASCII 方案来保存英文文本。

后来就像巴别塔建成一样,全世界的人都开始使用电脑,但很多国家并没有使用英语。他们使用的许多字母都没有 ASCII 格式。为了将他们的文本保存在计算机中,他们决定用数字127后面的空格来表示这些新的字母和符号,以及添加绘图表时需要的许多水平线、垂直线、十字和其他形状,以及序列号延续到最后的状态255。本页从128到255的字符集称为“扩展字符集”。从此以后,就不会再有新的状态可供贪婪的人类利用了。美帝国主义者也许没有想到,第三世界国家的人民也希望使用计算机!

当中国人拥有计算机时,还没有可用的字节状态来表示汉字,需要保存的常用汉字有6000多个。但这并不困扰聪明的中国人。我们毫不客气地取消了数字127后面的那些奇怪的符号,并规定:小于127的字符与原来的含义相同,但大于127的两个字符连接在一起。在一起,就代表了一个汉字。第一个字节(他称之为高字节)是从0xA1到0xF7,下一个字节(低字节)是从0xA1到0xFE。这样,我们就可以组合出7000多个字符了。简体中文字符。在这些代码中,我们还包括数学符号、罗马和希腊字母以及日语假名。就连原本存在于ASCII中的数字、标点符号、字母都被重新编码成了两个字节长的代码。 ,通常被称为“全角”字符,而那些原本低于127的字符被称为“半角”字符。

中国人看到这个很好,就把这个汉字方案称为“汉字方案”。它是ASCII的中文扩展。

但中国的汉字太多了,我们很快就发现有很多人的名字在这里打不出来。所以我们必须继续寻找未使用的代码位并诚实地使用它们。

后来还是不够,所以我们干脆不再要求低字节必须是127之后的内码。只要第一个字节大于127,就总是意味着这是一个汉字的开头,无论其后面是否带有扩展字符集。里面的内容。由此产生的扩展编码方案称为 GBK 标准。 GBK收录了全部内容,同时增加了近2万个新汉字(含繁体字)和符号。

后来少数民族也想用计算机,我们就对其进行了扩展,增加了几千个新的少数民族字符,GBK就被扩展了。从此,中华民族的文化得以在计算机时代得以传承。

中国的程序员看到这一系列的汉字编码标准不错,就称其为“DBCS”(Byte Set -Byte Set)。 DBCS系列标准中,最大的特点是两字节长的汉字和一字节长的英文字符在同一个编码方案中共存。因此,为了支持中文处理,他们编写的程序必须注意字符串中的字符。每个字节的值,如果这个值大于127,那么就认为出现了双字节字符集中的字符。那时,凡是有加持、会编程的电脑修士,每天必须念诵以下咒语数百遍:

“一个汉字算两个英文字符!一个汉字算两个英文字符……”

因为当时每个国家都像中国一样有自己的编码标准。结果,没有人理解彼此的编码,也没有人支持其他人的编码。即使是相距仅150海里的中国和台湾,使用相同语言的兄弟地区也采用了不同的DBCS编码方案。当时,中国人想要计算机显示汉字,就必须安装一套“汉字系统”,专门处理汉字的显示和输入。但台湾无知的封建人民写的算命程序必须安装另一套支持BIG5编码的“倚天汉字系统”才可以使用。如果安装了错误的字符系统,显示就会乱七八糟!该怎么办?而且,在世界各国中,仍然有一些穷人暂时无法使用计算机。他们的写作又怎样呢?

这真是计算机的巴别塔命题!

就在这时,大天使加百列及时出现了——一个名为ISO(国际标准化组织)的国际组织决定解决这个问题。他们采用的方法很简单:废弃所有区域编码方案,创建一个包含地球上所有文化、字母和符号的新编码!他们计划将其称为“-Octet Coded Set”,简称UCS,俗称“”。

当它首次被提出时,计算机的内存容量已经大大增加,空间不再是问题。因此ISO直接规定必须用两个字节,即16位来统一表示所有字符。对于ASCII中的那些“半角”字符,原来的编码保持不变,只是长度从原来的8位改变了。扩展到16位,而其他文化和语言的字符都统一重新编码。由于“半角”英文符号仅使用低8位,因此高8位始终为0。因此,这种慷慨的解决方案在保存英文文本时会浪费两倍的空间。

这时,来自旧社会的程序员开始发现一个奇怪的现象:他们的功能不可靠。一个汉字不再相当于两个字,而是一个!是的,从一开始,无论是半角英文字母还是全角汉字,都是统一的“一个字”!同时,它们都是统一的“两个字节”。请注意术语“字符”和“字节”之间的区别。 “字节”是一个8位的物理存储单元,而“字符”是一个与文化相关的符号。在 中,一个字符是两个字节。一个汉字相当于两个英文字符的日子快结束了。

过去,当存在多种字符集时,制作多语言软件的公司遇到了很多麻烦。为了在不同的国家销售同一套软件,他们在对软件进行区域化时不得不应用双字节字符集咒语。您不仅必须小心不要犯错误,而且还必须以不同的字符集移动软件中的文本。这对他们来说是一个很好的打包解决方案,因此从 NT 开始,MS 趁机更改了他们的操作系统,并将所有核心代码更改为可以正常工作的版本。从此,系统终于不需要再安装各种本地语言系统,世界上所有文化的文字都可以显示出来了。

然而,在制定时没有考虑与任何现有编码方案的兼容性。这使得GBK与汉字的内码排列完全不同。没有简单的算术方法可以将文本内容从编码转换为编码。另一种编码进行转换,而这种转换必须通过查找表来进行。

正如前面提到的,用两个字节来表示一个字符。它总共可以组合65535个不同的字符,这大概可以涵盖世界上所有文化的符号。如果还不够,也没关系。 ISO已经准备了UCS-4解决方案。简单来说,四个字节代表一个字符,这样我们就可以组合21亿个不同的字符(最高位还有其他用途)。这大概可以用到银河联邦成立的那一天!

当它到来时,计算机网络的兴起也随之而来。如何在网络上传输也是必须考虑的问题。因此,出现了许多用于传输的UTF(UCS)标准。顾名思义,UTF8 一次传输 8 位数据。 ,而UTF16是一次16位,但是为了传输的可靠性,UTF到UTF没有直接的对应关系,而是需要一些算法和规则来转换。

受过网络编程加持的计算机僧人都知道,在网络上传输信息时有一个非常重要的问题,就是数据高位和低位的解读方式。有些计算机采用的是先发送低位的方式,比如我们的PC机就是采用的。英特尔架构;而另一些人则采用高端优先的方法。在网络中交换数据时,为了检查双方对高低位的理解是否相同,采用了一种很简单的方法,就是在文本流的开头向对方发送一个标识符——如果后续文本高位到位,则发送“FEFF”,否则发送“FFFE”。不信你可以用二进制方式打开一个UTF-X格式的文件,看看前两个字节是不是这两个字节?

以下是转换为UTF-8的规则

Unicode 
   
UTF-8 
   
0000 - 007F 
   
0xxxxxxx 
   
0080 - 07FF 
   
110xxxxx 10xxxxxx 
   
0800 - FFFF 
   
1110xxxx 10xxxxxx 10xxxxxx

例如“中文”字符的编码是6C49。 6C49 位于 0800-FFFF 之间,因此使用 3 字节模板: 。将6C49写成二进制就是:0 1001,按照三字节模板的切分方法,将这个码流分成0110,依次替换模板中的x,得到:1110-0110 10- 10-,即E6 B1 89,这是它的UTF8编码。

说到这里,我们来说说一个非常著名的奇怪现象:当你在记事本中新建一个文件时,输入“中国联通”二字,保存,关闭,然后再次打开,你会发现这两个字有消失了,取而代之的是几个乱码!哈哈,有人说这就是联通无法与中国移动竞争的原因。

其实这是因为编码与UTF8编码冲突。

当软件打开一段文本时,首先要做的就是确定该文本保存的字符集和编码。软件一般使用三种方法来确定文本的字符集和编码:

检测文件头标识,提示用户选择,并根据一定规则进行猜测

最标准的方式是检测文本的前几个字节,以字节/开头,如下表所示:

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

当你新建一个文本文件时,记事本的默认编码是ANSI(代表系统默认编码,中文系统中一般为GB系列编码)。如果以ANSI编码输入汉字,那么实际上就是GB系列编码。 ,在此编码下,“中国联通”的内码为:

c1 1100 0001 
   
aa 1010 1010 
   
cd 1100 1101 
   
a8 1010 1000

注意到了吗?前两个字节以及第三、第四个字节的起始部分为“110”和“10”,与UTF8规则中的两字节模板一致。

所以当我们再次打开记事本时,记事本误以为这是一个UTF8编码的文件。让我们从第一个字节中删除 110,从第二个字节中删除 10,我们得到“00001”。然后对齐位并添加前导 0,就得到“0 1010”。抱歉,这是006A,也就是小写字母“j”,接下来的两个字节用UTF8解码出来是0368。这个字符没什么。这就是为什么只有“中国联通”字样的文件在记事本中无法正常显示的原因。

而如果你在“联通”后面多输入几个单词,其他单词的编码可能就不是以110和10开头的字节了。这样,当你再次打开它时,记事本就不会坚持认为这是一个utf8编码的文件。 ,并且会按照ANSI方式进行解释,不再出现乱码。

6. 为什么一个字符占用两个字节?

 public static void main(String[] args) {
     System.out.printf("The max value of type char is %d.%n",
             (int)Character.MAX_VALUE);
     System.out.printf("The min value of type char is %d.%n",
             (int)Character.MIN_VALUE);
 }

运行上面的程序并输出

ris0。

表明char的范围是从0到65535,这正是两个字节可以表示的范围(65535十六进制是,一个字节可以表示0~0xFF,两个字节可以表示0~),所以一个char占用两个字节。

那么char的值是多少呢?例如,当我写 charc='put'; 时

public static void main(String[] args) throws Exception {
    char c = '放';
    System.out.printf("The value of char %c is %d.%n", c, (int)c);
      
    String str = String.valueOf(c);
    byte[] bys = str.getBytes("Unicode");
    for (int i = 0; i < bys.length; i++) {
        System.out.printf("%X ", bys[i]);
    }
    System.out.println();
      
    int unicode = (bys[2] & 0xFF) << 8 | (bys[3 & 0xFF]);
    System.out.printf("The unicode value of %c is %d.%n", c, unicode);
}

运行输出:

放。

放。

首先你看到这个char的值为25918,那么它是什么呢?暂时忽略它,然后我将这个字符放入 a 中并对其进行编码以获得四个字节。前两个其实和内容无关。它们是BOM,即字节顺序标识符。 FEFF的意思是是,即高位在前,低位在后,所以根据这个规则,将653E转换成十进制int,发现最终输出的是25918,也就是说这个字符的值为25918,所以现在你知道 char 存储什么了。

至于GBK、UTF-8、UTF-16之间的关系,我先把GBK放在一边,因为它有点特殊。

首先你要知道UTF-8、UTF-16和UTF-32是为了方便传输和存储而产生的字符编码方式。

我们先来说说UTF-8。随着全球化的流行,无论做什么,支持都会成为趋势。即使你可能永远不会使用它,这对西方国家来说也不好,因为以前的ASCII字符集,一个A字符只需要一个字节,但现在一个英文字母需要两个字节。如果需要传输和存储,就会浪费一半的空间或流量,所以设计了一种变长编码方式,这就是UTF。 -8,它只使用一个字节对ASCII字符集中的字符进行编码,而其他字符则按照一定的规则用两个、三个或四个字节进行编码。具体规则是:

编码(十六进制)UTF-8字节流(二进制)

-

-xx

-

-

但一些东方国家已经不再这样做了,因为他们的字符基本上都在-范围内。使用UTF-8需要多一个字节,一共需要三个字节来表示,用UTF-8来处理它们的字符。字符不能直接转换,需要进行一些操作。以“放置”为例。它的编码是25918,它的二进制表示是11110。如果想转成UTF-8,先取0110的高四位,和1110连接起来,然后中间六位和10连接起来,得到最后六个低位与 10 连接,因此这三个字节是 10,即十六进制,即您上面写的 -26-108-66。可见,虽然运算量不大,基本上都是位运算,但如果非要对每个字符都做这样的操作,那就实在太损害效率了。考虑到这些点,我又做了一个UTF-16,不够严谨。据说相当于原生编码。它使用双字节来表示一个字符(其实还有一个四字节的区域,不过现在一般不用了),而且因为是用多字节表示的,所以需要一个字节顺序标识符如上面的代码,你发现它得到-2,-1,101,62。当转换为十六进制时,它与我的第二个示例程序中的相同。它表示 UTF-16 的代码值(例如 'put' 的 653E)和本机编码相同。

UTF-32的诞生其实并不奇怪,因为UTF-16仍然是一种变长编码方式。一个字符可以由两个或四个字节表示。有些强迫症的人总感觉不好,所以对他们来说就有UTF-32,用四个字节来表示一个字符,具体用得不多就不详细说了。

最后我们来说说GBK是什么。 GBK是国家标准(扩展)拼音的第一个字母。它是我国于1995年专门针对汉语和一些少数民族语言制定的编码方法。和 之间不存在一一对应的关系,这意味着 GBK 中的某些字符不一定存在,GBK 中的某些字符可能不存在。而且GBK和汉字的编码值之间不存在简单的对应关系,即无法通过简单的计算得到,只能通过查表进行转换。为什么会有GBK这种奇怪的东西呢?其实当时还没有制定出来,更不用说在全球范围内推广了,中国人想用电脑也不可能永远用英语吧?于是我国就自己制定了国家标准。当时是,(其实台湾也有繁体中文的Big5,这里就不详细说了)。后来又增加了很多字符,包括很多少数民族语言,就成为了新的编码标准,就是GBK。

7.深入分析Java中的中文编码问题(转载)

原始链接:#ibm-pcon

需要Java编码的场景

上面已经介绍了几种常见的编码格式。接下来我们将介绍Java中如何支持编码以及什么情况下需要编码。

I/O 操作中存在的编码

我们知道编码一般涉及到字符到字节或者字节到字符的转换,而需要这种转换的场景主要是在I/O的时候。此 I/O 包括磁盘 I/O 和网络。 I/O,网络I/O部分稍后主要以Web应用为例介绍。下图是Java中处理I/O问题的接口:

Class是Java的I/O中读取字符的父类,class是读取字节的父类。类是将字节与字符关联起来的桥梁。它负责处理I/O过程中读取字节到字符的过程。转换,而具体字节到字符的解码是通过解码来实现的,但在解码过程中必须由用户指定编码格式。值得注意的是,如果不指定,将使用本地环境中的默认字符集,例如中文环境中将使用GBK编码。

写作情况也类似。字符的父类是,字节的父类是,通过将字符转换为字节。如下图:

同一个类负责将字符编码为字节。编码格式和默认编码规则与解码一致。

例如,下面的代码实现了文件读写功能:

清单 1. I/O 涉及的编码示例

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操作时,只要注意指定统一的编解码字符集,一般就不会出现乱码的情况。如果有些应用程序不注意指定字符编码,那么在中文环境下就会使用操作系统的默认编码。如果编码和解码都是在中文环境下,一般是没有问题的。不过,仍然强烈不建议使用操作系统的默认编码,因为这样,你的应用程序的编码格式就与运行环境绑定了,跨环境是非常困难的。可能会出现乱码。

内存操作中的编码

在Java开发中,除了涉及编码的I/O之外,最常用的方法就是在内存中将字符转换为字节数据类型。在Java中,字符串是用来表示字符串的,因此类提供了转换为字节的方法。还支持将字节转换为字符串的构造函数。下面的代码示例:

String s = "这是一段中文字符串"; 
byte[] b = s.getBytes("UTF-8");   //参数为编码类型
String n = new String(b,"UTF-8"); //参数为编码类型

另一种是废弃的 和 类,它们分别提供了实现 byte[] 和 char[] 相互转换的方法。如下代码所示:

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

这两个类已被分别提供从 char[] 到 byte[] 和 byte[] 到 char[] 编码和解码的类所取代。如下代码所示:

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

编码和解码都是在一个类中完成的。通过设置编码和解码字符集,更容易统一编码格式,比and类更方便。

Java中还有一个类提供了char和byte之间的软转换。它们之间的转换不需要编码和解码。它只是将 16 位字符格式拆分为两个 8 位字节表示形式。它们的实际值没有被修改,只是数据类型被转换。下面的代码是这样的:

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

上面提供了字符和字节之间的相互转换。只要我们统一设置编码和解码格式,一般不会有问题。

如何用Java进行编码和解码

前面已经介绍了几种常见的编码格式。这里我们将通过实际例子来介绍如何用Java实现编码和解码。接下来我们以字符串“我是君山”为例,介绍如何在Java中将其编码为ISO-8859-1、GBK、UTF-16、UTF-8编码格式进行编码。

清单 2. 编码

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(); 
        } 
 }

我们按照上面提到的编码格式将名称字符串编码成字节数组,然后以十六进制输出。我们先来看看Java是如何编码的。

以下是Java编码所需的类图

图 1. Java 编码类图

首先根据指定的pass.()设置类,然后创建对象然后调用。对字符串进行编码。不同的编码类型会对应一个类,实际的编码过程是在这些类中完成的。以下是。 ()编码过程时序图

图2.Java编码时序图

从上图可以看出,找到了类,然后根据这个字符集编码生成了类。该类是所有字符编码的父类。对于不同的字符编码集,其子类中定义了如何实现编码。有了对象之后,就可以调用方法来实现编码了。这是编码方式,其他类似。我们看看不同的字符集是如何将前面的字符串编码成字节数组的?

例如字符串“我是君山”的char数组为49 20 61 6d 20 541b 5c71。接下来根据不同的编码格式转换成相应的字节。

根据 ISO-8859-1 编码

字符串“我是”采用ISO-8859-1编码。编码结果如下:

从上图我们可以看到,7个char字符通过ISO-8859-1编码转换成了7个byte数组。 ISO-8859-1是单字节编码,将中文“君山”转成字节,值为3f。 3f 是“?”字符,所以汉字经常变成“?”,这可能是由于ISO-8859-1编码使用不正确造成的。汉字经过ISO-8859-1编码后会丢失信息。我们通常称其为“黑洞”,它会吸收未知的字符。由于大多数基础Java框架或系统的默认字符集编码都是ISO-8859-1,因此很容易出现乱码。后面我们会分析不同形式的乱码是如何出现的。

根据编码

字符串“我是君山”被编码。编码结果如下:

对应的是sun.nio.cs.ext。相应的编码类是sun.nio.cs.ext。该字符集具有从字节到字节的代码表。对于不同的字符编码,您可以检查此代码表以查找与每个字符相应字节的每个字节匹配的代码,然后将其组装到字节数组中。表格的规则如下:

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

如果发现的代码点值大于OXFF,则是双字节,否则是单字节。双字节的高8位用作第一个字节,低8位用作第二个字节,如以下代码所示:

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

从上图可以看出,编码后的前5个字符仍然是5个字节,而中文字符则编码为双字节。正如第一部分中介绍的那样,仅支持6763个汉字,因此并非所有汉字都可以编码。 。

根据GBK编码

字符串“ I Am ”是用GBK编码的。以下是编码结果:

您可能已经注意到上面的图片与编码结果相同。是的,GBK和编码结果相同。可以得出结论,GBK编码与编码兼容,并且它们的编码算法也相同。不同之处在于他们的代码表具有不同的长度,而GBK包含更多的汉字。因此,只要可以用GBK解码编码的汉字,相反的情况就不正确。

根据UTF-16编码

字符串“ I Am ”是在UTF-16中编码的。以下是编码结果:

使用UTF-16编码将char阵列加倍。单个字节范围内的字符在高点中填充0,以成为两个字节,而汉字也转换为两个字节。从UTF-16编码规则的角度来看,字符的高位和位置仅分为两个字节。特征是编码效率很高,规则非常简单。由于不同的处理器处理2个字节不同,因此big-(高字节首先,低字节上次)或 - (低字节第一,高字节(后)编码,因此编码字符串时,您需要指定它是大还是 - ,因此,前面有两个字节来保存该值。

根据UTF-8编码

字符串“ I Am ”在UTF-8中编码。以下是编码结果:

尽管UTF-16具有较高的编码效率,但它也使单个字节范围内的字符大小增加一倍,从而将存储空间浪费了。此外,UTF-16使用顺序编码,无法验证单个字符的编码值。如果中间一个字符的代码值被损坏,则将影响所有后续代码值。 UTF-8都不存在这些问题。 UTF-8仍然使用一个字节来表示单字节范围内的字符,并使用三个字节来表示汉字。它的编码规则如下:

清单3.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不同,不需要查找表,因此UTF-8编码效率会更好,因此在存储汉字时,UTF-8编码是理想的选择。

比较几种编码格式

它可以处理以下四种汉字的编码格式。它类似于GBK编码规则,但是GBK的范围更大,可以处理所有汉字。因此,应选择GBK与GBK相比。 UTF-16和UTF-8都是处理编码,其编码规则并不相同。相对而言,UTF-16编码是最有效的,更容易将字符转换为字节,并且更好地执行字符串操作。它适合在本地磁盘和内存之间使用,并且可以在字符和字节之间快速切换。例如,Java的内存编码使用UTF-16编码。但是,它不适用于网络之间的传输,因为网络传输可以轻松损坏字节流。一旦字节流损坏,就很难恢复。相比之下,UTF-8更适合网络传输,并使用ASCII字符的单字节存储。此外,对单个字符的损害不会影响其他后续字符。编码效率在GBK和UTF-16之间。因此,UTF-8平衡编码效率和编码安全性,是一种理想的中文编码方法。

Java Web涉及的编码

对于使用I/O的中文使用,将涉及编码。如前所述,I/O操作将导致编码,并且由I/O引起的大多数乱码代码是网络I/O,因为现在几乎所有大多数应用程序都涉及网络操作,并且通过网络传输的数据都在字节中,因此所有数据必须序列化为字节。要在Java中序列化数据,必须继承该接口。

这里有一个问题。您是否认真考虑如何计算文本的实际规模?我曾经遇到一个问题:我想找到一种压缩大小并减少网络传输量的方法。当时,我选择了不同的压缩算法,发现压缩的字符数减少了,但字节数不会减少。所谓的压缩仅通过编码将多个单字字符转换为一个多字节字符。减少的是(),但最终字节数不会减少。例如,两个字符“ ab”通过一些编码将其转换为一个奇怪的字符。尽管字符的数量从两个变为一个,但如果使用UTF-8编码此奇怪的字符,则编码后可能会变成三个。或更多字节。例如,例如,如果整数编号存储为字符,则使用UTF-8来占用7字节,并且UTF-16编码将占据14字节,但是它仅需4个字节作为一个INT编号将其存储为用于存储它的int-type号码。来商店。因此,查看文本的大小,角色本身的长度毫无意义。即使是相同的字符也使用不同的代码,最终存储大小也会有所不同。因此,它必须查看从字符到字节的编码类型。

另一个问题,您是否考虑过,当我们在计算机中的文本编辑器中输入中文字符时意味着什么?我们知道计算机中的所有信息均以01表示,那么多少0和1是中文字符?我们可以看到的汉字以字符的形式出现。例如,在Java中,“ ”字符,计算机中10的值为28120和23453。这两个字符由仅两个数字表示。 Java中的一个字符相当于两个字节,因此两个汉字用于占据相当于内存中四个字节的空间。

在这两个问题很明确之后,让我们看一下编码转换中可能存在的Java Web中的那些地方?

用户从浏览器启动HTTP请求。需要编码编码的地方是URL ...接收HTTP请求后,服务器端需要解析为HTTP协议。其中,需要解码URI和POST表单参数。服务器也可能需要读取数据库中的数据。问题,在处理所有请求数据后,您需要编码此数据并将其发送到用户请求的浏览器,然后通过浏览器解码以成为文本。这些过程如下图所示:

提醒:请联系我时一定说明是从浚耀商务生活网上看到的!