在没有外部库的情况下正确读取utf-16文本文件到字符串中?

nem*_*nem 6 c++ unicode winapi utf-16

我从一开始就一直在使用StackOverflow,并且有时候很想发布问题,但我总是要么自己想出来,要么最终找到答案......直到现在.这感觉就像它应该是相当简单的,但我一直在围绕互联网没有成功小时徘徊,所以我在这里转:

我有一个非常标准的utf-16文本文件,混合了英文和中文字符.我希望这些字符以字符串结尾(技术上是一个wstring).我已经看到很多相关的问题得到解答(这里和其他地方),但是他们要么想要解决在不知道编码的情况下读取任意文件这么难的问题,要么在编码之间进行转换,或者只是对"Unicode"感到困惑. "是一系列编码.我知道我正在尝试阅读的文本文件的来源,它将始终是UTF16,它有一个BOM和一切,它可以保持这种方式.

我一直在使用这里描述的解决方案,它适用于所有英文文本文件,但在遇到某些字符后,它停止读取文件.我发现的唯一其他建议是使用ICU,这可能会起作用,但我真的不想在应用程序中包含一个完整的大型库,只需在一个地方读取一个文本文件.我不关心系统独立性 - 我只需要它在Windows中编译和工作.当然,一个不依赖于这个事实的解决方案会更漂亮,但是对于使用stl同时依赖于关于Windows体系结构的假设,甚至涉及win32函数或ATL的解决方案的解决方案,我也会感到高兴.我只是不想要包括像ICU这样的另一个大型第三方库.除非我想自己重新实现,否则我还是完全没有运气了吗?

编辑:我坚持使用VS2008来完成这个特定的项目,所以C++ 11代码很遗憾无济于事.

编辑2:我意识到我以前借过的代码并没有像我认为的那样在非英文字符上失败.相反,它在我的测试文档中的特定字符失败,其中包括':'(FULLWIDTH COLON,U + FF1A)和')'(FULLWIDTH RIGHT PARENTHESIS,U + FF09).bames53的发布解决方案也大部分都有效,但被这些相同的角色难住了?

编辑3(和答案!):我一直在使用的原始代码-did-主要是工作 - 正如bames53帮助我发现的那样,ifstream只需要以二进制模式打开才能工作.

Cub*_*bbi 11

C++ 11解决方案(据您所知,自2010年以来,在您的平台上由Visual Studio支持)将是:

#include <fstream>
#include <iostream>
#include <locale>
#include <codecvt>
int main()
{
    // open as a byte stream
    std::wifstream fin("text.txt", std::ios::binary);
    // apply BOM-sensitive UTF-16 facet
    fin.imbue(std::locale(fin.getloc(),
       new std::codecvt_utf16<wchar_t, 0x10ffff, std::consume_header>));
    // read     
    for(wchar_t c; fin.get(c); )
            std::cout << std::showbase << std::hex << c << '\n';
}
Run Code Online (Sandbox Code Playgroud)

  • 在具有像Windows一样的两字节wchar_t的平台上,这将从UTF-16转换为UCS-2.具体来说,VS2010实现会截断BMP之外的字符. (5认同)
  • 请注意,在 macOS 上,我必须为编码为包含相应 BOM 的 UTF-16 LE 的文件显式设置 `std::little_endian` 而不是 `std::consume_header`。否则我会收到大端输出。 (2认同)

Mar*_*som 8

打开UTF-16文件时,必须以二进制模式打开它.这是因为在文本模式下,某些字符被特殊解释 - 具体而言,0x0d被完全过滤掉,0x1a标记文件的末尾.有一些UTF-16字符将其中一个字节作为字符代码的一半,并将弄乱文件的读取.这不是一个错误,它是故意行为,是使用单独的文本和二进制模式的唯一原因.

由于0x1a被认为是文件的结尾,请参阅Raymond Chen的这篇博客文章,跟踪Ctrl-Z的历史记录.这基本上是向后兼容性的.


bam*_*s53 5

编辑:

