在编码转错的情况下,如何恢复
编码问题的确非常的棘手。很多时候,你一不小心,在某个阶段没有正确的转码,结果造成存储下来的文件 或者 保存进入数据库的字段是乱码。如果这个字段是非常重要的信息,你可能认为,你已经无法恢复这些信息了,其实,在大多数情况下面这个是能够恢复的,你要分析你转码的每个过程,确定出差错的位置,然后进行这个编码的逆转。
这里我只举一个我实际中遇到的一个例子。这个例子的情况是这样的,我在cookie里面保存了一个username字段,写cookie是通过PHP程序写的,这个PHP程序的编码是gbk的。另外一个统计系统,这个统计系统,通过Javascript 读取cookie信息,然后通过url编码这些信息,发送到服务器端。这个统计的服务器的编码是utf-8的。
我在设计这个系统的时候,username是英文或者数字,但是运行了一段时间以后,那个PHP网站变了,允许中文字符了,但是我这边的统计系统并不知道,这样可想而知,保存进入数据库的是一堆乱码。
因为用户名是一个用户的唯一标志,这个字段要是丢失,后果非常的严重。 如何把这段乱码恢复过来呢?
首先你要分析整个过程:
1. PHP写cookie,不会进行转换编码,但是会对字符进行urlencode。
2. Javascript 读取cookie的内容,进行urldecode,保存进入一个字符串
3. 调用Javascript urlencode函数,这个函数有问题,他默认认为Javascript 的 编码是本地Unicode的。
并转换成utf-8 。顺便提一句,比如你的页面是gbk的,但是 a = "变量值" a内部保存是Unicode的,不再是gbk。浏览器会进行编码转换。
4. 服务器端把这个字符串转码,然后保存进入数据库。
可以发现,实际上出错的地方就是Javascript 认为cookie读到的字符串是本地Unicode的。而实际上是一个gbk编码的字符串。
这样只要倒过来就可以了:读出数据库字符串的内容,把utf-8的代码转成Unicode,这个转过后的Unicode 就是原来的 gbk编码的序列。
下面附上一段源代码。为了查看字符串内部字节的排列,我把序列都用urlencode过。
![](https://i-blog.csdnimg.cn/blog_migrate/8f900a89c6347c561fdf2122f13be562.gif)
![ExpandedBlockStart.gif](https://i-blog.csdnimg.cn/blog_migrate/961ddebeb323a10fe0623af514929fc1.gif)
$name = " 王志文 " ; // 著名电影明星
$urlencode_name = " %CD%F5%D6%BE%CE%C4 " ;
// 存在数据库中的字节序列
$db_name = " 脥玫脰戮脦脛 " ;
$urlencode_dnname = " %C3%8D%C3%B5%C3%96%C2%BE%C3%8E%C3%84 " ;
$unicode = utf8_to_unicode( urldecode ( $urlencode_dnname ));
$decodename = "" ;
foreach ( $unicode as $code )
{
$decodename .= chr ( $code );
}
echo $decodename ;
function utf8_to_unicode( $str )
{
$mState = 0 ; // cached expected number of octets after the current octet
// until the beginning of the next UTF8 character sequence
$mUcs4 = 0 ; // cached Unicode character
$mBytes = 1 ; // cached expected number of octets in the current sequence
$out = array ();
$len = strlen ( $str );
for ( $i = 0 ; $i < $len ; $i ++ ) {
$in = ord ( $str { $i });
if ( $mState == 0 ) {
// When mState is zero we expect either a US-ASCII character or a
// multi-octet sequence.
if ( 0 == ( 0x80 & ( $in ))) {
// US-ASCII, pass straight through.
$out [] = $in ;
$mBytes = 1 ;
} else if ( 0xC0 == ( 0xE0 & ( $in ))) {
// First octet of 2 octet sequence
$mUcs4 = ( $in );
$mUcs4 = ( $mUcs4 & 0x1F ) << 6 ;
$mState = 1 ;
$mBytes = 2 ;
} else if ( 0xE0 == ( 0xF0 & ( $in ))) {
// First octet of 3 octet sequence
$mUcs4 = ( $in );
$mUcs4 = ( $mUcs4 & 0x0F ) << 12 ;
$mState = 2 ;
$mBytes = 3 ;
} else if ( 0xF0 == ( 0xF8 & ( $in ))) {
// First octet of 4 octet sequence
$mUcs4 = ( $in );
$mUcs4 = ( $mUcs4 & 0x07 ) << 18 ;
$mState = 3 ;
$mBytes = 4 ;
} else if ( 0xF8 == ( 0xFC & ( $in ))) {
/* First octet of 5 octet sequence.
*
* This is illegal because the encoded codepoint must be either
* (a) not the shortest form or
* (b) outside the Unicode range of 0-0x10FFFF.
* Rather than trying to resynchronize, we will carry on until the end
* of the sequence and let the later error handling code catch it.
*/
$mUcs4 = ( $in );
$mUcs4 = ( $mUcs4 & 0x03 ) << 24 ;
$mState = 4 ;
$mBytes = 5 ;
} else if ( 0xFC == ( 0xFE & ( $in ))) {
// First octet of 6 octet sequence, see comments for 5 octet sequence.
$mUcs4 = ( $in );
$mUcs4 = ( $mUcs4 & 1 ) << 30 ;
$mState = 5 ;
$mBytes = 6 ;
} else {
/* Current octet is neither in the US-ASCII range nor a legal first
* octet of a multi-octet sequence.
*/
trigger_error (
' utf8_to_unicode: Illegal sequence identifier ' .
' in UTF-8 at byte ' . $i ,
E_USER_WARNING
);
return FALSE ;
}
} else {
// When mState is non-zero, we expect a continuation of the multi-octet
// sequence
if ( 0x80 == ( 0xC0 & ( $in ))) {
// Legal continuation.
$shift = ( $mState - 1 ) * 6 ;
$tmp = $in ;
$tmp = ( $tmp & 0x0000003F ) << $shift ;
$mUcs4 |= $tmp ;
/* *
* End of the multi-octet sequence. mUcs4 now contains the final
* Unicode codepoint to be output
*/
if ( 0 == -- $mState ) {
/*
* Check for illegal sequences and codepoints.
*/
// From Unicode 3.1, non-shortest form is illegal
if ((( 2 == $mBytes ) && ( $mUcs4 < 0x0080 )) ||
(( 3 == $mBytes ) && ( $mUcs4 < 0x0800 )) ||
(( 4 == $mBytes ) && ( $mUcs4 < 0x10000 )) ||
( 4 < $mBytes ) ||
// From Unicode 3.2, surrogate characters are illegal
(( $mUcs4 & 0xFFFFF800 ) == 0xD800 ) ||
// Codepoints outside the Unicode range are illegal
( $mUcs4 > 0x10FFFF )) {
trigger_error (
' utf8_to_unicode: Illegal sequence or codepoint ' .
' in UTF-8 at byte ' . $i ,
E_USER_WARNING
);
return FALSE ;
}
if ( 0xFEFF != $mUcs4 ) {
// BOM is legal but we don't want to output it
$out [] = $mUcs4 ;
}
// initialize UTF8 cache
$mState = 0 ;
$mUcs4 = 0 ;
$mBytes = 1 ;
}
} else {
/* *
*((0xC0 & (*in) != 0x80) && (mState != 0))
* Incomplete multi-octet sequence.
*/
trigger_error (
' utf8_to_unicode: Incomplete multi-octet ' .
' sequence in UTF-8 at byte ' . $i ,
E_USER_WARNING
);
return FALSE ;
}
}
}
return $out ;
}
关于 utf8 和 Unicode 序列转换规则 你可以google。PHP里面不支持本地Unicode。也没有提供相关的函数。