`
cqupt123
  • 浏览: 66175 次
  • 性别: Icon_minigender_1
  • 来自: 成都
社区版块
存档分类
最新评论

探索java编码——UNICODE、GBK、UTF-8等

阅读更多

  必须知道的几个简单概念:

  1. unicode:字符集,包含了全世界几乎所有的字符。解除了ascii、iso-8859-1等字符集的局限。

  2. unicode码:与每一个字符相对应的数字,一对一映射,常用的BMP区段范围在0x0000—0xffff。编码时,通过字符找到数字,解码时,相反。

  3. utf-16:编码方式,将unicode中的每个字符转换成两个字节,并与BMP保持一致。由于编码方式简单,java内部采用utf-16的编码字符。

  4. utf-8:编码方式,将字符转化为1-4个字节。如英文字母会用一个字节来表示,汉字则会使用三个字节来表示。

  5. gbk:字符集、编码方式。gbk拥有自己的字符集以及对应的编码方式。向下兼容gb2312,而gb18030兼容了gbk。

  6. ... ...

 

  现在来看看我们常用的一个编码方法:String.getBytes(String charset);它具体做了什么。

  1. charset指定了字符集以及使用的编码方式,比如传入:gbk。

 

//关键代码
Charset cs = lookupCharset(csn); // 这里会找到sun.nio.cs.ext.GBK
if (cs != null)
	se = new StringEncoder(cs, csn); // 最终通过sun.nio.cs.ext.GBK.newEncoder()方法返回gbk的encoder
return se.encode(ca, off, len);
 

 

  2. 通过调用encoder的ecode方法。

 

//关键代码
CoderResult cr = encodeLoop(in, out);//  in为刚传入的字符序列,out为即将返回的字节序列

 

  3. gbk ecoder的encodeLoop方法在其父类DoubleByteEncoder中实现。

 

protected CoderResult encodeLoop(CharBuffer src, ByteBuffer dst) {
if (true && src.hasArray() && dst.hasArray())
  return encodeArrayLoop(src, dst);
else
  return encodeBufferLoop(src, dst); 
}

 

 

  4. 按照array和buffer两种形式去编码,但是本质都是一样的,看其中的一个:encodeBufferLoop

 

while (src.hasRemaining()) {
  char c = src.get();
  int b = encodeSingle(c);
  if (b != -1) { // Single-byte character
    if (dst.remaining() < 1)
      return CoderResult.OVERFLOW;
    mark++;
   out.put((byte)b);//对单字节的处理,直接返回b对应的字节
   continue;
  }
  // Double Byte character
  int ncode = encodeDouble(c);
  if (ncode != 0 && c != '\u0000') {
    if (dst.remaining() < 2)
      return CoderResult.OVERFLOW;
    mark++;
    //对双字节字符的处理
    //第一个字节通过ncode的高8位组成
    out.put((byte) ((ncode & 0xff00) >> 8));
    //第二个字节由低8位组成
    out.put((byte) ncode);
    continue;
  }
}

 

  5. 那么以上的int类型的b和ncode是怎么来的呢?

 

protected int encodeSingle(char inputChar) {
  if (inputChar < 0x80) // inputChar对应的int值小于128,即:1000 0000
     return (byte)inputChar;
  else
     return -1;
}

protected int encodeDouble(char ch) {
  int offset = index1[((ch & 0xff00) >> 8 )] << 8; //关注index1
  //关注index2
  return index2[offset >> 12].charAt((offset & 0xfff) + (ch & 0xff));
}

 

  6.如何判断单字节还是双字节?

     通过unicode码表,找到字符inputChar以及其对应的unicode码,例如:'一'对应的值为0x4E00。如果这个值小于0x80,则为单字节字符,否则为双字节。

     如果是单字节字符,直接返回该unicode码的低8位。

     如果为双字节字符,则通过index1和index2来定位对应的值。

 

  7. index1和index2分别是什么?

    在gbk encoder中分别定义了index1和index2,用来找到unicode码对应的gbk编码。

    index2 由7个字符串组成,每个字符串定义了不同的gbk编码,形式如下:

 

private final static String innerIndex0=
    "\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000"+...+
    "\uA8A8\uA8A6\uA8BA\u0000\uA8AC\uA8AA\u0000\u0000"+...+
    "\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000";
private final static String innerIndex1=
... ....
private final static String innerIndex6=

 private final static String index2[] = {
    innerIndex0,
    innerIndex1,
    innerIndex2,
    innerIndex3,
    innerIndex4,
    innerIndex5,
    innerIndex6
};

    index1主要用于定位index2中的字符,代码如下:

 

private final static short index1[] = {
  1, 2, 3, 4, 5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
  6, 7, 8, 9, 10, 11, 12, 0, 0, 0, 0, 0, 0, 0, 0, 0,
  ... ...
  0, 0, 0, 0, 0, 0, 0, 0, 0, 108, 109, 0, 0, 0, 110, 111
};

 

  这样GBK编码的整个过程就完成了(其中只展示了关键步骤)。

 

  再来看看utf-8的编码过程有什么不一样呢,直接看sun.nio.cs.UTF_8.encodeBufferLoop方法。

 

 private CoderResult encodeBufferLoop(CharBuffer src,
                                           ByteBuffer dst)
     {
         while (src.hasRemaining()) {
             int c = src.get(); // unicode码
             if (c < 0x80) { // 128个US-ASCII字符只需一个字节编码
                 dst.put((byte)c);
             } else if (c < 0x800) { 
                 // 2 bytes, 11 bits
                 // 带有附加符号的拉丁文、希腊文、西里尔字母、亚美尼亚语、希伯来文、阿拉伯文、叙利亚文及它拿字母则需要二个字节编码
                 // 以11开头,代表由双字节组成
                 dst.put((byte)(0xc0 | ((c >> 06))));
                 dst.put((byte)(0x80 | (c & 0x3f)));
             } else if (Surrogate.is(c)) { 
                  // 极少使用的Unicode 辅助平面的字符使用四字节编码
                  // Have a surrogate pair
                 if (sgp == null)
                    sgp = new Surrogate.Parser();
                 int uc = sgp.parse((char)c, src);
                // 以1111开头,代表由4个字节组成
                dst.put((byte)(0xf0 | ((uc >> 18))));
                dst.put((byte)(0x80 | ((uc >> 12) & 0x3f)));
                dst.put((byte)(0x80 | ((uc >> 06) & 0x3f)));
                dst.put((byte)(0x80 | (uc & 0x3f)));
                 mark++;  //2 chars
              } else {
                  // 3 bytes, 16 bits
                 // 其他BMP中的字符(这包含了大部分常用字)使用三个字节编码,中文字符一般使用3个字节编码。
                 // 以111开头,代表由三个字节字节组成
                 dst.put((byte)(0xe0 | ((c >> 12))));
                 dst.put((byte)(0x80 | ((c >> 06) & 0x3f)));
                 dst.put((byte)(0x80 | (c & 0x3f)));
              }
            }
         return CoderResult.UNDERFLOW;
      }

 

  可以看到,utf-8并没有像gbk中index1和index2的定义。utf-16也是一样,也就是说utf编码并不会去查询其他的码表。类似gbk编码方式的还有很多,例如:韩文编码等。从这里可以看出,utf-8编码的方式更加简单,少了查询码表的过程,所以性能会更高一些。但是,对于中文等字符,utf-8采用3个字节来存储,占用了更多的存储空间,用于网络传输会占用更大的带宽,所以各有利弊。

 

  utf-16则采用了unicode默认的编码方式,即直接将unicode码放入两个字节中,并分为big-endian和little-endian的字节序的存储方式,需要根据不同的硬件或者操作系统选择。

 

  当然,解码可以通过编码的逆向过程来得到对应的字符。到此为止,对字符集和编码的原理有了基本的认识。

分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics