关于编码的思考(unicode字符集和utf-8 utf-16)

本文介绍了Unicode字符集的概念、作用及内部映射方式,还阐述了UTF - 8和UTF - 16两种编码方式的特点。同时探讨了编码方式与Java的关系,如Java中char类型的编码、外码与内码的区别,以及相关API的使用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1.unicode    字符集的前世今生

     你可能有点疑惑,文章标题是说编码的问题,可是和字符集有什么关系? 别急,先想象一下有两个人,他们使用书信来交流。在 他们交流的过程中,必须将自己要表达的意思用相互都了解的规则转化为文字以让对方了解自己的想法。在写信中这个相互都了解的规则就是我们要说的字符集。

       知道了字符集是什么,下面说下字符集有什么用:它将现实中的字符映射为逻辑中的数字(就是给定一个规则让一个数字对应一个字符)。那么有几种字符集呢:简单来说可以分为3类:1.ASCII字符集 2 .ANSI字符集 3.Unicode字符集 (这是我们今天讨论的主角)。

    有了unicode字符集,我们可以把字符映射为数字了,这个数字就叫做码点(codePoint),可是在unicode字符集内部是如何进行映射的? 在其内部,将码点映射到了0 - 16 号 17个平面,每个平面有2^16个字符。所以用使用了21位2进制数字来表示了所有的字符。其中平面0的字符比较特殊,它兼容ASCII字符集。

2.utf - 8 编码方式, utf - 16 编码方式

     现在我们已经知道如何将字符变为码点了,下面我们看如何将码点转为为物理上的表示,也就是说码点如何用码元(code unit) 表示。回到写信的例子,当我们想表达一个概念的时候,可能要用一个或多个字来表示。那么对应一个码点来说,也需要一个或多个码元来表示。

    在unicode 中由码元到码点的编码方式主要有2种 :utf-8(变长编码) , utf-16(初始为不可变长编码)。

     UTF-16 具体定义了 Unicode 字符在计算机中存取方法。UTF-16 用两个字节来表示 Unicode 转化格式,这个是定长的表示方法,大多数常用字符都可以用两个字节表示,两个字节是 16 个 bit,所以叫 UTF-16。UTF-16 表示字符非常方便,每两个字节表示一个字符,这个在字符串操作时就大大简化了操作,这也是 Java 以 UTF-16 作为内存的字符存储格式的一个很重要的原因。 UTF-16的码元是16位的。

    UTF-16 统一采用两个字节表示一个字符,虽然在表示上非常简单方便,但是也有其缺点,有很大一部分字符用一个字节就可以表示的现在要两个字节表示,存储空间放大了一倍,在现在的网络带宽还非常有限的今天,这样会增大网络传输的流量,而且也没必要。而 UTF-8 采用了一种变长技术,每个编码区域有不同的字码长度。不同类型的字符可以是由 1~3 个字节组成。UTF-8的码元是8位的。UTF-8 有以下编码规则:

  1. 如果一个字节,最高位(第 8 位)为 0,表示这是一个 ASCII 字符(00 - 7F)。可见,所有 ASCII 编码已经是 UTF-8 了。
  2. 如果一个字节,以 11 开头,连续的 1 的个数暗示这个字符的字节数,例如:110xxxxx 代表它是双字节 UTF-8 字符的首字节。
  3. 如果一个字节,以 10 开始,表示它不是首字节,需要向前查找才能得到当前字符的首字节。

3.编码方式与Java的关系

现在我们来思考2个很普遍的问题:

1.java中能否用一个char表示所有字符?

要回答这个问题,需要再明确一个概念,外码内码

内码是程序内部使用的字符编码,特别是某种语言实现其char或String类型在内存里用的内部编码;
外码是程序与外部交互时外部使用的字符编码。“外部”相对“内部”而言;不是char或String在内存里用的内部编码的地方都可以认为是“外部”。例如,外部可以是序列化之后的char或String,或者外部的文件、命令行参数之类的。

Java语言规范规定,Java的char类型是UTF-16的code unit,也就是一定是16位(2字节);

 

char, whose values are 16-bit unsigned integers representing UTF-16 code units ( §3.1).

 

字符串是UTF-16 code unit的序列:

 

The Java programming language represents text in sequences of 16-bit code units, using the UTF-16 encoding.

这样,Java规定了字符的内码要用UTF-16编码。或者至少要让用户无法感知到String内部采用了非UTF-16的编码。

所以,在java中char的物理实现方式是utf - 16,对于大多数的字符来说,都可以用一个char来表示。但是对于扩展后的字符来说一个char就不够了。

 

2.既然一个char长为2字节,但是为什么如new String("字").getBytes().length,返回为3呢?

这里的原因主要在于外码,在String中长度为2个字节,但是用getBytes()后,转为了外码使用的utf-8编码,所以返回了3.

 

另外说下java中对于2种编码方式的选择。

Java标准库实现的对char与String的序列化规定使用UTF-8作为外码。Java的Class文件中的字符串常量与符号名字也都规定用UTF-8编码。这大概是当时设计者为了平衡运行时的时间效率(采用定长编码的UTF-16)与外部存储的空间效率(采用变长的UTF-8编码)而做的取舍。

以及java中的相关的api:

1.String.charAt(int i) 返回指定位置的码元

2.Stirng.codePointAt()  返回指定位置处的码点

3.String.length() 返回String中码元的数量

4.String.codePointCount(int start, int end) 返回指定范围内码点的数量

length 方法将返回采用 UTF-16 编码表示的给定字符串所需要的代码单元数量。例如:
String greeting = "Hello";
int n = greeting.length。; // is 5 .
要想得到实际的长度,即码点数量,可以调用:
int cpCount = greeting.codePointCount(0, greeting.length());
调用 s.charAt(n) 将返回位置 n 的代码单元,n 介于 0 ~ s.length()-l 之间。例如:
char first = greeting.charAt(0); // first is 'H'
char last = greeting.charAt(4); // last is ’o’
要想得到第 i 个码点,应该使用下列语句
int index = greeting.offsetByCodePoints(0,i);
int cp = greeting.codePointAt(index); 

codePoints 方法, 它会生成一个 int 值的“ 流”,
每个 int 值对应一个码点。可以将它转换为一个数组,再完成遍历。
int[] codePoints = str.codePoints.toArray;
反之, 要把一个码点数组转换为一个字符串, 
String str = new String(codePoints, 0, codePoints.length) ;

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值