博客搬家: 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个字节编码,大部分汉字采用三个字节

java字符编码-Unicode编码问题刨根究底的更多相关文章

  1. java中文和unicode编码相互转换(转)

    工具类代码如下: package aa.com; import java.io.UnsupportedEncodingException; public class UnicodeUtil { pub ...

  2. Python如何将字符和Unicode编码转变

    小小总结一下,以防过几天忘记,自己的复习资料,如果能帮到大家,也是有所作用!! 1,字符转化为Unicode编码方法: ord("字符") ord("A") o ...

  3. C# 获取字符的Unicode编码

    using UnityEngine;using System.Collections;using System.Collections.Generic; List<); string chars ...

  4. 字符串及其操作,字符的Unicode编码

    plainText=input('message:') for c in plainText: print(chr(ord(c)-3),end='') plainText=input('message ...

  5. Java 字符转Unicode

    static String unicode2String(String unicodeStr) { StringBuffer sb = new StringBuffer(); String str[] ...

  6. 【字符编码】Java字符编码详细解答及问题探讨

    一.前言 继上一篇写完字节编码内容后,现在分析在Java中各字符编码的问题,并且由这个问题,也引出了一个更有意思的问题,笔者也还没有找到这个问题的答案.也希望各位园友指点指点. 二.Java字符编码 ...

  7. Java实现 中文转换成Unicode编码 和 Unicode编码转换成中文

    想要实现中文字符转换为Unicode编码的话主要用到的是一个这样的包,自己可以去API文档里面查看下的 java.util.Properties; 直接进入主题吧,主要是 package Test01 ...

  8. Java 字符编码(二)Java 中的编解码

    Java 字符编码(二)Java 中的编解码 java.nio.charset 包中提供了一套处理字符编码的工具类,主要有 Charset.CharsetDecoder.CharsetEncoder. ...

  9. java中文乱码解决之道(三)-----编码详情:伟大的创想---Unicode编码

    随着计算机的发展.普及,世界各国为了适应本国的语言和字符都会自己设计一套自己的编码风格,正是由于这种乱,导致存在很多种编码方式,以至于同一个二进制数字可能会被解释成不同的符号.为了解决这种不兼容的问题 ...

随机推荐

  1. 「Luogu P3866」[TJOI2009]战争游戏 解题报告

    题面 好难表述啊~ 在n*m的矩阵上,有一些大兵(为0),一些空地(一个正整数),障碍物(-1),现在摧毁一些空地,使所有大兵不能走出矩阵去(代价为表示空地的整数),求最小代价 思路: 网络流最小割 ...

  2. zabbix配置企业微信报警

    +++++++++++++++++++++++++++++++++++++++++ 1. 工作中最长使用的就是微信,普及,开源,而且免费!!! 2. 在企业微信中要记录的值: · 部门id · 企业i ...

  3. mock造数据

    前端开发,需要和后台联调:很多时候,前端开发并不需要等后台完全写好接口在去联调,自己可以写死数据,渲染数据,加样式.后台人员有时会很忙,他没有时间写好返回所有的数据等等,特别是新开一个项目,从零开始的 ...

  4. 【转】Java面试题:多继承

    招聘和面试对开发经理来说是一个无尽头的工作,虽然有时你可以从HR这边获得一些帮助,但是最后还是得由你来拍板,或者就像另一篇文章“Java 面试题:写一个字符串的反转”所说: 面试开发人员不仅辛苦而且乏 ...

  5. Kettle中JavaScript内置函数说明

    本文链接:https://blog.csdn.net/u010192145/article/details/102220563 我们在使用JavaScript组件的时候,在左侧核心树对象栏中可以看到K ...

  6. Adobe Acrobat DC 安装

    Adobe Acrobat DC 制作pdf模板 下载:http://www.downza.cn/soft/20562.html 安装出错解决: 可以将C:\Program Files (x86)\C ...

  7. postman的测试,用对象接收所有的字符串

    1.post请求 Headers: Content-Type  application/json { "taskId":"1000001161", " ...

  8. 关于爬虫的日常复习(7)—— DOM操作及selenium库

  9. __init__.py在导包中起到的作用

    说明:__init__.py这个文件本人使用频率不高 例子: __init__.py文件作用:初始化这个包 1.v1.py def vi(): return 'views' 2.views->_ ...

  10. python 找到项目使用的所有组件和版本

    1.下载模块 pip3 install -i https://pypi.douban.com/simple pipreqs 2.生成文件 pipreqs ./ --encoding=utf-8