手动做UTF-8编码的转换

时间:2023-02-13 10:48:14

看到一个题目:不使用 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/