本文是关于 Unicode 也就是 LPWSTR 转换成 UTF-8 的实现,在 Win32k 平台中我们可以借助 “MultiByteToWideChar / WideCharToMultiByte”【核心编程】两个函数进行多字节与宽字节字符串进行转换【PS:A2W / W2A 无法转换,CP_ACP在WIN32中文版上为936(GBK简体中文)】
.NET Framework 提供 “System.Text.Encoding.UTF8” 可以进行 .NET 字符串与 UTF8 字符串之间的转换,例:C# 语言执行字符集是 Unicode,并且这是强制性的不存在可以无法更改 “执行字符集” 的说法。
现代的 C/CXX 语言,都可以利用 “#pragma execution_character_set("utf-8")” 设定执行字符集,但这仅仅只是编译器层面的,而 “工程属性-字符集” 设定它仅仅是影响到几个编译器内部指定的约定宏。
Unicode/UCS-4 | bit数 | UTF-8 | byte数 | 备注 |
0000 ~ 007F | 0~7 | 0XXX XXXX | 1 | |
0080 ~ 07FF | 8~11 | 110X XXXX 10XX XXXX | 2 | |
0800 ~ FFFF | 12~16 | 1110XXXX 10XX XXXX 10XX XXXX | 3 | 基本定义范围:0~FFFF |
1 0000 ~ 1F FFFF | 17~21 | 1111 0XXX 10XX XXXX 10XX XXXX 10XX XXXX | 4 | Unicode6.1定义范围:0~10 FFFF |
20 0000 ~ 3FF FFFF | 22~26 | 1111 10XX 10XX XXXX 10XX XXXX 10XX XXXX 10XX XXXX | 5 | 说明:此非unicode编码范围,属于UCS-4 编码 早期的规范UTF-8可以到达6字节序列,可以覆盖到31位元(通用字符集原来的极限)。尽管如此,2003年11月UTF-8 被 RFC 3629 重新规范,只能使用原来Unicode定义的区域, U+0000到U+10FFFF。根据规范,这些字节值将无法出现在合法 UTF-8序列中 |
400 0000 ~ 7FFF FFFF | 27~31 | 1111 110X 10XX XXXX 10XX XXXX 10XX XXXX 10XX XXXX 10XX XXXX | 6 |
上表是UTF-8字符集编码定义的取值范围, 但事实情况 JavaScript、C# 语言的执行字符集都是 “Unicode” 编码 而不是UTF-8 或许会有人感到奇怪,但本人或许可以理解,一般来说 Unicode 的字符表示范围足够表示目前世界上已知的所有语言的字符,而UTF-8字符集中占用4、5、6字节的字符串你几乎很难遇到它们。
本文中 C# 的 Unicode To UTF-8 的代码,摘要自本人的 nsjsdotnet 框架,你可以从 “https://github.com/liulilittle/nsjs/blob/master/nsjsdotnet/NSJSString.cs” 中获取到更多与 “UTF8” 字符集相关的内容。
public static byte[] GetUTF8StringBuffer(string s, out int count)
{
count = 0;
if (s == null)
{
throw new ArgumentNullException("s");
}
byte[] buf = null;
int k = 0;
int i = 0;
while (i < s.Length)
{
char ch = s[i++];
if (ch < 0x80)
{
k++;
}
else if (ch < 0x800)
{
k += 2;
}
else if (ch < 0x10000)
{
k += 3;
}
}
buf = new byte[(count = k) + 1];
fixed (byte* p = buf)
{
i = 0;
k = 0;
while (i < s.Length)
{
char ch = s[i++];
if (ch < 0x80)
{
p[k++] = (byte)(ch & 0xff);
}
else if (ch < 0x800)
{
p[k++] = (byte)(((ch >> 6) & 0x1f) | 0xc0);
p[k++] = (byte)((ch & 0x3f) | 0x80);
}
else if (ch < 0x10000)
{
p[k++] = (byte)(((ch >> 12) & 0x0f) | 0xe0);
p[k++] = (byte)(((ch >> 6) & 0x3f) | 0x80);
p[k++] = (byte)((ch & 0x3f) | 0x80);
}
}
}
return buf;
}
第一个计次循环用于确定转换到UTF-8字符串,所需的字符串缓冲区大小(这是必须的)否则可以采取空间换效率的方式,一个wchar_t 的 Unicode 字符转换成 UTF-8 最大只占三个字节(所以随便浪),但是一个严格的C风格字符串必须携带 “\x0” 所以这就是为什么上面需要在测量的长度上加上 1 的问题,另外根据 “编码规则表” 上定义的说法,<= 0x7F 的字符也就是ASCII字符 都只占用一个字节而不是与 Unicode 一般,不论是不是ASCII字符都需要用双字节进行表示。
var GetUtf8StringBuffer = function (s) {
'use strict'
var buf = [];
var k = 0;
if (!s || typeof s !== 'string') {
return buf;
}
for (var i = 0; i < s.length; i++) {
var ch = s.charCodeAt(i);
if (isNaN(ch)) {
continue;
}
if (ch < 0x80) {
k++;
}
else if (ch < 0x800) {
k += 2;
}
else if (ch < 0x10000) {
k += 3;
}
}
buf = new Array(k + 1);
buf[k] = 0;
k = 0;
for (var i = 0; i < s.length; i++) {
var ch = s.charCodeAt(i);
if (isNaN(ch)) {
continue;
}
if (ch < 0x80) {
buf[k++] = (ch & 0xff);
}
else if (ch < 0x800) {
buf[k++] = ((ch >> 6) & 0x1f) | 0xc0;
buf[k++] = (ch & 0x3f) | 0x80;
}
else if (ch < 0x10000) {
buf[k++] = ((ch >> 12) & 0x0f) | 0xe0;
buf[k++] = ((ch >> 6) & 0x3f) | 0x80;
buf[k++] = (ch & 0x3f) | 0x80;
}
}
return buf;
}
以下是 JavaScript 语言对于 “utf8” 字符集数组缓冲区转换成 “JavaScript” 的 Local<String> 的实现,(PS:JavaScript 语言默认并不支持UTF8的转换)
var GetStringFromUtf8Buffer = function (utf8) {
'use strict'
var s = '';
if (!utf8 || !(utf8 instanceof Array) || utf8.length <= 0) {
return s;
}
for (var k = 0; k < utf8.length;) {
var ch = utf8[k++];
if (ch < 0xE0) {
ch = (ch & 0x1f) << 6;
ch |= (utf8[k++] & 0x3f);
} else if (ch < 0xF0) {
ch = (ch & 0x0f) << 12;
ch |= (utf8[k++] & 0x3f) << 6;
ch |= (utf8[k++] & 0x3f);
}
if (ch > 0) {
s += String.fromCharCode(ch);
} else {
break;
}
}
return s;
}