Unicode(UTF-8, UTF-16)令人混淆的概念

2023-10-29

为啥需要Unicode

 

      我们知道计算机其实挺笨的,它只认识0101这样的字符串,当然了我们看这样的01串时肯定会比较头晕的,所以很多时候为了描述简单都用十进制,十六进制,八进制表示.实际上都是等价的,没啥太多不一样.其他啥文字图片之类的其他东东计算机不认识.那为了在计算机上表示这些信息就必须转换成一些数字.你肯定不能想怎么转换就怎么转,必须得有定些规则.于是刚开始的时候就有ASCII字符集(American Standard Code for Information Interchange, "美国信息交换标准码),它使用7 bits来表示一个字符,总共表示128个字符,我们一般都是用字节(byte,即8个01串)来作为基本单位.那么怎么当用一个字节来表示字符时第一个bit总是0,剩下的七个字节就来表示实际内容.后来IBM公司在此基础上进行了扩展,用8bit来表示一个字符,总共可以表示256个字符.也就是当第一个bit是0时仍表示之前那些常用的字符.当为1时就表示其他补充的字符.

        英文字母再加一些其他标点字符之类的也不会超过256个.一个字节表示主足够了.但其他一些文字不止这么多 ,像汉字就上万个.于是又出现了其他各种字符集.这样不同的字符集交换数据时就有问题了.可能你用某个数字表示字符A,但另外的字符集又是用另外一个数字表示A.这样交互起来就麻烦了.于是就出现了Unicode和ISO这样的组织来统一制定一个标准,任何一个字符只对应一个确定的数字.ISO取的名字叫UCS(Universal Character Set),Unicode取的名字就叫unicode了.

      总结起来为啥需要Unicodey就是为了适应全球化的发展,便于不同语言之间的兼容交互,而ASCII不再能胜任此任务了.

 

Unicode详细介绍

 

1.容易产生后歧义的两字节

        unicode的第一个版本是用两个字节(16bit)来表示所有字符

        .实际上这么说容易让人产生歧义,我们总觉得两个字节就代表保存在计算机中时是两个字节.于是任何字符如果用unicode表示的话保存下来都占两个字节.其实这种说法是错误的.

     其实Unicode涉及到两个步骤,首先是定义一个规范,给所有的字符指定一个唯一对应的数字,这完全是数学问题,可以跟计算机没半毛钱关系.第二步才是怎么把字符对应的数字保存在计算机中,这才涉及到实际在计算机中占多少字节空间.

     所以我们也可以这样理解,Unicode是用0至65535之间的数字来表示所有字符.其中0至127这128个数字表示的字符仍然跟ASCII完全一样.65536是2的16次方.这是第一步.第二步就是怎么把0至65535这些数字转化成01串保存到计算机中.这肯定就有不同的保存方式了.于是出现了UTF(unicode transformation format),有UTF-8,UTF-16.

 

2.UTF-8 与UTF-16的区别

    UTF-16比较好理解,就是任何字符对应的数字都用两个字节来保存.我们通常对Unicode的误解就是把Unicode与UTF-16等同了.但是很显然如果都是英文字母这做有点浪费.明明用一个字节能表示一个字符为啥整两个啊.

   于是又有个UTF-8,这里的8非常容易误导人,8不是指一个字节,难道一个字节表示一个字符?实际上不是.当用UTF-8时表示一个字符是可变的,有可能是用一个字节表示一个字符,也可能是两个,三个.当然最多不能超过3个字节了.反正是根据字符对应的数字大小来确定.

   于是UTF-8和UTF-16的优劣很容易就看出来了.如果全部英文或英文与其他文字混合,但英文占绝大部分,用UTF-8就比UTF-16节省了很多空间.而如果全部是中文这样类似的字符或者混合字符中中文占绝大多数.UTF-16就占优势了,可以节省很多空间.另外还有个容错问题,等会再讲

  看的有点晕了吧,举个例子.假如中文字"汉"对应的unicode是6C49(这是用十六进制表示,用十进制表示是27721为啥不用十进制表示呢?很明显用十六进制表示要短点.其实都是等价的没啥不一样.就跟你说60分钟和1小时一样.).你可能会问当用程序打开一个文件时我们怎么知道那是用的UTF-8还是UTF-16啊.自然会有点啥标志,在文件的开头几个字节就是标志.

EF BB BF 表示UTF-8

FE FF 表示UTF-16.

 

用UTF-16表示"汉"

假如用UTF-16表示的话就是01101100   01001001(共16 bit,两个字节).程序解析的时候知道是UTF-16就把两个字节当成一个单元来解析.这个很简单.

用UTF-8表示"汉"

用UTF-8就有复杂点.因为此时程序是把一个字节一个字节的来读取,然后再根据字节中开头的bit标志来识别是该把1个还是两个或三个字节做为一个单元来处理.

0xxxxxxx,如果是这样的01串,也就是以0开头后面是啥就不用管了XX代表任意bit.就表示把一个字节做为一个单元.就跟ASCII完全一样.

110xxxxx 10xxxxxx.如果是这样的格式,则把两个字节当一个单元

1110xxxx 10xxxxxx 10xxxxxx 如果是这种格式则是三个字节当一个单元.

这是约定的规则.你用UTF-8来表示时必须遵守这样的规则.我们知道UTF-16不需要用啥字符来做标志,所以两字节也就是2的16次能表示65536个字符.

而UTF-8由于里面有额外的标志信息,所有一个字节只能表示2的7次方128个字符,两个字节只能表示2的11次方2048个字符.而三个字节能表示2的16次方,65536个字符.

由于"汉"的编码27721大于2048了所有两个字节还不够,只能用三个字节来表示.

所有要用1110xxxx 10xxxxxx 10xxxxxx这种格式.把27721对应的二进制从左到右填充XXX符号(实际上不一定从左到右,也可以从右到左,这是涉及到另外一个问题.等会说.

刚说到填充方式可以不一样,于是就出现了Big-Endian,Little-Endian的术语.Big-Endian就是从左到右,Little-Endian是从右到左.

由上面我们可以看出UTF-8需要判断每个字节中的开头标志信息,所以如果一当某个字节在传送过程中出错了,就会导致后面的字节也会解析出错.而UTF-16不会判断开头标志,即使错也只会错一个字符,所以容错能力强.

 

Unicode版本2

    前面说的都是unicode的第一个版本.但65536显然不算太多的数字,用它来表示常用的字符是没一点问题.足够了,但如果加上很多特殊的就也不够了.于是从1996年开始又来了第二个版本.用四个字节表示所有字符.这样就出现了UTF-8,UTF16,UTF-32.原理和之前肯定是完全一样的,UTF-32就是把所有的字符都用32bit也就是4个字节来表示.然后UTF-8,UTF-16就视情况而定了.UTF-8可以选择1至8个字节中的任一个来表示.而UTF-16只能是选两字节或四字节..由于unicode版本2的原理完全是一样的,就不多说了.

前面说了要知道具体是哪种编码方式,需要判断文本开头的标志,下面是所有编码对应的开头标志

EF BB BF    UTF-8
FE FF     UTF-16/UCS-2, little endian
FF FE     UTF-16/UCS-2, big endian
FF FE 00 00  UTF-32/UCS-4, little endian.
00 00 FE FF  UTF-32/UCS-4, big-endian.

其中的UCS就是前面说的ISO制定的标准,和Unicode是完全一样的,只不过名字不一样.ucs-2对应utf-16,ucs-4对应UTF-32.UTF-8是没有对应的UCS



简单几句话总结Unicode,UTF-8和UTF-16

概念

先说一说基本的概念,这包括什么是Unicode,什么是UTF-8,什么是UTF-16。

Unicode,UTF-8,UTF-16完整的说明请参考Wiki(UnicodeUTF-8UTF-16)。用比较简单的话来说就是,Unicode定义了所有可以用来表示字符的数值集合(称之为Code Point)。UTF-8和UTF-16等UTF标准定义了这些数值和字符的映射关系。

UTF-8

优势

UTF-8最大的优势是,没有字节序的概念。所以特别适合用于字符串的网络数据传输,不用考虑大小端问题。

劣势

本地字符串处理过程中,如果使用UTF-8,对于英文字符的处理没有太大的问题。一个char变量表示一个英文字符。但是对于中文等远东字符集来说,就比较坑爹了。char str[]; str[0]并不能完整表示一个汉字。UTF-8编码格式下,一个汉字需要至少3个char才能表示。这对于通过下标来操作字符串的操作来说是非常痛苦的一件事情。

另外,一个汉字需要至少3个char来表示,也让汉字在网络传输上存在劣势,占用太多流量。

UTF-16

优势

UTF-16 LE是windows上默认的Unicode编码方式,使用wchar_t表示。所有wchar_t *类型的字符串(包括硬编码在.h/.cpp里的字符串字面值),VC都自动采用UTF-16的编码(字符串字面值,literal string,存在很多坑。特别是char *类型的字面值,最终内存使用何种编码方式完全取决于当前文件的编码方式。也就是说当前文件如果是GBK编码的,那么文件里char * str = "中午",str指向的内存字符串二进制是使用GBK编码的。如果文件编码是UTF-8,那么内存是使用UTF-8编码。所以为什么一直要强调字符串应该放在资源文件里,而不是硬编码在.h/.cpp文件里!)。

UTF-16另外一个优势就是常用字符都可以使用两个个字节表示,也就是一个wchar_t(这里指Windows平台)。所以,在Windows平台上,特别适合使用wchar_t来作为字符串的存储基类型。一个wchar_t表示一个字符。操作使用非常方便。

劣势

没有统一的表示UTF-16编码的字符类型。C++98/03里对wchar_t的定义是非常宽泛的。这导致在Windows平台上,wchar_t是2字节的;在Unix-like系统上是4字节的。代码移植上,可能会遇到挑战(我没移植过,所以不确定会有什么难度,以及难度有多大)。

即使最新的C++11里已经定义除了char16_t表示UTF-16,MS的VS2013还不支持char16_t。所以目前使用char16_t还不具移植性。

据我了解,UTF-16编码和GBK编码相比,还存在一个排序的劣势。也就是说,如果要按照汉语拼音的字母顺序对汉字进行排序,GBK会得到正确的结果,而UTF-16就不行(暂时我还没这种需求,所以我没验证过,不过好像我马上就要与到这种需求了,到时候我再验证下)。

UTF-16编码字符串的网络传输,要考虑大小端的问题。

UTF-32

优势

这个优势就明显了,所有字符都是4字节,fix-length。一个wchar_t(Unix-like系统上)表示一个字符。

劣势

对于以英文为主的字符串来说,空间消耗大。

面临和上面UTF-16一样的问题。一致性,排序,网络传输。char32_t VS2013还不支持(甚至VS 14 CPT也没打算支持)。

总结

UTF-8最适合用来作为字符串网络传输的编码格式。UTF-16最适合当作本地字符串编码格式。如果定义好了网络传输协议,那么UTF-16也非常合适当作网络字符串传输的编码格式,特别是中文等远东地区字符集。比起UTF-8来说,节省流量。UTF-32没什么特殊癖好或者需求的话,暂时还用不上。


本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Unicode(UTF-8, UTF-16)令人混淆的概念 的相关文章

随机推荐

  • 关于jquery获取input的value问题

    刚开始接触jquery 很多东西不熟悉 在用 id 来获得页面的input元素的时候 发现 id value不能取到值 后来终于在伟大的百度帮助下 找到了问题的原因 是一个jquery对象 而不是一个dom element value是do
  • Python continue 语句

    Python continue 语句跳出本次循环 而break跳出整个循环 continue 语句用来告诉Python跳过当前循环的剩余语句 然后继续进行下一轮循环 continue语句用在while和for循环中 Python 语言 co
  • 全面解析文件操作~快来深入学习~

    目录 1 为什么使用文件 2 什么是文件 2 1 程序文件 2 2 数据文件 2 3 文件名 3 文件的打开和关闭 3 1 文件指针 3 2 文件的打开和关闭 4 文件的顺序读写 4 1 对比一组函数 5 文件的随机读写 5 1 fseek
  • linux 文件及目录的基本操作

    文件的操作命令 1 显示指定目录和文件 ls eg ls l etc d 2 显示当前目录的名称 pwd eg pwd 3 进入 退出目录 cd eg cd etc 进入根目录下的etc目录 cd 退回到上一级目录 cd 退回到根目录 4
  • Scrum Master 面试题 – 你必须知道的22个Scrum基础知识

    以下的22个问题基本上涵盖了Scrum所涉及的内容 如果你能够正确回答出所有问题 那么你已经具备了作为一名Scrum Master的基本素质 当然 作为一名合格的Scrum Master 更重要的是你的经验 因为Scrum Master更多
  • 尺取法--例题模板详解

    尺取法 一种神奇的技巧 在Codeforces中显示它的算法名称叫做 two pointers 直译成中文的话叫双指针法 尺取法 顾名思义 像尺子一样取一段 尺取法通常是对数组保存一对下标 即所选取的区间的左右端点 然后根据实际情况不断地推
  • 细谈Vue中插槽Slots

    细谈Vue中插槽Slots 浅谈Vue中插槽Slots 1 默认插槽 2 后备内容 3 具名插槽 4 作用域插槽 5 代码实践 浅谈Vue中插槽Slots
  • Java中的SPI机制及接口多实现调用

    1 SPI机制 SPI 全称为 Service Provider Interface 是JDK内置的一种服务提供发现机制 SPI充分体现了面向接口编程的特点 系统内置接口方法 在实际运行中用户可以自定义实现类来满足不通的实现需求 SPI机制
  • Mybatis-plus接口绑定xml语句

    我们启动没有配置任何东西的时候的 报错 AbstractHandlerExceptionResolver java 194 org springframework web servlet mvc method annotation Exce
  • 搭建redis哨兵集群,master宕机后无法执行故障切换的解决方法

    最近在学习搭建redis哨兵集群时 遇到的一些问题 分享一下 首先说明一下 我是同一台虚拟机中开启3个redis实例 模拟主从集群 一个master端口为7001 2个slave端口为7002 7003 3个哨兵sentinel实例端口为2
  • MyBatis-Generator在Eclipse上配置及使用

    之前用MyBatis框架的时候 都是手敲的代码 就感觉到好麻烦的样子 今天就到网上搜了一下MyBatis自动构建工具 就发现在官网上推荐了MyBatis Generator这个构建工具 官网推荐地址 http mybatis github
  • fio数据生成曲线图

    1 背景 我们常常在测试fio的时候 一般只会看到最终测试结果 如下所示 root localhost zhangyi fio filename test file direct 1 rw randwrite numjobs 1 iodep
  • HTML中表格的属性

    tips 不敢说经常用table 介绍本篇文章源于朋友问的一个简单问题 怎么获取table中某个单元格中的数据 table rows length 表格是网页制作时最常使用的元素了 可以说 大部分的网页都是由表格支撑的 没有表格的网页绝对是
  • 邮件发送工具类及邮箱有效性校验

    邮件对象 import lombok Data import java util Date 邮件对象 author huxiang Data public class Email 邮件标题 private String subject 邮件
  • 时间复杂度和空间复杂度的计算

    1 时间复杂度 1 1一层循环 1 1 1解题思路 列出循环趟数t和每轮循环i的值 找到t与i的关系 确定循环停止条件 联立解方程 写结果 1 1 2举例 求时间复杂度 i n n while i 1 i 2 t 0 1 2 i t与i的关
  • Go语言sync.Map

    Go语言中的 map 在并发情况下 只读是线程安全的 同时读写是线程不安全的 如果想实现并发线程安全有两种方法 map加互斥锁或读写锁 标准库sync map Go1 19 新特性 sync map源码 https github com g
  • Servlet的生命周期

    在 Java 中 任何对象都有生命周期 Servlet 也不例外 Servlet 的生命周期如下图所示 图 Servlet生命周期 图 描述了 Servlet 的生命周期 按照功能的不同 大致可以将 Servlet 的生命周期分为三个阶段
  • 一篇文章了解什么是串口,UART、RS-232、RS-422、RS-485

    通讯问题 和交通问题一样 也有高速 低速 拥堵 中断等等各种情况 如果把串口通讯比做交通 UART比作车站 那么一帧的数据就好比汽车 汽车跑在路上 要遵守交通规则 如果是市内 一般限速30 40 而高速公路则可以到120 而汽车走什么路 限
  • android 项目中出现红色感叹号的解决方法

    文本转载 http www apkbus com forum php mod viewthread tid 5629 问题原因 工程中classpath中指向的包路径错误 解决办法 右键项目名称 BuildPath gt Configure
  • Unicode(UTF-8, UTF-16)令人混淆的概念

    为啥需要Unicode 我们知道计算机其实挺笨的 它只认识0101这样的字符串 当然了我们看这样的01串时肯定会比较头晕的 所以很多时候为了描述简单都用十进制 十六进制 八进制表示 实际上都是等价的 没啥太多不一样 其他啥文字图片之类的其他