修复损坏的 UTF-8 编码

Posted

技术标签:

【中文标题】修复损坏的 UTF-8 编码【英文标题】:Fixing broken UTF-8 encoding 【发布时间】:2010-11-23 14:25:37 【问题描述】:

我正在修复一些错误的 UTF-8 编码。我目前正在使用 php 5 和 mysql

在我的数据库中,我有几个错误的编码实例,打印如下:î

数据库排序规则是 utf8_general_ci PHP 正在使用正确的 UTF-8 标头 Notepad++ 设置为使用不带 BOM 的 UTF-8 数据库管理在phpMyAdmin中处理 并非所有重音字符都被破坏

我需要某种函数来帮助我将 î、ÃÂ、ü 和其他类似的实例映射到它们正确的重音 UTF-8 字符。

【问题讨论】:

也许你可以列出那些应该代表的字符?也许是十六进制转储? 快速浏览似乎表明您的字符串可能是“双”utf-8 编码的。 IE。以 utf-8 编码,将那些字节视为 unicode 字符,并将结果以 utf-8 编码。向后看:“î”="\xC3\x83\xC2\xAE" 它可能是双重编码的。有没有一种安全的方法来以编程方式检查这一点,如果有,安全解码双重编码的最佳方法是什么? 是的,Jayrox,请在下面查看我的答案。 afaik 的问题之一是 utf8_general_ci,它显然不能保证良好的 UTF8 ***.com/a/1036459/183677。你提到的那些字符 有效的 UTF8 hexutf8.com/… (但我意识到它可能正是你在控制台或其他地方看到的)。支付实际字节数 【参考方案1】:

如果您有双重编码的 UTF8 字符(各种智能引号、破折号、撇号 ’、引号 “ 等),您可以在 mysql 中转储数据,然后将其读回以修复损坏的编码.

像这样:

mysqldump -h DB_HOST -u DB_USER -p DB_PASSWORD --opt --quote-names \
    --skip-set-charset --default-character-set=latin1 DB_NAME > DB_NAME-dump.sql

mysql -h DB_HOST -u DB_USER -p DB_PASSWORD \
    --default-character-set=utf8 DB_NAME < DB_NAME-dump.sql

这是对我的双编码 UTF-8 的 100% 修复。

来源: http://blog.hno3.org/2010/04/22/fixing-double-encoded-utf-8-data-in-mysql/

【讨论】:

似乎已经为我成功转换了 Typo3 数据库。感谢您发布此信息;它比任何其他转换方法都干净得多。 :) 我希望我能给你更多的支持,你真的值得他们。 是的,也为我工作!感谢您在这里分享并感谢博客的所有者:) 使用 Sequel Pro 将 Wordpress 数据库从暂存环境转移到本地环境时遇到问题。 完美运行!我还必须修复一个旧的 TYPO3 数据库,这才成功!【参考方案2】:

如果您 utf8_encode() 处理一个已经是 UTF-8 的字符串,那么在多次编码时它看起来会出现乱码。

我创建了一个函数toUTF8(),将字符串转换为UTF-8。

您不需要指定字符串的编码是什么。它可以是 Latin1 (iso 8859-1)、Windows-1252 或 UTF8,或者这三者的混合。

我自己在同一字符串中混合编码的提要中使用了它。

用法:

$utf8_string = Encoding::toUTF8($mixed_string);

$latin1_string = Encoding::toLatin1($mixed_string);

我的另一个函数 fixUTF8() 修复了乱码的 UTF8 字符串,如果它们被多次编码为 UTF8。

用法:

$utf8_string = Encoding::fixUTF8($garbled_utf8_string);

例子:

echo Encoding::fixUTF8("Fédération Camerounaise de Football");
echo Encoding::fixUTF8("Fédération Camerounaise de Football");
echo Encoding::fixUTF8("FÃÂédÃÂération Camerounaise de Football");
echo Encoding::fixUTF8("Fédération Camerounaise de Football");

将输出:

