UTF-8 与 UTF-16编码详解

article/2025/11/10 14:11:46

目录

一、UTF-8编码

1、UTF-8介绍

2、UTF-8是如何编码的?

3、上述Unicode码点值范围中十进制值127、2047、65535、2097151这几个临界值是怎么来的呢?

二、UTF-16编码

1、UTF-16介绍

2、UTF-16编码方式

1)设计思路

2)具体编码方式

3)字节顺序问题

3、BOM

三、两者比较

1、存储容量

2、存储效率

3、字节序


一、UTF-8编码

1、UTF-8介绍

UTF-8编码是Unicode字符集的一种编码方式(CEF),其特点是使用变长字节数(即变长码元序列、变宽码元序列)来编码。一般是1到4个字节,当然,也可以更长。

为什么要设计为变长字节数?

可以理解为按需分配,比如一个字节足以容纳所有的ASCII字符,那何必补一堆0用更多的字节来存储呢?

变长编码的优势与劣势

优势是节省空间、自动纠错性能好、利于传输、扩展性强,劣势是不利于程序内部处理,比如正则表达式检索;而UTF-32这样等长码元序列(即等宽码元序列)的编码方式就比较适合程序处理,当然,缺点是比较耗费存储空间。

2、UTF-8是如何编码的?

UTF-8编码最短的为一个字节、最长的目前为四个字节,从首字节就可以判断一个UTF-8编码有几个字节:

  • 如果首字节以0开头,肯定是单字节编码(即单个单字节码元);
  • 如果首字节以110开头,肯定是双字节编码(即由两个单字节码元所组成的双码元序列);
  • 如果首字节以1110开头,肯定是三字节编码(即由三个单字节码元所组成的三码元序列),以此类推。

另外,UTF-8编码中,除了单字节编码外,由多个单字节码元所组成的多字节编码其首字节以外的后续字节均以10开头(以区别于单字节编码以及多字节编码的首字节)。

0、110、1110以及10相当于UTF-8编码中各个字节的前缀,因此称之为前缀码。其中,前缀码110、1110及10中的0,是前缀码中的终结标志。

UTF-8编码中的前缀码起到了很好的区分和标识的作用——当解码程序读取到一个字节的首位为0,表示这是一个单字节编码的ASCII字符;当读取到一个字节的首位为1,表示这是一个非ASCII字符的多字节编码字符中的某个字节(可能是首字节,也可能是后续字节),接下来若继续读取到一个1,则确定为首字节,再继续读取直到遇见终结标志0为止,读取了几个1,就表示该字符为几个字节的编码;当读取到一个字节的首位为1,紧接着读取到一个终结标志0,则该字节显然是非ASCII字符的后续字节(即非首字节)。

所以,1~4字节的UTF-8编码看起来分别是这样的:

  • 单字节可编码的Unicode码点值范围十六进制为0x0000 ~ 0x007F,十进制为0 ~ 127;
  • 双字节可编码的Unicode码点值范围十六进制为0x0080 ~ 0x07FF,十进制为128 ~ 2047;
  • 三字节可编码的Unicode码点值范围十六进制为0x0800 ~ 0xFFFF,十进制为2048 ~ 65535;
  • 四字节可编码的Unicode码点值范围十六进制为0x10000 ~ 0x1FFFFF,十进制为65536 ~ 2097151

目前Unicode字符集码点编号的最大值为0x10FFFF,实际尚未编号到0x1FFFFF;这说明作为变长字节数的UTF-8编码其未来扩展性非常强,即便目前的四字节编码也还有大量编码空间未被使用,更不论还可扩展为五字节、六字节…...。

3、上述Unicode码点值范围中十进制值127、2047、65535、2097151这几个临界值是怎么来的呢?

因为UTF-8编码中的每个字节中都含有起到区分和标识之用的前缀码0、110、1110以及10之一,所以1~4个字节的UTF-8编码其实际有效位数分别为8-1=7位(2^7-1=127)、16-5=11位(2^11-1=2047)、24-8=16位(2^16-1=65535)、32-11=21位(2^21-1=2097151),如下表所示:

