php对gb编码动态转utf-8编码的几种方法评测 |
在《IP地址->地理位置转换的评测》一文中提到用ip2addr函数直接读取IP数据库文件是效率最高的,相比用MySQL数据库存储IP数据,用SQL 查问是效率最低的 。然而IP数据库文件QQWry.dat是GB2312编码的 。现在我需求UTF-8编码的地理位置 后果 。假如用MySQL 步骤, 可以在数据存入数据库时就转换为UTF-8编码,一劳永逸 。然而QQWry.dat文件又 无奈 批改,不得不把ip2addr函数的输出 后果再进行动态转换 。 动态转换GB->UTF-8编码至少有四种 步骤: 用PHP的iconv 扩大转换 用PHP的mb_string 扩大转换 用对换表转换,对换表存储在MySQL数据库中 用对换表转换,对换表存储在文本文件中 前两种 步骤要服务器作了相应设置(编译安装了相应 扩大) 威力 使用 。我的 虚构主机没有这两个 扩大,只好考量后两种 步骤 。前两个 步骤本文也不进行评测 。 评测程序如下(func_ip.php参见《IP地址->地理位置转换的评测》一文): <?php require_once ("func_ip.php"); function u2utf8($c) { $str = ""; if ($c < 0x80) { $str .= $c; } elseif ($c < 0x800) { $str .= chr(0xC0 | $c >> 6); $str .= chr(0x80 | $c & 0x3F); } elseif ($c < 0x10000) { $str .= chr(0xE0 | $c >> 12); $str .= chr(0x80 | $c >> 6 & 0x3F); $str .= chr(0x80 | $c & 0x3F); } elseif ($c < 0x200000) { $str .= chr(0xF0 | $c >> 18); $str .= chr(0x80 | $c >> 12 & 0x3F); $str .= chr(0x80 | $c >> 6 & 0x3F); $str .= chr(0x80 | $c & 0x3F); } return $str; } function GB2UTF8_SQL($strGB) { if (!trim($strGB)) return $strGB; $strRet = ""; $intLen = strlen($strGB); for ($i = 0; $i < $intLen; $i++) { if (ord($strGB{$i}) > 127) { $strCurr = substr($strGB, $i, 2); $intGB = hexdec(bin2hex($strCurr)) - 0x8080; $strSql = "SELECT code_unicode FROM nnstats_gb_unicode WHERE code_gb = ".$intGB." LIMIT 1" ; $resResult = mysql_query($strSql); if ($arrCode = mysql_fetch_array($resResult)) $strRet .= u2utf8($arrCode["code_unicode"]); else $strRet .= "??"; $i++; } else { $strRet .= $strGB{$i}; } } return $strRet; } function GB2UTF8_FILE($strGB) { if (!trim($strGB)) return $strGB; $arrLines = file("gb_unicode.txt"); foreach ($arrLines as $strLine) { $arrCodeTable[hexdec(substr($strLine, 0, 6))] = hexdec(substr($strLine, 7, 6)); } $strRet = ""; $intLen = strlen($strGB); for ($i = 0; $i < $intLen; $i++) { if (ord($strGB{$i}) > 127) { $strCurr = substr($strGB, $i, 2); $intGB = hexdec(bin2hex($strCurr)) - 0x8080; if ($arrCodeTable[$intGB]) $strRet .= u2utf8($arrCodeTable[$intGB]); else $strRet .= "??"; $i++; } else { $strRet .= $strGB{$i}; } } return $strRet; } function EncodeIp($strDotquadIp) { $arrIpSep = explode('.', $strDotquadIp); if (count($arrIpSep) != 4) return 0; $intIp = 0; foreach ($arrIpSep as $k => $v) $intIp += (int)$v * pow(256, 3 - $k); return $intIp; //return sprintf('%02x%02x%02x%02x', $arrIpSep[0], $arrIpSep[1], $arrIpSep[2], $arrIpSep[3]); } function GetMicroTime() { list($msec, $sec) = explode(" ", microtime()); return ((double)$msec + (double)$sec); } for ($i = 0; $i < 100; $i++) { // 随机产生100个ip地址 $strIp = mt_rand(0, 255).".".mt_rand(0, 255).".".mt_rand(0, 255).".".mt_rand(0, 255); $arrAddr[$i] = ip2addr(EncodeIp($strIp)); } $resConn = mysql_connect("localhost", "netnest", "netnest"); mysql_select_db("test"); // 评测MySQL 查问的编码转换 $dblTimeStart = GetMicroTime(); for ($i = 0; $i < 100; $i++) { $strUTF8Region = GB2UTF8_SQL($arrAddr[$i]["region"]); $strUTF8Address = GB2UTF8_SQL($arrAddr[$i]["address"]); } $dblTimeDuration = GetMicroTime() - $dblTimeStart; // 评测 完毕并输出 后果 echo $dblTimeDuration; echo " "; // 评测文本文件 查问的编码转换 $dblTimeStart = GetMicroTime(); for ($i = 0; $i < 100; $i++) { $strUTF8Region = GB2UTF8_FILE($arrAddr[$i]["region"]); $strUTF8Address = GB2UTF8_FILE($arrAddr[$i]["address"]); } $dblTimeDuration = GetMicroTime() - $dblTimeStart; // 评测 完毕并输出 后果 echo $dblTimeDuration; echo " "; ?> 评测两次 后果(准确到3位小数,单位是秒): MySQL 查问转换:0.112 文本 查问转换:10.590 MySQL 查问转换:0.099 文本 查问转换:10.623 可见这次是MySQL 步骤遥遥率先于文件 查问法 。然而现在还不急于 使用MySQL 步骤,由于文本文件 步骤之所以如此耗时,重要由于它每次转换都要把整个gb_unicode.txt读入内存,而gb_unicode.txt又是文本文件, 格局如下: 0x2121 0x3000 # IDEOGRAPHIC SPACE 0x2122 0x3001 # IDEOGRAPHIC COMMA 0x2123 0x3002 # IDEOGRAPHIC FULL STOP 0x2124 0x30FB # KATAKANA MIDDLE DOT 0x2125 0x02C9 # MODIFIER LETTER MACRON (Mandarin Chinese first tone) …… 0x552A 0x6458 # <CJK> 0x552B 0x658B # <CJK> 0x552C 0x5B85 # <CJK> 0x552D 0x7A84 # <CJK> …… 0x777B 0x9F37 # <CJK> 0x777C 0x9F3D # <CJK> 0x777D 0x9F3E # <CJK> 0x777E 0x9F44 # <CJK> 文本文件效率较低,于是考量把文本文件转换为二进制文件, 而后用折半法搜索这个文件,而不需求把整个文件读入内存 。文件 格局为:文件头2字节,存储记录数;接着一条接一条记录存入文件,每条记录4字节,前2字节对应GB代码,后2字节对应Unicode代码 。转换程序如下: <?php $arrLines = file("gb_unicode.txt"); foreach ($arrLines as $strLine) { $arrCodeTable[hexdec(substr($strLine, 0, 6))] = hexdec(substr($strLine, 7, 6)); } ksort($arrCodeTable); $intCount = count($arrCodeTable); $strCount = chr($intCount % 256) . chr(floor($intCount / 256)); $fileGBU = fopen("gbu.dat", "wb"); fwrite($fileGBU, $strCount); foreach ($arrCodeTable as $k => $v) { $strData = chr($k % 256) . chr(floor($k / 256)) . chr($v % 256) . chr(floor($v / 256)); fwrite($fileGBU, $strData); } fclose($fileGBU); ?> 执行程序后就 获得了二进制的GB->Unicode对比表gbu.dat,而且数据记录按GB代码排了序,便于折半法搜索 。 使用gbu.dat进行转码的函数如下: function GB2UTF8_FILE1($strGB) { if (!trim($strGB)) return $strGB; $fileGBU = fopen("gbu.dat", "rb"); $strBuf = fread($fileGBU, 2); $intCount = ord($strBuf{0}) + 256 * ord($strBuf{1}); $strRet = ""; $intLen = strlen($strGB); for ($i = 0; $i < $intLen; $i++) { if (ord($strGB{$i}) > 127) { $strCurr = substr($strGB, $i, 2); $intGB = hexdec(bin2hex($strCurr)) - 0x8080; $intStart = 1; $intEnd = $intCount; while ($intStart < $intEnd - 1) { // 折半法搜索 $intMid = floor(($intStart + $intEnd) / 2); $intOffset = 2 + 4 * ($intMid - 1); fseek($fileGBU, $intOffset); $strBuf = fread($fileGBU, 2); $intCode = ord($strBuf{0}) + 256 * ord($strBuf{1}); if ($intGB == $intCode) { $intStart = $intMid; break; } if ($intGB > $intCode) $intStart = $intMid; else $intEnd = $intMid; } $intOffset = 2 + 4 * ($intStart - 1); fseek($fileGBU, $intOffset); $strBuf = fread($fileGBU, 2); $intCode = ord($strBuf{0}) + 256 * ord($strBuf{1}); if ($intGB == $intCode) { $strBuf = fread($fileGBU, 2); $intCodeU = ord($strBuf{0}) + 256 * ord($strBuf{1}); $strRet .= u2utf8($intCodeU); } else { $strRet .= "??"; } $i++; } else { $strRet .= $strGB{$i}; } } return $strRet; } 把其加到原来的评测程序,对三种 步骤同时评测2次得到数据(准确到3位小数,单位:秒): MySQL 步骤:0.125 文本文件 步骤:10.873 二进制文件折半法:0.106 MySQL 步骤:0.102 文本文件 步骤:10.677 二进制文件折半法:0.092 可见二进制文件折半法还比MySQL法略有优势 。然而上述评测都是对短的地理位置进行转码,假如对较长的文本转码又如何呢?我找来5个Blog的RSS 2.0文件,都是GB2312编码 。评测三种 步骤对5个文件编码 消费的 工夫,2次测量数据如下(准确到3位小数,单位:秒): MySQL 步骤:7.206 文本文件 步骤:0.772 二进制文件折半法:5.022 MySQL 步骤:7.440 文本文件 步骤:0.766 二进制文件折半法:5.055 可见对长的文本是用文本文件的 步骤最优,由于转码对比表读入内存后,转码就 可以很高效了 。既然如此,我们还 可以尝试改良一下,把文本文件 步骤改为:转码对比表从二进制文件gbu.dat读入内存,而不是文本文件 。评测数据如下(精度和单位同上): 从文本文件读入对比表:0.766 从二进制文件读入对比表:0.831 从文本文件读入对比表:0.774 从二进制文件读入对比表:0.833 表明这次改良失败了,从文本文件读入转码对比表更高效 。 总结:用PHP对GB编码到UTF-8编码的动态转换,假如每次转换的文本很小, 合 实用二进制文件 联合折半法转换;假如每次转换的文本较大, 合 实用文本文件存储转码对比表,并在转换前一次性把对比表读入内存 。 |