在一些算法题目中中,有的程序会被卡常数,就是说,程序虽然时间复杂度可以接受,但因为算法本身的时间常数过大,导致程序在一些算法竞赛中超时。这是,快读就显得尤为重要了。
当然,如果程序算法本身就不高效,快读就更加重要了,可以让一些暴力程序获得更多的测试点分数,如果数据不大甚至能AC,此时快读就是“得分法宝”.
在默认情况下, std::cin/std::cout 是极为迟缓的读入/输出方式,而 scanf/printf 比 std::cin/std::cout 快得多。

可是为什么会这样呢?有没有什么办法解决读入输出缓慢的问题呢?

关闭同步/解除绑定

std::ios::sync_with_stdio(false)

这个函数是一个“是否兼容 stdio”的开关,C++ 为了兼容 C,保证程序在使用了 printf 和 std::cout 的时候不发生混乱,将输出流绑到了一起。

这其实是 C++ 为了兼容而采取的保守措施。我们可以在进行 IO 操作之前将 stdio 解除绑定,但是在这样做之后要注意不能同时使用 std::cin/std::cout 和 scanf/printf 。
tie
tie 是将两个 stream 绑定的函数,空参数的话返回当前的输出流指针。

在默认的情况下 std::cin 绑定的是 std::cout ,每次执行 << 操作符的时候都要调用 flush() ,这样会增加 IO 负担。可以通过 std::cin.tie(0) (0 表示 NULL)来解除 std::cin 与 std::cout 的绑定,进一步加快执行效率。

代码实现

#include<iostream>
#include<algorithm>
using namespace std;
int x[1000005];
int main()
{//关闭同步流    ios::sync_with_stdio(false);cin.tie(NULL);cout.tie(NULL);//int t;cin >> t;while( t-- ){int n;cin >> n;for( int i  = 0 ; i < n ; i++ ) cin >> x[i];sort( x , x + n );for( int i  = 0 ; i < n ; i++ ){if( i == n - 1 ) cout << x[i] << endl;else cout << x[i] << " ";}}
}

如果编译开启了 C++11 或更高版本,建议使用 std::cin.tie(nullptr)

优化

读入优化

scanf 和 printf 依然有优化的空间,这就是本章所介绍的内容——读入和输出优化。

注意,本页面中介绍的读入和输出优化均针对整型数据,若要支持其他类型的数据(如浮点数),可自行按照本页面介绍的优化原理来编写代码。
原理
众所周知, getchar 是用来读入 1 byte 的数据并将其转换为 char 类型的函数,且速度很快,故可以用“读入字符——转换为整型”来代替缓慢的读入

每个整数由两部分组成——符号和数字

整数的 ‘+’ 通常是省略的,且不会对后面数字所代表的值产生影响,而 ‘-’ 不可省略,因此要进行判定

10 进制整数中是不含空格或除 0~9 和正负号外的其他字符的,因此在读入不应存在于整数中的字符(通常为空格)时,就可以判定已经读入结束

C 和 C++ 语言分别在 ctype.h 和 cctype 头文件中,提供了函数 isdigit , 这个函数会检查传入的参数是否为十进制数字字符,是则返回 true ,否则返回 false 。对应的,在下面的代码中,可以使用 isdigit(ch) 代替 ch >= ‘0’ && ch <= ‘9’ ,而可以使用 !isdigit(ch) 代替 ch <‘0’ || ch> ‘9’

代码实现

