09年研究技术的大神真的好多,本文测试有很多错误,更正后发布下(可能与编辑器相关)。

file.imbue(locale(file.getloc(), new codecvt_utf8<wchar_t, 0x10FFFF, consume_header>));

读取UTF-8编码的文件:

#include<string>
#include<iostream>
#include<locale>
#include <fstream>
#include <codecvt>
#include <io.h>
#include <fcntl.h>
using namespace std; int main()
{
wstring wstr; wifstream file("test.txt");
file.imbue(locale(file.getloc(), new codecvt_utf8<wchar_t, 0x10FFFF, consume_header>)); _setmode(_fileno(stdout), _O_U8TEXT);
//wcout.imbue(locale("chs"));//在console输出 while (file >> wstr)
wcout << wstr << '\n';
file.close();
}

转载地址:
凡用到文件读写,输入输出,就得和编码、Unicode 打交道。这系列实验来测试一下 C++ STL 的 IO流 对 ANSI 编码、Unicode 编码的支持特性,看能否找到一个自动识别编码,自动转码的解决方案。从基础开始,一步一步来:
 
平台 Win7  VS2013
 
实验 01:

#include<string>
#include<iostream>
#include<locale>
using namespace std; int main()
{
locale prevloc;
locale loc("chs");
string str1("string class");
string str2("汉字与字符");
wstring wstr1(L"wstring class"); //去掉L前缀则编译错误
wstring wstr2(L"汉字与字符"); prevloc = cout.imbue(locale(""));
cout << "Default Locale: " << prevloc.name() << endl;
cout << "System Locale: " << locale("").name() << endl;
cout << "C风格字符串\n" << L"w-string\n" << str1 << '\n' << str2 << '\n' << endl; prevloc = wcout.imbue(loc); //若去掉此句,则wstr2无法正常输出
wcout << "Default Locale: " << prevloc.name().c_str() << endl; //若不加 .c_str() 则编译错误
wcout << "chs Locale Name: " << loc.name().c_str() << endl;
wcout << "C-string\n" << "C风格字符串\n" << L"宽字符串\n" << wstr1 << '\n' << wstr2 << '\n' << endl;
}

输出

Default Locale: C
System Locale: Chinese (Simplified)_People's Republic of China.936
C风格字符串
00,963,004string class
汉字与字符 Default Locale: C
chs Locale Name: Chinese (Simplified)_People's Republic of China.936
C-string
C
宽字符串
wstring class
汉字与字符 请按任意键继续. . .

 
结论:
        1.cout 与 string 配合使用,wcout 与 wstring 配合使用,交错则编译错误(类型问题)

        2.wstring 初始化时需用 L"xxx" 的宽字符形式,同样 string 初始化时不能加 L 前缀

        3.默认locale ("C")下 cout 可以正常输出 C风格字符串与std::string类型,包括汉字也能正常显示

    但对 L"xxx" 宽字符串无能为力

          默认locale ("C")下 wcout 不能输出中文,包括C风格字符串(可以输出 wcout << "你好"; wcout << L"你好";不能输出)、宽字符串与std::wstring

    设定系统 locale ("chs")后,正常输出宽字符串与std::wstring,但 C风格字符串 中的汉字无法显示
 
        总之,string cout "C-style 字符串" 自成体系
                  wstring wcout L"宽字符串" 自成体系,但 wcout 要选择 locale 后才能正常输出中文(不包括C风格字符串)。

console输出包含非ASCII字符的宽字符串的方法:
1、WINAPI WriteConsoleW
2、_setmode
3、locale或者STL的imbue
 
实验 02:
#include<string>
#include<iostream>
#include<locale>
using namespace std; int main()
{
cout.imbue(locale(""));
wcout.imbue(locale("")); string str1("string class");
string str2("汉字与字符"); string str3("abc汉字");
wstring wstr1(L"wstring class");
wstring wstr2(L"汉字与字符");
wstring wstr3(L"abc汉字"); cout << "str1 length: " << str1.length() << '\n'; // 12
cout << "str2 length: " << str2.length() << '\n'; // 10
cout << "str3 length: " << str3.length() << '\n'; // 7
cout << str2[0] << " " << str2[1] << '\n'; // 输出:?
cout << endl;
wcout << L"wstr1 length: " << wstr1.length() << '\n'; // 13
wcout << L"wstr2 length: " << wstr2.length() << '\n'; // 5
wcout << L"wstr3 length: " << wstr3.length() << '\n'; // 5
wcout << wstr2[0] << " " << wstr2[1] << '\n'; // 输出:汉 字
}