注:上图中的Unicode range即Unicode码点值范围(也就是Unicode码点编号范围),Hex为16进制,Binary为二进制;Encoded bytes即UTF-8编码中各字节的编码方式(即编码算法),其中,x代表Unicode二进制码点值的单字节或低字节中的低7位或8位、y代表两字节码点值的高字节中的低3位或8位以及三字节码点值的中字节中的8位、z代表三字节码点值的高字节中的低5位。

因此,UTF-8编码的算法简单地用一句话来概括就是:首先确定UTF-8编码中各个字节的前缀码;之后再将UTF-8编码中各个字节除了前缀码所占用之外的位,依次分配给Unicode字符码点值二进制中各个位的值,换言之,就是用Unicode字符码点值二进制中各个位的值,依次填充UTF-8编码中的各个字节除了前缀码所占用之外的位。

参考文章:刨根究底字符编码之十二——UTF-8究竟是怎么编码的 - 腾讯云开发者社区-腾讯云 (tencent.com)

二、UTF-16编码

1、UTF-16介绍

UTF-16是Unicode字符编码五层次模型的第三层:字符编码表(Character Encoding Form,也称为 "storage format")的一种实现方式。即把Unicode字符集的抽象码位映射为16位长的整数(即码元, 长度为2 Byte)的序列,用于数据存储或传递。Unicode字符的码位,需要1个或者2个16位长的码元来表示,因此这是一个变长表示。

引用维基百科中对于UTF-16编码的解释我们可以知道,UTF-16最少也会用2 Byte来表示一个字符,因此没有办法兼容ASCII编码(ASCII编码使用1 Byte来进行存储)。 

2、UTF-16编码方式

1)设计思路

我们知道Unicode的范围为0x0~0x10FFFF,首先是0x0~0xFFFF这段区间,正好16位就可以表示,那么超过这个区间的怎么办呢?也就是0xFFFF~0x10FFFF这段,我们先看这段区间有多少个码位,0x10FFFF-0xFFFF=0x100000,那么这个十六进制表示的十进制也就是:1048576个码位

我们既然16位存不下,那肯定就是32位存咯,将32位分开前16位和后16位,每个16位各存一半,那么每一半存的就是1024(由来:1024*1024=1048576),1024代表的是2的10次幂,也就是10位二进制数。

32位二进制数字中,前后16位中各存10位就够用了,但是剩余的6位用来干什么呢?和UTF-8的设计一样,为了让识别字符串变得容易(从文本的任意位置开始,均能区分一个字符的起始)。

我们通过前6位来区分数据,那么前6位就是2^6=64,也就是开头数字的区间。我们设定如下:
54开头的为32位的前16位,55开头的为32位的后16位,其他开头的为单16位,这样我们就能区分开这三个16位了,在读取文档中的任意位置,都能随意区分出间隔咯。

  • 那么54开头的数据区间是多少呢,就是1101 10xx xxxx xxxx,区间就是D800~DBFF
  • 那么55开头的数据区间是多少呢,就是1101 11xx xxxx xxxx,区间就是DC00~DFFF

为了配合UTF-16,Unicode中也将这两个区间屏蔽掉,不允许分配任何字符,这个区间就是代理区。

2)具体编码方式

在UTF-16中,我们将Unicode分为了两个范围,分别通过不同的方式进行存储。具体表示见下图。

Unicode范围

UTF-16编码方式

U+000~U+FFFF

2 Byte存储,编码后等于Unicode值

U+10000~U+10FFFF

