分享

小小换行符乱谈(文本文件vs二进制文件)

 ruiruiruiruichen 2013-08-29


       
   

       
        分类:
            C/C++
            Qt
       

    2011-05-18 17:59
    6763人阅读
    评论(11)
    收藏
    举报
   




   

目录(?)[+]








  • 使用 C 语言的 fopen 打开文件时,可以指定的 mode 有 12 个,其中 6 个包含 

    "b"




  • 使用 C++ 的 fstream 打开文件时,可用的模式组合有 24 个(?),其中 12 个包含 

    "binary"




  • 使用 python 的 open 打开文件,除了可以使用 C 中的 12 个模式外,还可以使用 

    "U" 或 "rU"




  • 使用 Qt 库的 QFile 打开文件时,可以指定 

    QIODevice::Text

     

    或不指定



  • ...


如此种种,看起来是如此的复杂,难怪很多刚接触编程的网友都不相信(或者不想相信):




  • 这一切仅仅是为了一个小小的换行符!




是啊,一个小小的换行符值得如此大动干戈么?



  • 当使用 windows 下弱智的记事本时,会不会遇到:本该换行的地方,它显示一个黑色方块?

  • 当使用高级点的编辑器时,是不是都提供设置换行符的功能?

  • 当使用跨平台的工具 (比如windows下git) ,是不是需要特别注意换行符设置?

  • ...


文本 vs 二进制


哎,等等...


你前面提的C中的"b",C++中的"fstream::binary",Qt的"QFile::Text",我都知道啊:不是区分文本和二进制操作的么?和换行符有什么关系?!


那么我们有必须要看看:


什么是文本文件(Text File)?




  • 所有的文件都是二进制文件(Binary File)



  • 如果一个二进制文件的内容全是可打印的字符和空白字符(空格、Tab、回车、换行等)组成,可称其为文本文件。


换句话说:本来就不存在 文本文件 这个独立类别,文本文件属于二进制文件。


如果这样,为何C、C++等等打开文件是都提供文本和二进制两种模式么?(暂不解释^_^)


考虑一个例子:打开文件(不管后缀名等等),分别写入:















"/x10/x11/x12/x13/x14"



不可见字符



"/x30/x31/x32/x33/x34"



"01234"




而后者由于全部是可打印字符,你可能就会称其为文本文件。


文件 vs 模式



注意区分两个概念:当我们提C、C++打开文件的方式时,我们一直在说 文本模式 和二进制模式,而不是说打开 文件文件 和二进制文件。这中间有很微妙的区别。


任何一个文件,你都可以用文本或二进制模式打开。但是对于 *.png 等这些东西,你用文本模式打开读进来的往往不是你期望的结果。


考虑这样一个文件 hello.txt,其内容:


line1/r/nline2/r/n

如果在windows下:你用文本模式打开,读进来多少个字符?用二进制模式打开,又是多少个字符?为何同一个文件,读进来的不一样?


换个角度考虑考虑


我们前面提到(C、C++、Python、还有不该和语言并列Qt)的文件操作,都是需要通过系统调用对文件进行操作的。具体一点:



  • 在Windows下,不管通过哪种方式,最终都需要使用


HANDLE WINAPI CreateFile(
  __in      LPCTSTR lpFileName,
  __in      DWORD dwDesiredAccess,
  __in      DWORD dwShareMode,
  __in_opt  LPSECURITY_ATTRIBUTES lpSecurityAttributes,
  __in      DWORD dwCreationDisposition,
  __in      DWORD dwFlagsAndAttributes,
  __in_opt  HANDLE hTemplateFile
);

参数很多,每一个参数又有很多标记位组成(具体看MSDN)。但是你可以发现:对它来说,不存在文本文件和二进制文件的区别,你也无法设置text或binary等标记位!!



  • 在posix 系统下,文件操作需要


int open(const char *pathname, int flags);
int open(const char *pathname, int flags, mode_t mode);
int creat(const char *pathname, mode_t mode);

同样,这儿可以设置flags和mode,可以设置的标记很多。但是就是没有提供text和binary相关的东西!!


是不是很有意思?



  • 系统的文件操作接口压根就没有二进制和文本的区别!

  • 使用这些接口的C、C++、Python 却提供了二进制和文本两种模式


换行符


是时候谈 

换行符

 

了:



  • newline

  • line break

  • EOL (end-of-line)


想象一下,一个文本编辑器打开一个"文本文件",遇到哪个字符开始换行呢?



  • 想想Windows下的记事本,遇到遇到"/r/n"它处理成换行,遇到'/n'它就只会显示黑方框。


应用程序和操作系统通常用1到2个字符代表换行:



































CR+LF



Windows、DOS、Symbian、Palm ...



LF



GNU/Linux、Mac OS X、FreeBSD ...