Fédération Camerounaise de Football
Fédération Camerounaise de Football
Fédération Camerounaise de Football
Fédération Camerounaise de Football

下载:

https://github.com/neitanod/forceutf8

【讨论】:

似乎可以解决问题。我不将它用于正常输出,但我很喜欢使用您的课程来提供数据迁移帮助。 谢谢。这很神奇,不是吗?我认为就用它解决的问题而言,这段小代码是我制作的最令人满意的东西之一。 :-) 我建议将它用于迁移,正如 Kristopher 所说,但不是在生产环境中。在某些情况下,您希望“乱码字符串”保持乱码,就像在这个答案中一样。 我一直在与混合编码的第三方系统作斗争。我测试了你的课,效果很好。我只是在我们的数据库中以混合编码存储外部输入的字段上运行它,然后它清理了所有内容。现在我正在我们的插入连接处实现它。顺便说一下,PDO 不会识别混合编码,因此您的解决方案很糟糕! +1 非常好——fixUTF8 甚至可以处理我见过的一些奇怪的编码错误。【参考方案3】:

过去,我不得不尝试“修复”一些 UTF8 损坏的情况,不幸的是,这绝非易事,而且往往是不可能的。

除非你能确定它是如何被破坏的,而且它总是以完全相同的方式被破坏,否则很难“消除”损坏。

如果您想尝试消除损坏,最好的办法是开始编写一些示例代码,在其中尝试对mb_convert_encoding() 的调用进行多种变体,看看是否可以找到“来自”和“的组合” to' 修复您的数据。最后,通常最好不要因为涉及的痛苦程度而担心修复旧数据,而是只修复未来的问题。

但是,在执行此操作之前,您需要确保首先解决导致此问题的所有问题。您已经提到您的数据库表排序规则和编辑器设置正确。但是还有更多地方需要检查以确保所有内容都是正确的 UTF-8:

确保以 UTF-8 格式提供 htmlheader("Content-Type: text/html; charset=utf-8"); 将您的 PHP 默认字符集更改为 utf-8: ini_set("default_charset", 'utf-8'); 如果您的数据库不总是以 utf-8 格式通信,那么您可能需要在每个连接的基础上告诉它以确保它处于 utf-8 模式,在 MySQL 中,您可以通过发出以下命令来做到这一点: 字符集 utf8 您可能需要告诉您的网络服务器始终尝试使用 UTF8 进行通信,在 Apache 中此命令为: AddDefaultCharset UTF-8 最后,您需要始终确保您使用的 PHP 函数符合 UTF-8 标准。这意味着始终使用mb_* 风格的“多字节感知”字符串函数。这也意味着在调用 htmlspecialchars() 等函数时,您需要在末尾包含适当的 'utf-8' 字符集参数,以确保它不会对它们进行错误编码。

如果您错过了整个过程中的任何一步,编码可能会被破坏并出现问题。但是,一旦您进入执行 utf-8 的“常规”,这一切都将成为第二天性。当然,PHP6 从一开始就应该是完全 unicode 的投诉,这将使这一切变得更容易(希望如此)

【讨论】:

非常感谢!因为数据库中还有许多正确编码的字符串,这使问题变得更糟,我选择 str_replace 我知道的字符串被正确的字符损坏。它工作得很好。我已经实现了你关于 PHP 和服务器设置的大部分技巧,但它是一个很好的总结,所以我会选择这个作为答案,因为我的解决方案并不是很漂亮。 关于此建议的一个重要说明:不要将 'utf-8' 作为函数 htmlspecialchars() 的第二个参数添加。如果没有参数,该函数对 UTF-8 字符串执行正确的操作,因为它会忽略所有设置了高位的字节并传递它们。这将保护他们并“做正确的事”。使用 'utf-8',htmlspecialchars() 解释 UTF-8 字符串 - 但不处理 BMP 之外的字符(那些代码点为 U+10000 及以上的字符,编码为四个字节)。它错误地编码了那些碰巧与特殊模块 65536 匹配的内容。行为既慢又错误。 请看下面我的回答。我在一个纯 PHP 函数中解决了所有这些问题:fixUTF8()。您不需要更改服务器配置,甚至不需要安装多字节函数。该函数足够聪明,可以独立修复任何字符,即使编码混合在同一个字符串中(不管它被转换了多少次,或者它是否已经在 UTF8 中)。 PHP 6 被跳过,PHP 7 将在一个月内稳定发布。 @Jayrox:有一个来自 github 的工具有更好的答案:***.com/a/3521340/196210【参考方案4】:

