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


    • 使用 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系统自带的记事本 和写字板 怎么办?看好了:

    • 先用写字板打开文件,不用管乱码问题,直接保存。
    • 再用记事本打开。(恩,此时一切正常)

    参考

    http://blog.csdn.net/dbzhang800/article/details/6430280

  • 相关阅读:
    [LeetCode] 394. Decode String 解码字符串
    [LeetCode] 393. UTF-8 Validation 编码验证
    [LeetCode] Is Subsequence 是子序列
    [LintCode] Maximum Gap 求最大间距
    [LintCode] Nuts & Bolts Problem 螺栓螺母问题
    [LintCode] Kth Smallest Number in Sorted Matrix 有序矩阵中第K小的数字
    [LeetCode] Perfect Rectangle 完美矩形
    LaTex Remove Left Margin 去除左边空间
    LaTex Font Size 字体大小命令
    [LintCode] Continuous Subarray Sum 连续子数组之和
  • 原文地址:https://www.cnblogs.com/findumars/p/5111579.html
Copyright © 2020-2023  润新知