0

文字列を取り、スペースなしでその長さをチェックする小さな perl 関数を書きました。基本的なコードは次のようになります。

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

この関数に特殊文字 (ヘブライ文字など) を含む文字列を指定すると、うまく機能するようです。この問題は、文字セットが utf8mb4 の MySql 列からの値を使用するときに発生します。このような場合、計算される値は前の例の値より高くなります。

このような動作が発生する理由は推測できます。特殊文字はテーブルに 4 バイトで記述されているため、utf8 エンコーディングでは各文字が 2 文字として計算されます。

utf8mb4として定義されたDBテーブルからの文字列から正しい文字数を取得するために、上記を解決する方法を知っている人はいますか?

編集:

上記のコードに関するその他の情報:

関数の引数として使用される DB 列は、utf8mb4_unicode_ci の照合で VARCHAR(1000) 型です。次のように構成された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 になります (各ヘブライ文字は 2 文字としてカウントされ、その間の 1 つのスペースは無視されます)。この場合のダンパーは次のとおりです。

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

2) 呼び出す場合foo("שלום עולם");:

  • を宣言するuse utf8;と、結果は 8 になります (この文字列には 8 つの可視文字があるため)。この場合のダンパー (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は、文字あたり 3 バイトのみを許可し、0xFFFF までのコード ポイントをカバーする UTF-8 の限定されたサブセットです。最大 6 バイトの長さのエンコードされた文字をサポートする完全な UTF-8 範囲をカバーしてutf8mb4いません。

結果として、autf8またはutf8mb4列のデータはすべて Perl の単なる UTF-8 文字列であり、2 つのデータベース エンコーディングに違いはないはずです。

DBIハンドルに対して UTF-8 を有効にしていないため、すべてが単なる一連のバイトとして扱われていると思います。mysql_enable_utf8呼び出しを行うときに を有効にする必要があります。これは次のconnectようになります。

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

追加データを使用すると、データベースから取得している文字列が実際に UTF-8 でエンコードされていることがわかります。

ただし、それをデコードすると、まず最初に、あなたのfooサブルーチンと私のサブルーチンの両方から、9 ではなく 8 の非スペース文字カウントが得られます。また、バイトではなく、データベースから文字を取得する必要があります

最初にエンコードされた文字列をデータベースに書き込んだのではないかと思います。以下は、MySQL テーブルを作成し、そこに 2 つのレコード (1 つの文字列と 1 つのエンコードされた文字列) を書き込み、書き込まれた内容を取得する短いプログラムです。違いを生むのは の設定だけです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 に答える