Mac终端PHP转换UTF-16LE文件为UTF-8遇乱码及匹配失败问题
解决方案:UTF-16LE转UTF-8乱码及preg_match匹配失败问题
问题根源分析
你的问题核心出在单字节读取函数处理多字节编码文件和空白字符清理不彻底这两个点上:
- 乱码原因:UTF-16LE是双字节编码,而
fgets()默认按单字节逻辑读取,遇到单字节的换行符(0x0A)就截断内容,导致读取的字节序列不完整(比如某个UTF-16字符只读了一半),mb_convert_encoding处理残缺的多字节序列时就会生成乱码。 - preg_match不匹配原因:Windows生成的UTF-16LE文件换行是
\r\n的双字节形式(0x0D00 0x0A00),rtrim()默认只处理单字节空白字符(如\r、\n),无法移除UTF-16编码的空白字节,转换后这些残留字节会变成不可见的控制字符,导致正则匹配失效。
正确处理方案
推荐两种可靠的处理方式,根据文件大小选择:
方案1:一次性读取转换(适合中小文件)
直接读取整个文件内容再转换,避免逐行读取的字节截断问题:
// 读取UTF-16LE格式的完整文件内容 $content = file_get_contents('myfile.ini'); // 转换为UTF-8编码,自动处理BOM(如果存在) $content = mb_convert_encoding($content, 'UTF-8', 'UTF-16LE'); // 按UTF-8换行符分割为行数组 $lines = explode("\n", $content); foreach ($lines as $line) { // 清理所有UTF-8格式的空白字符(包括回车、换行、空格等) $line = trim($line); if (empty($line)) continue; // 跳过空行 var_dump($line); // 现在正则匹配可以正常工作 if (preg_match('/Optimization/', $line)) { print "hit\n"; } }
方案2:流式编码转换(适合大文件)
如果文件过大不适合一次性读取,用流过滤器先转码再读取:
// 打开文件并添加编码转换过滤器 $handle = fopen('myfile.ini', 'r'); stream_filter_append($handle, 'convert.iconv.UTF-16LE/UTF-8'); while ($line = fgets($handle)) { $line = trim($line); if (empty($line)) continue; var_dump($line); if (preg_match('/Optimization/', $line)) { print "hit\n"; } } fclose($handle);
关键注意事项
- 避免用
fgets()直接读取多字节编码文件,它是单字节导向的,会破坏多字节字符的完整性。 - 转换编码后用
trim()代替rtrim(),确保清理所有UTF-8格式的空白字符。 - 如果文件带有UTF-16LE的BOM(
0xFFFE),mb_convert_encoding会自动处理;若仍有问题,可手动移除:$content = preg_replace('/^\x{FEFF}/u', '', $content);
内容的提问来源于stack exchange,提问作者whitebear




