小小换行符乱谈(文本文件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系统自带的记事本 和写字板 怎么办?看好了:
- 先用写字板打开文件,不用管乱码问题,直接保存。
- 再用记事本打开。(恩,此时一切正常)
参考
- C99标准 (ISO/IEC 9899 7.19.5.3 The fopen function)
- C++ Primer (8.4.2 文件模式)
http://www.cplusplus.com/reference/iostream/fstream/fstream/
- Python2.7 manual
- Qt4.7 manual
- Windows核心编程(10.1 打开和关闭设备)
http://blog.csdn.net/dbzhang800/article/details/6430280
小小换行符乱谈(文本文件vs二进制文件)的更多相关文章
- Kettle文本文件输出和输入控件使用中,换行符导致的问题处理
1.如下图通过输入控件从数据库读取数据然后生成TXT文本文件,TXT文件生成原则是每一条数据生成一行数据,第二条数据换行保存 2.如下图所示,使用文本文件输入控件读入上图生成的文件,文件读入原则是按行 ...
- C#写文本文件,如何换行(添加换行符)
把文本写到文件中,如果是几段文字拼合起来输出到文件中,通常每段非结尾文字后需要添加换行符,不然几段文字都变成一段. 在 C# 中,文本换行有两种方法,一种在需要换行的文本后面添加换行符 \r\n 即可 ...
- python学习:python文件中空格和换行符的捕获和文本文件的转存
0. 背景 之前公司的项目中,需要在嵌入式系统中实现一个http的网页端内容,由于项目历史遗留问题,公司是采用的将html文件转成c语言头文件的方式,每次修改页面端都需要从新编译一下程序,非常的繁琐. ...
- Java 输入流读取文本文件换行符问题
一问题 在学习流编程的过程中,我遇到了一下问题.首先来看一下我写的java源程序: package StreamLearn; import java.io.*; public class TestFi ...
- 浅谈PHP在各系统平台下的换行符
<?php echo 'aaa\n';//用于linux.unix平台C的换行也是如此 echo 'bbb\r';//用于mac平台 echo 'ccc\r\n';//用于windows平台 / ...
- 将文本文件中的\n字符串变成换行符
1.用notepad打开文件 2.查看换行符,不同操作系统的换行符是不同的. [视图]——[显示符号]——[显示行尾符]. 我的操作系统是windows,所以行尾符是CR LF——对应的正则表达式是\ ...
- 【VS开发】fopen 文本文件与二进制文件区别
在学习C语言文件操作后,我们都会知道打开文件的函数是fopen,也知道它的第二个参数是 标志字符串.其中,如果字符串中出现'b',则表明是以打开二进制(binary)文件,否则是打开文本文件. 那么什 ...
- file.seek()方法引出的文本文件和二进制文件问题
问题的起因 菜鸟教程上有一段关于file.seek()方法的讲解,先简短描述一下seek()方法: seek(offset, whence)方法用于移动文件读取指针到指定位置 参数offset--开始 ...
- linux怎么区别文本文件和二进制文件
linux的文本文件与二进制文件的区分与windows的区分是相同的!说到底计算机存储的文件都是以二进制形式存储的,但是区别是,习惯上认为: (1).文本文件 文本文件是包含用户可读信息的文件.这些文 ...
随机推荐
- Google AdSense的CPC点击单价超百度联盟(2014)
很久没有关注AdSense了,一是访问不太方便,二是网站投放AdSense广告相当少,估计每天收入都不到1美元,所以就懒得去看了,一般都是几个月才去看一看. AdSense还行吗? AdSense点击 ...
- objective-C学习笔记(五)函数成员:初始化器和析构器
初始化器:init 对象初始化器: -(id)init 可以重载多个. 类型初始化器: +(id)initialize只能有一个. 对象初始化器: 初始化对象实例时,init通常和alloc(手动内存 ...
- ajax验证码检测
1.验证码文件 <%@ page language="java" pageEncoding="UTF-8"%> <%@ page conten ...
- POJ 3630 Phone List(trie树的简单应用)
题目链接:http://poj.org/problem?id=3630 题意:给你多个字符串,如果其中任意两个字符串满足一个是另一个的前缀,那么输出NO,否则输出YES 思路:简单的trie树应用,插 ...
- [LeetCode]题解(python):108-Convert Sorted Array to Binary Search Tree
题目来源: https://leetcode.com/problems/convert-sorted-array-to-binary-search-tree/ 题意分析: 给出一个排好序的数组,根据这 ...
- poj 2074 Line of Sight 计算几何
/** 大意:给定一个建筑--水平放置,给定n个障碍物, 给定一条街道,从街道上能看到整个建筑的最长的连续的区域 思路: 分别确定每一个障碍物所确立的盲区,即----建筑物的终点与障碍物的起点的连线, ...
- 通过读取excel数据和mysql数据库数据做对比(二)-代码编写测试
通过上一步,环境已搭建好了. 下面开始实战, 首先,编写链接mysql的函数conn_sql.py import pymysql def sql_conn(u,pwd,h,db): conn=pymy ...
- mybatis foreach where test用法
<select id="selectAny" resultType="user" parameterType="user"> s ...
- Spring学习笔记1——IOC: 尽量使用注解以及java代码(转)
在实战中学习Spring,本系列的最终目的是完成一个实现用户注册登录功能的项目. 预想的基本流程如下: 1.用户网站注册,填写用户名.密码.email.手机号信息,后台存入数据库后返回ok.(学习IO ...
- 基于Visual C++2013拆解世界五百强面试题--题1-定义各种类型指针
用变量a给出下面的定义 a)一个整型数 b)一个指向整型数的指针 c)一个指向指针的指针,它指向的指针是指向一个整型数 d)一个有10个整型数的数组 e)一个有10个指针 ...