输出:

str1 length: 12
str2 length: 10
str3 length: 7
? wstr1 length: 13
wstr2 length: 5
wstr3 length: 5
汉 字
请按任意键继续. . .




结论:
        4.std::string 内部以 char 类型储存字符,当有汉字时以双字节存储,此时 length() 给出

    字符串所占字节数而不是字符数

          std::wstring 内部以 wchar_t 类型存储字符,字母汉字统一都是双字节,此时 length()

    给出是正确的字符数。

        5.当std::string中有汉字存在时,通过下标访问不能得到正确的字符。这是显而易见的,

    一方面字符宽度不统一无法随机访问,另一方面 std::string[] 返回 char 类型。std::wstring
    不存在此问题。
 
实验 03:
// test.txt 为 ANSI 编码(GB2312),内容为以上 str1 ~ str3 的3行。
#include<string>
#include<iostream>
#include<locale>
#include <fstream>
using namespace std; int main()
{
string str;
wstring wstr; ifstream fin("test.txt");
//fin.imbue(locale(""));
while (fin >> str)
cout << str << '\n';
fin.close(); wifstream wfin("test.txt");
//wfin.imbue(locale(""));
//wfin.imbue(locale(".936"));
while (wfin >> wstr)
wcout << wstr << '\n';
wfin.close();
}

输出:

 
abc汉字
wstring
class
汉字与字符
abc汉字 abc汉字
wstring
class
汉字与字符
abc汉字
请按任意键继续. . .

结论:
       6.std::ifstream 读取 ANSI 编码正常,std::wifstream 读取 ANSI 编码错误(也正常)…默认 locale("C") 不能识别中文字符(可以识别中文字符,当做C风格字符串)

          std::wifstream 设置 imbue(locale("")) 或 locale(".936") 后正常读取(不能正确读取)。936 为 GB2312 的代码页。
 
 实验 04:

 test.txt 为 Shift-JIS 编码,内容为

 うみねこのなく頃に
 程序代码同实验3
 ifstream 输出为

 偆傒偹偙偺側偔崰偵

 wifstream 设定 imbue(locale("")) 后输出相同
 
结论:
       7.显而易见的,其他地区的编码无法正确识别。这也是很多日本游戏和文本文件运行

    或读取时产生乱码的原因。
 
 实验 05:

 test.txt 为 Shift-JIS 编码,内容同上

 ifstream 与 wifstream 都添加 imbue(locale("jpn")) 或 locale(".932")
932 为 Shift-JIS 的代码页

 输出为:
 偆傒偹偙偺側偔崰偵

 うみねこのなく頃に
 
 
结论:
       8.这里可以看出一个显著性差异。wifstream 在读取时按照 Shift-JIS 编码将其转换为

    Unicode 储存,在 wcout 输出时又按照 ANSI (GB2312) 转换,其结果是 —— 正确显示
    了其他地区编码的字符。而 ifstream 与 cout 则缺少那两步转换,结果与上例相同
    以后的实验将不再考虑 ifstream 而只实验 wifstream。
 
 实验 06:

 test.txt 存为 UTF-16 编码(Win32 默认的 little endian),内容同上。

 wifstream 设定为 imbue(locale(".1200"))

 1200 为 UTF-16 的 code page
 
 结果,运行出错…发现是 imbue(locale(".1200")); 这句的问题

 试着将 ".1200" 改为 ".936" 则运行正常,输出乱码。(936是 GB2312 的代码页)

 翻 MSDN 时在 Code Page 那页1200 UTF-16 后面发现一行小字:

 "available only to managed applications"…郁闷

 看来用 locale 转Unicode的想法到此结束了?记得 STL 书中貌似说过,locale 的名

 字在各平台上是不统一的,因为关系到各平台的支持问题。这样的话,要么自己写

 代码,要么就只好用 API 显式转换了:MultiByteToWideChar
 另外,在 setlocale 函数说明中也写到,UTF-8 和 UTF-7 等每字符有可能大于2字节

 的编码不被支持,所以 UTF-8 也只能用 MultiByteToWideChar 转咯…

 目前大概只能得出结论 C++ STL locale 在 Win32 平台上支持不完善吧
 
 实验 07: 用 API 重写读文件部分代码
