编程语言与字符编码
# 20.编程语言与字符编码
聊聊在编程语言中的字符,例如 Java
# 一个字符占多少个字节?
得看情况,不同编码下情况不同。这里引用 肖国栋 (opens new window) 大佬的知乎回答:
具体地讲,脱离具体的编码谈某个字符占几个字节是没有意义的。
就好比有一个抽象的整数“42”,你说它占几个字节?这得具体看你是用 byte,short,int,还是 long 来存它。用 byte 存就占一字节,用 short 存就占两字节,int 通常是四字节,long 通常八字节。当然,如果你用 byte,受限于它有限的位数,有些数它是存不了的,比如 256 就无法放在一个 byte 里了。字符是同样的道理,如果你想谈“占几个字节”,就要先把编码说清楚。
同一个字符在不同的编码下可能占不同的字节。
就以你举的“字”字为例,“字”在 GBK 编码下占 2 字节,在 UTF-16 编码下也占 2 字节,在 UTF-8 编码下占 3 字节,在 UTF-32 编码下占 4 字节。
不同的字符在同一个编码下也可能占不同的字节。
“字”在 UTF-8 编码下占 3 字节,而“A”在 UTF-8 编码下占 1 字节。(因为 UTF-8 是变长编码)
# Java 中的字符串
对于 String 类型,有这样一个方法:getBytes()
,其将一个字符串转化为 Byte 序列,并存储到新的 byte[]
数组:
String str2 = "abcdefg";
byte[] b = str2.getBytes();
for(int i = 0; i < b.length; i++){
System.out.println(b[i]); //输出97.98.99.100...103, 就是 abcdefg 的 ASCII 码
}
2
3
4
5
问个面试题:new String("字").getBytes().length
的值是多少?还是上面提到的那句话,得看编码。
在 UTF8 下,"字"
占 3 个; 而在 GBK 下,一个汉字占 2 个字节;如果 getBytes()
没有传参,那么默认使用操作系统编码,通常,Windows 系统下是 GBK,Linux 和 Mac 是 UTF-8:
String str = "严";
System.out.println("str.getBytes().length: "+ str.getBytes().length);
System.out.println("str.getBytes(\"GBK\").length: "+ str.getBytes("GBK").length);
System.out.println("str.getBytes(\"UTF-8\").length: "+ str.getBytes("UTF-8").length);
2
3
4
编译和运行结果:
str.getBytes().length: 2
str.getBytes("GBK").length: 2
str.getBytes("UTF-8").length: 3
2
3
注意事项:
- 可以在启动 JVM 时设置一个默认编码,例如:
java -Dfile.encoding=GBK Main
。不过如果你在代码里还是显示的指定了编码,还是以指定编码为准; - 如果你用 IDE 来运行代码,并且设置了工程的默认编码是 UTF-8,那么此时运行程序的时候会加上
-Dfile.encoding=UTF-8
参数。 - 由于受启动参数及所在操作系统环境的影响,不带参数的
getBytes
方法通常是不建议使用的,最好是显式地指定参数以此获得稳定的预期行为。
# char 类型
Java 语言规范规定,Java 的 char 类型是 UTF-16 的 code unit,也就是一定是 16 位(2 字节):
官方文档 (opens new window):
char
, whose values are 16-bit unsigned integers representing UTF-16 code units (§3.1 (opens new window)).
值得一提的是,在 Java 设计之初,UTF-16 还是定长编码的,但是随着 Unicode 字符的增多,UTF-16 就改为变长编码了(2 个字节或 4 个字节)。 所以 char 中只能放 UTF-16 编码下只占 2 字节的那些字符,如果一个字符在 UTF-16 编码下占 4 字节,显然它是不能放到 char 中的。
# Python 下的字符编码
Python2 默认用的编码是 ASCII,Python3 默认用的是 UTF8
分别使用 len("你好") 在 Python3 得到的结果是 2,在 Python2 得到的结果是 6(6 个字节)
# Java 中的 String.length()
在 Java 中,可以通过 String.length()
计算字符串的长度。那么这个长度是什么呢?我们来看看 length()
的注释和源码:
/**
* Returns the length of this string.
* The length is equal to the number of Unicode code units in the string.
*
* @return the length of the sequence of characters represented by this object.
*/
public int length() {
return value.length;
}
2
3
4
5
6
7
8
9
注释里说,会返回字符串中 Unicode unit 的数量。在 Java 中,使用的是 UTF-16,因此这个方法的功能可以解释为:返回字符串的长度,这一长度等于字符串中的 UTF-16 的代码单元的数目。
也就是说,如果一个字符使用 4 个字节,那么 String.length()
长度是 2!
我们来测试下:
public class TestStringLength {
public static void main(String[] args) {
String str = "A";
System.out.println(str.length());
String str2 = "😀";
System.out.println(str2.length());
}
}
2
3
4
5
6
7
8
9
对于大多数 emoji 表情来说,其码点都比较大,得用 4 个字节,因此在 UTF-16 中就得用 2 个字节,因此结果分别输出 1, 2。
不会输入 emoji 的可以参考我的另一篇博客:输入法的技巧 (opens new window)
补充:String.length()
返回的是 value.length
,这个 value 是什么呢?是一个 char 数组,前面我们也讲过 Java 的 char 类型是 UTF-16 的 code unit,也就是一定是 16 位(2 字节):
private final char value[];
# String.codePointCount()
那么我们如何统计字符串有几个字符呢?可以用 String.codePointCount()
文档说明:返回指定字符序列的文本范围内的 Unicode 代码点数量。
public class TestStringLength {
public static void main(String[] args) {
String str2 = "😂";
System.out.println(str2.length());
System.out.println(str2.codePointCount(0,str2.length())); //输出1
}
}
2
3
4
5
6
7
# 代码
相关代码已上传到 Gitee (opens new window):https://gitee.com/peterjxl/LearnJava/tree/master/src/01.JavaSE/05.OOP/15.character (opens new window)