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个字节编码,大部分汉字采用三个字节
java字符编码-Unicode编码问题刨根究底的更多相关文章
- java中文和unicode编码相互转换(转)
工具类代码如下: package aa.com; import java.io.UnsupportedEncodingException; public class UnicodeUtil { pub ...
- Python如何将字符和Unicode编码转变
小小总结一下,以防过几天忘记,自己的复习资料,如果能帮到大家,也是有所作用!! 1,字符转化为Unicode编码方法: ord("字符") ord("A") o ...
- C# 获取字符的Unicode编码
using UnityEngine;using System.Collections;using System.Collections.Generic; List<); string chars ...
- 字符串及其操作,字符的Unicode编码
plainText=input('message:') for c in plainText: print(chr(ord(c)-3),end='') plainText=input('message ...
- Java 字符转Unicode
static String unicode2String(String unicodeStr) { StringBuffer sb = new StringBuffer(); String str[] ...
- 【字符编码】Java字符编码详细解答及问题探讨
一.前言 继上一篇写完字节编码内容后,现在分析在Java中各字符编码的问题,并且由这个问题,也引出了一个更有意思的问题,笔者也还没有找到这个问题的答案.也希望各位园友指点指点. 二.Java字符编码 ...
- Java实现 中文转换成Unicode编码 和 Unicode编码转换成中文
想要实现中文字符转换为Unicode编码的话主要用到的是一个这样的包,自己可以去API文档里面查看下的 java.util.Properties; 直接进入主题吧,主要是 package Test01 ...
- Java 字符编码(二)Java 中的编解码
Java 字符编码(二)Java 中的编解码 java.nio.charset 包中提供了一套处理字符编码的工具类,主要有 Charset.CharsetDecoder.CharsetEncoder. ...
- java中文乱码解决之道(三)-----编码详情:伟大的创想---Unicode编码
随着计算机的发展.普及,世界各国为了适应本国的语言和字符都会自己设计一套自己的编码风格,正是由于这种乱,导致存在很多种编码方式,以至于同一个二进制数字可能会被解释成不同的符号.为了解决这种不兼容的问题 ...
随机推荐
- LibreOJ 6278. 数列分块入门 2 题解
题目链接:https://loj.ac/problem/6278 题目描述 给出一个长为 \(n\) 的数列,以及 \(n\) 个操作,操作涉及区间加法,询问区间内小于某个值 \(x\) 的元素个数. ...
- js中的事件冒泡
事件冒泡和阻止事件冒泡: 事件冒泡的原理:从实际操作的元素(事件)向上级父元素一级一级执行下去,直到达到document/window,冒泡过程结束.例如:假设我有一个 div 盒子,里面嵌套了1个子 ...
- nginx 负载均衡的配置
首先搭建好三台nginx,我是用VM搭建的 nginx搭建,https://www.cnblogs.com/liubaoqing/p/10507962.html 这里的三台nginx ,ip分别是 1 ...
- jdk1.7扩容时,不论是否有链表,并发都可能出现循环链表
扩容时使用transfertransfer不同于put时的判断hash冲突,直接使用头插法,如果没有冲突,则next为null.如下:e.next = newTable[i];newTable[i] ...
- 对Java中可变参数的理解
说明 可变参数:是DK1.5之后出现的新特性,其实可变参数是0.1.2.3.....个参数的数组 使用前提 当方法的参数列表数据类型已经确定,但是参数的个数不确定,就可以使用可变参数 使用格式 修饰符 ...
- Google搜索成最大入口,简单谈下个人博客的SEO
个人静态博客SEO该考虑哪些问题呢?本篇文章给你答案 咖啡君在开始写文章时首选了微信公众号作为发布平台,但公众号在PC端的体验并不好,连最基本的文章列表都没有,所以就搭建了运维咖啡吧的网站,可以通过点 ...
- java jvm jre jdk三者的关系
jvm:java虚拟机器(跨平台的关键) jre:java运行环境 jdk:java 开发工具包(kit) jdk>jre>jvm 环境变量配置 https://www.cnblogs.c ...
- C++中的四个智能指针
只能指针的行为类似常规指针,重要的区别是它负责自动释放所指向的对象.智能指针定义在memory头文件中. 1. auto_ptr(C++11已经舍弃) 由new expression获得的对象,在au ...
- 编程基础--XML约束
2020年新年第一天,不写一篇博客纪念一下都感觉对不起这个跨年 为什么会写一篇关于xml的博客呢?xml在编程中用的又不多,再多也用不着自己写约束文件,只要能看懂就行了不是吗?嗯,没别的原因,就是想研 ...
- 用自定义变量作为动作方法参数 URL路由 精通ASP-NET-MVC-5-弗瑞曼