Okio原理分析之字符编码

OKio的原理分析,准备分3个部分来分析:

  • 字符编码 先了解一些背景知识,Okio里面基本上是基于UTF-8来编码实现的
  • Okio简介 熟悉OKio里面引入的一些概念,如Source、Sink、Timeout、Buffer、Segment、SegmentPool、ByteString等
  • Okio里面Segment数据移动管理 OKio高效的原因,在数据移动方面的一些巧妙的设计,来节约内存和节省CPU

1.字符编码

字符编码(Character encoding)、字集码是把字符集中的字符编码为指定集合中某一对象(例如:比特模式、自然数序列、8位组或者电脉冲),以便文本在计算机中存储和通过通信网络的传递。常见的例子包括将拉丁字母表编码成摩斯电码和ASCII。其中,ASCII将字母、数字和其它符号编号,并用7比特的二进制来表示这个整数。通常会额外使用一个扩充的比特,以便于以1个字节的方式存储。

在计算机系统里面,编码的原因可以总结为:

  • 计算机中存储信息的最小单元是一个字节即 8 个 bit,所以能表示的字符范围是 0~255 个
  • 人类要表示的符号太多,无法用一个字节来完全表示
  • 要解决这个矛盾必须需要一个新的数据结构 char,从 char 到 byte 必须编码
UCS和Unicode

通用字符集(英语:Universal Character Set, UCS)是由ISO制定的ISO 10646(或称ISO/IEC 10646)标准所定义的标准字符集。

通用字符集包括了其他所有字符集。它保证了与其他字符集的双向兼容,即,如果你将任何文本字符串翻译到UCS格式,然后再翻译回原编码,你不会丢失任何信息。

Unicode,中文又称万国码、国际码、统一码、单一码,是计算机科学领域里的一项业界标准

Unicode规范定义了,每个文件的最前面分别加入一个表示编码顺序的字符,用FEFF来表示,正好是2个字节。如果文本文件的头2个字节是FEFF,表示用的大头方式(第一个字节在前),用FFFE的表示用的小头方式(第二个字节在前)

Unicode是一个符号集合(字符集),定义了符号的二进制代码,但是没有规定如何存储二进制代码

Unicode和UCS是来自2个不同的组织,Unicode对集合添加了一些新的规则和规范

历史上存在两个独立的尝试创立单一字符集的组织

  • 国际标准化组织(ISO)于1984年创建的ISO/IEC
  • 由Xerox、Apple等软件制造商于1988年组成的统一码联盟。前者开发的ISO/IEC 10646项目,后者开发的Unicode项目。因此最初制定了不同的标准。

从Unicode 2.0开始,Unicode采用了与ISO 10646-1相同的字库和字码;ISO也承诺,ISO 10646将不会替超出U+10FFFF的UCS-4编码赋值,以使得两者保持一致。两个项目仍都独立存在,并独立地公布各自的标准。但统一码联盟和ISO/IEC JTC1/SC2都同意保持两者标准的码表兼容,并紧密地共同调整任何未来的扩展。在发布的时候,Unicode一般都会采用有关字码最常见的字体,但ISO 10646一般都尽可能采用Century字体

UTF

Unicode的实现方式不同于编码方式。一个字符的Unicode编码是确定的。但是在实际传输过程中,由于不同系统平台的设计不一定一致,以及出于节省空间的目的,对Unicode编码的实现方式有所不同。Unicode的实现方式称为Unicode转换格式(Unicode Transformation Format,简称为UTF)。

UTF是"Unicode/UCS Transformation Format"的首字母缩写,即把Unicode字符转换为某种格式之意。UTF-16正式定义于ISO/IEC 10646-1的附录C,而RFC2781也定义了相似的做法。

编码格式
  • ASCII :美国制定了一套字符编码,对英语字符与二进制位之间的关系,做了统一规定。规定了128个字符的编码,用一个byte来表示,只占用7个bit,最高为0
  • ISO-8859-1:ISO-8859-1 仍然是单字节编码,它总共能表示 256 个字符
  • GB2312 双字节编码,总的编码范围是 A1-F7,其中从 A1-A9 是符号区,总共包含 682 个符号,从 B0-F7 是汉字区,包含 6763 个汉字
  • GBK 它的出现是为了扩展 GB2312,加入更多的汉字,它的编码范围是 8140~FEFE(去掉 XX7F)总共有 23940 个码位,它能表示 21003 个汉字,它的编码是和 GB2312 兼容的,也就是说用 GB2312 编码的汉字可以用 GBK 来解码,并且不会有乱码。
  • UTF-16 是Unicode字符编码五层次模型的第三层:字符编码表(Character Encoding Form,也称为"storage format")的一种实现方式。即把Unicode字符集的抽象码位映射为16位长的整数(即码元)的序列,用于数据存储或传递。Unicode字符的码位,需要1个或者2个16位长的码元来表示,因此这是一个变长表示。
  • UTF-8:是Unicode的实现方式之一,使用变长的编码形式,可以用1-6个byte来表示一个符号(In UTF-8, characters are encoded using sequences of 1 to 6 octets.)

