【问题标题】:How can I read from files in UTF-16LE encoding in PHP?如何在 php 中读取 UTF-16LE 编码的文件?
【发布时间】:2015-02-17 12:10:13
【问题描述】:

我有csv 文件以utf-16le 编码和BOM。它们可能很大,所以我不太喜欢在内存中读取整个文件的想法。问题显然是,我该如何阅读它们?

【问题讨论】:

    标签: php csv utf-16 utf-16le


    【解决方案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);
        ...
    

    不过我不知道如何重现。

    【讨论】:

      【解决方案2】:

      逐行阅读并使用mb_convert_encoding():

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

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

      此功能需要启用mbstring扩展。

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

      【讨论】:

      • 又一只熊猫!!!问题是逐行读取,fgets() 不适用于 UTF16
      • 我想使用 stream_get_line 使用 UTF-16LE 换行符 '0x00 0x0A' 可能会起作用。
      • 如果没有stream_get_line$length 参数,我相信您的解决方案将是完美的。 my 1.txt file的测试代码:$fh = fopen(PRJ_ROOT . '1.txt', 'r'); fread($fh, 2); $s = stream_get_line($fh, 1000, "\x0d\x00\x0a\x00"); printf("%s\n", to_hex($s)); $s = stream_get_line($fh, 1000, "\x0d\x00\x0a\x00"); printf("%s\n", to_hex($s)); $s = stream_get_line($fh, 1000, "\x0d\x00\x0a\x00"); var_dump($s);
      • 那么 $length 参数可以看作是一种保护措施,因此您最终不会使用比您想要的更多的内存。文档没有说明 -1 或 0 是否具有重要值,考虑到还有其他 PHP 函数通过使用这些值来允许“无限”长度,这很奇怪。
      猜你喜欢
      • 2022-03-06
      • 2018-07-18
      • 1970-01-01
      • 2019-05-06
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-04-12
      • 2020-01-12
      相关资源
      最近更新 更多