我遇到了一个编码损坏的 xml 文件的问题,它说它是 utf-8,但它的字符不是 utf-8。 经过mb_convert_encoding() 的几次试验和错误,我设法修复它

mb_convert_encoding($text, 'Windows-1252', 'UTF-8')

【讨论】:

经过几天的努力解决这个问题后,这对我有用(一切都是 UTF-8 端到端,但在 RSS 中却不是!)谢谢! 我的问题是:数据库字段保存为 latin1_swedish_ci,PHP 输出为 utf-8,将 Umlaute ü 显示为 üöö。这有助于解决这个问题。【参考方案5】:

正如 Dan 指出的:您需要将它们转换为二进制,然后转换/更正编码。

例如,对于存储为 latin1 的 utf8,以下 SQL 将修复它:

UPDATE table
   SET field = CONVERT( CAST(field AS BINARY) USING utf8)
 WHERE $broken_field_condition

【讨论】:

有趣;如果我再次遇到问题,我会记住这一点。谢谢 有道理。我猜它确实是双重编码的,只是该字段被标记为 latin1,即使它确实包含 UTF8,所以当您将字段请求为 UTF8 时,它会再次对其进行编码。 伙计,你成就了我的一天,它对我有用。现在我想了解我正在使用的转储具有这些错误字符的真正原因(也许它在 utf-8 中正确编码,但转储过程将输出打印为 latin1) WHERE LENGTH( field ) != CHAR_LENGTH( field ) ;)【参考方案6】:

我知道这不是很优雅,但是在提到字符串可能是双重编码之后,我做了这个函数:

function fix_double encoding($string)

    $utf8_chars = explode(' ', 'À Á Â Ã Ä Å Æ Ç È É Ê Ë Ì Í Î Ï Ð Ñ Ò Ó Ô Õ Ö × Ø Ù Ú Û Ü Ý Þ ß à á â ã ä å æ ç è é ê ë ì í î ï ð ñ ò ó ô õ ö');
    $utf8_double_encoded = array();
    foreach($utf8_chars as $utf8_char)
    
            $utf8_double_encoded[] = utf8_encode(utf8_encode($utf8_char));
    
    $string = str_replace($utf8_double_encoded, $utf8_chars, $string);
    return $string;

这似乎可以完美地消除我遇到的双重编码。我可能遗漏了一些可能对其他人造成问题的角色。但是,根据我的需要,它运行良好。

【讨论】:

看看我的回答。函数 Encoding::fixUTF8()。它修复了所有 UTF8 字符(有数百万个),并且可以处理多次编码的字符串,而不仅仅是两次。【参考方案7】:
$bad_string = "Luis Pérez Casas, del Collettivo di avvocati “José Alvear Restrepoâ€, Colombia, un’organizzazione soggetta a costanti minacce";

$good_string = fix_broken_chars($bad_string);

echo $good_string;

