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