2

我正在尝试帮助一个朋友完成一个应该是 1H 并且现在已经 3 天的项目。不用说我感到非常沮丧和愤怒 ;-) ooooouuuu... 我呼吸。

所以用 C++ 编写的程序只是读取一堆文件并处理它们。问题是我的程序读取使用 UTF-16 编码的文件(因为这些文件包含用不同语言编写的单词)并且对 ifstream 的简单使用似乎不起作用(它读取并输出垃圾)。我花了一段时间才意识到这是因为文件是 UTF-16 格式的。

现在,我整个下午都在网上寻找有关阅读 UTF16 文件并将 UTF16 行的内容转换为字符的信息!我就是看不出来!这是一场噩梦。我尝试了解<locale><codecvt>、 wstring 等(我专注于图形应用程序,而不是桌面应用程序)。我就是不明白。

这就是我所做的(但不起作用):

std::wifstream file2(fileFullPath);
std::locale loc (std::locale(), new std::codecvt_utf16<char32_t>);
std::cout.imbue(loc);
while (!file2.eof()) {
    std::wstring line;
    std::getline(file2, line);
    std::wcout << line << std::endl;
}

这是我能想到的最大值,但它甚至不起作用。它并没有做任何更好的事情。但问题是我一开始就不明白我在做什么。

请帮忙!我什至可以阅读 G*** D*** 文本文件,这真是太疯狂了。

最重要的是,我的朋友使用 Ubuntu(我使用 clang++),并且这段代码需要 -stdlib=libc++,这似乎不被他的 gcc 支持(即使他使用了一个非常高级的 gcc 版本,即 4.6.3我相信)。所以我什至不确定使用 codecvt 和 locale 是一个好主意(如“可能”)。会有更好的(另一种)选择吗?

如果我仅从命令行(使用 linux 命令)将所有文件转换为 utf-8,我是否会丢失信息?

非常感谢,如果您在这方面帮助我,我将永远感激您。

4

3 回答 3

3

如果我仅从命令行(使用 linux 命令)将所有文件转换为 utf-8,我是否会丢失信息?

不,所有 UTF-16 数据都可以无损转换为 UTF-8。这可能是最好的做法。


当引入宽字符时,它们旨在成为专门用于程序内部的文本表示,并且永远不会作为宽字符写入磁盘。宽流通过将您写出的宽字符转换为输出文件中的窄字符,并在读取时将文件中的窄字符转换为内存中的宽字符来反映这一点。

std::wofstream wout("output.txt");
wout << L"Hello"; // the output file will just be ASCII (assuming the platform uses ASCII).

std::wifstream win("ascii.txt");
std::wstring s;
wout >> s; // the ascii in the file is converted to wide characters.

当然,实际编码取决于codecvt流的灌输语言环境中的方面,但流所做的是在写入时使用codecvt从转换wchar_tchar使用该方面,并在读取时转换char为使用。wchar_t


然而,自从有些人开始用 UTF-16 写出文件后,其他人就不得不处理它了。他们使用 C++ 流的方式是创建codecvt将被char视为持有一半 UTF-16 代码单元的方面,这就是这样codecvt_utf16做的。

因此,通过这种解释,您的代码存在以下问题:

std::wifstream file2(fileFullPath); // UTF-16 has to be read in binary mode
std::locale loc (std::locale(), new std::codecvt_utf16<char32_t>); // do you really want char32_t data? or do you want wchar_t?
std::cout.imbue(loc); // You're not even using cout, so why are you imbuing it?
// You need to imbue file2 here, not cout.
while (!file2.eof()) { // Aside from your UTF-16 question, this isn't the usual way to write a getline loop, and it doesn't behave quite correctly
    std::wstring line;
    std::getline(file2, line);
    std::wcout << line << std::endl; // wcout is not imbued with a locale that will correctly display the original UTF-16 data
}

这是重写上述内容的一种方法:

// when reading UTF-16 you must use binary mode
std::wifstream file2(fileFullPath, std::ios::binary);

// ensure that wchar_t is large enough for UCS-4/UTF-32 (It is on Linux)
static_assert(WCHAR_MAX >= 0x10FFFF, "wchar_t not large enough");

// imbue file2 so that it will convert a UTF-16 file into wchar_t data.
// If the UTF-16 files are generated on Windows then you probably want to
// consume the BOM Windows uses
std::locale loc(
    std::locale(),
    new std::codecvt_utf16<wchar_t, 0x10FFFF, std::consume_header>);