UTF-8 有以下编码规则:

  • 如果一个字节,最高位(第 8 位)为 0,表示这是一个 ASCII 字符(00 – 7F)。可见,所有 ASCII 编码已经是 UTF-8 了。
  • 如果一个字节,以 11 开头,连续的 1 的个数暗示这个字符的字节数,例如:110xxxxx 代表它是双字节 UTF-8 字符的首字节。
  • 如果一个字节,以 10 开始,表示它不是首字节,需要向前查找才能得到当前字符的首字节
   UCS-4 range (hex.)     UTF-8 octet sequence (binary)
 
   0000 0000-0000 007F   0xxxxxxx
 
   0000 0080-0000 07FF   110xxxxx 10xxxxxx
 
   0000 0800-0000 FFFF   1110xxxx 10xxxxxx 10xxxxxx

   0001 0000-001F FFFF   11110xxx 10xxxxxx 10xxxxxx 10xxxxxx
 
   0020 0000-03FF FFFF   111110xx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx
 
   0400 0000-7FFF FFFF   1111110x 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx

详细的编码说明可以参考:https://tools.ietf.org/html/rfc2279

2.Java里面的编码说明

Java字符和字节
  • 二进制位bit:计算机,所有的信息都是bit的形式存在,每个bit有0和1两种状态
  • 字节byte:8个二进制位是一个byte,1 byte = 8 bit
  • 字符Character:1 char = 2 byte = 16 bit(Java对字符默认是UTF-16编码)

此节内容,截取自深入分析 Java 中的中文编码问题

以字符串”I am 君山”的 char 数组为 49 20 61 6d 20 541b 5c71,为例,下面把它按照不同的编码格式转化成相应的字节。

    public static void encode() {
        String name = "I am 君山";
        System.out.println( " default charset = " + Charset.defaultCharset()+" : " +bytesToHex(name.getBytes()));
        try {
            byte[] iso8859 = name.getBytes("ISO-8859-1");
            System.out.println("ISO-8859-1 = " + bytesToHex(iso8859));
            byte[] gb2312 = name.getBytes("GB2312");
            System.out.println("GB2312 = " + bytesToHex(gb2312));
            byte[] gbk = name.getBytes("GBK");
            System.out.println("GBK = " + bytesToHex(gbk));
            byte[] utf16 = name.getBytes("UTF-16");
            System.out.println("UTF-16 = " + bytesToHex(utf16));
            byte[] utf8 = name.getBytes("UTF-8");
            System.out.println("UTF-8 = " + bytesToHex(utf8));
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        }
    }

    public static String bytesToHex(byte[] bytes) {
        char[] HEX_ARRAY = "0123456789ABCDEF".toCharArray();
        char[] hexChars = new char[bytes.length * 2];
        for (int j = 0; j < bytes.length; j++) {
            int v = bytes[j] & 0xFF;
            hexChars[j * 2] = HEX_ARRAY[v >>> 4];
            hexChars[j * 2 + 1] = HEX_ARRAY[v & 0x0F];
        }
        return new String(hexChars);
    }

执行结果是:

 default charset = UTF-8 : 4920616D20E5909BE5B1B1
ISO-8859-1 = 4920616D203F3F
GB2312 = 4920616D20BEFDC9BD
GBK = 4920616D20BEFDC9BD
UTF-16 = FEFF004900200061006D0020541B5C71
UTF-8 = 4920616D20E5909BE5B1B1

按照 ISO-8859-1 编码
Okio原理分析之字符编码

按照 GB2312 编码
Okio原理分析之字符编码

按照 GBK 编码
Okio原理分析之字符编码

按照 UTF-16 编码
Okio原理分析之字符编码

按照 UTF-8 编码
Okio原理分析之字符编码

上一篇:android – 为什么Okio比BufferedInputStream和BufferedOutputStream更有效?


下一篇:条件变量 sync.Cond