#include<windows.h>
HANDLE hFile;

if(INVALID_HANDLE_VALUE != (hFile = CreateFileW(L"test.txt",

        GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, NULL))){


    int iFileLength, iUniTest, i;

    iFileLength = GetFileSize(hFile,NULL);

    char *pBuffer, *pText;

    pBuffer = new char[iFileLength+2];

    DWORD dwBytesRead;
    ReadFile(hFile,pBuffer,iFileLength,&dwBytesRead,NULL);

    CloseHandle(hFile);

    pBuffer[iFileLength] = ”;

    pBuffer[iFileLength + 1] = ”;
    iUniTest = IS_TEXT_UNICODE_SIGNATURE | IS_TEXT_UNICODE_REVERSE_SIGNATURE;

    if(IsTextUnicode(pBuffer,iFileLength,&iUniTest)){

        pText = pBuffer + 2;

        iFileLength -= 2;

        if(iUniTest & IS_TEXT_UNICODE_REVERSE_SIGNATURE){

            for(i = 0;i < iFileLength; i+=2)

                swap(pText[i],pText[i+1]);

        }

        wstr = (wchar_t*)(pBuffer+2);

    }

    delete [] pBuffer;
    wcout<<wstr<<‘\n’;

}
 
        输出正确。以上程序段自动识别 Unicode 编码文件开头的 0xFFFE 标记判断是 Little Endian 还是
    Big Endian 并做相应转换。但是代码量较大,且与 C++ 的 IO流 很不搭调…
 
结论:
       9.可以看到,只是把输入内容去掉UTF-16开头的0xFFFE,直接把内存指针改为

    wchar_t* 后 std::wstring 即可正确识别,说明程序中的宽字符存储格式实际上用的就是

    UTF-16 little endian
 
 实验 08:

 不死心又去翻了 boost 库,发现 codecvt_null 这个好东西,看下实现是把文件存储内容

 按照 wchar_t 为单位直接读入内存不做任何转换。这其实不正好是 UTF-16 需要做的么

 以下把 test.txt 存为 UTF-16 little endian 再次实验
#include<boost/archive/codecvt_null.hpp>
wifstream wfin(L"test.txt");

locale utf16(loc, new boost::archive::codecvt_null<wchar_t>);

wfin.imbue(utf16);

while(wfin>>wstr){

    wcout<<wstr<<endl;
}

wfin.close();
 
输出正确。
 
结论:
       10. 看来可以把 codecvt_null 作为 UTF-16 的 codecvt_facet 读入 locale

    来使用,避免使用类似上面 API 那么多代码。
 
 实验 09:

 将 test.txt 存为 UTF-16 Big Endian ,内容不变。程序不变
 
无法输出任何内容。
结论:
       11. wcout 不认识 big endian 的 wchar_t …
    看来想读取 UTF-16 Big Endian,仅靠 codecvt_null 还不够。稍微翻了一下
    《C++ 输入输出流与本地化》这本书,现在可以考虑写一个自己的 codecvt_facet
    了。有了 codecvt_null 的代码,稍作改动即可用于 UTF-16 big endian。虽说有了
    现在的知识自己写个 utf-16 的codecvt_facet 也可以,但效率大概比不上 boost 里的。
 
代码准备:用类似的方法写出了自己的 codecvt_utf16 和 codecvt_utf16_reverse 两个
codecvt_facet…然后继续实验。自己写的内容放入咱自己的头文件吧:codecvt_utf.h,
内容加入自己的 namespace : tvt
 
 实验 10: 用 codecvt_utf.h 代替 codecvt_null.hpp。用 codecvt_utf16 和

 codecvt_utf16_reverse 实现 little endian 与 big endian 的输入。
