一 charAt 与 codePonitAt
我们知道 Java 内部使用的是 utf-16 作为它的 charString 的字符编码方式, 这里我们叫它内部字符集而 utf-16 是变长编码, 一个字符的编码被称为一个 code point, 它可能是 16 位 一个 code unit, 也可能是 32 位 两个 code unit
作为一个输入法爱好者, 我偶尔会编程处理一些生僻字其中有些生僻字大概是后来才加入 unicode 字符集里的, 直接用 charAt 方法读取它们, 会得到一堆问号原因很清楚 因为这些字符是用两个 code unit, 也就是两个 char 表示的 charAt 找不到对应的编码, 就会将这些 char 输出成?
- // 示例
- public class Test {public static void main(String[] args){
- String s = "";
- System.out.println(s.length()); // 输出: 2
- System.out.println(s.charAt(0)); // 输出:?
- System.out.println(s.charAt(1)); // 输出:?
- }
- }
因此, 涉及到中文, 一定要使用 String 而不是 char, 并且使用 codePoint 相关方法来处理它否则的话, 如果用户使用了生僻字, 很可能就会得到不想要的结果
下面是一个使用 codePoint 遍历一个字符串的示例, 需要注意的是, codePoint 是 int 类型的, 因此需要做些额外的转换:
- public class Test {
- public static void main(String[] args){
- String s = "赵孟";
- for (int i = 0; i < s.codePointCount(0,s.length()); i++) {
- System.out.println(
- new String(Character.toChars(s.codePointAt(i))));
- }
- }
- }
- /* 结果:
- 赵
- 孟
- */
二内部字符集与输出字符集(内码与外码)
现在我们知道了中文字符在 java 内部可能会保存成两个 char, 可还有个问题: 如果我把一个字符输出到某个流, 它还会是两个 char, 也就是 4 字节么?
回想一下, Java io 有字符流, 字符流使用 jvm 默认的字符集输出, 而若要指定字符集, 可使用转换流
因此, 一个中文字符, 在内部是使用 utf-16 表示, 可输出就不一定了
来看个示例:
- import java.io.UnsupportedEncodingException;
- public class Test {
- public static void main(String[] args)
- throws UnsupportedEncodingException {
- String s = "中"; //
- System.out.println(s + ": chars:" + s.length());
- System.out.println(s + ": utf-8 bytes:" + s.getBytes("utf-8").length);
- System.out.println(s + ": utf-16 bytes:" + s.getBytes("utf-16").length);
- }
- }
输出为:
中: chars: 1
中: utf-8 bytes:3
中: unicode bytes: 4
中: utf-16 bytes: 4
- : chars: 2
- : utf-8 bytes:4
- : unicode bytes: 6
- : utf-16 bytes: 6
一个中字, 内部存储只用了一个 char, 也就是 2 个字节可转换成 utf-8 编码后, 却用了 3 个字节怎么会不一样呢, 是不是程序出了问题?
当然不是程序的问题, 这是内码转换成外码, 字符集发生了改变, 所使用的字节数自然也可能会改变(尤其这俩字符集还都是变长编码)
三 utf-16utf-16leutf-16be,utf-8bom
不知道在刚刚的示例中, 你有没有发现问题: 同是 utf-16, 为何中和在转换成 byte 后, 都多出了两字节数据
原因在于, utf-16 以 16 位为单位表示数据, 而计算机是以字节为基本单位来存储 / 读取数据的因此一个 utf-16 的 code unit 会被存储为两个字节, 需要明确指明这两个字节的先后顺序, 计算机才能正确地找出它对应的字符而 utf-16 本身并没有指定这些, 所以它会在字符串开头插入一个两字节的数据, 来存储这些信息 (大端还是小端) 这两个字节被称为 BOM(Byte Order Mark)刚刚发现的多出的两字节就是这么来的
如果你指定编码为 utf-16le 或 utf-16be, 就不会有这个 BOM 的存在了这时就需要你自己记住该文件的大小端
四更多
在 windows 中, utf-8 格式的文件也可能会带有 BOM, 但 utf-8 的基本单位本来就是一个字节, 因此它不需要 BOM 来表示 所谓大小端这个 BOM 一般是用来表示该文件是一个 utf-8 文件不过 linux 系统则对这种带 BOM 的文件不太友好不般不建议加
unicode 字符集 UCS(Unicode Character Set) 就是一张包含全世界所有文字的一个编码表, 但是 UCS 太占内存了, 所以实际使用基本都是使用它的其他变体一般来说, 指定字符集时使用的 unicode 基本等同于 utf-16.(所以你会发现第二节演示的小程序里, utf-16 和 unicode 得出的结果是一样的)
四与 Python3 对比
python 3 在字符串表示上, 做了大刀阔斧的改革, 内部完全用 unicode 表示, 因此 python 程序员完全不需要去考虑字符的底层表示的问题 len(str) 得到的就是 unicode 字符数(不过在 windows 平台上, 即使是 python, 也要注意带不带 BOM 的问题详细的请 Google)
参考
java 语言中的一个字符占几个字节? - RednaxelaFX - 知乎
彻底搞懂字符编码(unicode,mbcs,utf-8,utf-16,utf-32,big endian,little endian...)
Java_字符编码
来源: https://www.cnblogs.com/kirito-c/p/8544408.html