• 【JAVA编码专题】总结



    第一部分:编码基础

    为什么需要编码:用计算机看得懂的语言(二进制数)表示各种各样的字符。

    一、基本概念
    ASCII、Unicode、big5、GBK等为字符集,它们只定义了这个字符集内有哪些字符,以及分别用什么数字表示。
    而UTF-8与UTF-16则定义了Unicode字符集如何使用计算机看得懂的语言进行传输和保存。
    例如: Unicode 字符 U+00A9 = 1010 1001 (版权符号) 在 UTF-8 里的编码为:

           11000010 10101001 = 0xC2 0xA9

    事实上,没有必要将它们严格区分,一些字符集本身就是编码方式,如ASCII。它们均表示如何用二进制数表示一个字符。

    因此很多地方将UTF-8、UTF-16与ASCII、GBK等统一当作字符编码方式。



    二、常见编码

    明白了各种语言需要交流,经过翻译是必要的,那又如何来翻译呢?计算中提拱了多种翻译方式,常见的有 ASCII、ISO-8859-1、GB2312、GBK、UTF-8、UTF-16 等。它们都可以被看作为字典,它们规定了转化的规则,按照这个规则就可以让计算机正确的表示我们的字符。目前的编码格式很多,例如 GB2312、GBK、UTF-8、UTF-16 这几种格式都可以表示一个汉字,那我们到底选择哪种编码格式来存储汉字呢?这就要考虑到其它因素了,是存储空间重要还是编码的效率重要。根据这些因素来正确选择编码格式,下面简要介绍一下这几种编码格式。

    ASCII 码
    学过计算机的人都知道 ASCII 码,总共有 128 个,用一个字节的低 7 位表示,0~31 是控制字符如换行回车删除等;32~126 是打印字符,可以通过键盘输入并且能够显示出来。

    ISO-8859-1
    128 个字符显然是不够用的,于是 ISO 组织在 ASCII 码基础上又制定了一些列标准用来扩展 ASCII 编码,它们是 ISO-8859-1~ISO-8859-15,其中 ISO-8859-1 涵盖了大多数西欧语言字符,所有应用的最广泛。ISO-8859-1 仍然是单字节编码,它总共能表示 256 个字符。

    GB2312
    它的全称是《信息交换用汉字编码字符集 基本集》,它是双字节编码,总的编码范围是 A1-F7,其中从 A1-A9 是符号区,总共包含 682 个符号,从 B0-F7 是汉字区,包含 6763 个汉字。

    GBK
    全称叫《汉字内码扩展规范》,是国家技术监督局为 windows95 所制定的新的汉字内码规范,它的出现是为了扩展 GB2312,加入更多的汉字,它的编码范围是 8140~FEFE(去掉 XX7F)总共有 23940 个码位,它能表示 21003 个汉字,它的编码是和 GB2312 兼容的,也就是说用 GB2312 编码的汉字可以用 GBK 来解码,并且不会有乱码。

    GB18030
    全称是《信息交换用汉字编码字符集》,是我国的强制标准,它可能是单字节、双字节或者四字节编码,它的编码与 GB2312 编码兼容,这个虽然是国家标准,但是实际应用系统中使用的并不广泛。

    UTF-16
    说到 UTF 必须要提到 Unicode(Universal Code 统一码),ISO 试图想创建一个全新的超语言字典,世界上所有的语言都可以通过这本字典来相互翻译。可想而知这个字典是多么的复杂,关于 Unicode 的详细规范可以参考相应文档。Unicode 是 Java 和 XML 的基础,下面详细介绍 Unicode 在计算机中的存储形式。

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

    UTF-8
    UTF16固定使用2个字节(或者4个字节)来表示字符,这导致与早期大量使用的ASCII码无法兼容,同时一些特殊字符在UNIX系统中存在特殊含义,如 '/0' 或 '/', 它们在 文件名和其他 C 库函数参数里都有特别的含义。此外,一些最常用的字符(西欧字符)只用一个字节就可以表示,若使用UTF16则浪费带宽或者存储空间。


    三、 UTF-8详细介绍
    首先 UCS 和 Unicode 只是分配整数给字符的编码表. 现在存在好几种将一串字符表示为一串字节的方法. 最显而易见的两种方法是将 Unicode 文本存储为 2 个 或 4 个字节序列的串. 这两种方法的正式名称分别为 UCS-2 和 UCS-4. 除非另外指定, 否则大多数的字节都是这样的(Bigendian convention). 将一个 ASCII 或 Latin-1 的文件转换成 UCS-2 只需简单地在每个 ASCII 字节前插入 0x00. 如果要转换成 UCS-4, 则必须在每个 ASCII 字节前插入三个 0x00.

    在 Unix 下使用 UCS-2 (或 UCS-4) 会导致非常严重的问题. 用这些编码的字符串会包含一些特殊的字符, 比如 '/0' 或 '/', 它们在 文件名和其他 C 库函数参数里都有特别的含义. 另外, 大多数使用 ASCII 文件的 UNIX 下的工具, 如果不进行重大修改是无法读取 16 位的字符的. 基于这些原因, 在文件名, 文本文件, 环境变量等地方, UCS-2 不适合作为 Unicode 的外部编码.

    在 ISO 10646-1 Annex R 和 RFC 2279 里定义的 UTF-8 编码没有这些问题. 它是在 Unix 风格的操作系统下使用 Unicode 的明显的方法.

    UTF-8 有一下特性:

    UCS 字符 U+0000 到 U+007F (ASCII) 被编码为字节 0x00 到 0x7F (ASCII 兼容). 这意味着只包含 7 位 ASCII 字符的文件在 ASCII 和 UTF-8 两种编码方式下是一样的.
    所有 >U+007F 的 UCS 字符被编码为一个多个字节的串, 每个字节都有标记位集. 因此, ASCII 字节 (0x00-0x7F) 不可能作为任何其他字符的一部分.
    表示非 ASCII 字符的多字节串的第一个字节总是在 0xC0 到 0xFD 的范围里, 并指出这个字符包含多少个字节. 多字节串的其余字节都在 0x80 到 0xBF 范围里. 这使得重新同步非常容易, 并使编码无国界, 且很少受丢失字节的影响.
    可以编入所有可能的 231个 UCS 代码
    UTF-8 编码字符理论上可以最多到 6 个字节长, 然而 16 位 BMP 字符最多只用到 3 字节长.
    Bigendian UCS-4 字节串的排列顺序是预定的.
    字节 0xFE 和 0xFF 在 UTF-8 编码中从未用到.
    下列字节串用来表示一个字符. 用到哪个串取决于该字符在 Unicode 中的序号.

    U-00000000 - U-0000007F:    0xxxxxxx
    U-00000080 - U-000007FF:    110xxxxx 10xxxxxx
    U-00000800 - U-0000FFFF:    1110xxxx 10xxxxxx 10xxxxxx
    U-00010000 - U-001FFFFF:    11110xxx 10xxxxxx 10xxxxxx 10xxxxxx
    U-00200000 - U-03FFFFFF:    111110xx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx
    U-04000000 - U-7FFFFFFF:    1111110x 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx
    xxx 的位置由字符编码数的二进制表示的位填入. 越靠右的 x 具有越少的特殊意义. 只用最短的那个足够表达一个字符编码数的多字节串. 注意在多字节串中, 第一个字节的开头"1"的数目就是整个串中字节的数目.

    例如: Unicode 字符 U+00A9 = 1010 1001 (版权符号) 在 UTF-8 里的编码为:

    11000010 10101001 = 0xC2 0xA9

    而字符 U+2260 = 0010 0010 0110 0000 (不等于) 编码为:

    11100010 10001001 10100000 = 0xE2 0x89 0xA0

    这种编码的官方名字拼写为 UTF-8, 其中 UTF 代表 UCS Transformation Format. 请勿在任何文档中用其他名字 (比如 utf8 或 UTF_8) 来表示 UTF-8, 当然除非你指的是一个变量名而不是这种编码本身.



    第二部分:JAVA编码

    一、String中的编码

    1、JVM的默认编码为UTF-8

    		System.out.println(Charset.defaultCharset());
    
    输出:UTF-8


    2、获取String的编码

    可以通过public byte[] getBytes(String charsetName) throws UnsupportedEncodingException获取某个String的编码

    		// 二、获取String在各种编码格式中的编码
    		String s = "aZ中文";
    		// 1、默认的Unicode编码
    		byte[] bytesDefault = s.getBytes();
    		System.out.println(getHexString(bytesDefault));
    
    		// 2、也是默认的编码
    		byte[] bytesDefault2 = s.getBytes(Charset.defaultCharset());
    		System.out.println(getHexString(bytesDefault2));
    
    		// 3、UTF-8编码
    		byte[] bytesUTF8 = s.getBytes("UTF-8");
    		System.out.println(getHexString(bytesUTF8));
    
    		// 4、UTF-16编码
    		byte[] bytesUTF16 = s.getBytes("UTF-16");
    		System.out.println(getHexString(bytesUTF16));
    
    		// 5、unicode编码
    		byte[] bytesUnicode = s.getBytes("UNICODE");
    		System.out.println(getHexString(bytesUnicode));
    
    		// 6、GBK编码
    		byte[] bytesGBK = s.getBytes("GBK");
    		System.out.println(getHexString(bytesGBK));
    输出:

    615ae4b8ade69687
    615ae4b8ade69687
    615ae4b8ade69687
    feff0061005a4e2d6587
    feff0061005a4e2d6587
    615ad6d0cec4

    由此可以看出,unicode与utf16可以认为是同一方式。


    3、通过byte[]编码构建String

    public String(byte[] bytes, String charsetName) throws UnsupportedEncodingException
    用什么方式生成的编码,就应该用原有的方式进行还原 

    		// 三、通过byte[]编码构建String,用什么方式生成的编码,就应该用原有的方式进行还原
    		// 1、默认编码
    		String sDefault = new String(bytesDefault);
    		System.out.println(sDefault);
    
    		// 2、默认编码2
    		String sDefault2 = new String(bytesDefault2, Charset.defaultCharset());
    		System.out.println(sDefault2);
    
    		// 3、UTF-8编码
    		String sUTF8 = new String(bytesUTF8, "UTF8");
    		System.out.println(sUTF8);
    
    		// 4、UTF-16编码
    		String sUTF16 = new String(bytesUTF16, "UTF16");
    		System.out.println(sUTF16);
    
    		// 5、unicode编码
    		String sUnicode = new String(bytesUnicode, "Unicode");
    		System.out.println(sUnicode);
    
    		// 6、GBK编码
    		String sGBK = new String(bytesGBK, "GBK");
    		System.out.println(sGBK);
    输出:

    aZ中文
    aZ中文
    aZ中文
    aZ中文
    aZ中文
    aZ中文

    若用其它编码格式来构建String,则出现乱码

    		// 若通过其它编码方式进行还原,则出现乱码
    		// 1、用UTF-16编码解码UTF8生成的字节
    		String sUTF16Erro = new String(bytesDefault, "UTF16");
    		System.out.println(sUTF16Erro);
    
    		// 2、用unicode编码解码UTF8生成的字节
    		String sUnicodeError = new String(bytesDefault, "Unicode");
    		System.out.println(sUnicodeError);
    
    		// 3、用GBK编码解码UTF8生成的字节
    		String sGBKError = new String(bytesDefault, "GBK");
    		System.out.println(sGBKError);

    输出:

    慚�귦隇
    慚�귦隇
    aZ涓�枃

    附完整代码及输出

    package org.ljh.javademo.encode;
    
    import java.io.IOException;
    import java.nio.charset.Charset;
    
    public class DefaultEncode {
    
    	public static void main(String[] args) throws IOException {
    		// 一、获取当前JVM环境的默认编码
    		System.out.println(Charset.defaultCharset());
    
    		// 二、获取String在各种编码格式中的编码
    		String s = "aZ中文";
    		// 1、默认的Unicode编码
    		byte[] bytesDefault = s.getBytes();
    		System.out.println(getHexString(bytesDefault));
    
    		// 2、也是默认的编码
    		byte[] bytesDefault2 = s.getBytes(Charset.defaultCharset());
    		System.out.println(getHexString(bytesDefault2));
    
    		// 3、UTF-8编码
    		byte[] bytesUTF8 = s.getBytes("UTF-8");
    		System.out.println(getHexString(bytesUTF8));
    
    		// 4、UTF-16编码
    		byte[] bytesUTF16 = s.getBytes("UTF-16");
    		System.out.println(getHexString(bytesUTF16));
    
    		// 5、unicode编码
    		byte[] bytesUnicode = s.getBytes("UNICODE");
    		System.out.println(getHexString(bytesUnicode));
    
    		// 6、GBK编码
    		byte[] bytesGBK = s.getBytes("GBK");
    		System.out.println(getHexString(bytesGBK));
    
    		// 三、通过byte[]编码构建String,用什么方式生成的编码,就应该用原有的方式进行还原
    		// 1、默认编码
    		String sDefault = new String(bytesDefault);
    		System.out.println(sDefault);
    
    		// 2、默认编码2
    		String sDefault2 = new String(bytesDefault2, Charset.defaultCharset());
    		System.out.println(sDefault2);
    
    		// 3、UTF-8编码
    		String sUTF8 = new String(bytesUTF8, "UTF8");
    		System.out.println(sUTF8);
    
    		// 4、UTF-16编码
    		String sUTF16 = new String(bytesUTF16, "UTF16");
    		System.out.println(sUTF16);
    
    		// 5、unicode编码
    		String sUnicode = new String(bytesUnicode, "Unicode");
    		System.out.println(sUnicode);
    
    		// 6、GBK编码
    		String sGBK = new String(bytesGBK, "GBK");
    		System.out.println(sGBK);
    
    		// 若通过其它编码方式进行还原,则出现乱码
    		// 1、用UTF-16编码解码UTF8生成的字节
    		String sUTF16Erro = new String(bytesDefault, "UTF16");
    		System.out.println(sUTF16Erro);
    
    		// 2、用unicode编码解码UTF8生成的字节
    		String sUnicodeError = new String(bytesDefault, "Unicode");
    		System.out.println(sUnicodeError);
    
    		// 3、用GBK编码解码UTF8生成的字节
    		String sGBKError = new String(bytesDefault, "GBK");
    		System.out.println(sGBKError);
    
    	}
    
    	// 输入byte[],将之转化为16进制的字符进行输出,如输入{90,20,21},则返回5A1415。因为默认情况下byte以10进制格式进行输出
    	private static String getHexString(byte[] b) {
    		String hexs = "";
    		for (int i = 0; i < b.length; i++) {
    			String hex = Integer.toHexString(b[i] & 0xFF);
    			if (hex.length() == 1) {
    				hex = '0' + hex;
    			}
    			hexs += hex;
    		}
    		return hexs;
    	}
    
    }
    

    输出:

    UTF-8
    615ae4b8ade69687
    615ae4b8ade69687
    615ae4b8ade69687
    feff0061005a4e2d6587
    feff0061005a4e2d6587
    615ad6d0cec4
    aZ中文
    aZ中文
    aZ中文
    aZ中文
    aZ中文
    aZ中文
    慚�귦隇
    慚�귦隇
    aZ涓�枃


    二、JAVA IO中的编码


  • 相关阅读:
    netty ByteToMessageDecoder 分析
    netty 编/解码处理
    MAC 入门
    netty 学习
    php ioc and web rest design
    spring 启动流程
    淘宝美衣人
    ecslipe cdt lib link
    阿里巴巴中间件团队招人了!
    架构师速成-架构目标之伸缩性安全性
  • 原文地址:https://www.cnblogs.com/eaglegeek/p/4557824.html
Copyright © 2020-2023  润新知