2

我有带有BOM的UTF-16LE编码的CSV文件。它们可能很大,所以我不太喜欢在内存中读取整个文件的想法。我该如何阅读它们?

4

2 回答 2

4

逐行阅读并使用mb_convert_encoding()

$decoded_line = mb_convert_encoding ($line, "UTF-8", "UTF-16LE");

您可以选择任何目标编码,但我假设您想使用当今最常见的 UTF-8 字符串。

此功能需要启用mbstring扩展。

然后,您可以将解码的行传递给str_getcsv函数,该函数返回一个表示当前行的数组。

于 2014-12-18T16:28:49.557 回答
1

这是我想出的:

class readutf16le_filter extends php_user_filter {
    function filter($in, $out, &$consumed, $closing) {
        while ($bucket = stream_bucket_make_writeable($in)) {
            # printf("filter: %s\n", to_hex($bucket->data));
            $bucket->data = iconv('UTF-16LE', 'UTF-8',
                strlen($bucket->data) && substr($bucket->data, 0, 2) == "\xff\xfe"
                    ? substr($bucket->data, 2)
                    : $bucket->data);
            $consumed += $bucket->datalen;
            stream_bucket_append($out, $bucket);
        }
        return PSFS_PASS_ON;
    }
}

stream_filter_register('readutf16le', 'readutf16le_filter');

$fh = fopen('1.txt', 'r');
stream_filter_append($fh, 'readutf16le');

$s = fgets($fh);
printf("%s\n", to_hex($s));

$s = fgets($fh);
printf("%s\n", to_hex($s));

$s = fgets($fh);
var_dump($s);

文件1.txt

a
b

输出:

filter: ff fe 61 00 0d 00 0a 00 62 00 0d 00 0a 00
61 0d 0a
62 0d 0a
bool(false)

我仍然不喜欢的是我没有看到任何方法来检测过滤器中文件的开头。但是,它不太可能引起问题。维基百科

BOM 的使用是可选的,如果使用,应该出现在文本流的开头。

如果 BOM 字符出现在数据流的中间,Unicode 表示它应该被解释为“零宽度不间断空格”(禁止字形之间的换行)。在 Unicode 3.2 中,这种用法已被弃用,取而代之的是“Word Joiner”字符 U+2060。[1] 这允许 U+FEFF 仅用作 BOM。

对于 IANA 注册的字符集 UTF-16BE 和 UTF-16LE,不应使用字节顺序标记,因为这些字符集的名称已经确定了字节顺序。如果在这样的文本流中的任何地方遇到,U+FEFF 将被解释为“零宽度不间断空格”。

可能这可以通过流包装器来完成。fread($fh, 2);在将过滤器附加到流之前可能可以这样做。

另一个可能的问题是strlen($bucket->data)理论上可能是一个奇数。据我所知,PHP 使用缓冲并且不太可能遇到大小为奇数的缓冲区(通常它们是 2 的幂)。但为了适应这种情况:

...
while ($bucket = stream_bucket_make_writeable($in)) {
    $data = strlen($bucket->data) ?
        substr($bucket->data, 0, floor(strlen($bucket->data) / 2) * 2) : '';
    $bucket->data = iconv('UTF-16LE', 'UTF-8',
        strlen($data) && substr($data, 0, 2) == "\xff\xfe"
            ? substr($data, 2)
            : $data);
    $consumed += strlen($data);
    stream_bucket_append($out, $bucket);
    ...

我不知道如何重现这个。

于 2014-12-18T16:47:23.910 回答