如何在MySQL中删除不适用于utf8编码的坏字符?

18

我的数据很脏。有时它包含像这个这样的字符。 我使用这些数据来进行查询。

WHERE a.address IN ('mydatahere')

针对这个字符,我收到以下错误信息:

org.hibernate.exception.GenericJDBCException: Illegal mix of collations (utf8_bin,IMPLICIT), (utf8mb4_general_ci,COERCIBLE), (utf8mb4_general_ci,COERCIBLE) for operation ' IN '

我该如何过滤掉这样的字符? 我使用Java。

谢谢。

6个回答

10

当我遇到类似的问题时,我使用Perl脚本来确保数据被转换为有效的UTF-8,使用类似以下代码:

use Encode;
binmode(STDOUT, ":utf8");
while (<>) {
    print Encode::decode('UTF-8', $_);
}

该脚本接受可能损坏的UTF-8格式的输入,在stdin中重新打印有效的UTF-8格式到stdout。无效的字符将被替换为U+FFFDUnicode替换字符)。

如果您在良好的UTF-8格式输入上运行此脚本,则输出应与输入完全相同。

如果您的数据存储在数据库中,则使用DBI扫描您的表并使用此方法清理所有数据是有意义的,以确保所有内容都是有效的UTF-8格式。

这是同样脚本的Perl一行版本:

perl -MEncode -e "binmode STDOUT,':utf8';while(<>){print Encode::decode 'UTF-8',\$_}" < bad.txt > good.txt

编辑:添加了仅适用于Java的解决方案.

以下是如何在Java中实现此示例:

import java.nio.ByteBuffer;
import java.nio.CharBuffer;
import java.nio.charset.CharacterCodingException;
import java.nio.charset.Charset;
import java.nio.charset.CharsetDecoder;
import java.nio.charset.CodingErrorAction;

public class UtfFix {
    public static void main(String[] args) throws InterruptedException, CharacterCodingException {
        CharsetDecoder decoder = Charset.forName("UTF-8").newDecoder();
        decoder.onMalformedInput(CodingErrorAction.REPLACE);
        decoder.onUnmappableCharacter(CodingErrorAction.REPLACE);
        ByteBuffer bb = ByteBuffer.wrap(new byte[] {
            (byte) 0xD0, (byte) 0x9F, // 'П'
            (byte) 0xD1, (byte) 0x80, // 'р'
            (byte) 0xD0,              // corrupted UTF-8, was 'и'
            (byte) 0xD0, (byte) 0xB2, // 'в'
            (byte) 0xD0, (byte) 0xB5, // 'е'
            (byte) 0xD1, (byte) 0x82  // 'т'
        });
        CharBuffer parsed = decoder.decode(bb);
        System.out.println(parsed);
        // this prints: Пр?вет
    }
}

抱歉,我需要Java解决方案。 - Stepan Yakovenko
您首先需要一个具有相同替换行为的CharsetEncoder,以获取输入字符串的字节,然后再将其解码回字符串。 - bobince
在这个例子中,您需要使用源中的实际字节流(文本文件或SQL列)替换ByteBuffer。这个例子的要点是如何确保您的UTF-8流绝对符合UTF-8标准。您可以自行决定如何将数据放入您的“ByteBuffer”或“ByteArrayInputStream”中。 - mvp
请注意,如果您需要一个Reader而不是直接处理缓冲区,在Java 7中,InputStreamReader可以接受预配置的CharsetDecoder,例如此示例。 - MandisaW
为什么Java解决方案没有使用“?”而是Unicode替换字符?更糟糕的是,所有文档都表明默认的替换字符是\uFFFD...也许这是一个错误? - Mike McCoy
我编辑了帖子,向Perl oneliner 中的 $_ 变量添加了一个反斜杠。 - Diego Sevilla

6
您可以将其编码为UTF-8,然后进行解码/从中解码:
String label = "look into my eyes 〠.〠";

Charset charset = Charset.forName("UTF-8");
label = charset.decode(charset.encode(label)).toString();

System.out.println(label);

输出:

look into my eyes ?.?

编辑:我认为这只适用于Java 6。


1
好的,您刚刚使用这种方法删除了完全有效的Unicode字符“〠.〠”。问题是如何仅替换无效字符,而不是所有字符。 - mvp
刚刚测试了一下,输出结果是 look into my eyes 〠.〠,这是正确的,因为该代码不应该导致 Unicode 字符的丢失,除非您在 Java 源文件中使用 ASCII。 - Dmitry Ratty

