要将UTF32Char字符串转换为NSString,使用stringWithCString:encoding:方法,关键是编码方式的选择。
const char *cstring = [@"你好,世界" cStringUsingEncoding:NSUTF32StringEncoding]; NSString *string = [NSString stringWithCString:cstring encoding:NSUTF32StringEncoding];
不知道为什么string是nil,放弃;换成NSUTF16StringEncoding也有问题,这个后面说;只有UTF8正常。
UTF32转换为UTF8
UTF32使用定长编码,每个unicode码位使用4个字节,UTF8跟下面的UTF16都是不定长编码,分别有自己的格式;要做的就是把UTF32字符串按格式填到UTF8中。
UTF8最少一个字节,最多6个字节,编码规则如下():
- 对于单字节的符号,字节的第一位设为0,后面7位为这个符号的unicode码。因此对于英语字母,UTF-8编码和ASCII码是相同的。
- 对于n字节的符号(n>1),第一个字节的前n位都设为1,第n+1位设为0,后面字节的前两位一律设为10。剩下的没有提及的二进制位,全部为这个符号的unicode码。
另外,NSUTF8StringEncoding按大端编码,所以要把后面的字节放在低位。
int convertUTF32to8(UTF8Char *dest, const UTF32Char *orig) { UTF32Char c; int i, len = 0; while ((c = *orig++) != '\0') { printf("%s %lx\n", __func__, c); if (c < 0x80) { i = 1; } else if (c < 0x800) { i = 2; } else if (c < 0x10000) { i = 3; } else if (c < 0x200000) { i = 4; } else if (c < 0x4000000) { i = 5; } else { i = 6; } if (i == 1) { *dest++ = (UTF8Char)c; } else { UTF8Char *dp = dest = dest + i; printf("===%d===\n", i); for (int m = 0; m < i; ++m) { *--dp = (UTF8Char)((c | (m == i - 1 ? (~0 << (8 - i)) : 0x80)) & (m == i - 1 ? (~(0x80 >> i)) : 0xbf)); printf("%x ", *dp); c >>= 6; } printf("\n"); } len += i; } *dest = '\0'; return len;}
试一下:
NSString *sample = @"大一二";const char *cstring = [sample cStringUsingEncoding:NSUTF32StringEncoding];int len = 0;UTF32Char c, *cp = (UTF32Char *)cstring;while ((c = *cp++) != '\0') ++len;UTF8Char *c8 = malloc(6 * len + sizeof(UTF8Char));convertUTF32to8(c8, (UTF32Char *)cstring);NSString *string = [NSString stringWithCString:(const char *)c8 encoding:NSUTF8StringEncoding];free(c8);
UTF32转换成UTF16
UTF16也是不定长编码,unicode把常用的字符放在0x0-0xffff中,所以通常UTF16是两个字节,这时UTF16和UTF32是相等的,可以通过类型转换赋值;对于0x10000和往上的字符,占用4个字节。
unicode定义了0x0-0x10ffff的码位,最高21位。0x10000和往上的字符,去掉最高位然后把剩下的20位分别放在两个UTF16Char中;高、低10位分别加上0xd800和0xdc00。 这样32位的UTF16字符高、低部分的范围分别是0xd800-0xd8ff和0xdc00-0xdcff,而unicode 0x0-0xffff中0xd800-0xdfff之间的码位“永久保留不映射到字符”,所以2字节的UTF16和4字节的UTF16高、低部分永远不会重叠。int convertUTF32to16(UTF16Char *dest, const UTF32Char *orig){ UTF32Char c; int len = 0; while ((c = *orig++) != '\0') { printf("%s %lx\n", __func__, c); if (c < 0x10000) { *dest++ = (UTF16Char)c; printf("%x ", *(dest - 1)); ++len; } else { c -= 0x10000; *dest++ = ((UTF16Char)c & 0x3ff) | 0xd800; printf("%x ", *(dest - 1)); *dest++ = ((UTF16Char)(c >> 10)) | 0xdc00; printf("%x ", *(dest - 1)); len += 2; } printf("\n"); } *dest = '\0'; return len;}
试验代码跟UTF8的大致相同,NSUTF16StringEncoding也是大端:
UTF16Char *c16 = malloc(4 * len + sizeof(UTF16Char));convertUTF32to16(c16, (UTF32Char *)cstring);NSString *string = [NSString stringWithCString:(const char *)c16 encoding:NSUTF16LittleEndianStringEncoding];
结果string只有”大”字,“一”字的unicode编码是0x4e00,所以怀疑是NSString UTF16编码的BUG,把0x0字节当作结束符而不是(UTF16Char)'\0',“刀”、“匀”等字和所有的ASCII字符也不能正常编码。
参考资料: