• java字符编码-Unicode编码问题刨根究底



    博客搬家: java字符编码问题

    前段时间在读《java核心技术卷一》,遇到一些名词:码点、代码单元等,其实字面意思不难理解,解释如下

    • 码点(code point):Unicode编码表中某个字符对应的代码值
    • 代码单元(code unit):用于UTF-16编码的最小单元,16个bit

    注意上述只是针对java中字符和字符串的Unicode+UTF-16机制的解释。若是其他编码方式就另说,如UTF-8的代码单元是用8个bit编码。

    下面问题来了

    书中建议,尽量不要使用char类型,最好将字符串转化为抽象数据类型来处理,即codepoints数组

    //将String转化为码点数组
    int[] codePoints = str.codePoints().toArray();
    

    那么为什么要这样做呢,像c语言那样直接使用char数组不好吗?当然不行,因为在Unicode+UTF-16这种机制中,一个码点可由一个代码单元表示,但很多特殊字符也可能由两个代码单元表示。而char类型只能是一个代码单元。所以,若字符串中存在特殊字符,遍历char数组或者使用charAt等方法时就会出问题。但使用码点数组就OK,因为数组中每一个元素都代表一个码点,而不是一个代码单元。而c语言中采用ASCII字符集,每个码点都由一个8bit代码单元表示,使用char数组则不存在这个问题。

    这样的描述对懂Unicode和UTF-16的人来说,很容易理解。但我想在我的博客中深究一下编码机制背后的原理。为了便于小白理解,先介绍一下编码规范的基本概念。

    编码规范

    制定编码规范为了将计算机能识别的二进制数,映射成人类能识别的字符。依据编码规范,计算机就可以将二进制数显示成字符。常见的编码规范有,ASCII码、GBK、ISO-8859-1、Unicode等。编码规范中有三个子概念,字库表、字符集、编码方式。

    字库表

    字库表中存储该编码规范能表示的所有字符。一套编码规范不一定能表示世界上所有的字符。例如GBK规范可以显示汉字,但不能显示法语、俄语等。

    字符集

    字库表中每一个字符都有一个二进制地址,字符集就是这些二进制数的集合。例如 00000000 - 01111111 为ASCII字符集的范围。

    编码方式

    某些编码规范包括大量的字符,例如Unicode中包含上百万个字符。若每个字符都采用同样长度的二进制数来编码,即定长编码,则要用三个字节来存储,甚至在将来会用到四个字节。这样很多本身只需要单字节存储的字符也会占用三四个字节,会导致极大的资源浪费。

    如果能采用一些算法,使得部分字符采用单字节编码,部分采用双字节等(即变长编码),可节省不少资源,这些算法即为编码方式。常见的编码方式有UTF-8、UTF-16、UTF-32等。我前文所说的:Unicode+UTF-16机制,就容易理解了,即基于Unicode编码规范并采用UTF-16编码方式的机制。java中的字符串和字符正是采用这种机制进行编码的,下面详细介绍Unicode和UTF-16。

    注意UTF的全称是Unicode Transformation Format,含义为将Unicode转换为某种格式。所以UTF-8、UTF-16等都是针对Unicode来说的。

    Unicode

    在Unicode出现之前,已经有了很多编码规范,如美国的ASCII码、中国的GBK、西欧的ISO-8859-1等,每一种规范都不能涵盖所有国家的语言。Unicode设计的初衷就是将所有语言中的字符进行统一编码。

    Unicode最早的1.0版本中,字符集数量远远不到65536,因为当时的字符集不是那么庞大,使用2个字节编码足够使用,java也正是此时引进了16位的Unicode字符集。

    然而,在Unicode增加了大量的汉语、日语、韩语字符之后,字符数量超过了65536,于是16位的char类型也就不能满足了。实际上,这些海量的Unicode字符可以被划分为17个代码级别(code plane)

    • 第一级别,基本多语言级别(basic multilingual plane),范围U+0000~U+FFFF,下文称其为基本平面
    • 其它16个级别,范围U+10000~U+10FFFF,存储辅助字符,下文把它称作增补平面

    UTF-16编码则是对不同的代码级别做文章,采用不同长度的编码表示不同代码级别的码点

    UTF-16

    UTF-16使用16位作为一个代码单元。基本平面的码点采用一个代码单元进行编码,增补平面的码点使用两个代码单元。

    可能你会问,使用一对代码单元表示一个增补平面的字符时,有没有可能把它判别成两个基本平面的码点?也就是说,有没有可能出现冲突的情况?

    • 当然不会,UTF-16中使用了代理机制,即使用基本平面中未映射字符的字符集区域,来作为增补平面中字符的代码单元的区域

    这些码点区域称为“替代区域”(syrrogate area),即U+D800 ~ U+DFFF范围,该区域在基本平面中属于空闲区域,2048个值。如此一来,便避免了冲突。

    该替代区域分割为两部分,U+D800 ~ U+DBFF用于第一个代码单元,U+DC00 ~ U+DFFF用于第二个代码单元。

    代码单元1 代码单元2
    1101 10pp ppxx xxxx 1101 11xx xxxx xxxx

    pppp是指16个级别的级别编号,24=16。两个代码单元的变数部分(p和x)共20位,可表示220=1048576个码点。而增补平面范围U+10000~U+10FFFF恰好也是1048576个码点。所以这两个代码单元可完美表示出增补平面中的所有字符。

    这种方式十分巧妙。

    事实上,只有UTF-16使用了“替代区域”方法,像现在被广泛接纳的UTF-8编码,是通过首字节的比特位判断码点的代码单元数量。

    最后简单介绍下UTF-16和UTF-8的区别,以后再找时间细究,把UTF-8的坑填上。

    • UTF-16使用2个或4个字节进行编码,大部分汉字采用两个字节编码,少量汉字采用四个字节
    • UTF-8使用1个到4个字节编码,大部分汉字采用三个字节
  • 相关阅读:
    20172303 2017-2018-2 《程序设计与数据结构》实验一报告
    20172303 2017-2018-2 《程序设计与数据结构》第3周学习总结
    20172303 2017-2018-2 《程序设计与数据结构》第2周学习总结
    20172303 2017-2018-2 《程序设计与数据结构》第1周学习总结
    预备作业03
    预备作业02
    预备作业01
    日语学习笔记
    [Redux] redux之combineReducers
    [Vue] vue跳转外部链接
  • 原文地址:https://www.cnblogs.com/buptleida/p/12090280.html
Copyright © 2020-2023  润新知