Chinaunix首页 | 论坛 | 博客
  • 博客访问: 451095
  • 博文数量: 150
  • 博客积分: 2706
  • 博客等级: 少校
  • 技术积分: 1200
  • 用 户 组: 普通用户
  • 注册时间: 2009-11-09 11:41
文章分类

全部博文(150)

文章存档

2012年(7)

2011年(6)

2010年(68)

2009年(69)

我的朋友

分类:

2010-01-15 18:09:40

我们都知道对于UNICODE来说,UCS-2是内码,而UTF-8则是它的实现方式。每一个字节都有8个位,而对于UTF-8来说,每一个字节的前两位尤为重要,按照前两位的不同,一共有四种排列组合:00xxxxxx,01xxxxxx,10xxxxxx,11xxxxxx。

按照UTF-8标准,

(1)所有以0开始的字节,都与原来的ASCII码兼容,也就是说,0xxxxxxx不需要额外转换,就是我们平时用的ASCII码。

(2)所有以10开始的字节,都不是每个UNICODE的第一个字节,都是紧跟着前一位。例如:10110101,这个字节不可以单独解析,必须通过前一个字节来解析,如果前一个也是10开头,就继续前嗍。

(3)所有以11开始的字节,都表示是UNICODE的第一个字节,而且后面紧跟着若干个以10开头的字节。如果是110xxxxx(就是最左边的0的左边有2个1),代表后面还有1个10xxxxxx;如果是1110xxxx(就是最左边的0的左边有3个1),代表后面还有2个10xxxxxx;以此类推,一直到1111110x。

具体的表格如下:

1字节 0xxxxxxx
2字节 110xxxxx 10xxxxxx
3字节 1110xxxx 10xxxxxx 10xxxxxx
4字节 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx
5字节 111110xx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx
6字节 1111110x 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx

(很明显,以11开头的,最左边的0左边有多少个1,那这个UCS的UTF-8的表示长度就有多少个字节)

上面是用6个字节,最多可以表示2 ^ 31个的字符,实际上,只有UCS-4才有这么多的字符,对于UCS-2,仅仅有2 ^ 16个字符,只需要三个字节就可以,也就是说,只需要用到下面的格式:

1字节 0xxxxxxx
2字节 110xxxxx 10xxxxxx
3字节 1110xxxx 10xxxxxx 10xxxxxx

大家可以试一下下面的program,来看看UTF-8的每个字节。

Java代码 复制代码
  1. package com.ray.utf8;   
  2.   
  3. import java.io.UnsupportedEncodingException;   
  4.   
  5. public class UTF8Tester {   
  6.        
  7.     private static String toBin(int n) {   
  8.         StringBuilder b = new StringBuilder();   
  9.         if (n < 0) n += 256;   
  10.         for (int i = 7; i >= 0; i--) {   
  11.             if (1 == ((n >> i) & 1)) {   
  12.                 b.append('1');   
  13.             } else {   
  14.                 b.append('0');   
  15.             }   
  16.         }   
  17.         return b.toString();   
  18.     }   
  19.        
  20.     private static String HEX = "0123456789ABCDEF";   
  21.     private static String toHex(int n) {   
  22.         StringBuilder b = new StringBuilder();   
  23.         if (n < 0) n += 256;   
  24.         b.append(HEX.charAt(n >> 4));   
  25.         b.append(HEX.charAt(n & 0x0F));   
  26.         return b.toString();   
  27.     }   
  28.        
  29.     private static void printUTF8(char ch) throws UnsupportedEncodingException {   
  30.         String unicode = toHex(ch >> 8) + toHex(ch & 0xFF);   
  31.         String unicodeBin = toBin(ch >> 8) + ' ' + toBin(ch & 0xFF);   
  32.            
  33.         String s = "" + ch;   
  34.         byte[] b = s.getBytes("UTF-8");   
  35.         String hex = "";   
  36.         for (int i = 0; i < b.length; i++) {   
  37.             hex += toHex((int) b[i]);   
  38.             hex += " ";   
  39.         }   
  40.         String bin = "";   
  41.         for (int i = 0; i < b.length; i++) {   
  42.             bin += toBin((int) b[i]);   
  43.             bin += " ";   
  44.         }   
  45.         String sf = String.format("U+%s %s : %-8s : %s", unicode, unicodeBin, hex.trim(), bin.trim());   
  46.         System.out.println(sf);   
  47.     }   
  48.        
  49.     public static void main(String[] args) throws Exception {   
  50.         printUTF8('\u002A');   
  51.         printUTF8('\u012A');   
  52.         printUTF8('\u012B');   
  53.         printUTF8('\u052C');   
  54.         printUTF8('\u013C');   
  55.         printUTF8('\uAA2A');   
  56.         printUTF8('\uFDFD');   
  57.     }   
  58.   
  59. }  


