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. 用实例理解设计模式——代理模式(Python版)

    代理模式:为其他对象提供一种代理以控制对这个对象的访问. 在某些情况下,一个对象不适合或者不能直接引用另一个对象,而代理对象可以在客户端和目标对象之间起到中介的作用. 代理模式分为: 静态代理 动态代 ...

  2. linux下配置vnc-server 和gnome-session

    机器比较老,安装时间也十分久远,所以也不知道实验室系统当时是不是完全安装,最近需要使用vnc登录显示界面,结果问题就来了...没有安装vnc-server. (1)机器系统是rhel6.2的,所以就从 ...

  3. 【转】ArcGIS ADF 实时轨迹问题初步解决方案

    Web ADF 实时轨迹是指在Web客户端指定一资源项,并对资源项进行实进跟踪并绘制出轨迹图.实时绘制可采用Ajax实现服务端与客户端无刷新动态绘制,在.net2.0 框架下可轻易实现:通过客户端时钟 ...

  4. oop(面向对象语言的三大特征):封装,继承,多态; (抽象),函数绑定

    封装/隐藏 : 通过类的访问限定符实现的   private    public 继承的意义之一:代码的复用 类的继承是指在一个现有类的基础上去构建一个新的类,构造出来的新类被称为派生类(子类),现有 ...

  5. GPU图形绘制管线简介

    (阅读GPU+编程与CG+语言之阳春白雪下里巴人所得总结) GPU图形绘制管线是描述GPU渲染(把三维世界显示为屏幕上的二维图像)的流程,主要分为三个主要阶段应用程序阶段.几何阶段.光栅阶段. 1.应 ...

  6. JSP九大内置对象之session以及eclispe如何关联源码

    一.session的特点及其实例 session:同一次会话共享 a.浏览网站:开始->关闭 b.购物:浏览.付款.退出 c.电子邮件:浏览.写邮件.退出  从一次开始到一次结束,是一次会话.  ...

  7. 网络流学习 - dinic

    推荐博客:https://www.cnblogs.com/SYCstudio/p/7260613.html#4246029

  8. 构造分组背包(CF)

    Ivan is a student at Berland State University (BSU). There are n days in Berland week, and each of t ...

  9. GitHub进阶之利用Git远程仓库篇

    #在上一篇文章,相信大家对GitHub已经有了一个基础的理解 接下来我们来学习一下如何利用git来远程仓库 一,git是什么 git:一个免费的开源版本控制软件 用途:利用Git管理GitHub上的代 ...

  10. border 边框

    <!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8&quo ...