因此,问题似乎在于 Windows 将某些魔术字节序列视为文本模式下的文件结尾。这是通过使用二进制模式读取文件,std::ifstream fin("filename", std::ios::binary);然后将数据复制到 wstring 中来解决的,就像您已经做的那样。



最简单的、不可移植的解决方案是将文件数据复制到 wchar_t 数组中。这依赖于 Windows 上的 wchar_t 为 2 个字节并使用 UTF-16 作为其编码这一事实。


以完全可移植的方式将 UTF-16 转换为特定于语言环境的 wchar_t 编码会遇到一些困难。

以下是标准 C++ 库中提供的 unicode 转换功能(尽管 VS 10 和 11 仅实现第 3、4 和 5 项)

  1. codecvt<char32_t,char,mbstate_t>
  2. codecvt<char16_t,char,mbstate_t>
  3. 编码cvt_utf8
  4. 编码cvt_utf16
  5. codecvt_utf8_utf16
  6. c32rtomb/mbrtoc32
  7. c16rtomb/mbrtoc16

以及每个人所做的事情

  1. 始终在 UTF-8 和 UTF-32 之间转换的 codecvt 方面
  2. 在 UTF-8 和 UTF-16 之间转换
  3. 根据目标元素的大小在 UTF-8 和 UCS-2 或 UCS-4 之间进行转换(BMP 之外的字符可能会被截断)
  4. 使用 UTF-16 编码方案和 UCS-2 或 UCS-4 在字符序列之间进行转换
  5. 在 UTF-8 和 UTF-16 之间转换
  6. 如果定义了宏,__STDC_UTF_32__这些函数将在当前语言环境的 char 编码和 UTF-32 之间进行转换
  7. 如果定义了宏,__STDC_UTF_16__这些函数将在当前语言环境的 char 编码和 UTF-16 之间进行转换

如果__STDC_ISO_10646__定义了,那么直接使用转换codecvt_utf16<wchar_t>应该没问题,因为该宏指示所有语言环境中的 wchar_t 值对应于 Unicode 宪章的短名称(因此意味着 wchar_t 足够大以容纳任何此类值)。

不幸的是,没有定义任何直接从 UTF-16 到 wchar_t 的定义。可以使用 UTF-16 -> UCS-4 -> mb (if __STDC_UTF_32__) -> wc,但是您会丢失任何在语言环境的多字节编码中无法表示的内容。当然,无论如何,从 UTF-16 转换为 wchar_t 都会丢失任何无法在语言环境的 wchar_t 编码中表示的内容。


因此,它可能不值得移植,相反,您可以将数据读入 wchar_t 数组,或者使用其他一些 Windows 特定工具,例如文件上的 _O_U16TEXT 模式。

这应该在任何地方构建和运行,但需要做出一系列实际工作的假设:

#include <fstream>
#include <sstream>
#include <iostream>

int main ()
{
    std::stringstream ss;
    std::ifstream fin("filename");
    ss << fin.rdbuf(); // dump file contents into a stringstream
    std::string const &s = ss.str();
    if (s.size()%sizeof(wchar_t) != 0)
    {
        std::cerr << "file not the right size\n"; // must be even, two bytes per code unit
        return 1;
    }
    std::wstring ws;
    ws.resize(s.size()/sizeof(wchar_t));
    std::memcpy(&ws[0],s.c_str(),s.size()); // copy data into wstring
}
Run Code Online (Sandbox Code Playgroud)

您可能至少应该添加代码来处理字节顺序和“BOM”。此外,Windows 换行符不会自动转换,因此您需要手动进行转换。

  • 哦,我该死的上帝。我花了一天多的时间想弄清楚,结果就这么明显吗?我尝试了其他涉及以二进制模式打开文件的方法,但我从未尝试过仅以二进制模式打开文件的原始解决方案?你赢了这么多。您应该将其编辑到您的解决方案中,以防其他人稍后偶然发现这个问题(我无法想象我是唯一遇到此问题的人):)。 (2认同)