输出:
U+002A 00000000 00101010 : 2A       : 00101010
U+012A 00000001 00101010 : C4 AA    : 11000100 10101010
U+012B 00000001 00101011 : C4 AB    : 11000100 10101011
U+052C 00000101 00101100 : D4 AC    : 11010100 10101100
U+013C 00000001 00111100 : C4 BC    : 11000100 10111100
U+AA2A 10101010 00101010 : EA A8 AA : 11101010 10101000 10101010
U+FDFD 11111101 11111101 : EF B7 BD : 11101111 10110111 10111101


UCS-2和UTF-8的转换,只涉及到位运算,不需要像GBK般需要查找代码表,所以转换效率很高。

先来说说UTF-8转UCS-2

(1)对于以0开始的字节,直接在前面部补一个0的字节凑成2个字节(即0xxxxxxx ==> 00000000 0xxxxxxxx);
(2)对于以110开始(110xxxxx)的字节,把后面紧跟着的一个10xxxxxx拿过来,首先在高位字节的左边补5个零,然后把11个“x”放在右边(即110xxxxx 10yyyyyy ==> 00000xxx xxyyyyyy);
(3)对于以1110开始(1110xxxx)的字节,把后面紧跟着的两个10xxxxxx拿过来,数一下,一共有16个“x”,没错,就是把这16个“x”组成两个字节(即1110xxxx 10yyyyyy 10zzzzzz ==> xxxxyyyy yyzzzzzz)。

在来说说UCS-2转UTF-8:

(1)对于不大于0x007F(即00000000 01111111)的,直接把它转成一个字节,变成ASCII;
(2)对于不大于0x07FF(即00000111 11111111)的,转换成两个字节,转换的时候把右边的11位分别放到110xxxxx 10yyyyyy里边,即00000aaa bbbbbbbb ==> 110aaabb 10bbbbbb
(3)剩下的回转换成三个字节,转换的时候也是把16个位分别填写到那三个字节里面,即aaaaaaaa bbbbbbbb ==> 1110aaaa 10aaaabb 10bbcccccc

下面是转换的实现代码:

