UTF-8解码

要想了解UTF-8编码规则,请参考我的文章:http://blog.csdn.net/sheismylife/article/details/8570015

在我的另一篇文章"UTF-8编码实测"http://blog.csdn.net/sheismylife/article/details/8571726中,我使用了boost::locale库的代码来解码UTF-8. 现在来仔细研究一下解码的算法:


如何分辨leading byte和continuation bytes呢?关键在于任何一个continuation byte都以10开始。下面的函数可以帮助判断是否为continuation byte:

bool is_trail(char ci) {
  unsigned char c = ci;
  return (c & 0xC0) == 0x80;
}
因为0xC0二进制格式是1100 0000,和c按位与后也就是低六位全部设置为0,进保留c的高两位.

而0x80二进制格式是1000 0000, 如果两者相等,说明c的高两位是10,因此c是continuation byte。返回true。

有了这个函数,判断一个字节是否为leading byte也很简单:

bool is_lead(char ci) {
  return !is_trail(ci);
}


再看一下函数trail_length, 该函数通过分析一个leading byte来确定continuation bytes的长度

int trail_length(char ci) {
  unsigned char c = ci;
  if(c < 128)
    return 0;
  if(BOOST_LOCALE_UNLIKELY(c < 194))
    return -1;
  if(c < 224)
    return 1;
  if(c < 240)
    return 2;
  if(BOOST_LOCALE_LIKELY(c <=244))
    return 3;
  return -1;
}

如果c < 128, 说明就是一个ASCII字符,用一个字节表示。因此conitunuation bytes长度为0,utf-8编码总长度为1.

因为11011111就是223,只要c在区间[128, 224), 说明continuation bytes长度为1,utf-8编码总长度为2.

继续推理,11101111等于239, 因此在[224, 240)区间的,continuation bytes长度为2,utf-8编码总长度为3.

11110111等于247, 因此在[240, 248)区间的,continuation bytes长度为3,utf-8编码总长度为4. 但是这里限定的区间实际上是[240, 244] 不明白为什么,可能还有什么编码规则我不清楚。


现在来看一下utf.hpp中的代码,http://www.boost.org/doc/libs/1_53_0/libs/locale/doc/html/utf_8hpp_source.html

00192         template<typename Iterator>
00193         static code_point decode(Iterator &p,Iterator e)
00194         {
00195             if(BOOST_LOCALE_UNLIKELY(p==e))
00196                 return incomplete;
00197 
00198             unsigned char lead = *p++;
00199 
00200             // First byte is fully validated here
00201             int trail_size = trail_length(lead);
00202 
00203             if(BOOST_LOCALE_UNLIKELY(trail_size < 0))
00204                 return illegal;
00205 
00206             //
00207             // Ok as only ASCII may be of size = 0
00208             // also optimize for ASCII text
00209             //
00210             if(trail_size == 0)
00211                 return lead;
00212             
00213             code_point c = lead & ((1<<(6-trail_size))-1);
00214 
00215             // Read the rest
00216             unsigned char tmp;
00217             switch(trail_size) {
00218             case 3:
00219                 if(BOOST_LOCALE_UNLIKELY(p==e))
00220                     return incomplete;
00221                 tmp = *p++;
00222                 if (!is_trail(tmp))
00223                     return illegal;
00224                 c = (c << 6) | ( tmp & 0x3F);
00225             case 2:
00226                 if(BOOST_LOCALE_UNLIKELY(p==e))
00227                     return incomplete;
00228                 tmp = *p++;
00229                 if (!is_trail(tmp))
00230                     return illegal;
00231                 c = (c << 6) | ( tmp & 0x3F);
00232             case 1:
00233                 if(BOOST_LOCALE_UNLIKELY(p==e))
00234                     return incomplete;
00235                 tmp = *p++;
00236                 if (!is_trail(tmp))
00237                     return illegal;
00238                 c = (c << 6) | ( tmp & 0x3F);
00239             }
00240 
00241             // Check code point validity: no surrogates and
00242             // valid range
00243             if(BOOST_LOCALE_UNLIKELY(!is_valid_codepoint(c)))
00244                 return illegal;
00245 
00246             // make sure it is the most compact representation
00247             if(BOOST_LOCALE_UNLIKELY(width(c)!=trail_size + 1))
00248                 return illegal;
00249 
00250             return c;
00251 
00252         }

decode函数负责从字符串中解析一个utf-8编码(可能包含1-4字节),返回对应的code point,一个uint32_t的整数。

总是假定第一个字节是leading byte,然后用trail_length获取continuation bytes的长度,如果为0,说明就是ASCII字符,直接返回。

210行到250行处理的都是非ASCII字符。先注意switch/case用法,这里没有break语句,也就是说,如果trail_size为3,会先执行case 3:里面的语句,然后再依次执行case 2和 case 1(不需要匹配)。这是来自switch/case的特殊语法。参考:http://msdn.microsoft.com/en-<wbr>us/library/k0t5wee3.aspx</wbr>

当然用循环能更好的表达,但是不知道Artyom为什么选择这种写法?性能更高么?至少有一条,如果有代码扫描工具的话,会认为这里三段代码重复,会报警告信息。:)

现在再看一下之前UTF-8编码一文中引用的wiki的例子:

