将Unicode UTF-8文件读入wstring

48

如何在Windows平台上将Unicode (UTF-8) 文件读入到 wstring 中?


你所说的“Unicode”是指UTF-8还是UTF-16?你使用的平台是什么? - dan04
3
阅读本文:使用C++流读取UTF-8 - Nawaz
5
另一篇不错的文章:在C++中以可移植方式使用UTF-8编码 - Nawaz
4
在Windows平台上,应使用std::string来处理UTF-8编码,使用std::wstring来处理UTF-16编码。 - anno
7个回答

44

在支持C++11的情况下,您可以使用std::codecvt_utf8 facet该特性封装了UTF-8编码的字节串和UCS2或UCS4字符串之间的转换,并可用于读写UTF-8文件,包括文本和二进制文件。

要使用facet,通常需要创建locale object 将特定的本地环境定义为一组特性的文化特定信息的封装体。一旦您有了locales对象,就可以通过将其与流缓冲区相结合来imbue它:

#include <sstream>
#include <fstream>
#include <codecvt>

std::wstring readFile(const char* filename)
{
    std::wifstream wif(filename);
    wif.imbue(std::locale(std::locale::empty(), new std::codecvt_utf8<wchar_t>));
    std::wstringstream wss;
    wss << wif.rdbuf();
    return wss.str();
}
可以像这样使用:
std::wstring wstr = readFile("a.txt");

或者你可以在使用字符串流之前设置全局C++本地化环境,这将导致所有未来对std::locale默认构造函数的调用返回全局C++本地化环境的副本(然后你不需要显式地把它注入到流缓冲区中):

std::locale::global(std::locale(std::locale::empty(), new std::codecvt_utf8<wchar_t>));

