Java中的字符集编码入门-增补字符(转载)

简介: 本文探讨Java对Unicode的支持及其发展历程。文章详细解析了Unicode字符集的结构,包括基本多语言面(BMP)和增补字符的表示方法,以及UTF-16编码中surrogate pair的使用。同时介绍了代码点和代码单元的概念,并解释了UTF-8的编码规则及其兼容性。

【版权声明】本篇文章以征得博主同意,再行转载。
出自【滴水成川
原文链接:https://blog.csdn.net/xiaofei125145/article/details/50818343

Java号称对Unicode提供天然的支持,这话在很久很久以前就已经是假的了(不过曾经是真的),实际上,到JDK5.0为止,Java才算刚刚跟上Unicode的脚步,开始提供对 增补字符 的支持。
现在的Unicode码空间为U+0000到U+10FFFF,一共1114112个码位,其中只有1,112,064 个码位是合法的(我来替你做算术,有2048个码位不合法),但并不是说现在的Unicode就有这么多个字符了,实际上其中很多码位还是空闲的,到Unicode 4.0 规范为止,只有96,382个码位被分配了字符(但无论如何,仍比很多人认为的65536个字符要多得多了)。其中U+0000 到U+FFFF的部分被称为 基本多语言面 (Basic Multilingual Plane,BMP)。U+10000及以上的字符称为补充字符。在Java中(Java1.5之后),补充字符使用两个char型变量来表示,这两个char型变量就组成了所谓的surrogate pair(在底层实际上是使用一个int进行表示的)。第一个char型变量的范围称为“高代理部分”(high-surrogates range,从"uD800到"uDBFF,共1024个码位), 第二个char型变量的范围称为low-surrogates range(从"uDC00到"uDFFF,共1024个码位),这样使用surrogate pair可以表示的字符数一共是1024的平方计1048576个,加上BMP的65536个码位,去掉2048个非法的码位,正好是1,112,064个码位。

关于Unicode的码空间实际上有一些稍不小心就会让人犯错的地方。比如我们都知道从U+0000到U+FFFF的部分被称为基本多语言面(Basic Multilingual Plane,BMP),这个范围内的字符在使用UTF-16编码时,只需要一个char型变量就可以保存。仔细看看这个范围,应该有65536这么大,因此你会说单字节的UTF-16编码能够表示65536个字符,你也会说Unicode的基本多语言面包含65536个字符,但是再想想刚才说过的surrogate pair,一个UTF-16表示的增补字符(再一次的,需要两个char型变量才能表示的字符)怎样才能被正确的识别为增补字符,而不是两个普通的字符呢?答案你也知道,就是通过看它的第一个char是不是在高代理范围内,第二个char是不是在低代理范围内来决定,这也意味着,高代理和低代理所占的共2048个码位(从0xD800到0xDFFF)是不能分配给其他字符的。
但这是对UTF-16这种编码方法而言,而对Unicode这样的字符集呢?在Unicode的编号中,U+D800到U+DFFF是否有字符分配?答案是也没有!这是典型的字符集为方便编码方法而做的安排(你问他们这么做的目的?当然是希望基本多语言面中的字符和一个char型的UTF-16编码的字符能够一一对应,少些麻烦,从中我们也能看出UTF-16与Unicode间很深的渊源与结合)。也就是说,无论Unicode还是UTF-16编码后的字符,在0x0000至0xFFFF这个范围内,只有63488个字符。这就好比最初的CPU被勉强拿来做多媒体应用,用得多了,CPU就不得不修正自己从硬件上对多媒体应用提供支持了。

尽管不情愿,但说到这里总还得扯扯相关的概念:代码点和代码单元。
代码点 (Code Point)就是指Unicode中为字符分配的编号,一个字符只占一个代码点,例如我们说到字符“汉”,它的代码点是U+6C49。
代码单元(Code Unit) 则是针对编码方法而言,它指的是编码方法中对一个字符编码以后所占的最小存储单元。例如UTF-8中,代码单元是一个字节,因为一个字符可以被编码为1个,2个或者3个4个字节;在UTF-16中,代码单元变成了两个字节(就是一个char),因为一个字符可以被编码为1个或2个char(你找不到比一个char还小的UTF-16编码的字符,嘿嘿)。说得再罗嗦一点,一个字符,仅仅对应一个代码点,但却可能有多个代码单元(即可能被编码为2个char)。
以上概念绝非学术化的绕口令,这意味着当你想以一种统一的方式指定自己使用什么字符的时候,使用代码点(即你告诉你的程序,你要用Unicode中的第几个字符)总是比使用代码单元更好(因为这样做的话你还得区分情况,有时候提供一个16进制数字,有时候要提供两个)。
例如我们有一个增补字符???(哈哈,你看到了三个问号对吧?因为我的系统显示不出这个字符),它在Unicode中的编号是U+2F81A,当在程序中需要使用这个字符的时候,就可以这样来写:

String s = String.valueOf(Character.toChars( 0x2F81A ));
char []chars = s.toCharArray();
for ( char  c:chars){
   
    System.out.format( " %x " ,( short )c);
}

后面的for循环把这个字符的UTF-16编码打印了出来,结果是
d87edc1a
注意到了吗?这个字符变成了两个char型变量,其中0xd87e就是高代理部分的值,0xdc1a就是低代理的值。

