使用 perl 从 MySql 获取 utf8mb4 字符串的长度
getting length of utf8mb4 string with perl from MySql
我写了一个小的 perl 函数,它接受一个字符串并检查它的长度,而不是 spaces。基本代码如下所示:
sub foo
{
use utf8;
my @wordsArray = split(/ /, $_[0]));
my $result = length(join('', @wordsArray));
return $result;
}
当我为这个函数提供一个包含特殊字符(例如希伯来字母)的字符串时,它似乎工作得很好。
当我使用来自 MySql 列且字符集为 utf8mb4 的值时,问题就开始了:在这种情况下,正在计算的值高于上一个示例中的值。
我能猜到为什么会出现这样的行为:特殊字符在table中以4字节的方式写入,因此每个字母在utf8编码中计算为两个字符。
有谁知道如何解决上述问题,这样我就可以从 DB table 定义为 utf8mb4 的字符串中获取正确数量的字符?
编辑:
关于上述代码的更多信息:
用作函数参数的 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(其中每个希伯来字符被算作两个字符,它们之间的一个space是无视)。在这种情况下,转储器是:
$VAR1 = "71747575 72757475";
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 = "71747575 72757475";
看来我需要在开始使用它之前找到一种将接收到的值转换为 UTF8 的方法。
MySQL 调用 utf8
的是 UTF-8 的有限子集,每个字符只允许三个字节,并且涵盖最多 0xFFFF 的代码点。即使 utf8mb4
也没有涵盖完整的 UTF-8 范围,它支持最长 6 个字节的编码字符
结果是来自 utf8
或 utf8mb4
列的任何数据在 Perl 中只是一个 UTF-8 字符串,两种数据库编码之间应该没有区别
我猜你还没有为你的 DBI
句柄启用 UTF-8,所以一切都被视为只是一个字节序列。您应该在进行 connect
调用时启用 mysql_enable_utf8
,然后看起来应该类似于
my $dbh = DBI->connect($dsn, $user, $password, { mysql_enable_utf8 => 1 });
根据附加数据,我可以看出您从数据库中检索的字符串确实是 שלום עולם UTF-8 编码
但是,如果我解码它,那么首先我从你的 foo
子程序和我自己的子程序中得到一个非 space 字符数 8,而不是 9;而且你应该从数据库中返回 characters,而不是 bytes
我怀疑您可能首先将编码字符串写入数据库。这是一个创建 MySQL table 的短程序,向其中写入两条记录(一个字符串和一个编码字符串)并检索它写入的内容。你会看到,唯一有所不同的是 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 = "71747575 72757475";
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
我写了一个小的 perl 函数,它接受一个字符串并检查它的长度,而不是 spaces。基本代码如下所示:
sub foo
{
use utf8;
my @wordsArray = split(/ /, $_[0]));
my $result = length(join('', @wordsArray));
return $result;
}
当我为这个函数提供一个包含特殊字符(例如希伯来字母)的字符串时,它似乎工作得很好。 当我使用来自 MySql 列且字符集为 utf8mb4 的值时,问题就开始了:在这种情况下,正在计算的值高于上一个示例中的值。
我能猜到为什么会出现这样的行为:特殊字符在table中以4字节的方式写入,因此每个字母在utf8编码中计算为两个字符。
有谁知道如何解决上述问题,这样我就可以从 DB table 定义为 utf8mb4 的字符串中获取正确数量的字符?
编辑:
关于上述代码的更多信息:
用作函数参数的 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(其中每个希伯来字符被算作两个字符,它们之间的一个space是无视)。在这种情况下,转储器是:
$VAR1 = "71747575 72757475";
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 = "71747575 72757475";
看来我需要在开始使用它之前找到一种将接收到的值转换为 UTF8 的方法。
MySQL 调用 utf8
的是 UTF-8 的有限子集,每个字符只允许三个字节,并且涵盖最多 0xFFFF 的代码点。即使 utf8mb4
也没有涵盖完整的 UTF-8 范围,它支持最长 6 个字节的编码字符
结果是来自 utf8
或 utf8mb4
列的任何数据在 Perl 中只是一个 UTF-8 字符串,两种数据库编码之间应该没有区别
我猜你还没有为你的 DBI
句柄启用 UTF-8,所以一切都被视为只是一个字节序列。您应该在进行 connect
调用时启用 mysql_enable_utf8
,然后看起来应该类似于
my $dbh = DBI->connect($dsn, $user, $password, { mysql_enable_utf8 => 1 });
根据附加数据,我可以看出您从数据库中检索的字符串确实是 שלום עולם UTF-8 编码
但是,如果我解码它,那么首先我从你的 foo
子程序和我自己的子程序中得到一个非 space 字符数 8,而不是 9;而且你应该从数据库中返回 characters,而不是 bytes
我怀疑您可能首先将编码字符串写入数据库。这是一个创建 MySQL table 的短程序,向其中写入两条记录(一个字符串和一个编码字符串)并检索它写入的内容。你会看到,唯一有所不同的是 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 = "71747575 72757475";
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