字符编码 在早期计算机系统中,为了给字符编码,美国国家标准学会(American National Standard Institute,ANSI)制定了一套英文编码规范,包含英文字母,数字和一些常用符号,编码范围从0
到127
,称为ascii
编码,每个字素(grapheme,a single unit of a human writing system)只占用一个字节,比如A
的编码为0x41(65)
。
但是随着计算机的发展的全球化,计算机需要能支持更多的语言,也就是说每一种语言的文字都需要一套与之对应的编码,对于拉丁母来说,一个字节的大小就能基本包含常用的字母和符号,但是对于东亚的表意文字来说,一个字节的大小显然是不够用的,需要更多的字节数,比如一个中
占用两个字节
在早期的时候并没有一套统一的规范,于是不同的国家和地区都制定了一套适用于本区域文字的编码,比如中文有GB2312
,日文有Shift_JIS
,韩文有EUC-KR
,不同的编码之间会冲突,这也导致了乱码的问题出现。
Unicode 为了统一全球所有语言的编码,全球统一码联盟发布了Unicode
编码,它把世界上的主要语言都纳入同一套编码中,这样,中文,日文,韩文和其他语言也就不会冲突了。它的长度为 2~4 个字节,比如A
的ascii
编码为0x41(65)
,而Unicode
编码为U+0041
,中
的GB2312
编码为0xd6d0
,Unicode
编码为U+4e2d
,除此之外,Unicode
编码还包含了 emoji 表情,比如🐂
的编码为U+1f402
,🍺
的编码为U+1f37a
。
UTF-8 而我们常说的UTF-8
编码是一种编码方式,它将固定长度的Unicode
编码转换成长度为 1~4 个字节的二进制码,比如A
的UTF-8
编码为0x41
,只有一个字节的长度,所以对于大量的英文文本,采用UTF-8
编码可以节省大量的存储空间,UTF-8
编码是通过高字节位来判断一个字素到底是几个字节的。
Java 中的 Unicode 码点 在Java
中,char
类型是采用UTF-16
编码的,也就是两个字节来表示一个字素,但是对于一些长度超过两个字节的Unicode
点(用来表示一个字素的 Unicode 编码)就不够用了, 所以就需要用两个 char 来表示一个码点,因此在用char
类型遍历字符串的时候就会产生错误。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 public class Main { public static void main (String[] args) { String s1 = "🐂🍺" ; String s2 = "牛啤" ; System.out.println(s1.length()); System.out.println(s2.length()); for (int i = 0 ; i < s1.length(); i++) { System.out.println(s1.charAt(i)); } for (int i = 0 ; i < s2.length(); i++) { System.out.println(s2.charAt(i)); } } }
所以为了解决这种问题,Java
提供了以码点的长度方式来遍历字符串的对应方法。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 public class Main { public static void main (String[] args) { String s1 = "🐂🍺" ; String s2 = "牛啤" ; System.out.println(s1.codePointCount(0 , s1.length())); System.out.println(s2.codePointCount(0 , s2.length())); int index1 = s1.offsetByCodePoints(0 , 0 ); System.out.println(index1); System.out.println(Integer.toHexString(s1.codePointAt(index1))); int index2 = s1.offsetByCodePoints(0 , 1 ); System.out.println(index2); System.out.println(Integer.toHexString(s1.codePointAt(index2))); int cp; for (int i = 0 ; i < s1.length(); i += Character.charCount(cp)) { cp = s1.codePointAt(i); System.out.println(Integer.toHexString(cp)); System.out.println(Character.toString(cp)); } for (int i = s1.length() - 1 ; i > 0 ; i--) { if (Character.isSurrogate(s1.charAt(i))) i--; cp = s1.codePointAt(i); System.out.println(Integer.toHexString(cp)); System.out.println(Character.toString(cp)); } } }
但是,以上遍历的方式显然不够优雅,其实Java
还提供了将字符串变为一个码点数组的方法,那我们就可以以数组的方式去遍历这个字符串。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 import java.util.Arrays;public class Main { public static void main (String[] args) { String s1 = "🐂🍺" ; String s2 = "牛啤" ; int [] codePoints = s1.codePoints().toArray(); System.out.println(Arrays.toString(codePoints)); String str = new String (codePoints, 0 , codePoints.length); System.out.println(str); } }
将单个码点转为字符串可以用Character.toString(int codePoint)
方法。
1 2 3 4 5 6 public class Main { public static void main (String[] args) { int codePoint = 0x1f37a ; System.out.println(Character.toString(codePoint)); } }