wifstream wfin(L"test.txt");

locale utf16(loc,new tvt::codecvt_utf16<wchar_t>);

wfin.imbue(utf16);

while(wfin>>wstr){

    wcout<<wstr<<endl;
}

wfin.close();
///////////////////////////////////////
wifstream wfin(L"test.txt");

locale utf16(loc,new tvt::codecvt_utf16_reverse<wchar_t>);

wfin.imbue(utf16);

while(wfin>>wstr){

    wcout<<wstr<<endl;
}

wfin.close();
 
第一段程序读取 UTF-16 little endian 编码的 text.txt 正确输出
第二段程序读取 UTF-16 big endian 编码的 text.txt 正确输出
 
UTF-16 的转码顺利完成。下面考虑 UTF-8 ,写法类似。在 boost 库中继续寻找,发现
这个东东 boost/detail/utf8_codecvt_facet.hpp 。看下说明,不支持直接使用此文件,这文件
是专门提供其他 boost 组件使用的。仅 include 它的话编译出问题。再寻找到同名的 cpp 文件
后即可看到 do_in do_out 这两个转码关键的虚函数。有了上面 UTF-16 的基础,我们类似可写
出 UTF-8 的转码 codecvt_facet。我给他起名为 codecvt_utf8, 依然加入 codecvt_utf.h 文件。
现在此文件有一两百行了。经试验可正确输入 UTF-8 编码。
 
对应编码有了处理方法后,下一个问题是编码识别。
 
实验 11:
wchar_t wc;

wchar_t buf[2];
wifstream wfin(L"text.txt");
wfin.read(&wc,1);
wfin.read(&buf[0],2);
 
将 wc 和 buf 的内容按2进制或16进制输出。
结论:
       12. wistream.read(buffer,count) 操作每次读入 count 个字节,但将每个字节存入一个

 wchar_t 类型的 buffer[i] 中。其实 buffer 中每个 wchar_t 的高位都字节是 0 …
 
 实验 12:

 加入判断条件,在 wfin 中自动加入合适的 utf16 facet,使得自动识别并读取

 little endian 和 big endian 编码的文件:
wchar_t buf[2];

wifstream wfin(L"test.txt");

wfin.read(buf,2);
if(buf[0] == wchar_t(0xFF) && buf[1] == wchar_t(0xFE)){

    cout<<"little endian"<<endl;

    wfin.imbue(locale(loc,new tvt::codecvt_utf16<wchar_t>));

}

else if(buf[0] == wchar_t(0xFE) && buf[1] == wchar_t(0xFF)){

    cout<<"big endian"<<endl;

    wfin.imbue(locale(loc,new tvt::codecvt_utf16_reverse<wchar_t>));

}

while(wfin>>wstr){

    wcout<<wstr<<endl;

}
 
对于两种编码的 text.txt 都实现了自动识别并正确读取。输出正确!
 
结论:
       13.UFT-16在传输时几乎都会加上 0xFFFE 等传输标志很容易判断,即使没有, Win32 下

    也有 IsTextUnicode 这 API 用专门方法判断。UTF-8 就很麻烦了,开头不一定都有 BOM 标
    记,与各地区字符集一样都可以用一个或多字节表示一个字符,编码长度不固定,如果是
    很长一段 ASCII 字符,那么用 UTF-8 和 GB2312 编码出来结果一样,就很难分辨
 
代码准备:经过一段时间思考,打算用这种算法。先读取前3字节,若是 BOM 头标记最好。若
不是则排除 UTF-16 ,下面集中力量分辨 UTF-8 与 ANSI 。从头开始寻找第一个 >127 的字节
若此字节内容 < 0xC0 或 >0xEF 则可判断不是 UTF-8 。否则,根据 UTF-8 的规则,在后面1 或
2 字节中看开头两位是不是 10 。若不是则断定不是 UTF-8 ,否则就算得到一个 UTF-8 字符。
如果能够找到 10个 满足条件的 UTF-8 字符就判断为 UTF-8 编码。若未到 10 个即遇到文件结
尾,那么找到 UTF-8 字符数大于 1 即断定为 UTF-8 否则断定为 ANSI …
用这种方式选择对应转码 facet:
wistrm.imbue(std::locale(wistrm.getloc(), new codecvt_utf8));
 
