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. .net 异步编程总结

    异步的方式,就是,先发起IO.CPU密集工作等,然后函数返回,在IO.CPU密集工作等完成了以后——某个不确定的时刻,再执行后续的代码.   所以,如果使用异步代码,必须注意代码的执行顺序. 所以,异 ...

  2. 1052 卖个萌 (20 分)C语言

    萌萌哒表情符号通常由"手"."眼"."口"三个主要部分组成.简单起见,我们假设一个表情符号是按下列格式输出的: [左手]([左眼][口][右 ...

  3. js滑动导航栏点击后居中效果

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

  4. python常用英语单词(初学,英语不好的适用)

    对于刚才是学习python这些也足够了,一天学个六七个单词记一下在配合自己寻找的视频.书籍等等方法去学习是有一定帮助的. 这里还是要说一句,仅供兴趣爱好学习使用,个人开发者(非考虑未来靠此为生的人士) ...

  5. 【记】VM VirtualBox 网络地址转换(NAT)使用详解

    1. 查看虚拟机Centos6的ip 但是这个IP地址并不能直接连接,因为本地VBox网络连接方式采用的是“网络地址转换(NAT)”(如上上图所示),也就是说 10.0.2.15 这地址是转换的. 2 ...

  6. 【转】最受欢迎的8位Java牛人

    本文由 ImportNew - 唐尤华 翻译自 javatyro.如需转载本文,请先参见文章末尾处的转载要求. 下面是8位Java牛人,他们为Java社区编写框架.产品.工具或撰写书籍改变了Java编 ...

  7. Spring Boot2 系列教程(一) | 如何使用 IDEA 构建 Spring Boot 工程

    微信公众号:一个优秀的废人 如有问题或建议,请后台留言,我会尽力解决你的问题. Search 前言 新年立了个 flag,好好运营这个公众号.具体来说,就是每周要写两篇文章在这个号发表.刚立的 fla ...

  8. Tarjin + 缩点

    链接:https://www.nowcoder.com/acm/contest/81/C来源:牛客网 题目描述 给出一个 0 ≤ N ≤ 105 点数.0 ≤ M ≤ 105 边数的有向图, 输出一个 ...

  9. 使用 OAS(OpenAPI标准)来描述 Web API

    无论哪种类型的Web API, 都可能需要给其他开发者使用. 所以API的开发者体验是很重要的. API的开发者体验, 简写为 API DX (Developer Experience). 它包含很多 ...

  10. python 黏包现象

    一.黏包 1.tcp有黏包现象 表现两种情况 发送的数据过小且下面还有一个发送数据,这两个数据会一起发送 发送的数据过大,超过最大缓存空间,超出的部分在下一次发送的时候发送 原因: tcp是面向流的, ...