从字节到字符
编码原因
计算机不能直接表示各种文字,只能二进制,文字只能映射到数值
任何文字底层全是byte,编码决定了二进制流怎么切割,怎么翻译成文字
各地编码
- ASCII
也称为ANSI, 7位编码,数字、字母、标点、控制符,美国编码标准 - ISO-8859-1
也称为Latin-1,单字节编码,兼容ASCII,加入了带有注音字母等扩展字符,适用于欧洲的编码标准 - GB2312
变长,小于127为ASCII码单字节,大于127的两个字节构成一个汉字,国标编码标准,还新编了2字节版本的ASCII码作为全角字符,共涵盖6000+汉字 - GBK
国标编码扩展,兼容GB2312,支持繁体,涵盖20000+汉字 - GB18030
GBK基础上新增了少数民族文字,最长可以4字节Unicode
国际统一编码
Unicode(Universal Multiple-Octet Coded Character Set,UCS), UCS-2两字节,UCS-4四字节,国际编码标准
Unicode和各个编码都不兼容,只是前面值可视为单字节的编码点和ISO-8859-1一致
Unicode转GB系列需要查表,而转UTF系列使用算法映射
Unicode制定了字符集,如果直接按照Unicode传输比较浪费,而UTF(UCS Transfer Format)定义了不同的表现形式
- UTF-8
变长,8位为码元,用1~4个码元编码,英文1字节,中文3字节 - UTF-32
定长,所有都用4字节表示 - UTF-16
变长,2或4字节大部分都是2字节,16位为码元,字符集被划分成基本平面和辅助平面,位于基本平面的1个码元,位于辅助平面的2个码元
UTF8确定长度规则
- 字节最高位是0,编码只有一个字节
- 字节开头是11,连续1的个数表示编码字节数
- 字节开头是10,非首字节,需要到前面查找首字节
比较
在网络传输情况下,可能损坏单字节
- Utf-8变长节省流量,单字节单位有规则确认位置,即使损坏也不会影响后文
- Utf-16基本定长,更适合内存和硬盘中使用
BOM
BOM(Byte Order Mark),值FE FF
表示大端序,值FF FE
表示小端序
UTF-8单字节为单位,不存在字节序问题无需BOM。但是也允许携带作为标志EF BB BF
Windows平台有时编辑器也会加BOM,因此务必选择UTF-8无BOM模式。
Java中的编解码
- Java源代码的编码可以自由指定,IDE可以设置,最终表现为javac的encoding参数,如果不指定就会操作系统编码,即要告知编译器源文件是什么编码
- 编译时会进行编码转换,编译后的class文件采用Utf-8编码存储
- 为了支持国际化,Java字符串采用Unicode编码。JVM运行时内部采用Utf-16编码,因而Java中的char是16位双字节。虽然比较耗空间,但是相对定长更易于处理。
- System.out输出时由Utf-16转为系统编码
- 解析外部资源,使用file.encoding指定的编码,默认Utf-8
字符集类
java.nio.charset.Charset提供编码字符集相关功能,JVM解析外部资源所使用的编码由defaultCharset方法获得,与JVM启动时的操作系统默认编码有关,默认编码首先检查System.getProperty("file.encoding")
是否指定,没设置就采用UTF-8
作为默认编码
volatile + sync延迟加载模式
1 | private static volatile Charset defaultCharset; |
Charset可以直接用于编解码,在ByteBuffer和CharBuffer之间转换
1 | public final ByteBuffer encode(CharBuffer cb) { |
内置字符集名称
直接以字符串形式手写编码类型是不太好的形式,至少也应该是常量定义
Java 1.7之后存在java.nio.charset.StandardCharsets,无需使用第三方库,直接引用
1 | public final class StandardCharsets { |
字符串的编解码
- 文字转byte数组,编码过程
String类提供的getBytes方法可以获得文字编码后的字节数组结果,可以指定编码或者使用默认编码
1 | DatatypeConverter.printHexBinary("Java".getBytes("UTF-8")) //4A 61 76 61 每个英文只用1字节编码 |
获取和指定Unicode
1 | System.out.println(DatatypeConverter.printHexBinary("编码".getBytes("Unicode"))); //FEFF 7F16 7801 |
- byte数组转文字,解码过程
编解码使用同一规则才能正确,否则会出现乱码
1 | byte[] src = "编码".getBytes("Utf-8"); |
对于ASCII,因为都兼容,因此不会出现问题
1 | byte[] src = "Java".getBytes("Utf-8"); |
IO编解码
InputStream/OutputStream读字节流,Reader/Writer读字符流,InputStreamReader/OutputStreamWriter实现了字节到字符的转化
字节流处理二进制数据,是不考虑编解码的,所以FileInputStream并无任何处理编解码功能
InputStreamReader进行了字节字符转化,可以接受字符集参数,通过内部的StreamDecoder进行处理
1 | public InputStreamReader(InputStream in) { |
FileReader是InputStreamReader的简单继承,构造时没有传入字符集参数,只能使用默认编码,因此需要制定编码时不能直接用FileReader
1 | public class FileReader extends InputStreamReader { |
版权声明
This site by Linest is licensed under a Creative Commons BY-NC-ND 4.0 International License.
由Linest创作并维护的博客采用创作共用保留署名-非商业-禁止演绎4.0国际许可证。