按以上想法写成函数 int IsStreamUnicode(std::wistream &wistrm); UTF-16 LE 返回1,BE 返回2,
UTF-8 返回3,否则返回 0 (判断为ANSI)
 
实验 13:
 
std::wifstream wfin(L"test.txt");

if(!tvt::IsStreamUnicode(wfin))

    wfin.imbue(loc);

while(wfin>>wstr)

    wcout<<wstr<<endl;
 
 在我试验的各种情况下,均能自动识别 UTF-16 LE UTF-16 BE UTF-8 与 ANSI 编码

 并正确设定转码 locale .
 
 
————————————————————————————-
8小时后,关于后续实验的补充:
 
使用中发现某些情况下 UTF-16 的读写出现问题,特别是有换行符或某字节中编码刚好
等于控制符时。经过反复测试认定是 读写mode 问题。在读写 Unicode 文件时,
wifstream 与 wofstream 都设定为 ios_base::binary 模式即可。后来又补充了一个添加
BOM 头的小东西。为了使用简便把 utf_16 的 template 也去掉了。最终情形使用起来
像这个样子:
 
#include<iostream>
#include<fstream>
#include<codecvt_utf.h>
using namespace std;
 
wstring wstr;
wcout.imbue(locale(""));
 
// Open the Input and Output Files:
std::wifstream wfin(L"test.txt", ios_base::binary);

std::wofstream wfout(L"testout.txt", ios_base::binary);
 
// Set Output Format and Write BOM tag:

wfout.imbue(locale(locale(""), new tvt::codecvt_utf16));
wfout<<tvt::utf_bom;
 
// Detect the Format of the Input File
if(!tvt::IsStreamUnicode(wfin))

    wfin.imbue(locale(""));
 
// Read and Write
//while(wfin>>wstr){

//    wcout<<wstr<<endl;

//    wfout<<wstr<<endl;

//}
 
// Another way:
while(getline(wfin,wstr)){

    wcout<<wstr<<endl;

    wfout<<wstr<<endl;

}
 
// Close Files:
wfin.close();

wfout.close();
 
读写测试全部通过!
 
感谢 记事本、EditPlus 和 HxDen 的大力支持…
 至此,关于 Unicode 编码和 C++ STL IO流 的协作算是大功告成了吧,呵呵。以后有需要再
在实践中改进
 花了整整一天时间 + 8 小时 = = 还算有价值吧,因为在网上看到很多人都在问且没有结果
 
 ===========分隔线============
 另附:现在来看用 c++ 的 IO stream locale 系列实现转码并不是一个经济的选择,如果用 STLport 的话还好些,用 VC STL 则存在较严重的效率问题:

