unicode字符集和多字符集区别,unicode和非unicode

请点赞关注,你的支持对我意义重大。

Hi,我是小彭。本文已收录到 GitHub · Android-NoteBook 中。这里有 Android 进阶成长知识体系,有志同道合的朋友,关注公众号 带你建立核心竞争力。

前言

在日常开发过程中,Unicode & UTF-8 并不是很受关注的知识,但在阅读源码或文章时,出现频率很高。如果你没有理解清楚 Unicode、UTF-8、UTF-16 和 UTF-32 之前的关系,会带来阅读障碍。在这篇文章里,我将带你理解 Unicode 字符集的原理,希望能帮上忙。


1. 什么是字符编码

1.1 什么是字符?

字符(Character)是对文字和符号的总称,例如汉字、拉丁字母、emoji 都是字符。在计算机中,一个字符由 2 部分组成:

  • 1、用户看到的图画
  • 2、字符的编码

你经常会在很多词语上看到 “编码” 这个单词,对初学者来说很容易混淆。今天我列举出 “编码” 常见的 3 层解释,希望能帮助你以后在阅读文章时快速理解作者的意思。

  • 含义 1 – 作为动词:表示把一个字符转换为一个二进制机器数的过程,这个机器数才是字符在计算机中真实存储/传输的格式。例如把 A 转换为 65(ASCII) 的动作,就是一个编码动作;
  • 含义 2 – 作为名词:表示经过编码动作后得到的那个机器数,对于 A 来说,65(ASCII) 就是 A 的编码(值),有时会称为编号;
  • 含义 3 – 作为名词:表示把字符转换为机器数的编码方案,例如 ASCII 编码、GBK 编码、UTF-8 编码。

1.2 什么是字符集

字符集(Character Set)是多个字符与字符编码组成的系统,由于历史的原因,曾经发展出多种字符集,例如:

unicode字符集和多字符集区别,unicode和非unicode

字符集一多起来,就容易出现兼容问题: 即同一个字符在不同字符集上对应不同的字符编码。例如,最早的 emoji 在日本的一些手机厂商创造并流行起来,使得 emoji 在不同厂商的设备间无法兼容。要想正确解析一个字符编码,就需要先知道它使用的字符编码集,否则用错误的字符集解读,就会出现乱码。想象以下,你发送的一个在女朋友的手机上看到的是另一个 emoji,是一件多么可怕的事情。


2. 认识 Unicode 字符集

2.1 为什么要使用 Unicode 字符集?

为了解决字符集间互不兼容的问题,包罗万象的 Unicode 字符集出场了。Unicode(统一码)由非营利组织统一码联盟负责,整理了世界上大部分的字符系统,使得计算机可以用更简单统一的方式来呈现和处理文字。

Unicode 字符集与 ASCII 等字符集相比,在概念上相对复杂一些。我们需要从 2 个维度来理解 Unicode 字符集:编码标准 + 编码格式。

2.2 Unicode 编码标准

关键理解 2 个概念:码点 + 字符平面映射:

  • 码点(Code Point):从 0 开始编号,每个字符都分配一个唯一的码点,完整的十六进制格式是 U+XXXX,具体可表示的范围为 U+0000 ~ U+10FFFF (所需要的空间最大为 3 个字节的空间),例如 U+0011 。这个范围可以容纳超过 100 万个字符,足够容纳目前全世界已创造的字符。

unicode字符集和多字符集区别,unicode和非unicode

  • 字符平面(Plane):这么多字符并不是一次性定义完成的,而是采用了分组的方式。每一个组称为一个平面,每个平面能够容纳 216=65536 个字符。Unicode 一共定义了 17 个平面:基本多文种平面(Basic Multilingual Plane, BMP):第一个平面,包含最常用的通用字符。当然,基本平面并不是填满的,而是刻意空出一段区域,这个我们下文再说。辅助平面(Supplementary Plane):剩下的 16 个平面,包含多种语言的字符。

完整的 unicode 码点列表可以参考:unicode.org

2.3 Unicode 编码格式