现在看一个来自wiki的例子演示如何对字符€进行UTF-8编码:
step 1:获取€的Unicode code point,是0xU+20AC
step 2:0xU+20AC范围在U+07FF和U+FFFF之间,因此用三字节表示。
step 3:0xU+20AC的二进制码是:10000010101100,14位长,要想表示3字节编码,必须凑成16 bits.因此高位补上两个0,变成2字节16位长:0010000010101100,我下面称为数值串。
step 4: 根据规则,添加一个leading byte,开头是1110,那么这个leading byte还有4个bit需要填充,从数值串高位取4个bit来,leading byte变成了:
11100010,而数值串值为000010101100
step 5: 第一个continuation byte高位应该是10,还缺少6 bits,从数值串中按高位取6 bit,这样第一个continuation byte为:
10000010,而数值串变为101100
step 6: 第二个continuation byte高位也应该是10,还缺少6 bits, 从数值串取6 bits,这样第二个continuation byte为:
10101100
最终编码形成的三字节:
11100010 10000010 10101100
写成16进制就是0xE282AC

解码也就是编码的逆过程,从leading byte中抽取低4位,从两个continuation bytes中都抽取低6位,拼接成16bits的整数,然后转换类型变成uint32_t的整数。

下面的代码其实就是取出低四位:

code_point c = lead & ((1<<(6-trail_size))-1);
这是个很好的技巧,总结一下可以写成一个函数, 函数接受两个参数,一个是要提取bit的x,一个是提取的位数。

uint8_t GetLowNBit(uint8_t x, uint8_t n) {
  return x & ((1<<n)-1);
}

为什么这里用6-trail_size,纯粹是观察出来的规律。

2字节utf-8编码时,leading byte以110开头,因此要提取低5位数值, 6-trail_size=6-1=5,刚好能够提取低5位。

3字节utf-8编码,leading byte以1110开头,因此要提取低4位数值,6-trail_size=6-2=4.

4字节utf-8编码,leading byte以11110开头,因此要提取低3位数值,6-trail_size=6-3=3.

所以这里用6,Artyom的观察力很敏锐。


提取出leading byte的低位数据后,现在要提取continuation bytes的低位数据,也就是那个switch/case的功能。


这就很简单了,tmp & 0x3F就是取出低6位数据,因为continuation byte永远都是10开头。每次取出低6位后,将c左移6位然后按位或,就达到合并bit成新的整数的目的。

前面已经解释过swtich/case在without break的用法。这里就会运行两次,取出后面两个continuation bytes的低6位数据,并合并。


UTF-8解码算法分析完成。


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: 如果你在使用 UTF-8 解码某些中文时遇到了问题,有以下几种解决方法: 1. 检查文件的编码格式:确保文件是使用 UTF-8 编码保存的,如果不是,请将其转换为 UTF-8。 2. 检查文件是否损坏:如果文件有损坏,可能导致解码错误。请尝试使用其他工具打开文件并检查其完整性。 3. 尝试使用其他编码:如果文件不是 UTF-8 编码,请尝试使用其他编码(如 GB2312)进行解码。 4. 在代码中指定编码:在代码的顶部指定文件的编码格式,以确保 Python 正确识别文件的编码。例如: ``` # -*- coding: utf-8 -*- ``` 如果以上方法都不起作用,请尝试联系 Python 社区或技术支持,以获得更多帮助。 ### 回答2: 如果python无法正确解码某些中文字符,可能是因为缺少对应的字符编码支持或者在解码过程中产生了错误。以下是一些可能的解决方案: 1. 检查源代码文件头部是否声明了正确的编码方式。在python文件的开头添加`# coding=utf-8`可以确保文件以UTF-8编码读取。 2. 尝试手动指定其他编码方式进行解码。使用`decode()`函数时,可以尝试其它编码方式参数,如`gbk`或`utf-16`。例如:`data.decode('gbk')`。 3. 使用`errors='ignore'`参数捕捉解码错误时不抛出异常,并忽略无法解码的字符。例如:`data.decode('utf-8', errors='ignore')`。 4. 确保所使用的python版本支持需要的字符编码。Python 3.x相对于Python 2.x有更好的中文编码支持。如果可能的话,升级到较新的版本。 5. 检查可能使用到的第三方库是否支持当前字符编码。有些库可能需要进行配置或者额外的设置来支持特定的编码。 6. 如果问题涉及到从外部来源获取数据,例如从网络请求或者文件读取中文字符,请确保数据本身就是以正确的编码存储的。 如果以上的方案都无法解决问题,那可能是由于特殊字符或编码错误导致的问题,这种情况下需要进一步调查和排查具体的错误原因,可能需要更多的代码和数据细节来定位并解决问题。 ### 回答3: 当遇到Python无法解码某些中文字符时,我们可以尝试以下几种解决方法: 1. 指定正确的编码格式:Python默认使用UTF-8编码,但如果遇到特定编码的中文字符导致解码错误,可以尝试指定其他常见的编码格式,如gbk、gb2312等。例如,使用`decode('gbk')`来解码gbk编码的中文字符。 2. 使用错误处理参数:在进行解码操作时,可以添加`errors='ignore'`参数来忽略解码错误的字符,这样可以在解码时略过无法解码的中文字符,继续处理其他能够解码的内容。 3. 使用chardet库检测编码:如果无法确定特定中文字符的编码方式,可以使用chardet库来检测字符串的编码格式。从而得到正确的编码方式进行解码操作。 4. 更新Python版本:有时,问题可能是由于Python版本较旧导致的。考虑升级Python到最新版本,以获取更好的编码支持。 5. 检查源数据:如果解码问题发生在从外部数据源读取中文文本时,请确保源数据本身没有损坏或使用了非标准编码。 总的来说,根据具体情况,我们可以尝试调整编码格式、添加错误处理参数、使用工具库或升级Python版本等方法来解决Python无法解码某些中文字符的问题。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值