##################################
UTF-8 采用变长度字节来表示字符,理论上最多可以到 6 个字节长度(一个字符六个字节)。
UTF-8 编码兼容了 ASC II(0-127), 也就是说 UTF-8 对于 ASC II 字符的编码是和 ASC II 一样的。
对于超过一个字节长度的字符,才用以下编码规范:
左边第一个字节1的个数表示这个字符编码字节的位数,
例如两位字节字符编码样式为为:110xxxxx 10xxxxxx;
三位字节字符的编码样式为:1110xxxx 10xxxxxx 10xxxxxx.;
以此类推,六位字节字符的编码样式为:1111110x 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx。
xxx 的值由字符编码的二进制表示的位填入。只用最短的那个足够表达一个字符编码的多字节串。
例如:
Unicode 字符: 00 A9(版权符号) = 1010 1001,
UTF-8 编码为:11000010 10101001 = 0x C2 0xA9;
字符 22 60 (不等于符号) = 0010 0010 0110 0000,
UTF-8 编码为:11100010 10001001 10100000 = 0xE2 0x89 0xA0

UTF-8的编码原理和特性:

U+0000~U+007E 1 _ (7bits)

U+0080~U+07FF 1 1 0 1 0 _ (11bits)

U+0800~U+FFFF 1 1 1 0 1 0 1 0 (16bits)

目录
相关文章
|
4月前
|
Java
Java实现随机生成某个省某个市的身份证号?如何编码?
【10月更文挑战第18天】Java实现随机生成某个省某个市的身份证号?如何编码?
285 5
|
2月前
|
Java 开发者 微服务
Spring Boot 入门:简化 Java Web 开发的强大工具
Spring Boot 是一个开源的 Java 基础框架,用于创建独立、生产级别的基于Spring框架的应用程序。它旨在简化Spring应用的初始搭建以及开发过程。
96 6
Spring Boot 入门:简化 Java Web 开发的强大工具
|
2月前
|
监控 架构师 Java
Java虚拟机调优的艺术:从入门到精通####
本文作为一篇深入浅出的技术指南,旨在为Java开发者揭示JVM调优的神秘面纱,通过剖析其背后的原理、分享实战经验与最佳实践,引领读者踏上从调优新手到高手的进阶之路。不同于传统的摘要概述,本文将以一场虚拟的对话形式,模拟一位经验丰富的架构师向初学者传授JVM调优的心法,激发学习兴趣,同时概括性地介绍文章将探讨的核心议题——性能监控、垃圾回收优化、内存管理及常见问题解决策略。 ####
|
3月前
|
监控 安全 Java
Java中的多线程编程:从入门到实践####
本文将深入浅出地探讨Java多线程编程的核心概念、应用场景及实践技巧。不同于传统的摘要形式,本文将以一个简短的代码示例作为开篇,直接展示多线程的魅力,随后再详细解析其背后的原理与实现方式,旨在帮助读者快速理解并掌握Java多线程编程的基本技能。 ```java // 简单的多线程示例:创建两个线程,分别打印不同的消息 public class SimpleMultithreading { public static void main(String[] args) { Thread thread1 = new Thread(() -> System.out.prin
|
3月前
|
Java 大数据 API
14天Java基础学习——第1天:Java入门和环境搭建
本文介绍了Java的基础知识,包括Java的简介、历史和应用领域。详细讲解了如何安装JDK并配置环境变量,以及如何使用IntelliJ IDEA创建和运行Java项目。通过示例代码“HelloWorld.java”,展示了从编写到运行的全过程。适合初学者快速入门Java编程。
|
3月前
|
存储 安全 Java
🌟Java零基础-反序列化:从入门到精通
【10月更文挑战第21天】本文收录于「滚雪球学Java」专栏,专业攻坚指数级提升,希望能够助你一臂之力,帮你早日登顶实现财富自由🚀;同时,欢迎大家关注&&收藏&&订阅!持续更新中,up!up!up!!
120 5
|
3月前
|
安全 Java 调度
Java中的多线程编程入门
【10月更文挑战第29天】在Java的世界中,多线程就像是一场精心编排的交响乐。每个线程都是乐团中的一个乐手,他们各自演奏着自己的部分,却又和谐地共同完成整场演出。本文将带你走进Java多线程的世界,让你从零基础到能够编写基本的多线程程序。
47 1
|
3月前
|
Java 数据处理 开发者
Java多线程编程的艺术:从入门到精通####
【10月更文挑战第21天】 本文将深入探讨Java多线程编程的核心概念,通过生动实例和实用技巧,引导读者从基础认知迈向高效并发编程的殿堂。我们将一起揭开线程管理的神秘面纱,掌握同步机制的精髓,并学习如何在实际项目中灵活运用这些知识,以提升应用性能与响应速度。 ####
62 3
|
4月前
|
Java
Java中的多线程编程:从入门到精通
本文将带你深入了解Java中的多线程编程。我们将从基础概念开始,逐步深入探讨线程的创建、启动、同步和通信等关键知识点。通过阅读本文,你将能够掌握Java多线程编程的基本技能,为进一步学习和应用打下坚实的基础。
|
4月前
|
存储 安全 Java
从入门到精通:Java Map全攻略,一篇文章就够了!
【10月更文挑战第19天】本文介绍了Java编程中重要的数据结构——Map,通过问答形式讲解了Map的基本概念、创建、访问与修改、遍历方法、常用实现类(如HashMap、TreeMap、LinkedHashMap)及其特点,以及Map在多线程环境下的使用和性能优化技巧,适合初学者和进阶者学习。
129 4

热门文章

最新文章