【C++】小心使用文件读写模式:回车('r') 换行('n')问题的一次纠结经历
原来没有仔细注意C++读写文件的二进制模式和文本模式,这次吃了大亏。(平台:windows VS2012)
BUG出现:
写了一个程序A,生成一个文本文件F保存在本地,然后用程序B读取此文件计算MD5值。
将该文件上传到服务器,再用程序B将文件从服务器上下载下来计算MD5值,神奇的发现两次计算的MD5值不一样,文件被谁改了??
排除问题:
1.首先对比了生成文件F和上传到服务器的文件,发现文件复制过程无差错,是同一个文件。
2.用程序B下载文件F后,保存在本地,发现文件与原文件F不一致,对比二进制发现每行多了一个r。
3.怀疑服务器传输前对文件格式进行了更改,用wireshark抓包,发现文件内容与服务器上文件一致。那么这个多出来的r从何而来呢,行结尾变成了rrn。
4.查看文件F,行结尾是rn,而我记得当初生成文件的时候是以n作为换行符的,纠结一番后想起来了文件读写的模式,只记得是文本与二进制的区别,没有想起来换行符的问题。
5.几经纠结,查阅C++ primer plus后恍然大悟,都是默认使用文本模式读写文件惹的祸:windows下,文本模式会将n输出成rn,读取时也会将rn变成一个n;所以开始程序B读取文件F并且计算MD5时,是以n来计算的。然而当从服务器上下载下来时,文件是以rn作为行结尾的,直接计算MD5会导致值不一样。而将下载下来的文件保存时,由于仍然使用的文本模式,将rn变成了rrn,导致了当初匪夷所思的结果。
总结:
这BUG从出现到调查各方面的原因排除花费了大量的时间,说到底还是因为基础不扎实,这里讲《C++ primer plus》的关键一段话抄下来作为提醒。
“使用二进制文件模式时,程序将数据从内存传递给文件(反之亦然)时,将不会发生任何隐藏的转换,而默认的文本模式并非如此。例如,对于Windows文本文件,他们使用两个字符的组合吧(回车和换行)表示换行符;Mac文本文件使用回车表示换行符;而UNIX和Linux文件使用换行来表示换行符。C++是从UNIX系统上发展而来的,因此也使用换行来表示换行符。为增加可移植性,Windows C++程序在写文本模式文件时,自动将C++换行符转换为回车和换行;Mac C++程序在写文件时,将换行符转换为回车。在读取文本文件时,这些程序将本地换行符转换为C++模式。对于二进制数据,文本格式会引起问题,因为double值中间的字节可能与换行符的ASCII码有相同的位模式。另外,在文件末尾的检测方式也有区别。因此以二进制格式保存数据时,应使用二进制文件模式。”
后续验证:
后来写了一个小程序验证了一下所知,不懂的话可以复制下来跑一下,注意是Windows平台,生成的文件可以用wxHexEditor来查看以二进制形式查看。另外再说一点题外的,不用语言的字符串类型编码可能会不同,例如JavaScript里是UTF-16,而C++默认的是ANSI,下载下来同一个文件计算MD5值的话可能会有问题。
1 #include <iostream>
2 #include <fstream>
3 #include <string>
4 using namespace std;
5 int main()
6 {
7
8 string str1 = "hello!n";
9 ofstream fout("file1");//默认文本模式
10 fout << str1;
11 fout.close();
12
13 ifstream fin("file1");
14 char ch = 0;
15 string temp;
16 if (fin) {
17 while (fin.get(ch))
18 temp += ch;
19 cout << "读入file1长度:"<<temp.length() <<endl;
20 fin.close();
21 }
22
23 string temp2;
24 fin.open("file1", ios::binary);//以n作为换行
25 getline(fin, temp2);
26 cout << "二进制模式getline读入file1的长度(结尾包含了\r):" << temp2.length() << endl;
27 fin.close();
28
29 ofstream fout2("file2");
30 fout2 << "hello!rn";
31 fout2.close();
32
33 string temp3;
34 fin.open("file2");
35 if (fin) {
36 getline(fin, temp3);
37 cout << "文本模式getline读入file2的长度(同样多了一个\r):" << temp2.length() << endl;
38 }
39
40 return 0;
41 }
- SpringBoot之前端文件管理
- Spring Boot 设置静态资源访问
- IDEA更换主题
- 用正则表达式给字符串属性值都加上双引号
- Spring Boot修改启动端口
- Packet for query is too large (12238 > 1024). You can change this value
- win10下端口被占用解决办法
- 微信小程序开发教程第九章:微信小程序拍照收纳开发以及删除名片等
- centos 安装sbt
- 微信小程序开发教程第七章:微信小程序编辑名片页面开发
- idea中使用scala运行spark出现Exception in thread "main" java.lang.NoClassDefFoundError: scala/collection/Gen
- 使用JPA中@Query 注解实现update 操作
- 微信小程序开发教程!博卡君第二弹【微信小程序项目结构以及配置】
- WCF浅尝
- JavaScript 教程
- JavaScript 编辑工具
- JavaScript 与HTML
- JavaScript 与Java
- JavaScript 数据结构
- JavaScript 基本数据类型
- JavaScript 特殊数据类型
- JavaScript 运算符
- JavaScript typeof 运算符
- JavaScript 表达式
- JavaScript 类型转换
- JavaScript 基本语法
- JavaScript 注释
- Javascript 基本处理流程
- Javascript 选择结构
- Javascript if 语句
- Javascript if 语句的嵌套
- Javascript switch 语句
- Javascript 循环结构
- Javascript 循环结构实例
- Javascript 跳转语句
- Javascript 控制语句总结
- Javascript 函数介绍
- Javascript 函数的定义
- Javascript 函数调用
- Javascript 几种特殊的函数
- JavaScript 内置函数简介
- Javascript eval() 函数
- Javascript isFinite() 函数
- Javascript isNaN() 函数
- parseInt() 与 parseFloat()
- escape() 与 unescape()
- Javascript 字符串介绍
- Javascript length属性
- javascript 字符串函数
- Javascript 日期对象简介
- Javascript 日期对象用途
- Date 对象属性和方法
- Javascript 数组是什么
- Javascript 创建数组
- Javascript 数组赋值与取值
- Javascript 数组属性和方法
- 深度学习入门Fast.ai 2.0上线!自带中文字幕,所有笔记、资源全部免费!
- 七夕节脱单“神助攻”!AI教你写情话
- Python初学者请注意!别这样直接运行python命令,否则电脑等于“裸奔”
- 一篇文章构建你的 Node.js 知识体系
- MySQL:The CHAR and VARCHAR Types
- 更新一个10年有效期的 Kubernetes 证书
- 哇,ElasticSearch多字段权重排序居然可以这么玩
- Python 自动化,Appium 凭什么使用 UiAutomator2?
- 我用几行 Python 自动化脚本完美解决掉了小姐姐的微信焦虑感
- 【设计模式】692- TypeScript 设计模式之发布-订阅模式
- 强网杯-upload
- 基于暗通道去雾算法
- 全套 | 人脸检测 & 人脸关键点检测 & 人脸卡通化
- 使用Jenkins Dashboard插件可视化部署
- 全面综述:图像特征提取与匹配技术