CR



Mac OS 9(之前)...



LF+CR



Acom BBC



RS



QNX 在posix之前



NEL



z/OS、i5/OS ...



...



...




这些之中,其实我们也只对 CR+LF 与 LF 这两种换行符感兴趣。


有什么问题么?


本来一切很正常的:


在Windows下:




  • 调用 CreateFile 打开文件


    HANDLE hFile = CreateFile (TEXT("twoline.txt"), GENERIC_WRITE, 0, NULL, OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL,   NULL);



  • 调用 WriteFile 写入两行


    DWORD dwBytesWritten;
    WriteFile (hFile, "line1/r/nline2/r/n", 14,
                     &dwBytesWritten, NULL);



  • 调用CloseHandle关闭文件


    CloseHandle(hFile);



在Posix系统下



  • 调用open打开文件
    int fd = open("twolines.txt", O_WRONLY|O_CREAT);


  • 调用write写入两行
    write(fd, "line1/nline2/n", 12);


  • 调用close关闭文件
    close(fd);



各个平台相安无事,windows下你想换行就用'/r/n',posix下想换行就用'/n'


如何就出问题了呢?


各个平台的换行符不一致,一旦涉及跨平台问题就出来了。


考虑一下,如果使用C语言的binary模式的话,我们想生成一个像前面一样包含两行代码的文件,该怎么办?



  • 根据平台不同,用#if #else 进行预处理?
     #ifdef _WIN
     fwrite("line1/r/nline2/r/n");
     #else
     fwrite("line1/nline2/n");
     #endif


  • 还是采用某种方式,同一行代码:在不同平台下生成不同的东西
      fwrite("line1/nline2/n");



应该就是为了这个吧,引入了一个"文本模式"



  • 写入时,遇到'/n'就转换成平台相关的换行符(对与windows就是"/r/n");

  • 读入时,遇到平台相关的换行符(比如windows下的"/r/n"),转换成'/n'

  • 注意:对与posix系统,'/n'就是系统换行符,不存在转换

    • 所以我们经常听说:linux下文本文件和二进制文件没有区别。




正是为了这个换行符,所以C、C++、Python等语言提供的文件操作函数才都有了Text、Binary两种模式:


C、C++、Qt


C语言的文件操作


#include <stdio.h>
FILE *fopen(const char * restrict filename, const char * restrict mode);

除了文件名之外,还要传递一个 mode 的字符串作为标记。而这些标记分为带b和不带b两类:













































文本



二进制


  

r



rb



只读或只写



文件必须存在



w



wb



文件存在则清空、不存在则创建



a



ab



追加;文件不存在则创建



r+



r+b

 

rb+



读写



同r和rb



w+



w+b或 wb+



同w和wb



a+



a+b或 ab+



同a和ab




C++的文件操作时


explicit fstream ( const char * filename,ios_base::openmode mode = ios_base::in | ios_base::out ); 

除了文件名之外,我们需要传递一个 mode:































app



(app

end) 每次写操作前找到文件尾



ate



(at e

nd) 打开文件后立即将文件定位到文件尾



binary



(binary

) 以二进制模式进行IO操作



in



(in

put) 允许读操作



out



(out

put) 允许写操作



trunc



(trunc

ate) 打开文件时清空文件流




这样看似乎没神马意思哈?一般都是组合使用的:



  • in、out、app、trunc的有效组合如下


































out



只写



清空文件内容



out|app



追加



out|trunc



等同out



in



只读


 

in|out



读写


 

in|out|trunc



清空文件内容





  • 6个标记这儿只提了4个,其他两个和这儿的可以随意组合,不受限制(我对此不太确定,dbzhang800 2011.5.18)


  • 也就是:带binary和不带binary的组合数目一样多的




Qt的文件操作


bool QFile::open ( OpenMode mode ) 

这儿是mode又是什么东西?































QIODevice::NotOpen



QIODevice::ReadOnly



QIODevice::WriteOnly



QIODevice::ReadWrite



QIODevice::Append



QIODevice::Truncate



QIODevice::Text



QIODevice::Unbuffered




其他


现在国内用linux的似乎越来越多了,很多人有这个问题:


linux下创建了一个包含中文的文件,拷贝到windows下面。
 用记事本打开看 ==> 汉字正确,换行的地方出现了黑方块
 用写字板打开看 ==> 换行正确,汉字乱码

很有意思?可是如何解决?



  • 找个支持utf8编码和'/n'换行的编辑器即可解决问题。

  • 在linux采用"/r/n"换行和gb18030编码保存文件,也可以解决问题


如果就用windows系统自带的记事本

写字板

怎么办?看好了:



  • 先用写字板打开文件,不用管乱码问题,直接保存。

  • 再用记事本打开。(恩,此时一切正常)


参考


    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多