4 Byte存储,现将Unicode值减去(0x10000),得到20bit长的值。再将Unicode分为高10位和低10位。UTF-16编码的高位是2 Byte,高10位Unicode范围为0-0x3FF,将Unicode值加上0XD800,得到高位代理(或称为前导代理,存储高位);低位也是2 Byte,低十位Unicode范围一样为0~0x3FF,将Unicode值加上0xDC00,得到低位代理(或称为后尾代理,存储低位)

  • 0x3FF  -->  0011 1111 1111 
  • 0xD800  -->  1101 1000 0000 0000
  • 0xDC00  -->  1101 1100 0000 0000

3)字节顺序问题

由于一开始的Unicode只需要两个字节,所以UTF-16虽然也是变长编码方式,但是在最初却可以当做定长编码方式使用。UTF-16每个字符都直接使用两个字节存储,所以就有字节顺序的问题,同一字节流可能会被解释为不同内容。如某字符为十六进制编码4E59,按两个字节拆分为4E59,在Mac中和Windows中会解析如下:

-读取顺序显示字符
Windows4E 59
Mac59 4E

在Mac上从低字节开始和在Windows上从高字节开始读取显示不同,从而导致在同一编码下的乱码问题。为了解决这个问题便引入了字节顺序标记(英语:byte-order mark,BOM)来标记是大端序还是小端序。

3、BOM

字节顺序标记(英语:byte-order mark,BOM)是一个有特殊含义的统一码字符,码点为U+FEFF。当以UTF-16或UTF-32来将UCS/统一码字符所组成的字符串编码时,这个字符被用来标示其字节序。经常被用于区分是否为UTF编码。

字符U+FEFF如果出现在字节流的开头,则用来标识该字节流的字节序,是高位在前还是低位在前。如果它出现在字节流的中间,则表达零宽度非换行空格的意义,用户看起来就是一个空格。从Unicode3.2开始,U+FEFF只能出现在字节流的开头,只能用于标识字节序,就如它的名称——字节序标记——所表示的一样;除此以外的用法已被舍弃。取而代之的是,使用U+2060来表达零宽度无断空白。

UTF-8以字节为编码单元,没有字节序的问题。但是某些操作系统也会使用带BOM的UTF-8,叫做UTF-8 with BOM。Python中叫utf-8-sig。Unicode规范中说明UTF-8不必也不推荐使用BOM。多数时候UTF-8都是不带BOM的,但是微软公司的某些软件(如Excel)打开某些不带BOM的utf8文件(如cvs文件)会乱码,需要转换成带BOM的utf8编码才能正常显示。

所以Java中获取以UTF-16编码的字符串字节个数时,总是会比实际含有字符的字节个数多2。不过目前已经有很多主流的文本编辑器支持不带BOM的UTF编码了,通过后缀(LE和BE)区分是小端还是大端。

参考文章:UTF-16编码详解 - 知乎 (zhihu.com)

Unicode中UTF-8与UTF-16编码详解 - 腾讯云开发者社区-腾讯云 (tencent.com)

三、两者比较

1、存储容量

先说UTF-16,由于每个码位都使用2到4个字节来存储,对于含有大量中文或者其他二字节长的字符流来说,UTF-16可以节省大量的存储空间。因为UTF-16并不需要像UTF-8那样通过牺牲很多标记位来标识一个字节表示的是什么,它只需一个字符来表示是大端序和小端序。

但是对于有大量西文字符的字符流来说UTF-8的优势就变得十分明显:UTF-8只需要一个字节就能存储西文字符,这是UTF-16做不到的。所以在混合存储,或者是源代码、字节码文件等大量西文字符的文件,更倾向于UTF-8。

UTF-8存储中文比UTF-16要多出50%,不推荐要大量显示中文的程序使用。—— 知乎轮子哥

而由于UTF-8的兼容性和对西文的支持,所以西方都提倡统一使用UTF-8作为字符编码,这样也的确可以彻底根除乱码问题。目前基本上所有的开发环境和源代码文件也基本上是统一UTF-8。

2、存储效率

这里只从UTF-8和UTF-16两个编码来简单阐述下效率问题。