function fix_broken_chars($garbled_utf8_string)
   
    $conv_table = unserialize('a:5:i:0;a:3:s:8:"’";s:3:"’";s:8:"–";s:3:"–";s:8:"—";s:3:"—";i:1;a:12:s:7:"€";s:3:"€";s:7:"‚";s:3:"‚";s:7:"„";s:3:"„";s:7:"…";s:3:"…";s:7:"‡";s:3:"‡";s:7:"‰";s:3:"‰";s:7:"‹";s:3:"‹";s:7:"‘";s:3:"‘";s:7:"“";s:3:"“";s:7:"•";s:3:"•";s:7:"â„¢";s:3:"™";s:7:"›";s:3:"›";i:2;a:22:s:5:"À";s:2:"À";s:5:"Â";s:2:"Â";s:5:"Æ’";s:2:"ƒ";s:5:"Ä";s:2:"Ä";s:5:"Ã…";s:2:"Å";s:5:"â€";s:3:"”";s:5:"Æ";s:2:"Æ";s:5:"Ç";s:2:"Ç";s:5:"ˆ";s:2:"ˆ";s:5:"É";s:2:"É";s:5:"Ë";s:2:"Ë";s:5:"Å’";s:2:"Œ";s:5:"Ñ";s:2:"Ñ";s:5:"Ã’";s:2:"Ò";s:5:"Ó";s:2:"Ó";s:5:"Ô";s:2:"Ô";s:5:"Õ";s:2:"Õ";s:5:"Ö";s:2:"Ö";s:5:"×";s:2:"×";s:5:"Ù";s:2:"Ù";s:5:"Û";s:2:"Û";s:5:"Å“";s:2:"œ";i:3;a:77:s:4:"Ã";s:2:"Ã";s:4:"È";s:2:"È";s:4:"Ê";s:2:"Ê";s:4:"ÃŒ";s:2:"Ì";s:4:"Ž";s:2:"Ž";s:4:"ÃŽ";s:2:"Î";s:4:"Ëœ";s:2:"˜";s:4:"Ø";s:2:"Ø";s:4:"Å¡";s:2:"š";s:4:"Ú";s:2:"Ú";s:4:"Ãœ";s:2:"Ü";s:4:"ž";s:2:"ž";s:4:"Þ";s:2:"Þ";s:4:"Ÿ";s:2:"Ÿ";s:4:"ß";s:2:"ß";s:4:"¡";s:2:"¡";s:4:"á";s:2:"á";s:4:"¢";s:2:"¢";s:4:"â";s:2:"â";s:4:"£";s:2:"£";s:4:"ã";s:2:"ã";s:4:"¤";s:2:"¤";s:4:"ä";s:2:"ä";s:4:"Â¥";s:2:"¥";s:4:"Ã¥";s:2:"å";s:4:"¦";s:2:"¦";s:4:"æ";s:2:"æ";s:4:"§";s:2:"§";s:4:"ç";s:2:"ç";s:4:"¨";s:2:"¨";s:4:"è";s:2:"è";s:4:"©";s:2:"©";s:4:"é";s:2:"é";s:4:"ª";s:2:"ª";s:4:"ê";s:2:"ê";s:4:"«";s:2:"«";s:4:"ë";s:2:"ë";s:4:"¬";s:2:"¬";s:4:"ì";s:2:"ì";s:4:"­";s:2:"­";s:4:"í";s:2:"í";s:4:"®";s:2:"®";s:4:"î";s:2:"î";s:4:"¯";s:2:"¯";s:4:"ï";s:2:"ï";s:4:"°";s:2:"°";s:4:"ð";s:2:"ð";s:4:"±";s:2:"±";s:4:"ñ";s:2:"ñ";s:4:"²";s:2:"²";s:4:"ò";s:2:"ò";s:4:"³";s:2:"³";s:4:"ó";s:2:"ó";s:4:"´";s:2:"´";s:4:"ô";s:2:"ô";s:4:"µ";s:2:"µ";s:4:"õ";s:2:"õ";s:4:"¶";s:2:"¶";s:4:"ö";s:2:"ö";s:4:"·";s:2:"·";s:4:"÷";s:2:"÷";s:4:"¸";s:2:"¸";s:4:"ø";s:2:"ø";s:4:"¹";s:2:"¹";s:4:"ù";s:2:"ù";s:4:"º";s:2:"º";s:4:"ú";s:2:"ú";s:4:"»";s:2:"»";s:4:"û";s:2:"û";s:4:"¼";s:2:"¼";s:4:"ü";s:2:"ü";s:4:"½";s:2:"½";s:4:"ý";s:2:"ý";s:4:"¾";s:2:"¾";s:4:"þ";s:2:"þ";s:4:"¿";s:2:"¿";s:4:"ÿ";s:2:"ÿ";i:4;a:1:s:2:"Ã";s:2:"à";');

    foreach ($conv_table as $convert) 
        $garbled_utf8_string = str_replace(array_keys($convert), $convert, $garbled_utf8_string);    
    

    return $garbled_utf8_string;