file2.imbue(loc);

// imbue wcout so that wchar_t data printed will be converted to the system's
// encoding (which is probably UTF-8).
std::wcout.imbue(std::locale(""));

// Note that the above is doing something that one should not do, strictly
// speaking. The wchar_t data is in the wide encoding used by `codecvt_utf16`,
// UCS-4/UTF-32. This is not necessarily compatible with the wchar_t encoding
// used in other locales such as std::locale(""). Fortunately locales that use
// UTF-8 as the narrow encoding will generally also use UTF-32 as the wide
// encoding, coincidentally making this code work

std::wstring line;
while (std::getline(file2, line)) {
  std::wcout << line << std::endl;
}
于 2013-09-15T22:44:09.203 回答
0

我改编、纠正和测试了 Mats Petersson 令人印象深刻的解决方案。

int utf16_to_utf32(std::vector<int> &coded)
{
    int t = coded[0];
    if (t & 0xFC00 != 0xD800)
    {
    return t;
    }
    int charcode = (coded[1] & 0x3FF); // | ((t & 0x3FF) << 10);
    charcode += 0x10000;
    return charcode;
}



#ifdef __cplusplus    // If used by C++ code,
extern "C" {          // we need to export the C interface
#endif
void convert_utf16_to_utf32(UTF16 *input,
                            size_t input_size,
                            UTF32 *output)
{
     const UTF16 * const end = input + 1 * input_size;
     while (input < end){
       const UTF16 uc = *input++;
       std::vector<int> vec; // endianess
       vec.push_back(U16_LEAD(uc) & oxFF);
       printf("LEAD + %.4x\n",U16_LEAD(uc) & 0x00FF);
       vec.push_back(U16_TRAIL(uc) & oxFF);
       printf("TRAIL + %.4x\n",U16_TRAIL(uc) & 0x00FF);
       *output++ = utf16_to_utf32(vec);
     }
}
#ifdef __cplusplus
}
#endif
于 2016-04-01T03:35:56.777 回答
-1

UTF-8 能够表示所有有效的 Unicode 字符(代码点),优于 UTF-16(覆盖前 110 万个代码点)。[尽管正如评论所解释的,没有超过 110 万个值的有效 Unicode 代码点,因此 UTF-16 对所有当前可用的代码点都是“安全的”——并且可能在很长一段时间内,除非我们确实有额外的地球访客,他们的书写语言非常复杂……]

它通过在必要时使用多个字节/字来存储单个代码点(我们称之为字符)来做到这一点。在 UTF-8 中,这由设置的最高位标记 - 在“多字节”字符的第一个字节中,设置了前两位,在接下来的字节中,设置了最高位,下一个从顶部为零。

要将任意代码点转换为 UTF-8,您可以使用我之前回答中的代码。(是的,该问题与您所要求的相反,但我的答案中的代码涵盖了两个转换方向)

从 UTF16 转换为“整数”将是一个类似的方法,除了输入的长度。如果你幸运的话,你甚至可以不这样做......

UTF16 使用范围 D800-DBFF 作为第一部分,它保存 10 位数据,然后下一项是 DC00-DFFF,保存以下 10 位数据。

要遵循的 16 位代码...

16 位到 32 位转换的代码(我只测试了一点,但它似乎工作正常):

std::vector<int> utf32_to_utf16(int charcode)
{
    std::vector<int> r;
    if (charcode < 0x10000)
    {
    if (charcode & 0xFC00 == 0xD800)
    {
        std::cerr << "Error bad character code" << std::endl;
        exit(1);
    }
    r.push_back(charcode);
    return r;
    }
    charcode -= 0x10000;
    if (charcode > 0xFFFFF)
    {
    std::cerr << "Error bad character code" << std::endl;
    exit(1);
    }
    int coded = 0xD800 | ((charcode >> 10) & 0x3FF);
    r.push_back(coded);
    coded = 0xDC00 | (charcode & 0x3FF);
    r.push_back(coded);
    return r;
}


int utf16_to_utf32(std::vector<int> &coded)
{
    int t = coded[0];
    if (t & 0xFC00 != 0xD800)
    {
    return t;
    }
    int charcode = (coded[1] & 0x3FF) | ((t & 0x3FF) << 10);
    charcode += 0x10000;
    return charcode;
}
于 2013-09-15T15:50:42.297 回答