3
您可以使用以下正则表达式过滤代理字符:
String str  = ""; //U+20000, represented by 2 chars in java (UTF-16 surrogate pair)
str = str.replaceAll( "([\\ud800-\\udbff\\udc00-\\udfff])", "");
System.out.println(str.length()); //0

(注意,这将删除所有代理项,而不仅仅是无效序列。) - bobince
@bobince 是的,我在考虑MySQL,据我所知它在处理超出BMP字符时存在问题。http://bugs.mysql.com/bug.php?id=25666 - Esailija
啊,是的 - 你必须使用utfmb4来存储星际平面,这相对较新。(除非你只是将所有内容转储到二进制字符串中或使用误导性编码,这样你就无法获得Unicode感知字符串排序的好处。) - bobince
2
dc00不是在dbff之后吗?为什么不能直接写成“([\ud800-\udfff])”呢? - MattyB

2

当你在Java机器上将字节数组转换为字符串时,默认情况下(大多数机器),你将得到UTF-16编码的字符串。解决非UTF-8字符的正确方法是使用以下代码:

String[] values = {"\\xF0\\x9F\\x98\\x95", "\\xF0\\x9F\\x91\\x8C", "/*", "look into my eyes 〠.〠", "fkdjsf ksdjfslk", "\\xF0\\x80\\x80\\x80", "aa \\xF0\\x9F\\x98\\x95 aa", "Ok"};
for (int i = 0; i < values.length; i++) {
    System.out.println(values[i].replaceAll(
                    //"[\\\\x00-\\\\x7F]|" + //single-byte sequences   0xxxxxxx - commented because of capitol letters
                    "[\\\\xC0-\\\\xDF][\\\\x80-\\\\xBF]|" + //double-byte sequences   110xxxxx 10xxxxxx
                    "[\\\\xE0-\\\\xEF][\\\\x80-\\\\xBF]{2}|" + //triple-byte sequences   1110xxxx 10xxxxxx * 2
                    "[\\\\xF0-\\\\xF7][\\\\x80-\\\\xBF]{3}" //quadruple-byte sequence 11110xxx 10xxxxxx * 3
            , ""));
}

如果您想验证某个字符串是否包含非UTF8字符,则可以使用Pattern.matches,例如:

String[] values = {"\\xF0\\x9F\\x98\\x95", "\\xF0\\x9F\\x91\\x8C", "/*", "look into my eyes 〠.〠", "fkdjsf ksdjfslk", "\\xF0\\x80\\x80\\x80", "aa \\xF0\\x9F\\x98\\x95 aa", "Ok"};
for (int i = 0; i < values.length; i++) {
    System.out.println(Pattern.matches(
                    ".*(" +
                    //"[\\\\x00-\\\\x7F]|" + //single-byte sequences   0xxxxxxx - commented because of capitol letters
                    "[\\\\xC0-\\\\xDF][\\\\x80-\\\\xBF]|" + //double-byte sequences   110xxxxx 10xxxxxx
                    "[\\\\xE0-\\\\xEF][\\\\x80-\\\\xBF]{2}|" + //triple-byte sequences   1110xxxx 10xxxxxx * 2
                    "[\\\\xF0-\\\\xF7][\\\\x80-\\\\xBF]{3}" //quadruple-byte sequence 11110xxx 10xxxxxx * 3
                    + ").*"
            , values[i]));
}

为了使整个Web应用程序兼容UTF8,请阅读此处:
如何在Java Web应用程序中使UTF-8工作
有关字节编码和字符串的更多信息.
您可以在这里检查您的模式。
PHP中相同的内容请参考此处

你能解释一下“因为大写字母而被注释掉”的意思吗? - aliteralmind

-1

也许这篇文章能帮到其他人,就像帮助了我一样。

public static String removeBadChars(String s) {
  if (s == null) return null;
  StringBuilder sb = new StringBuilder();
  for(int i=0;i<s.length();i++){ 
    if (Character.isHighSurrogate(s.charAt(i))) continue;
    sb.append(s.charAt(i));
  }
  return sb.toString();
}

你也想删除那些 isLowSurrogate() 的字符。如果你的字符串有一个有效的 >BMP 字符,这将导致字符串损坏。 - Esailija

-1
在 PHP 中,我通过仅允许可打印数据来处理此问题。这真的有助于清理数据库中的数据。
不过这是预处理,有时您可能没有这种奢侈条件。
$str = preg_replace('/[[:^print:]]/', '', $str);

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接