实现这个表http://www.i18nqa.com/debug/utf8-debug.html

【讨论】:

不适用于某些角色,但效果足够好。谢谢!【参考方案8】:

方法是先转成二进制,然后再正确编码

【讨论】:

什么?这甚至没有意义!【参考方案9】:

要检查的另一件事,恰好是我的解决方案(找到here),是如何从您的服务器返回数据。在我的应用程序中,我使用 PDO 从 PHP 连接到 MySQL。我需要在连接中添加一个标志,表示以 UTF-8 格式取回数据

答案是

$dbHandle = new PDO("mysql:host=$dbHost;dbname=$dbName;charset=utf8", $dbUser, $dbPass, 
    array(PDO::MYSQL_ATTR_INIT_COMMAND => "SET NAMES 'utf8'"));

【讨论】:

【参考方案10】:

就我而言,我通过使用“mb_convert_encoding”发现之前的编码是 iso-8859-1 (这是 latin1)然后我使用 sql 查询解决了我的问题:

UPDATE myDB.myTable SET myColumn = CAST(CAST(CONVERT(myColumn USING latin1) AS binary) AS CHAR)

但是,mysql 文档中指出conversion may be lossy if the column contains characters that are not in both character sets.

【讨论】:

【参考方案11】:

您的 utf-8 似乎在某些时候被解释为 iso8859-1 或 Win-1250。

当您说“在我的数据库中我有一些错误编码的实例”时,您是如何检查的?通过您的应用程序、phpmyadmin 或命令行客户端? all utf-8 编码是像这样显示还是只显示一些?是否有可能您的编码错误,并且当它已经是 utf-8 时,它已从 iso8859-1 错误地转换为 utf-8?

【讨论】:

我使用 phpmyadmin 进行数据库管理。不,并非所有情况都编码错误。【参考方案12】:

我很久以前遇到过同样的问题,并使用它修复了它

<meta http-equiv="Content-Type" content="text/html; charset=iso-8859-15">

【讨论】:

【参考方案13】:

经过几天的搜索,我找到了解决方案。我的评论将被埋没,但无论如何......

    我用 php 得到了损坏的数据。

    我不使用设置名称 UTF8

    我对我的数据使用 utf8_decode()

    我用我的新解码数据更新了我的数据库,但仍然没有使用设置名称 UTF8

瞧 :)

【讨论】:

【参考方案14】:

这个脚本有一个很好的方法。将其转换为您选择的语言应该不会太难:

http://plasmasturm.org/log/416/

#!/usr/bin/perl
use strict;
use warnings;

use Encode qw( decode FB_QUIET );

binmode STDIN, ':bytes';
binmode STDOUT, ':encoding(UTF-8)';

my $out;

while ( <> ) 
  $out = '';
  while ( length ) 
    # consume input string up to the first UTF-8 decode error
    $out .= decode( "utf-8", $_, FB_QUIET );
    # consume one character; all octets are valid Latin-1
    $out .= decode( "iso-8859-1", substr( $_, 0, 1 ), FB_QUIET ) if length;
  
  print $out;

【讨论】:

以上是关于修复损坏的 UTF-8 编码的主要内容,如果未能解决你的问题,请参考以下文章

使用启发式方法修复错误编码文本的 Java 库

将 jpg 文件转换为 UTF-8 而不使其损坏

怎样修复损坏了的innodb 表

XFS 分区损坏修复方法

如何修复损坏的MySQL数据表

xlsx文件损坏如何修复(xls文件损坏怎么修复)