C++ STL IO流 与 Unicode (UTF-16 UTF-8) 的协同工作的更多相关文章

  1. 16个常用IO流

    在包java.io.*:下 有以下16个常用的io流类: (Stream结尾的是字节流,是万能流,通常的视频,声音,图片等2进制文件, Reader/Writer结尾的是字符流,字符流适合读取纯文本文 ...

  2. JAVA基础知识总结16(IO流)

    IO流:用于处理设备上数据. 流:可以理解数据的流动,就是一个数据流.IO流最终要以对象来体现,对象都存在IO包中. 流也进行分类: 1:输入流(读)和输出流(写). 2:因为处理的数据不同,分为字节 ...

  3. 35 编码 ASCII Unicode UTF-8 ,字符串的编码、io流的编码

    * 编码表: * 信息在计算机上是用二进制表示的,这种表示法让人理解就很困难.为保证人类和设备,设备和计算机之间能进行正确的信息交换,人们编制的统一的信息交换代码,这就是ASCII码表 *ASCII ...

  4. Java基础知识强化之IO流笔记16:IO流的概述和分类

    1. IO流的分类   流向:     (1)输入流:读取数据到内存     (2)输出流:写入数据到硬盘(磁盘)   操作的数据类型:    (1)字节流:操作的数据是字节             ...

  5. (16)IO流之输入字节流FileInputStream和输出字节流FielOutputStream

    IO流技术解决的问题:设备与设备之间的传输问题,内存-->硬盘,硬盘-->内存,等等 IO流技术 如果按照数据的流向划分可以划分为:输入流和输出流 输入输出的标准是以程序为参考物的,如果流 ...

  6. Java IO流

    File类 ·java.io.File类:文件和目录路径名的抽象表示形式,与平台无关 ·File能新建.删除.重命名文件和目录,但File不能访问文件内容本身.如果需要访问文件内容本身,则需要使用输入 ...

  7. Android(java)学习笔记167:Java中操作文件的类介绍(File + IO流)

    1.File类:对硬盘上的文件和目录进行操作的类.    File类是文件和目录路径名抽象表现形式  构造函数:        1) File(String pathname)       Creat ...

  8. 第十一章 IO流

    11.IO流 11.1 java.io.File类的使用 1课时 11.2 IO原理及流的分类 1课时 11.3 节点流(或文件流) 1课时 11.4 缓冲流 1课时 11.5 转换流 1课时 11. ...

  9. IO流 简介 总结 API 案例 MD

    目录 IO 流 简介 关闭流的正确方式 关闭流的封装方法 InputStream 转 String 的方式 转换流 InputStreamReader OutputStreamWriter 测试代码 ...

随机推荐

  1. Spring应用事件(Application Event)

    Spring的事件为Bean与Bean的消息通信提供的支持.当一个Bean处理完了一个任务以后,希望另一个Bean知道并能做出相应的处理,这是我们就需要让另一个Bean监听当前Bean所发送的事件. ...

  2. Persistence.beans

    SF_USERS user = new SF_USERS(); user.setCTIME("20170103"); String ids = "fish,water&q ...

  3. dp - 3维背包(东四省)

    题意: 给你 n 张卡片,总共可以消耗的法力值,求最多可以造成多少伤害, 卡片分为2种,一种是魔法卡,使用后可以使所有的连环卡的费用全部减1,另一种是连环卡,因魔法卡的使用可以使其费用减1,问最终最多 ...

  4. Spring Framework 源码编译导入

    预先准备环境 Window 10 JDK环境 List item Gradle 以及其环境变量配置 spring-framework源码(https://gitee.com/mirrors/Sprin ...

  5. 阿里云Centos7.X 如何对外开放端口

    一句话:如果你是买的各大厂商的云服务器,去安全组配置对应需要使用到的端口就可以啦! 因为博主用的是阿里云,所以这里就只介绍下阿里云如何开放端口,按着下面三张图来操作就行嘞 这个时候有同学就说了,不通过 ...

  6. struts2学习第一天

    Stuts2是基于MVC设计模式成熟的Web应用框架.不仅仅是Struts1的下一个版本,是一个全新的Struts架构.由WebWork社区跟Strut社区联手打造的.(教程来自W3Cschool) ...

  7. 关于爬虫的日常复习(16)—— pyspider的初高级用法

  8. 洛谷 UVA1395 苗条的生成树 Slim Span

    题目链接 题目描述 求所有生成树中最大边权与最小边权差最小的,输出它们的差值. 题目分析 要求所有生成树中边权极差最小值,起初令人无从下手.但既然要求所有生成树中边权极差最小值,我们自然需要对每一棵生 ...

  9. springboot的yml不显示的原因

    首先排除插件原因 1 安装好插件Ctrl+Alt+S 2 查看修改的application.yml是什么格 在yaml格式中添加*.yaml和*.yml 3 查看maven是否配置完善

  10. java小心机(4)| 继承与组合的爱恨情仇

    在java中,有两种主要复用代码的方法:继承和组合. 继承,是OOP的一大特性,想必大家都非常熟悉了;组合,其实也很常见,只是不知道它的名字罢了. 继承 子类拥有父类的基本特性,需使用extend关键 ...