因为每个字符使用不同数量的字节编码,所以UTF-8编码的字符串,寻找串中第N个字符是一个O(N)复杂度的操作。即串越长,则需要更多的时间来定位特定的字符。同时,还需要位变换来把字符编码成字节,把字节解码成字符。

而从UTF-16编码规则来看,仅仅将字符的高位和地位进行拆分变成两个字节。规则非常简单,编码效率很高,单字节O(1)的查找效率也非常好。

不过值得一提的是,这种时间效率问题正在随着内存和CPU的发展而减小,现在已经不会作为主要考虑的问题了。

3、字节序

UTF-8最大的优势是,没有字节序的概念。所以特别适合用于字符串的网络数据传输,不用考虑大小端问题。对于非英文网页(对于我们而言,简单说东亚文字网页),能够避免各种乱码问题。

UTF-16编码字符串的网络传输,要考虑大小端的问题。另外网络传输中如果一个字节信息丢失,剩下的字符串都无法正确解析,读取混乱,统统乱码,而UTF-8只会影响局部,因为有标识端,后面的数据可以正常读取。

参考文章:字符、编码和Java中的编码 - 简书 (jianshu.com)


http://chatgpt.dhexx.cn/article/uYSkiYdx.shtml

相关文章

了解一下UTF-16

1)先啰嗦一下 UTF-16是一种编码格式。啥是编码格式?就是怎么存储,也就是存储的方式。 存储啥?存二进制数字。为啥要存二进制数字? 因为Unicode字符集里面把二进制数字和字符一一对应了,存二进制数字就相当于存了二进制…

字符编码--UTF-16

2019独角兽企业重金招聘Python工程师标准>>> 第4节 UTF-16 UTF-16是Unicode字符编码五层次模型的第三层:字符编码表(Character Encoding Form,也称为"storage format")的一种实现方式。即把Unicode字符集的抽…

蔡勒公式、三角函数

1.蔡勒公式 2.三角函数

蔡勒(Zeller)公式及其推导:快速将任意日期转换为星期数

0. 本文的初衷及蔡勒公式的用处 前一段时间,我在准备北邮计算机考研复试的时候,做了几道与日期计算相关的题目,在这个过程中我接触到了蔡勒公式。先简单的介绍一下蔡勒公式是干什么用的。 我们有时候会遇到这样的问题:看到一个日期…

1185.一周中的几天 四种解法(java),主要新学一下蔡勒公式