Unicode 本身只定义了字符与码点的映射关系,相当于定义了一套标准,而这套标准真正在计算机中落地时,则有多种编码格式。目前常见到的有 3 种编码格式:UTF-8、UTF-16 和 UTF-32。UTF ****是英文 Unicode Transformation Format 的缩写,意思是 Unicode 字符转换为某种格式。

别看编码格式五花八门,本质上只是出于空间和时间的权衡,对同一套字符标准使用不同的编码算法而已。 举个例子,字符 A 的 Unicode 码点和编码如下:

  • 1、图像:A
  • 2、码点:U+0041
  • 3、UTF-8 编码:0X41
  • 4、UTF-16 编码:0X0041
  • 5、UTF-32 编码:0X00000041

当你根据 UTF-8、UTF-16 和 UTF-32 的编码规则进行解码后,你将得到什么结果呢?是的,它们的结果都是一样的 —— 0x41。懂了吗?


3. Unicode 的三实现方式

这一节,我们来讨论 Unicode 最常见的三种编码格式。

3.1 UTF-32 编码

UTF-32 使用 4 个字节的定长编码,前面说到 Unicode 码点最大需要 3 个字节的空间,这对于 4 个字节 UTF-32 编码来说就绰绰有余。

  • 缺点:任何一个码点编码后都需要 4 个字节的空间,每个字符都会浪费 1~3 个字节的存储空间;
  • 优点:编解码规则最简单,编解码效率最快。

UTF-32 编码举例

U+0000 => 0x00000000 U+6C38 => 0x00006C38 U+10FFFF => 0x0010FFFF

3.2 UTF-16 编码

UTF-16 是 2 个字节或 4 个字节的变长编码,结合了 UTF-8 和 UTF-32 两者的特点。前面提到 Unicode 码点最大需要 3 个字节,那么当 UTF-16 使用 2 个字节空间时,岂不是不够用了?

先说 UTF-16 的编码规则:

  • 规则 1:基本平面的码点(编号范围在 U+0000 ~ U+FFFF)使用 2 个字节表示。辅助平面的码点(编号范围在 U+10000 ~ U+10FFFF 的码点)使用 4 个字节表示;
  • 规则 2:16 个辅助平面总共有 220 个字符,至少需要 20 位的空间才能区分。UTF-16 将这 20 位拆成 2 半:高 10 位映射在 U+D800 ~ U+DBFF,称为高位代理(high surrogate);低 10 位映射在 U+DC00 ~ U+DFFF,称为低位代理(low surrogate)。

好复杂,为什么要这么设计?第一条规则比较好理解,1 个平面有最大的编码是 U+FFFF,需要用 16 位表示,用 2 个字节表示正好。第二条规则就不好理解了,我们重点说一下。

辅助平面最大的字符是 U+10FFFF,需要使用 21 位表示,用 4 个字节表示就绰绰有余了,例如说低 16 位 放在低 16 位,高 5 位放在高 16 位(不足位补零)。这样不是很简单也很好理解?

不行,因为前缀有歧义。这种方式会导致辅助平面编码的每 2 个字节的取值范围都与基本平面的取值范围重复,因此,解码程序在解析一段 UTF-16 编码的字符流时,就无法区分这 2 个字节是属于基本平面字符,还是属于辅助平面字符。

unicode字符集和多字符集区别,unicode和非unicode

为了解决这个问题,必须实现前缀无歧义编码(PFC 编码,类似的还有哈弗曼编码)。UTF-16 的方案是将用于基本平面字符编码的取值范围与辅助平面字符编码的取值范围错开,使得两者不会出现歧义(冲突)。这么做的前提,就需要在基本平面中提前空出一段区域,这就是上文提到基本平面故意空出一段区域的原因。

如下图所示,在基础平面中,浅灰色的 D8 ~ DF 为 UTF-16 代理区:

unicode字符集和多字符集区别,unicode和非unicode

—— 图片引用自维基百科

UTF-16 编码举例

unicode字符集和多字符集区别,unicode和非unicode