Java代码 复制代码
  1. package com.ray.utf8;   
  2.   
  3. import java.io.ByteArrayOutputStream;   
  4. import java.io.UnsupportedEncodingException;   
  5.   
  6. public class Utf8Utils {   
  7.   
  8.     private final static byte B_10000000 = 128 - 256;   
  9.     private final static byte B_11000000 = 192 - 256;   
  10.     private final static byte B_11100000 = 224 - 256;   
  11.     private final static byte B_11110000 = 240 - 256;   
  12.     private final static byte B_00011100 = 28;   
  13.     private final static byte B_00000011 = 3;   
  14.     private final static byte B_00111111 = 63;   
  15.     private final static byte B_00001111 = 15;   
  16.     private final static byte B_00111100 = 60;   
  17.        
  18.     /** Convert from UTF8 bytes to UNICODE character */  
  19.     public static char[] toUCS2(byte[] utf8Bytes) {   
  20.         CharList charList = new CharList();   
  21.         byte b2 = 0, b3 = 0;   
  22.         int ub1 = 0, ub2 = 0;   
  23.            
  24.         for (int i = 0; i < utf8Bytes.length; i++) {   
  25.             byte b = utf8Bytes[i];   
  26.             if (isNotHead(b)) {   
  27.                 // start with 10xxxxxx, skip it.   
  28.                 continue;   
  29.             } else if (b > 0) {   
  30.                 // 1 byte, ASCII   
  31.                 charList.add((char) b);   
  32.             } else if ((b & B_11110000) == B_11110000) {   
  33.                 // UCS-4, here we skip it   
  34.                 continue;   
  35.             } else if ((b & B_11100000) == B_11100000) {   
  36.                 // 3 bytes   
  37.                 b2 = utf8Bytes[i+1];   
  38.                 if (!isNotHead(b2)) continue;   
  39.                 i++;   
  40.                 b3 = utf8Bytes[i+1];   
  41.                 if (!isNotHead(b3)) continue;   
  42.                 i++;   
  43.                 ub1 = ((b & B_00001111) << 4) + ((b2 & B_00111100) >> 2);   
  44.                 ub2 = ((b2 & B_00000011) << 6) + ((b3 & B_00111111));   
  45.                 charList.add(makeChar(ub1, ub2));   
  46.             } else {   
  47.                 // 2 bytes   
  48.                 b2 = utf8Bytes[i+1];   
  49.                 if (!isNotHead(b2)) continue;   
  50.                 i++;   
  51.                 ub1 = (b & B_00011100) >> 2;   
  52.                 ub2 = ((b & B_00000011) << 6) + (b2 & B_00111111);   
  53.                 charList.add(makeChar(ub1, ub2));   
  54.             }   
  55.         }   
  56.            
  57.         return charList.toArray();   
  58.     }   
  59.        
  60.     private static boolean isNotHead(byte b) {   
  61.         return (b & B_11000000) == B_10000000;   
  62.     }   
  63.        
  64.     private static char makeChar(int b1, int b2) {   
  65.         return (char) ((b1 << 8) + b2);   
  66.     }   
  67.        
  68.     public static byte[] fromUCS2(char[] ucs2Array) {   
  69.         ByteArrayOutputStream baos = new ByteArrayOutputStream();   
  70.         for (int i = 0; i < ucs2Array.length; i++) {   
  71.             char ch = ucs2Array[i];   
  72.             if (ch <= 0x007F) {   
  73.                 baos.write(ch);   
  74.             } else if (ch <= 0x07FF) {   
  75.                 int ub1 = ch >> 8;   
  76.                 int ub2 = ch & 0xFF;   
  77.                 int b1 = B_11000000 + (ub1 << 2) +  (ub2 >> 6);   
  78.                 int b2 = B_10000000 + (ub2 & B_00111111);   
  79.                 baos.write(b1);   
  80.                 baos.write(b2);   
  81.             } else {   
  82.                 int ub1 = ch >> 8;   
  83.                 int ub2 = ch & 0xFF;   
  84.                 int b1 = B_11100000 + (ub1 >> 4);   
  85.                 int b2 = B_10000000 + ((ub1 & B_00001111) << 2) + (ub2 >> 6);   
  86.                 int b3 = B_10000000 + (ub2 & B_00111111);   
  87.                 baos.write(b1);   
  88.                 baos.write(b2);   
  89.                 baos.write(b3);   
  90.             }   
  91.         }   
  92.         return baos.toByteArray();   
  93.     }   
  94.        
  95.     private static class CharList {   
  96.         private char[] data = null;   
  97.         private int used = 0;   
  98.         public void add(char c) {   
  99.             if (data == null) {   
  100.                 data = new char[16];   
  101.             } else if (used >= data.length) {   
  102.                 char[] temp = new char[data.length * 2];   
  103.                 System.arraycopy(data, 0, temp, 0, used);   
  104.                 data = temp;   
  105.             }   
  106.             data[used++] = c;   
  107.         }   
  108.         public char[] toArray() {   
  109.             char[] chars = new char[used];   
  110.             System.arraycopy(data, 0, chars, 0, used);   
  111.             return chars;   
  112.         }   
  113.     }   
  114.        
  115.     private static void assert1(String s) throws UnsupportedEncodingException {   
  116.         byte[] b = s.getBytes("utf-8");   
  117.         char[] c = toUCS2(b);   
  118.         if (!s.equals(new String(c))) {   
  119.             throw new RuntimeException("Can not pass assert1 for: " + s);   
  120.         }   
  121.     }   
  122.        
  123.     private static void assert2(String s) throws UnsupportedEncodingException {   
  124.         byte[] b = s.getBytes("utf-8");   
  125.         byte[] b2 = fromUCS2(s.toCharArray());   
  126.         if (b.length == b2.length) {   
  127.             int i;   
  128.             for (i = 0; i < b.length; i++) {   
  129.                 if (b[i] != b2[i]) {   
  130.                     break;   
  131.                 }   
  132.             }   
  133.             if (i == b.length) {   
  134.                 return;   
  135.             }   
  136.         }   
  137.         throw new RuntimeException("Can not pass assert2 for: " + s);   
  138.     }   
  139.        
  140.     public static void main(String[] args) throws Exception {   
  141.         assert1("test");   
  142.         assert1("中文测试");   
  143.         assert1("A中V文c测d试E");   
  144.         assert1("\u052CA\u052CBc测");   
  145.            
  146.         assert2("test");   
  147.         assert2("中文测试");   
  148.         assert2("A中V文c测d试E");   
  149.         assert2("\u052CA\u052CBc测\u007F\u07FF");   
  150.            
  151.         System.out.println("pass");   
  152.     }   
  153.   
  154. }  
阅读(10027) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~