0

我写了一个小的 perl 函数,它接受一个字符串并检查它的长度,没有空格。基本代码如下所示:

sub foo
{
   use utf8;
   my @wordsArray = split(/ /, $_[0]));
   my $result = length(join('', @wordsArray));
   return $result;
}

当我为这个函数提供一个包含特殊字符(如希伯来字母)的字符串时,它似乎工作得很好。当我使用来自 MySql 列的值,字符集为 utf8mb4 时,问题就开始了:在这种情况下,计算的值高于上一个示例中的值。

我可以猜到为什么会发生这种行为:特殊字符以 4 字节的方式写入表中,因此每个字母在 utf8 编码中计算为两个字符。

有谁知道如何解决上述问题,以便我从来自定义为 utf8mb4 的 DB 表的字符串中获得正确数量的字符?

编辑:

有关上述代码的更多信息:

用作函数参数的 DB 列是 VARCHAR(1000) 类型,排序规则为 utf8mb4_unicode_ci。我通过如下配置的 MySql 连接获取行:

$mySql = DBI->connect(
  "DBI:mysql:$db_info{'database'}:$db_info{'hostname'};mysql_multi_statements=1;",
  "$db_info{'user'}",
  "$db_info{'password'}",
  {'RaiseError' => 1,'AutoCommit' => 0});
...
$mySql->do("set names utf8mb4");

示例数据值为“שלום עולם”(在希伯来语中意为“Hello World”)。

1) 调用时foo($request->{VALUE});(其中 VALUE 为 DB 中的列数据),结果为 16(其中每个希伯来字符计为两个字符,忽略它们之间的一个空格)。在这种情况下,Dumper 是:

$VAR1 = "\327\251\327\234\327\225\327\235 \327\242\327\225\327\234\327\235";

2)打电话时foo("שלום עולם");

  • 声明时use utf8;,结果为 8(因为此字符串中有 8 个可见字符)。在这种情况下,Dumper (Useqq=1) 是:

    $VAR1 = "\x{5e9}\x{5dc}\x{5d5}\x{5dd}\x{5e2}\x{5d5}\x{5dc}\x{5dd}";

  • 不声明 `use utf8;' 时,结果为 16,类似于从 DB 发送值的情况:

    $VAR1 = "\327\251\327\234\327\225\327\235\327\242\327\225\327\234\327\235";

看起来我需要在开始使用它之前找到一种将接收到的值转换为 UTF8 的方法。

4

1 回答 1

1

MySQL 调用utf8的是 UTF-8 的有限子集,它每个字符只允许三个字节,并且覆盖代码点高达 0xFFFF。Evenutf8mb4不涵盖完整的 UTF-8 范围,它支持长达 6 个字节的编码字符

utf8结果是来自 a或列的任何数据utf8mb4都只是 Perl 中的 UTF-8 字符串,两种数据库编码之间应该没有区别

我猜你没有为你的DBI句柄启用 UTF-8,所以一切都被视为一个字节序列。mysql_enable_utf8您应该在拨打电话时启用connect,然后看起来应该像

my $dbh = DBI->connect($dsn, $user, $password, { mysql_enable_utf8 => 1 });

通过附加数据,我可以看到您从数据库中检索的字符串确实是 שלום עולם UTF-8 编码的

但是,如果我对其进行解码,那么首先我会从您的foo子程序和我自己的子程序中得到 8 个非空格字符,而不是 9;而且你应该从数据库中取回字符,而不是字节

我怀疑您可能首先将编码字符串写入数据库。这是一个简短的程序,它创建一个 MySQL 表,向其中写入两条记录(一个字符串和一个编码字符串)并检索它写入的内容。您会看到,唯一不同的是 的设置mysql_enable_utf8。无论原始字符串是否被编码,无论是否编码,行为都是相同的SET NAMES utf8mb4

进一步的实验表明,要么 要么 mysql_enable_utf8 SET NAMES utf8mb4让DBI正确写入数据,但后者对读取没有影响

我建议您的解决方案应该是仅mysql_enable_utf8在阅读或写作时使用

您也应该use utf8只在所有程序的顶部。错过这一点意味着您不能在代码中使用任何非 ASCII 字符

use utf8;
use strict;
use warnings;

use DBI;
use open qw/ :std :encoding(utf-8) /;

STDOUT->autoflush;

my $VAR1 = "\327\251\327\234\327\225\327\235 \327\242\327\225\327\234\327\235";

my $dbh = DBI->connect(
    qw/ DBI:mysql:database=temp admin admin /, {
        RaiseError => 1,
        PrintError => 0,
        mysql_enable_utf8 => 1,
    }
) or die DBI::errstr;

$dbh->do('SET NAMES utf8mb4');

$dbh->do('DROP TABLE IF EXISTS temp');
$dbh->do('CREATE TABLE temp (value VARCHAR(64) CHARACTER SET utf8mb4)');

my $insert = $dbh->prepare('INSERT INTO temp (value) VALUES (?)');
$insert->execute('שלום עולם');
$insert->execute($VAR1);

my $values = $dbh->selectcol_arrayref('SELECT value FROM temp');
printf "string: %s  foo: %d\n", $_, foo($_) for @$values;

sub foo2 {
  $_[0] =~ tr/ //c;
}

sub foo {
  length join '', split / /, $_[0];
}

输出与mysql_enable_utf8 => 1

string: שלום עולם  foo: 8
string: שלום עולם  foo: 8

输出与mysql_enable_utf8 => 0

string: ש××× ×¢×××  foo: 16
string: ש××× ×¢×××  foo: 16
于 2015-05-17T20:11:34.120 回答