题目 给你一个日期,请你设计一个算法来判断它是对应一周中的哪一天。 输入为三个整数:day、month 和 year,分别表示日、月、年。 您返回的结果必须是这几个值中的一个 {“Sunday”, “Monday”, “Tuesday”, “Wednesday”, “Thursday”…

给定日期(年月日)求星期几(蔡勒公式?没那么简单!)

前言 前几日做到一个机试题,给出一个日期,让你输出那天是星期几,这种题无疑两种思路:一是从今天(前提是知道今天日期及周几)开始推算,计算今天与目标日期差的天数再取模运算,考虑到…

欧拉计划题-19 (蔡勒公式)

欧拉计划题19 前言一 题目描述二 题解分析1.暴力求解(低配版解法)2.蔡勒公式(公式法) 三 题解代码 前言 欧拉计划是学习数学、数论选手遨游的海洋,700道题让你我越来越强。 打卡网址链接: link. 一 题目描述 题目链接…

C语言——蔡勒(Zeller)公式:快速将任意日期转换为星期数

蔡勒公式 情景引入公式介绍公式细节代码实现 情景引入 在日常生活中,我们有时候会遇到这样的问题:看到一个日期想知道这一天是星期几。对于这个问题,如果用编程的方式,应该怎么实现呢?你可能已经有思路了,比…

自用笔记58——蔡勒(Zeller)公式

请你编写一个程序来计算两个日期之间隔了多少天。 日期以字符串形式给出,格式为 YYYY-MM-DD,如示例所示。 示例 1: 输入:date1 “2019-06-29”, date2 “2019-06-30” 输出:1 示例 2: 输入&#xff1…

蔡勒公式与Python

蔡勒公式 ( Zeller formula) 作用:从年月日推算星期几 来源:罗马教皇格里高利十三世在1582年组织了一批天文学家,根据哥白尼日心说计算出来的数据,对儒略历作了修改。将1582年10月5日到14日之间的10天宣布…

蔡勒(Zeller)公式理解Get(√)

Preface 偶然做到日期相关题目,了解到Zeller公式。不甘心停留在使用阶段,便想掌握其推导过程。 只适用于格利戈里历法,也就是现今的公历。 1. Zeller公式 标准形式 计算1582年10月4日或之前日期 (罗马教皇决定在1582年10月4日后使用格利戈里…

C语言——蔡勒(Zeller)公式的使用

C语言——蔡勒公式的使用 蔡勒公式简介: 蔡勒(Zeller)公式,是一个计算星期的公式,随便给一个日期,就能用这个公式推算出是星期几。 计算公式: 核心公式: w(y[y/4][c/4]-2c[26(m1…

c++ operator百样操作符重载(详解)

目录 一、operator &#xff1a;等号判断重载 二、operator &#xff1a; 等号赋值重载 三、operator ! : 不等于重载 四、operator> &#xff1a; 大于号 或者 小于号 重载 五、operator << &#xff1a;输入重定向重载 六、operator &#xff1a;加号重载 …

操作符重载!看这篇就够了!

实现一个操作符重载的方式通常有两种情况&#xff1a; 将操作符重载实现为类的成员函数。操作符重载实现为非类的成员函数(即全局函数)。 将操作符重载实现为类的成员函数 在类体中声明(定义)需要重载的操作符&#xff0c;声明方式跟普通的成员函数一样&#xff0c;只不过操作符…

C++基本操作符重载

基本操作符重载 基本操作符重载reference 基本操作符重载 操作符重载指的是将 C 提供的操作符进行重新定义&#xff0c;使之满足我们所需要的一些功能。 在 C 中可以重载的操作符有&#xff1a; - * / % ^ & | ~ ! < > - * / % ^ & | <…

【Groovy】map 集合 ( map 集合操作符重载 | + 操作符重载 | 代码示例 )

文章目录 一、map 集合 " " 操作符重载二、代码示例 一、map 集合 " " 操作符重载 对 map 集合使用 " " 操作符 , 操作符两侧都是 map 集合 , 调用的是 map 集合的 plus 方法 , plus 函数有 2 2 2 个参数 : 第一个参数 , Map<K, V> l…

【Groovy】map 集合 ( map 集合操作符重载 | - 操作符重载 | 代码示例 )

文章目录 一、map 集合 " - " 操作符重载二、完整代码示例 一、map 集合 " - " 操作符重载 对 map 集合 使用 " - " 操作符 , 相当于调用了 map 集合的 minus 方法 , 该方法传入 2 2 2 个参数 : Map<K,V> self 参数 : 相当于 " - &…

【C++】操作符重载

注意&#xff1a;操作符重载可以当做“自定义类的运算” 1 为什么需要操作符重载&#xff1f; 对于基础的变量&#xff0c;int等&#xff0c;不需要重载就知道如何做&#xff0c;但是对于自定义类&#xff0c;就无法进行运算&#xff0c;比如复数类。 2 操作符重载总结 1&…

C++ 操作符重载

输出操作符"<<" 和输入操作运算符">>" 操作符重载&#xff0c;也叫运算符重载&#xff0c;是C的重要组成部分&#xff0c;它可以让程序更加的简单易懂&#xff0c;简单的运算符使用可以使复杂函数的理解更直观。 操作符重载可对 已有的运算…

C++-操作符重载

定义&#xff1a; Salesitem.h /* * This file contains code from "C Primer, Fifth Edition", by Stanley B. * Lippman, Josee Lajoie, and Barbara E. Moo, and is covered under the * copyright and warranty notices given in that book: * * "Copyrig…