本文属于 字符编码系列文章之一,更多请前往 字符编码系列。
不同的编码直接如何转换的,这里先简单的描述下UTF-16、UTF-8、GBK直接的转换过程。
由于本文是基于JavaScript的,而JS现在的编码可以认为是UTF-16,所以都会经过UTF-16中转。
这两者都是Unicode,所以有一个大前提就是码点一致,仅仅是对于码点的编码方式不一致而已,因为UTF-16可以认为是固定2字节的实现(4字节的比较少见),所以参考如下Unicode和UTF-8转换关系表即可:
Unicode编码 | UTF-8字节流 |
---|---|
U+00000000 - U+0000007F | 0xxxxxxx |
U+00000080 - U+000007FF | 110xxxxx 10xxxxxx |
U+00000800 - U+0000FFFF | 1110xxxx 10xxxxxx 10xxxxxx |
U+00010000 - U+001FFFFF | 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx |
U+00200000 - U+03FFFFFF | 111110xx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx |
U+04000000 - U+7FFFFFFF | 1111110x 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx |
所以UTF16和UTF8之间的相互转换可以通过上表的转换表来实现,判断Unicode码所在的区间就可以得到这个字符是由几个字节所组成,之后通过移位来实现,分为新的多个字节来存储。
UTF-16和GBK直接的转换就稍微复杂点,因为Unicode和GBK的码点不一致,因此需要GBK个Unicode的码点映射关系表才能进行相应转换。
这里GBK和Unicode的码点映射表由于太长了,就不单独列出来,可以参考:Unicode编码和GBK的转换映射表 (如果链接不可用可以自动搜索或者参考博客中的源码链接)
然后通用,拿到Unicode码点后可以根据映射表转换为GBK码点,然后用GBK的编码方式编码即可完成转换
/**
* @description 将utf-16编码字符串转为utf-8编码字符串
* @param {String} str 传入的 utf16编码字符串(javascript内置的就是utf16编码)
* @return {String} utf8编码的字符串,js打印会有乱码
*/
exports.utf16StrToUtf8Str = function(str) {
if (!str) {
// ''字符属于ascii码,所以不必担心不同编码的转换问题
return '';
}
// res是用来存放结果的字符数组,最终会转为字符串返回
var res = [],
len = str.length;
for (var i = 0; i < len; i++) {
var code = str.charCodeAt(i);
if (code > 0x0000 && code <= 0x007F) {
// 单字节,这里并不考虑0x0000,因为它是空字节
// U+00000000 – U+0000007F 0xxxxxxx
res.push(str.charAt(i));
} else if (code >= 0x0080 && code <= 0x07FF) {
// 双字节
// U+00000080 – U+000007FF 110xxxxx 10xxxxxx
// 110xxxxx
// 0xC0 为12*16 = 192 二进制为 11000000
// 0x1F为 31 二进制 00011111,因为第一个字节只取5位
// code 右移六位是因为从高位开始取得,所以需要将低位的六位留到第二个字节
var byte1 = 0xC0 | ((code >> 6) & 0x1F);
// 10xxxxxx
// 0x80为128 二进制为 10000000
// 0x3F为63 二进制位 00111111,因为只需要取到低位的6位
var byte2 = 0x80 | (code & 0x3F);
res.push(String.fromCharCode(byte1), String.fromCharCode(byte2));
} else if (code >= 0x0800 && code <= 0xFFFF) {
// 三字节
// U+00000800 – U+0000FFFF 1110xxxx 10xxxxxx 10xxxxxx
// 1110xxxx
// 0xE0 为224 二进制为 11100000
// 同理,需要留下 12位给低位
// 0x0F为15 00001111
var byte1 = 0xE0 | ((code >> 12) & 0x0F);
// 10xxxxxx
// 再留6位给低位
var byte2 = 0x80 | ((code >> 6) & 0x3F);
// 10xxxxxx
var byte3 = 0x80 | (code & 0x3F);
res.push(String.fromCharCode(byte1), String.fromCharCode(byte2), String.fromCharCode(byte3));
} else if (code >= 0x00010000 && code <= 0x001FFFFF) {
// 四字节
// U+00010000 – U+001FFFFF 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx
// 同理,需要留下 18位给低位
// 0x07 00000111
// 0xF0 240 11110000
var byte1 = 0xF0 | ((code >> 18) & 0x07);
// 10xxxxxx
// 再留12位给低位
var byte2 = 0x80 | ((code >> 12) & 0x3F);
// 再留6位给低位
var byte3 = 0x80 | ((code >> 6) & 0x3F);
// 10xxxxxx
var byte4 = 0x80 | (code & 0x3F);
res.push(String.fromCharCode(byte1), String.fromCharCode(byte2), String.fromCharCode(byte3), String.fromCharCode(byte4));
} else if (code >= 0x00200000 && code <= 0x03FFFFFF) {
// 五字节
// U+00200000 – U+03FFFFFF 111110xx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx
// 同理,需要留下 24位给低位
// 0x03 00000011
// 0xF8 248 11111000
var byte1 = 0xF8 | ((code >> 24) & 0x03);
// 10xxxxxx
// 再留18位给低位
var byte2 = 0x80 | ((code >> 18) & 0x3F);
// 再留12位给低位
var byte3 = 0x80 | ((code >> 12) & 0x3F);
// 再留6位给低位
var byte4 = 0x80 | ((code >> 6) & 0x3F);
// 10xxxxxx
var byte5 = 0x80 | (code & 0x3F);
res.push(String.fromCharCode(byte1), String.fromCharCode(byte2), String.fromCharCode(byte3), String.fromCharCode(byte4), String.fromCharCode(byte5));
} else
/** if (code >= 0x04000000 && code <= 0x7FFFFFFF)*/
{
// 六字节
// U+04000000 – U+7FFFFFFF 1111110x 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx
// 同理,需要留下 24位给低位
// 0x01 00000001
// 0xFC 252 11111100
var byte1 = 0xFC | ((code >> 30) & 0x01);
// 10xxxxxx
// 再留24位给低位
var byte2 = 0x80 | ((code >> 24) & 0x3F);
// 再留18位给低位
var byte3 = 0x80 | ((code >> 18) & 0x3F);
// 再留12位给低位
var byte4 = 0x80 | ((code >> 12) & 0x3F);
// 再留6位给低位
var byte5 = 0x80 | ((code >> 6) & 0x3F);
// 10xxxxxx
var byte6 = 0x80 | (code & 0x3F);
res.push(String.fromCharCode(byte1), String.fromCharCode(byte2), String.fromCharCode(byte3), String.fromCharCode(byte4), String.fromCharCode(byte5), String.fromCharCode(byte6));
}
}
return res.join('');
};
/**
* @description UTF8编码字符串转为UTF16编码字符串
* @param {String} str utf8编码的字符串
* @return {String} utf16编码的字符串,可以直接被js用来打印
*/
exports.utf8StrToUtf16Str = function(str) {
if (!str) {
return '';
}
// res是用来存放结果的字符数组,最终会转为字符串返回
var res = [],
len = str.length;
for (var i = 0; i < len; i++) {
// 获得对应的unicode码
var code = str.charCodeAt(i);
// 对第一个字节进行判断
if (((code >> 7) & 0xFF) == 0x0) {
// 0xFF 255 11111111,代表只取前8位
// 右移7位,如果是只剩下0了,代表这个是单字节
// 单字节
// 0xxxxxxx
res.push(str.charAt(i));
} else if (((code >> 5) & 0xFF) == 0x6) {
// 双字节 110开头
// 110xxxxx 10xxxxxx
// 需要用到下一个字节
var code2 = str.charCodeAt(++i);
// 0x1F 31 00011111
// 取到第一个字节的后5位,然后左移6位(这6位留给第二个字节的低6位),由于js是number型,所以不必担心溢出
var byte1 = (code & 0x1F) << 6;
// 0x3F 63 00111111
var byte2 = code2 & 0x3F;
// 或运算,因为第一个字节第六位没有,第二个字节只有低6位,所以算是结合了
var utf16 = byte1 | byte2;
res.push(String.fromCharCode(utf16));
} else if (((code >> 4) & 0xFF) == 0xE) {
// 三字节 1110开头
// 1110xxxx 10xxxxxx 10xxxxxx
var code2 = str.charCodeAt(++i);
var code3 = str.charCodeAt(++i);
// 和00001111与后, 左移12位
var byte1 = (code & 0x0F) << 12;
// 和00111111与后,左移6位
var byte2 = (code2 & 0x3F) << 6;
// 和00111111与
var byte3 = code3 & 0x3F
var utf16 = byte1 | byte2 | byte3;
res.push(String.fromCharCode(utf16));
} else if (((code >> 3) & 0xFF) == 0x1E) {
// 四字节 11110开头
// 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx
var code2 = str.charCodeAt(++i);
var code3 = str.charCodeAt(++i);
var code4 = str.charCodeAt(++i);
// 和00000111与后, 左移18位
var byte1 = (code & 0x07) << 18;
// 和00111111与后,左移12位
var byte2 = (code2 & 0x3F) << 12;
// 和00111111与后,左移6位
var byte3 = (code3 & 0x3F) << 6;
// 和00111111与
var byte4 = code4 & 0x3F
var utf16 = byte1 | byte2 | byte3 | byte4;
res.push(String.fromCharCode(utf16));
} else if (((code >> 2) & 0xFF) == 0x3E) {
// 五字节 111110开头
// 111110xx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx
var code2 = str.charCodeAt(++i);
var code3 = str.charCodeAt(++i);
var code4 = str.charCodeAt(++i);
var code5 = str.charCodeAt(++i);
// 和00000011与后, 左移24位
var byte1 = (code & 0x03) << 24;
// 和00111111与后,左移18位
var byte2 = (code2 & 0x3F) << 18;
// 和00111111与后,左移12位
var byte3 = (code3 & 0x3F) << 12;
// 和00111111与后,左移6位
var byte4 = (code4 & 0x3F) << 6;
// 和00111111与
var byte5 = code5 & 0x3F
var utf16 = byte1 | byte2 | byte3 | byte4 | byte5;
res.push(String.fromCharCode(utf16));
} else
/** if (((code >> 1) & 0xFF) == 0x7E)*/
{
// 六字节 1111110开头
// 1111110x 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx
var code2 = str.charCodeAt(++i);
var code3 = str.charCodeAt(++i);
var code4 = str.charCodeAt(++i);
var code5 = str.charCodeAt(++i);
var code6 = str.charCodeAt(++i);
// 和00000001与后, 左移30位
var byte1 = (code & 0x01) << 30;
// 和00111111与后,左移24位
var byte2 = (code2 & 0x3F) << 24;
// 和00111111与后,左移18位
var byte3 = (code3 & 0x3F) << 18;
// 和00111111与后,左移12位
var byte4 = (code4 & 0x3F) << 12;
// 和00111111与后,左移6位
var byte5 = (code5 & 0x3F) << 6;
// 和00111111与
var byte6 = code6 & 0x3F
var utf16 = byte1 | byte2 | byte3 | byte4 | byte5 | byte6;
res.push(String.fromCharCode(utf16));
}
}
return res.join('');
};
注意,这里为了篇幅,没有将GBK和Unicode的码表映射放进来,更多详细可以参考博文中的源码。
/**
* @description 将utf16编码的字符串(js内置编码)转为GBK编码的字符串
* @param {String} str utf16编码的字符串(js内置)
* @return {String} 转换后gbk编码的字符串
*/
exports.utf16StrToGbkStr = function(str) {
if (!str) {
return '';
}
// res是用来存放结果的字符数组,最终会转为字符串返回
var res = [],
len = str.length;
for (var i = 0; i < len; i++) {
// 获得对应的unicode码
var code = str.charCodeAt(i);
if (code < 0) {
code += 65536;
}
if (code > 127) {
code = unicode2GBKCode(code);
}
if (code > 255) {
// gbk中,如果是汉字的,需要两位来表示
// 对所收录字符进行了“分区”处理,分为若干区,每区若干码位
// 第一个字节为“高字节”,对应不同分区
// 第二个字节为“低字节”,对应每个区的不同码位
var varlow = code & 65280;
// 取得低位
varlow = varlow >> 8;
// 取得高位
var varhigh = code & 255;
res.push(String.fromCharCode(varlow));
res.push(String.fromCharCode(varhigh));
} else {
res.push(String.fromCharCode(code));
}
}
return res.join('');
};
/**
* @description 将unicode通过查表转换,转为gbk的code
* @param {Number} chrCode 字符unicode编码
*/
function unicode2GBKCode(chrCode) {
var chrHex = chrCode.toString(16);
chrHex = "000" + chrHex.toUpperCase();
chrHex = chrHex.substr(chrHex.length - 4);
var i = unicodeCharTable.indexOf(chrHex);
if (i != -1) {
chrHex = gbkCharTable.substr(i, 4);
}
return parseInt(chrHex, 16)
};
/**
* @description 将GBK编码的字符串转为utf16编码的字符串(js内置编码)
* @param {String} str GBK编码的字符串
* @return {String} 转化后的utf16字符串
*/
exports.gbkStrToUtf16Str = function(str) {
if (!str) {
return '';
}
// res是用来存放结果的字符数组,最终会转为字符串返回
var res = [],
len = str.length;
for (var i = 0; i < len; i++) {
// 获得对应的unicode码
var code = str.charCodeAt(i);
// 如果不是ASCII码
if (code > 127) {
// 转为unicode
// 这里左移8位是因为编码时,被右移了8位
code = gbkCode2Unicode((code << 8) + str.charCodeAt(++i));
} else {
// 普通的ASCII码,什么都不做
}
res.push(String.fromCharCode(code));
}
return res.join('');
};
/**
* @description将 gbk的对应的code通过查表转换,转为unicode
* @param {Number} chrCode gbk字符对应的编码
*/
function gbkCode2Unicode(chrCode) {
//以16进制形式输出字符串
var chrHex = chrCode.toString(16);
//
chrHex = "000" + chrHex.toUpperCase();
//
chrHex = chrHex.substr(chrHex.length - 4);
var i = gbkCharTable.indexOf(chrHex);
if (i != -1) {
chrHex = unicodeCharTable.substr(i, 4);
}
return parseInt(chrHex, 16)
};
为了篇幅,如GBK何Unicode的码表映射没有直接放在文中,详细可以参考源码: https://github.com/dailc/charset-encoding-series