2
那个 new codecvt_utf8 需要相应的 delete 吗? - Dmitri Nesteruk
1
不需要显式删除codecvt_utf8。当codecvt_utf8的引用计数变为零时(参见http://en.cppreference.com/w/cpp/locale/locale/%7Elocale),std::locale的析构函数会自动执行此操作。 - MrTux
2
对于使用这个答案的人,std::locale::empty() 在clang上存在问题:error: no member named 'empty' in 'std::__1::locale'. - Felipe Valdes
2
遗憾的是,C++20中codecvt的所有有用部分都已被弃用。 - Bob Kline

14

根据@Hans Passant的评论,最简单的方法是使用_wfopen_s。使用模式rt, ccs=UTF-8打开文件。

这里还有另一种完全使用 C++ 的解决方案,至少可在 VC++ 2010 中使用:

#include <locale>
#include <codecvt>
#include <string>
#include <fstream>
#include <cstdlib>

int main() {
    const std::locale empty_locale = std::locale::empty();
    typedef std::codecvt_utf8<wchar_t> converter_type;
    const converter_type* converter = new converter_type;
    const std::locale utf8_locale = std::locale(empty_locale, converter);
    std::wifstream stream(L"test.txt");
    stream.imbue(utf8_locale);
    std::wstring line;
    std::getline(stream, line);
    std::system("pause");
}

除了locale::empty()(这里locale::global()也可能适用)和basic_ifstream构造函数的wchar_t*重载之外,这应该是相当符合标准的(当然,“标准”指的是C++0x)。


5
为什么不删除转换器? - Mikhail
1
通常使用第二个参数f直接从new-expression中获取来调用Overload 7:区域设置负责从其自身的析构函数中调用匹配的delete。 - sven
这个很有效。很好奇,因为我找不到太多关于它的信息,并且我的程序在没有它的情况下也可以正常工作,那么stream.imbue到底是做什么的?它似乎设置了某种默认类型,但这是否必要?另外,在第一行备注中,将你的getline放入while(getline(stream, line))循环中,以查看超过第一行。 - adprocas

12

这里是一个仅适用于Windows平台的特定功能:

size_t GetSizeOfFile(const std::wstring& path)
{
    struct _stat fileinfo;
    _wstat(path.c_str(), &fileinfo);
    return fileinfo.st_size;
}

std::wstring LoadUtf8FileToString(const std::wstring& filename)
{
    std::wstring buffer;            // stores file contents
    FILE* f = _wfopen(filename.c_str(), L"rtS, ccs=UTF-8");

    // Failed to open file
    if (f == NULL)
    {
        // ...handle some error...
        return buffer;
    }

    size_t filesize = GetSizeOfFile(filename);

    // Read entire file contents in to memory
    if (filesize > 0)
    {
        buffer.resize(filesize);
        size_t wchars_read = fread(&(buffer.front()), sizeof(wchar_t), filesize, f);
        buffer.resize(wchars_read);
        buffer.shrink_to_fit();
    }

    fclose(f);

    return buffer;
}

使用方式如下:

std::wstring mytext = LoadUtf8FileToString(L"C:\\MyUtf8File.txt");

请注意整个文件将被加载到内存中,因此您可能不希望将其用于非常大的文件。


3
可以这样做:_wfopen(filename.c_str(), L"rt, ccs=UTF-8"),这样转换就自动完成了。 - Hans Passant
实际上,我们进行了回滚,_wfopen 的文档称它会自动转换为宽字符,而该代码没有考虑到这一点。 - AshleysBrain
只有文件名。引用:“仅使用_wfopen对文件流中使用的编码字符集没有影响。” - Hans Passant
你确定吗?根据我的理解,指定模式中的t以及ccs=UTF-8会导致字符在读取和写入流时被转换。 - AshleysBrain
@Ashley:是的,引用是指在没有使用ccs=模式说明符的情况下使用_wfopen。您需要同时使用_wfopen(根据手册,应优先使用_wfopen_s)和ccs=UTF-8 - Philipp
八月的最后编辑:事实证明@Hans Passant的方法更好 - 编辑答案以使用该方法! - AshleysBrain

5
#include <iostream>
#include <fstream>
#include <string>
#include <locale>
#include <cstdlib>

int main()
{
    std::wifstream wif("filename.txt");
    wif.imbue(std::locale("zh_CN.UTF-8"));

    std::wcout.imbue(std::locale("zh_CN.UTF-8"));
    std::wcout << wif.rdbuf();
}

嗨,感谢分享。非常感激。你能多加点背景信息吗?为什么要回答一个六年前的问题呢?谢谢。 - wp78de
3
最近我有同样的问题,但现在已经解决了,我想分享我的解决方案来帮助其他人。 - Shen Yu
很好。但是你的答案和@LihO的答案有什么不同?你只是使用了不同的区域设置,对吗? - wp78de
对我没用。最终使用了 @LihO 的 <codecvt>。 - Peter L
在Windows上使用VS2022和C++20为我进行阅读和编写工作。谢谢。 - Benoit Andrieu

1

最近处理了所有编码问题,解决方法如下。最好使用std::u32string,因为它在所有平台上具有稳定的大小,并且大多数字体都支持utf-32格式。(文件仍应该是utf-8格式)

std::u32string readFile(std::string filename) {
    std::basic_ifstream<char32_t> fin(filename);
    std::u32string str{};
    std::getline(fin, str, U'\0');
    return str;
}

可以随意使用标准函数,除了 gcount,并且只将 tellg 的结果保存到 pos_type 中。此外,请确保将分隔符传递给 std::getline(如果不这样做,该函数会抛出异常 std::bad_cast)。


0

1
我认为你可以使用UTF-16的wstring。 - David Heffernan
1
@David:从技术上讲,在Windows上,wstring只是一个由16位整数组成的数组。您可以在其中存储UCS-2或UTF-16数据或任何您喜欢的内容。现在,大多数Windows API都接受UTF-16字符串。 - Philipp
1
@David 我认为这是一个Python问题,而不是Windows问题。我知道Python开发人员努力在各个地方实现Unicode支持,但我认为很难将实际的Windows语义带入到假定操作系统流始终基于字节和编码无关的模型中(这对Unix文件和控制台流以及Windows文件流是正确的,但不适用于Windows控制台)。我没有研究过Python源代码,但我认为至少在过去的某个时候,他们假定了这个模型的成立。 - Philipp
1
@Thomas:我认为MSVC++的iostreams库除了允许使用Unicode文件名外,并没有提供任何Unicode支持。在C++中使用Unicode的所有解决方案都是纯C解决方案,要么直接使用Windows API,要么使用C库的非标准扩展。 - Philipp
1
@thomas 你会用什么替代wstring? - David Heffernan
显示剩余26条评论

-6

这可能有点简单粗暴,不过如何将文件读取为普通字节,然后将字节缓冲区转换为wchar_t*?

类似于:

#include <iostream>
#include <fstream>
std::wstring ReadFileIntoWstring(const std::wstring& filepath)
{
    std::wstring wstr;
    std::ifstream file (filepath.c_str(), std::ios::in|std::ios::binary|std::ios::ate);
    size_t size = (size_t)file.tellg();
    file.seekg (0, std::ios::beg);
    char* buffer = new char [size];
    file.read (buffer, size);
    wstr = (wchar_t*)buffer;
    file.close();
    delete[] buffer;
    return wstr;
}

我认为这不会起作用——该文件包含的是UTF-8而不是wchar_t序列。 - ChrisW

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接