我的csv
编码文件utf-16le
BOM
。它们可能很大,所以我不太喜欢在内存中读取整个文件的想法。问题显然是,如何阅读它们?
答案 0 :(得分:2)
逐行阅读并使用mb_convert_encoding():
$decoded_line = mb_convert_encoding ($line, "UTF-8", "UTF-16LE");
您可以选择任何目标编码,但我假设您要使用utf-8字符串,这是现在最常见的字符串。
此功能需要启用mbstring扩展名。
然后,您可以将解码后的行传递给str_getcsv函数,该函数返回表示当前行的数组。
答案 1 :(得分: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)
我仍然不喜欢的是,我没有看到任何方法来检测过滤器中文件的开头。但是,它不太可能引起问题。维基百科says:
BOM使用是可选的,如果使用,应该出现在文本流的开头。
如果BOM字符出现在数据流的中间,Unicode表示它应该被解释为"零宽度不间断空格" (禁止单词字形之间的换行)。在Unicode 3.2中,不推荐使用此用法以支持" Word Joiner"字符,U + 2060。[1]这允许U + FEFF仅用作BOM。
对于IANA注册的字符集UTF-16BE和UTF-16LE,不应使用字节顺序标记,因为这些字符集的名称已经确定了字节顺序。如果遇到这样的文本流中的任何地方,U + FEFF将被解释为"零宽度不间断空格"。
可能这可以通过流包装器来完成。 UPD 在将过滤器附加到流之前,可能会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);
...
我不知道如何重现这一点。