到这里,UTF-16 的设计思路就说完了,下面就会解释具体的计算规则,不感兴趣可以跳过。


  • 1、辅助平面字符的范围是 U+10000 ~ U+10FFFF,换句话说,第一个辅助平面字符是 U+10000。那么就可先把每个码点减去 0x10000,映射到 U+0000 ~ U+0AFFFF,这样的好处是只需要 20 位就能表示所有辅助平面字符(否则需要 21 位);
  • 2、20 位正好可以拆分为 2 组:高 10 位作为一组,低 10 位作为一组,则有 codepoint=high<<10+low+0x10000
  • 3、high 和 low 会与基本平面冲突,那么就给它们分别加上一个偏移量,使它们落到基本平面中空出来的代理区(high 偏移 0xD800,low 偏移 0xDC00)。

至此,UTF-16 字符编码完成。计算公式总结:

codepoint=((high0xD800)<<10)+low0xDC00+0x10000

high=(codepoint0x10000)>>>10+0xD800

low=(codepoint & 0x3FFF)+0xDC00w


我们在 Java 源码中寻找一下这套计算规则,具体在 String 和 Character 中:

String.java

public String(int codePoints, int offset, int count) { // 0. 前处理:参数不合法的情况 final int end = offset + count; // 1. 计算总共需要的char数组容量 int n = count; for (int i = offset; i < end; i++) { int c = codePoints; // 分析点 1.1 if (Character.isBmpCodePoint(c)) continue; // 分析点 1.2 else if (Character.isValidCodePoint(c)) n++; // 每个辅助平面字符需要多一个char else throw new IllegalArgumentException(Integer.toString(c)); } // 2. 分配数组并填充数据 final char v = new char; for (int i = offset, j = 0; i < end; i++, j++) { int c = codePoints; // 分析点 2.1 if (Character.isBmpCodePoint(c)) v = (char)c; else // 分析点 2.2 Character.toSurrogates(c, v, j++); } // 结束 this.value = v; }

编码计算:

Character.java

// 分析点 1.1:判断码点是否处于基本平面 public static boolean isBmpCodePoint(int codePoint) { return codePoint >>> 16 == 0; } // 分析点 1.2:判断码点是否处于辅助平面 public static boolean isValidCodePoint(int codePoint) { int plane = codePoint >>> 16; return plane < ((0x10FFFF + 1) >>> 16); } // 分析点 2.2:辅助平面字符 - 规则2 static void toSurrogates(int codePoint, char dst, int index) { // high在高位,low在低位,是大端序 dst[index+1] = lowSurrogate(codePoint); dst = highSurrogate(codePoint); } // 计算高位代理 public static char highSurrogate(int codePoint) { return (char) ((codePoint >>> 10) + (0xDBFF - (0x010000 >>> 10))); } // 计算低位代理 public static char lowSurrogate(int codePoint) { return (char) ((codePoint & 0x3ff) + 0xDC00); }

解码计算:

Character.java

public static int toCodePoint(char high, char low) { // 源码有算术表达式优化,此处为等价逻辑 return ((high - 0xD800) << 10) + (low - 0xDC00) + 0x010000; }

3.3 UTF-8 编码

UTF-8 是 1~4 个字节的变长编码,相对来说最节省空间。下述规则表述与你在任何文章 / 百科里看到的规则表述不一样,但是逻辑上是一样的。因为我认为按照 “前缀无歧义” 的概念来理解最易懂。

  • 规则 1:不同范围的码点值使用不同长度的编码;
  • 规则 2:字节编码总长度为 1 时前缀为 0、总长度为 2 时前缀为 110、总长度为 3 时前缀为 1110、总长度为 4 时前缀为 11110 ;
  • 规则 3:除了首个字节,字符编码中其余字节的前缀为 10。

可以看到,这种编码方式是不会存在前缀歧义的,也比较好理解。

UTF-8 编码举例

unicode字符集和多字符集区别,unicode和非unicode

因为 UTF-8 编码相对来说是最节省空间的,因此在很多存储和传输的场景中,都会选择使用 UTF-8 编码。例如:

  • 1、XML文件的编码:在文件头定义了编码格式。<?xml version="1.0" encoding="utf-8"?>
  • 2、Java 字节码中字符串常量的编码:可以看到,Class 文件中的字符串常量是 UTF-8 编码的,并且长度最大只支持 u2(65535 个字符),这就是在 Java 中定义的变量名标识符或方法名标识符过长(超过 64 KB)将无法通过编译的根本原因。

类型标识描述CONSTANT_Utf8_info1UTF-8 编码的字符串CONSTANT_String_info8字符串类型字面量

其中CONSTANT_Utf8_info常量的结构:

名称类型数量tagu11lengthu21bytesu1length

  • 3、HTTP报文主体的编码: ****HTTP 报文首部字段 Content-Type 可以指定字符编码方式。在 OkHttp 源码中,当响应报文首部字段 Content-Type 缺省时,默认按 UTF-8 解码,看源码:

Http 报文示例

HTTP/1.1 200 OK ... 省略 Content-Type:text/html; charset=UTF-8

OkHttp 源码摘要:

ResponseBody.java

public final String string() throws IOException { BufferedSource source = source(); try { // 分析点 1 Charset charset = Util.bomAwareCharset(source, charset()); return source.readString(charset); } finally { Util.closeQuietly(source); } } // 分析点1:获得解码需要的charset private Charset charset() { // contentType为null时,使用 UTF_8 MediaType contentType = contentType(); return contentType != null ? contentType.charset(UTF_8) : UTF_8; }


参考资料

  • Unicode —— 维基百科
  • UTF-8, a transformation format of ISO 10646 —— 互联网工程任务组(IETF)
  • UTF-16, a transformation format of ISO 10646 —— 互联网工程任务组(IETF)
  • Unicode Format for Network Interchange —— 互联网工程任务组(IETF)
  • 《编码·隐匿在计算机软硬件背后的语言》(第23章) —— Charles Petzold 著
  • 隔空传情: emoji 简史 —— Google Play
  • 字符编码笔记:ASCII,Unicode 和 UTF-8 —— 阮一峰 著
  • Unicode 与 JavaScript详解 —— 阮一峰 著
  • 阮一峰老师文章的常识性错误之 Unicode 与 UTF-8 —— 刘志军 著

本文【unicode字符集和多字符集区别,unicode和非unicode】由作者: B/S结构 提供,本站不拥有所有权,只提供储存服务,如有侵权,联系删除!
本文链接:https://www.cuoshuo.com/blog/4462.html

(0)
上一篇 2023-03-13 08:59:30
下一篇 2023-03-13 09:06:28

相关推荐

  • dubbo负载均衡策略使用场景(dubbo原理和机制)

    Dubbo 是一款高性能、透明化、轻量级的 Java RPC 框架,致力于提供高性能的RPC远程服务调用方案。 本篇,我重点详解 Dubbo 的原理机制 @mikechen的互联网架构 目录 Dubbo核心功能 Dubbo核心组件 Dubbo的架构设计 Dubbo调用流程 Dubbo核心功能 Dubbo主要提供了3大核心功能:面向接口的远程方法调用,智能容错…

    2023-03-18
    100
  • 设置滚动条宽度和颜色,滚动条如何设置大小

    在本文中,我们将深入了解滚动条的世界。我知道,这听起来不太迷人,但相信我,精心设计的页面与匹配的滚动条齐头并进。老式的镀铬滚动条只是不适合那么多。 我们将研究滚动条的细节,然后看看一些很酷的例子。 滚动条的组成部分 要设置滚动条的样式,您需要熟悉滚动条的解剖结构。请看这张图: 这里要记住的两个主要组成部分是: scrollbar track是滚动条下方的背景…

    2023-03-16
    000
  • sql挂起另一个程序正在运行 sql调用的目标发生了异常

    一、 问题描述:某项SQLSERVER服务,运行状态为“正在挂起更改”,导致该服务无法使用,也不能启动、停止、重新启动。 二、解决方法 方法一:从任务管理器 → 进程 (勾上 显示所有用户进程) → 找到该服务对应的进程 KILL掉,再重启该服务。 方法二:重启机器,在开服务。 注意点:不管是方法一还是方法二,需要明白该操作所可能带来的影响,评估是否可以这么…

    2023-03-13
    900
  • 完全二叉树是什么意思_完全二叉树包括满二叉树吗

    1、定义: 二叉树是计算机数据结构的一种,是树形结构的一个重要类型,它的每个节点最多有左右两个子树。往往二叉树的存储结构和算法都相对较为简单,一般的树形结构也可以转化为二叉树的形式,因此二叉树十分重要。 二叉树的两个子树,是不相交的,分别称为左子树和右子树,以及根节点root。当为空时,又称为空二叉树。 2、二叉树的特殊形态 满二叉树:二叉树上只有度为0的节…

    2023-03-12
    300
  • linux文本编辑器有哪些_linux文本编辑器vi

    《大数据和人工智能交流》头条号向广大初学者新增C 、Java 、Python 、Scala、javascript 等目前流行的计算机、大数据编程语言,希望大家以后关注本头条号更多的内容。 vi是linux的文本编辑器,vim是vi编辑器的增强版本,习惯上也称为vi 一、vi编辑器的工作模式 vi编辑器有命令模式、输入模式、末行模式,我们通过"vi …

    2023-03-08
    1000
  • 屏幕像素密度是什么意思

    今天我给大家来讲讲这几个咱们经常打交道的词到底啥意思,以及他们之间到底有什么关系。这篇文章是我花了一个上午从N多篇文章里提炼出的一个白话版,保证让你看得懂。 咱们从手机开始说起吧。先上一张图,给大家看看关于手机屏幕方面的一些参数。红框内的三个参数,大家一定都不陌生,我也不陌生。不过讲真的,就在不久前,我连手机的屏幕尺寸到底是怎么算出来的都不知道。下面我们开始…

    2023-03-18
    100
  • c语言编写的小游戏有哪些_2048游戏c语言程序设计

    《2048》是最近比较流行的一款数字游戏。原版2048首先在github上发布,原作者是Gabriele Cirulli。它是基于《1024》和《小3传奇》(Threes!)的玩法开发而成的新型数字游戏。 游戏规则 游戏的规则很简单,你需要控制所有方块向同一个方向运动,两个相同数字的方块撞在一起之后合并成为他们的和,每次操作之后会在空白的方格处随机生成一个2…

    2023-03-08
    600
  • sql四舍五入保留两位小数_rank函数怎么对小数精确排序

    对于职场中经常使用Excel的小伙伴们,最希望掌握一些函数公式,毕竟给数据处理带来很多方便,可以提高我们的工作效率。 今天阿钟老师分享几组函数公式,适合于初学者,也是职场中经常用到的,下次碰到可以直接套用了。 01.文本提取函数:LEFT、MID、RIGHT Left函数用途:从一个文本字符串的第一个字符开始提取指定长度的字符 语法:Left(字符串,长度)…

    2023-03-11
    600
  • js确定取消对话框样式 js弹出框确定和取消

    为了避免误删数据,删除前应该不要嫌麻烦弹出一个确认/取消询问窗口,以前免错删数据后无法恢复。网站的数据删除由用 户在客户端发起,所以弹出确认/取消询问窗口由javascript来完成,它弹出的询问窗口跟Windows系统弹出的一样。 javascript删除前弹出一个确认/取消询问窗口有几种方法,我们只介绍一种方便调用的方法,这样既便于在 javascrip…

    2023-03-17
    200
  • tcp协议为什么要三次握手_tcp三次握手的通俗理解

    专注于Java领域优质技术,欢迎关注 作者:老钱 占小狼博客 TCP三次握手和四次挥手的问题在面试中是最为常见的考点之一。很多读者都知道三次和四次,但是如果问深入一点,他们往往都无法作出准确回答。本篇尝试使用动画来对这个知识点进行讲解,期望读者们可以更加简单地地理解TCP交互的本质。 TCP 三次握手 TCP 三次握手就好比两个人在街上隔着50米看见了对方,…

    2023-03-12
    400

发表回复

登录后才能评论
返回顶部
错说博客上线啦!