如何找到Java中无法存储在MySQL“ utf8”列中的字符

时间:2019-06-28 04:04:34

标签: java mysql unicode

我使用MySQL 5.7,并且我有一个表,该表的列使用“ utf8”字符集。不幸的是它不是utf8mb4,因此当我的应用尝试插入超出“ utf8”范围之外的字符(例如emojis)时,总是会出现错误。

不幸的是,我无法很快将字符集更改为“ utf8mb4”,所以我想知道是否有可能在将错误插入表之前检测出导致错误发生的那些字符,并让我们的客户知道他们无法使用它们。

我在某处读到,任何超出U + 0000到U + FFFF的范围都会导致错误发生。我的应用程序是用Java 8实现的。所以,我的问题是:如何编写可以从String实例中找到有问题的字符的代码? the following code做我想要的事情吗?

import java.util.Set;
import java.util.stream.Collectors;

class Utf8Mb3Validator {

    /**
     * finds characters which can’t be stored in a MySQL “utf8” column out of a given String.
     *
     * @param input a String which you want to check
     * @return a Set which contains strings that can't be inserted into MySQL "utf8" columns
     */
    Set<String> findProblematicStrings(String input) {
        // References:
        // https://dev.mysql.com/doc/refman/5.7/en/charset-unicode-utf8mb3.html
        // https://www.oracle.com/technetwork/java/javase/downloads/supplementary-142654.html?printOnly=1
        // https://stackoverflow.com/q/56800767/3591946
        return input
                .codePoints() // get Unicode code points
                .filter(codePoint -> Character.charCount(codePoint) > 1) // search for non-BMP characters
                .mapToObj(codePoint -> new String(Character.toChars(codePoint))) // convert code points into Strings
                .collect(Collectors.toSet());
    }
}

我也将此问题发布到了MySQL论坛:https://forums.mysql.com/read.php?39,675862,675862#msg-675862

2 个答案:

答案 0 :(得分:1)

实际上,MySQL的utf8是正确的,因为UTF-8多字节序列最多只有3个字节。但是Unicode获得了更多的符号,UTF-8也有所增加。并且 utf8mb4可以。

但是最多3个字节都可以:

return input
      .codePoints()
      .filter(codePoint -> codePoint >= 256) // Optional heuristic optimisation
      .mapToObj(codePoint -> new String(Character.toChars(codePoint)))
      .filter(cpString -> cpString.getBytes(StandardCharsets.UTF_8).length > 3)
      .collect(Collectors.toSet())

或者只是全部codepoints above U+FFFF

return input
      .codePoints()
      .filter(codePoint -> codePoint >= 0x1_0000)
      .mapToObj(codePoint -> new String(Character.toChars(codePoint)))
      .collect(Collectors.toSet());

老实说,我需要研究是否也可以使用Character.charCount(codePoint),因为它会检查UTF-16中的代理对,而不是UTF-8中的字节数。

有用的可能是Character.getName(codePoint)来替换代码点(如果这些字段的长度足够长)。

答案 1 :(得分:0)

如果Java中有一种方法可以生成UTF-8编码的字符串的十六进制表示形式,请在字符串中搜索F0字节。

如果Java中有一种方法可以生成UTF-16编码的字符串的16位表示形式,则搜索包含D8xx-DFFF值的任何16位。

(为我指出一些此类方法,也许我可以详细说明。)