int read() {int x = 0, w = 1;char ch = 0;while (ch < '0' || ch > '9') {  // ch 不是数字时if (ch == '-') w = -1;        // 判断是否为负ch = getchar();               // 继续读入}while (ch >= '0' && ch <= '9') {  // ch 是数字时x = x * 10 + (ch - '0');  // 将新读入的数字’加’在 x 的后面// x 是 int 类型,char 类型的 ch 和 ’0’ 会被自动转为其对应的// ASCII 码,相当于将 ch 转化为对应数字// 此处也可以使用 (x<<3)+(x<<1) 的写法来代替 x*10ch = getchar();  // 继续读入}return x * w;  // 数字 * 正负号 = 实际数值
}

举例
读入 num 可写为 num=read();

输出优化

原理

同样是众所周知, putchar 是用来输出单个字符的函数

因此将数字的每一位转化为字符输出以加速

要注意的是,负号要单独判断输出,并且每次 %(mod)取出的是数字末位,因此要倒序输出

代码实现

void write(int x) {if (x < 0) {  // 判负 + 输出负号 + 变原数为正数x = -x;putchar('-');}if (x > 9) write(x / 10);  // 递归,将除最后一位外的其他部分放到递归中输出putchar(x % 10 + '0');  // 已经输出(递归)完 x 末位前的所有数字,输出末位
}

但是递归实现常数是较大的,我们可以写一个栈来实现这个过程

inline void write(int x) {static int sta[35];int top = 0;do {sta[top++] = x % 10, x /= 10;} while (x);while (top) putchar(sta[--top] + 48);  // 48 是 '0'
}

举例
输出 num 可写为 write(num);

更快的读入/输出优化

通过 fread 或者 mmap 可以实现更快的读入。其本质为一次性将输入文件读入一个巨大的缓存区,如此比逐个字符读入要快的多 ( getchar , putchar )。因为硬盘的多次读写速度是要慢于内存的,所以先一次性读到缓存区里再从缓存区读入要快的多。

更通用的是 fread ,因为 mmap 不能在 Windows 环境下使用。

fread 类似于参数为 “%s” 的 scanf ,不过它更为快速,而且可以一次性读入若干个字符(包括空格换行等制表符),如果缓存区足够大,甚至可以一次性读入整个文件。

对于输出,我们还有对应的 fwrite 函数

std::size_t fread(void* buffer, std::size_t size, std::size_t count,std::FILE* stream);
std::size_t fwrite(const void* buffer, std::size_t size, std::size_t count,std::FILE* stream);

使用示例: fread(Buf, 1, SIZE, stdin) ,表示从 stdin 文件流中读入 SIZE 个大小为 1 byte 的数据块到 Buf 中。

读入之后的使用就跟普通的读入优化相似了,只需要重定义一下 getchar。它原来是从文件中读入一个 char,现在变成从 Buf 中读入一个 char,也就是头指针向后移动一位。

char buf[1 << 20], *p1, *p2;
#define gc()                                                               \(p1 == p2 && (p2 = (p1 = buf) + fread(buf, 1, 1 << 20, stdin), p1 == p2) \? EOF                                                               \: *p1++)

fwrite 也是类似的,先放入一个 OutBuf[MAXSIZE] 中,最后通过 fwrite 一次性将 OutBuf 输出。

参考代码:

namespace IO {const int MAXSIZE = 1 << 20;
char buf[MAXSIZE], *p1, *p2;
#define gc()                                                               \(p1 == p2 && (p2 = (p1 = buf) + fread(buf, 1, MAXSIZE, stdin), p1 == p2) \? EOF                                                               \: *p1++)
inline int rd() {int x = 0, f = 1;char c = gc();while (!isdigit(c)) {if (c == '-') f = -1;c = gc();}while (isdigit(c)) x = x * 10 + (c ^ 48), c = gc();return x * f;
}
char pbuf[1 << 20], *pp = pbuf;
inline void push(const char &c) {if (pp - pbuf == 1 << 20) fwrite(pbuf, 1, 1 << 20, stdout), pp = pbuf;*pp++ = c;
}
inline void write(int x) {static int sta[35];int top = 0;do {sta[top++] = x % 10, x /= 10;} while (x);while (top) push(sta[--top] + '0');
}
}  // namespace IO

输入输出的缓冲

printf 和 scanf 是有缓冲区的。这也就是为什么,如果输入函数紧跟在输出函数之后/输出函数紧跟在输入函数之后可能导致错误。

刷新缓冲区

  1. 程序结束
  2. 关闭文件
  3. printf 输出 \r 或者 \n 到终端的时候(注:如果是输出到文件,则不会刷新缓冲区)
  4. 手动 fflush()
  5. 缓冲区满自动刷新
  6. cout 输出 endl

使输入输出优化更为通用

如果你的程序使用多个类型的变量,那么可能需要写多个输入输出优化的函数。下面给出的代码使用 C++ 中的 template 实现了对于所有整数类型的输入输出优化。

template <typename T>
inline T
read() {  // 声明 template 类,要求提供输入的类型T,并以此类型定义内联函数 read()T sum = 0, fl = 1;  // 将 sum,fl 和 ch 以输入的类型定义int ch = getchar();for (; !isdigit(ch); ch = getchar())if (ch == '-') fl = -1;for (; isdigit(ch); ch = getchar()) sum = sum * 10 + ch - '0';return sum * fl;
}

如果要分别输入 int 类型的变量 a, long long 类型的变量 b 和 __int128 类型的变量 c,那么可以写成

a = read<int>();
b = read<long long>();
c = read<__int128>();

完整带调试版

关闭调试开关时使用 fread() , fwrite() ,退出时自动析构执行 fwrite() 。
开启调试开关时使用 getchar() , putchar() ,便于调试。
若要开启文件读写时,请在所有读写之前加入 freopen() 。

// #define DEBUG 1  // 调试开关
struct IO {#define MAXSIZE (1 << 20)
#define isdigit(x) (x >= '0' && x <= '9')char buf[MAXSIZE], *p1, *p2;char pbuf[MAXSIZE], *pp;
#if DEBUG
#elseIO() : p1(buf), p2(buf), pp(pbuf) {}~IO() { fwrite(pbuf, 1, pp - pbuf, stdout); }
#endifinline char gc() {#if DEBUG  // 调试,可显示字符return getchar();
#endifif (p1 == p2) p2 = (p1 = buf) + fread(buf, 1, MAXSIZE, stdin);return p1 == p2 ? ' ' : *p1++;}inline bool blank(char ch) {return ch == ' ' || ch == '\n' || ch == '\r' || ch == '\t';}template <class T>inline void read(T &x) {register double tmp = 1;register bool sign = 0;x = 0;register char ch = gc();for (; !isdigit(ch); ch = gc())if (ch == '-') sign = 1;for (; isdigit(ch); ch = gc()) x = x * 10 + (ch - '0');if (ch == '.')for (ch = gc(); isdigit(ch); ch = gc())tmp /= 10.0, x += tmp * (ch - '0');if (sign) x = -x;}inline void read(char *s) {register char ch = gc();for (; blank(ch); ch = gc());for (; !blank(ch); ch = gc()) *s++ = ch;*s = 0;}inline void read(char &c) {for (c = gc(); blank(c); c = gc());}inline void push(const char &c) {#if DEBUG  // 调试,可显示字符putchar(c);
#elseif (pp - pbuf == MAXSIZE) fwrite(pbuf, 1, MAXSIZE, stdout), pp = pbuf;*pp++ = c;
#endif}template <class T>inline void write(T x) {if (x < 0) x = -x, push('-');  // 负数输出static T sta[35];T top = 0;do {sta[top++] = x % 10, x /= 10;} while (x);while (top) push(sta[--top] + '0');}template <class T>inline void write(T x, char lastChar) {write(x), push(lastChar);}
} io;

举例:

 #include <cstdio>#include <ctime>#include <iostream>#define LOOP 2 //循环次数#define DATA 100000 //数据规模struct dat{int i,s,c;dat(int a=0,int b=0,int k=0){i=a,s=b,c=k;}}dats[100+1];inline int Readi(){//快读 int x;int fh=1;char a=getchar();while('0'>a || '9'<a){//首先过滤掉非数字字符(注意符号的处理) if(a=='-') fh=-1;a=getchar();}while('0'<=a && a<='9'){//小技巧:x=x*10 可以进位 x=x*10+a-'0';a=getchar();}return x*fh;}void TryPrint(){FILE* fp=fopen("in.txt","w");//输出数据 for(int i=1;i<=DATA;++i)fprintf(fp,"%d ",i);fclose(fp);//关闭文件//一定要注意,不关闭文件,数据会写到缓冲区里,可能会丢数据) }void TryReadi(){int t;for(int i=1;i<=DATA;++i)t=Readi();}void TryReads(){int t;for(int i=1;i<=DATA;++i)scanf("%d",&t);}void TryReadc(){int t;for(int i=1;i<=DATA;++i)std::cin>>t; }int main(){freopen("out.txt","a",stdout);printf("数据规模:%d 循环次数:%d 单位:msn",DATA,LOOP);freopen("in.txt","r",stdin);//读入数据文件(只读)for(int k=1;k<=LOOP;++k){freopen("out.txt","a",stdout);freopen("in.txt","r",stdin);//读入数据文件(只读)TryPrint();clock_t p=clock();TryReadi();clock_t i=clock();TryReads();clock_t s=clock();printf("快读:%un",i-p);printf("scanf输入:%un",s-i);dats[k]=dat(i-p,s-i,0);}double sum=0.0;for(int k=1;k<=LOOP;++k)sum+=dats[k].i;printf("快读平均:%.0fn",sum/LOOP);sum=0.0;for(int k=1;k<=LOOP;++k)sum+=dats[k].s;printf("scanf平均:%.0f",sum/LOOP);return 0;}

测试结果

数据规模:1000000 循环次数:5 单位:ms
快读:50
scanf输入:1113
快读:50
scanf输入:1130
快读:58
scanf输入:1116
快读:52
scanf输入:1168
快读:51
scanf输入:1130
快读平均:52
scanf平均:1131

由此得,快读比scanf快了很多。所以大家在读入较大数据规模时,应尝试快读。
参考:https://article.itxueyuan.com/rdyEdp
https://oi-wiki.org/contest/io/入门ACM的极简教程而且很有深度,个人觉得很好

C++“读取“大量数据时--快读相关推荐

  1. 读取 XML 数据时,超出最大字符串内容长度配额 (8192)

    格式化程序尝试对消息反序列化时引发异常: 尝试对参数 http://www.thermo.com/informatics/xmlns/limswebservice 进行反序列化时出错: Process ...

  2. wcf教程-传递数据过大怎么配置?读取 XML 数据时,超出最大字符串内容长度配额 (8192)

    昨天测试客户端程序与服务端wcf时,出现一个错误: 读取 XML 数据时,超出最大字符串内容长度配额 (8192).通过更改在创建 XML 读取器时所使用的 XmlDictionaryReaderQu ...

  3. 【操作系统】磁盘转速速度为7200PRM,平均寻道时间为6ms,每磁道存储1MB数据。如果数据块大小为4KB,则读取一块数据时,数据平均传输速率为

    磁盘转速速度为7200PRM,平均寻道时间为6ms,每磁道存储1MB数据.如果数据块大小为4KB,则读取一块数据时,数据平均传输速率为 背景知识 7200PRM = 7200r/min = 120r/ ...

  4. Python使用xlrd读取Excel数据时,“xlrd.biffh.XLRDError: Excel xlsx file; not supported”报错的解决方法

    最近,在使用PyCharm编辑器进行xlrd读取Excel数据时,出现了"xlrd.biffh.XLRDError: Excel xlsx file; not supported" ...

  5. java读取字节流设置字节数组长度_java读取流数据时,字节缓存数组,第一次读取时,是否读满,才进行下次读取??...

    使用缓存字节数组读取java字节流时,第一次读取是,读满缓存字节数组大小,才进行下次读取,还是随机读一个小于数组大小的值,再进行下次读取??? 读取本地文件时,首次读取读满整个字节数组,在进行下次读取 ...

  6. Qt TCP通信readAll()读取接收数据时无法读完大数据量的解决法案

    在测试利用TCP传输图片的时候,服务器使用Qt的TCP类库.在TCP接收槽函数中使用readAll()成员函数的时候发现readAll()并不能读取全部的数据,比如,在客户端发送一张2MB的图像,但是 ...

  7. 记录:Warning C6385 : 从“p”中读取的数据无效: 可读大小为“n * sizeof(int)”个字节,但可能读取了“8”个字节。

    官网解释: 警告 C6385:无效数据:访问buffer-name,可读大小为size1字节,但size2字节可以读取:行:x.y 缓冲区的可读范围可能小于用于从缓冲区读取的索引. 尝试读取有效范围外 ...

  8. 解决读取雷达数据时 Decode PUP data 报错问题

    读取PUP雷达数据官网示例: 运行示例代码 from cinrad.io import PUP pup_file='Z_RADR_I_Z9591_20210901004700_P_DOR_SA_V_1 ...

  9. SparkStreaming读取Kakfa数据时发生OffsetOutOfRangeException异常

    参考文章:http://www.jianshu.com/p/791137760c14 运行SparkStreming程序一段时间后,发现产生了异常: ERROR JobScheduler: Error ...

最新文章

  1. 浏览器是怎样工作的:渲染引擎,HTML解析
  2. android短信发送乱码,解决CEMAPI发送中文短信时会乱码
  3. suse 安装mysql5.6_SuSE11安装MySQL5.6.40:RPM安装方式
  4. Linux USB 驱动开发(五)—— USB驱动程序开发过程简单总结
  5. 服务器被入侵了?反手溯源出入侵者画像【网络安全】
  6. 北方大学 ACM 多校训练赛 第十五场 蜘蛛牌A
  7. 用U盘作为启动盘做系统步骤
  8. 精选 26 个 Python 实用技巧,想秀技能先 Get 这份技术列表!
  9. 0基础学python做什么工作好-零基础学了8个月的Python,到底有啥感悟
  10. 大型项目编译注意事项
  11. mfc 子窗体 按钮不触发_python项目实战:pyQT5 实现窗体之间传值
  12. 基于comsol软件的三维单模光纤模拟
  13. 水经注地图下载器为什么叫万能下载器
  14. creo怎么返回上一步_creo拔模怎么用?creo拔模操作技巧图文详解
  15. Mac Excel 次坐标轴/双坐标轴/柱状图+折线图
  16. Virtualbox Ubuntu增强功能
  17. javascript 模块加载器——coolie
  18. Linux - last 命令
  19. 2018年兰博基尼突破5000台销量大关
  20. 钽电容黑色和黄色的区别

热门文章

  1. 【应用】Markdown 在线阅读器
  2. 搜狐 Hive SQL 血缘关系解析与应用
  3. 书评|《小岛经济学》
  4. 图片编辑软件有哪些?推荐几款好用的专业工具
  5. srsLTE系统安装教程
  6. 服务器虚拟机限速,Wiwiz虚拟机实现连接限速的方法
  7. 血泪总结:如何从微信小程序的坑跳进支付宝小程序的大坑
  8. 笔记本电脑键盘没坏却无法打字的情况
  9. oracle orclpdb是什么,oracle cdb、pdb参考
  10. 如何快速打造淘宝爆款