看到一个题目:不使用 String.getBytes() 等其他工具类/函数完成下面功能
public static void main(String[] args) throws IOException { String str = "Hello, 我们是中国人。"; byte[] utf8Bytes = toUTF8Bytes(str); FileOutputStream fos = new FileOutputStream("f.txt"); fos.write(utf8Bytes); fos.close(); } public static byte[] toUTF8Bytes(String str) { return null; // TODO }
觉得挺有意思,便尝试去做,做完发现,还是费了不少力气,在这里分享下心得。
1. utf-8的编码规则和表,参考这里http://blog.csdn.net/lk_cool/article/details/7344244,不赘述
2. 实际上,unicode跟utf-8不是一回事,unicode是定长2字节,是一个编码标准,而utf-8是变长从1-6字节不等,是编码的实现。java内部默认采用unicode编码,并不代表utf-8
3. 编码本质上是将一种表示转为另一种表示,等价转换而不丢失信息量。而utf-8编码采用变长编码,利用了赫夫曼树的规则,能够最快速区分并解析。
4. 代码实现并不难,关键是有的小地方需要注意,一个是厘清int,char,byte三者关系;二是对位操作的熟练使用;三就是由于大小端以及编码规则,处理起来有些trick。
代码如下
/** * */ import java.io.FileOutputStream; import java.io.IOException; import java.util.ArrayList; import java.util.Collections; import java.util.List; /** * @author levi * */ public class UTF8Encoder { public static void main(String[] args) throws IOException { String str = "Hello, 我们是中国人。"; byte[] utf8Bytes = toUTF8Bytes(str); FileOutputStream fos = new FileOutputStream("f.txt"); fos.write(utf8Bytes); fos.close(); } public static byte[] toUTF8Bytes(String str) { List<Integer> result = new ArrayList<Integer>(); for (int i = 0;i < str.length(); i++) { int chari = (int)str.charAt(i); if(chari <= 127){ result.add((127 ^ leftShiftN(1)) & chari); }else{ //count n first int n = 2; if(chari < 0x7ff){ n = 2; }else if(chari < 0xffff){ n = 3; }else if(chari < 0x10ffff){ n = 4; } int lastByte = 0; List<Integer> rr = new ArrayList<Integer>(); for(int j = 0; j < n; j++){ int low = chari & 255; chari = chari >> 8; int x = 0; if(j == (n - 1)){ int leftShiftN = leftShiftN(n); x = leftShiftN | lastByte; }else{ x = (2 << 6) | (((low << (2 * j)) & (leftShiftN(2) ^ 255)) | lastByte); lastByte = (low & leftShiftN(2 * (j + 1))) >> 8 - 2 * (j + 1); } rr.add(x); } Collections.reverse(rr); result.addAll(rr); } } byte [] finalResult = new byte[result.size()]; for (int i = 0;i < result.size();i++) { finalResult[i] = (byte)result.get(i).intValue(); } return finalResult; } private static int leftShiftN(int n){ int orginal = 0; for(int i = 0; i < 7; i++){ if(i < n){ orginal |= 1; } orginal = orginal << 1; } return orginal; } }
java中也有自动探测编码的工具: http://jchardet.sourceforge.net/