c++ 流对象之streambuf(可当做缓冲区使用)

摘要:
Streambuf有两个子类,分别是stringbuf和filebuf,这两个子类可以被实例化,我们常用的文件流和字符串流,内部的缓冲区就是这两个类。在每个流初始化的时候都会初始化相应的streambuf用来缓冲数据。答案是ofcourse而且,有了streambuf,缓冲区的管理和写入写出都非常方便,最好的是流对象有复制拷贝等构造函数可以方便参数传递等需要拷贝的情景。

在C++ 中引入了流的概念,我们很方便的通过流来读写文本数据和二进制数据,那么流对象的数据究竟是怎么存储的呢,为了搞清这个问题,先来看一看c++ 的 io 体系:

c++ 流对象之streambuf(可当做缓冲区使用)第1张

由图可以看出,在stream 的实现中,除了虚基类IOS_BASE之外,所有的类内部都有一个streambuf, streambuf 是一个虚基类(不能被实例化,因此所内部包含streambuf(这个虚基类而非其子类)的类也是虚基类),代表流对象内部的缓冲区,就是我们流操作中输入输出的内容在内存中的缓冲区。

Streambuf有两个子类,分别是stringbuf 和 filebuf,这两个子类可以被实例化,我们常用的文件流和字符串流,内部的缓冲区就是这两个类。

我们平常使用到的流基本是标准输入输出流,文件流和字符串流。在每个流初始化的时候都会初始化相应的streambuf(其实是它的子类)用来缓冲数据。

当我们用文件或者字符串初始化流的时候,流内部会保存该文件和字符串的信息,而在内部实例化一个streambuf用来缓冲数据,些数据时,当缓冲区满的时候再将数据写到文件或者字符串,读数据时当缓冲区没有数据时从文件或字符串读数据到缓冲区。

在文件流这种情况下,streambuf 是为了避免大量的IO 操作

在字符串流的情况下,streambuf (其实是套在上面的流对象)是为了提供字符串的格式化读取和输出操作(想象字符串是你从键盘输入的数据)

所以streambuf 可以看作一块缓冲区,用来存储数据,在这种情况下,我们常常在程序中用的 char数组缓冲区是不是可以被替代呢?答案是of course

而且,有了streambuf ,缓冲区的管理和写入写出都非常方便,最好的是流对象有复制拷贝等构造函数可以方便参数传递等需要拷贝的情景。

但是streambuf 本身是个虚基类,不能实例化,所以要用streambuf 就需要自己继承streambuf 写一个新的类出来才能用,这个实现方法最后介绍,好在c++ 标准类库实现了两个子类stringbuf 和 filebuf ,所以我们可以选stringbuf 来作为我们的数据缓冲对象(不选filebuf 是因为它的实现和文件紧耦合的,只适合文件流)

流对象有一个构造函数是通过streambuf 来构造:

    1. stringbuf sb;
    2. istream is(&sb);

有了流对象我们就可以在流上进行各种输入输出操作,输入会从缓冲区读数据,输出会将数据写到缓冲区

注意对缓冲区的读写一定要注意方法,流符号是格式话输入输出,get,put,read,write等是二进制读写。

格式化输入的内容应当格式化读取,二进制写入应当二进制读取否则会出现写入和读出数据不一致的问题

格式化写入一个int 数据时,会将该数据每位分离出来,按照字符编码写到缓冲区,例如 int x= 123, 格式化写入以后缓冲区存以后,缓冲区有三个字节分别存放1,2,3的字符编码。格式化读出是相反的过程,将读到的字符转成相应的类型的数据

二进制写入时进行直接的内存拷贝不做任何动作,例如 int x = 123 二进制写入后(二进制写时需要取地址,转成char* 并指出要写入的字节数,如f.write((char*)&x,sizeof(int))

写完后缓冲区的数据是0x0000007b,是计算机内存中对123 的内存的完全拷贝

下面是缓冲区使用的情景:

考虑一个生产者,消费者的问题,线程A 生成的数据,线程B读取,可以解决的方案如下:

1. 设立全局变量,缓冲数据,A,B都可以访问(在这种情况下,A 生产的时候要考虑缓冲区是否够用,B读取的时候要判断当前是否有有效数据可读,而且很难设计一个合理分配内存的缓冲区(想象A生产的数据有时很大,有时很小))

2.网络通信(TCP,UDP)

3. streambuf 登场,有了streambuf配合stream, A就像正常操作流一样往流对象里塞数据,而B 就像正常操作流一样从流里面读数据,不用关心其他问题,只要这两个流的sterambuf 是同一个对象。

上一段代码:
————————————————

#include <iostream>#include <streambuf>#include <sstream>#include <fstream>#include <string>#include <cstring>#include <memory>#include <thread>
using namespacestd;
stringbuf buf;
istream in(&buf);
ostream out(&buf);
bool flag = false;
voidthreadb() {
    chardata;
    while (true) {
        if(flag) {
            in >>data;
            cout << "thread B recv:" << data <<endl;
            flag = false;
        }
    }
}
intmain() {
    thread consumer(threadb);
    chardata;
    while (true) {
        cin >>data;
        out <<data;
        flag = true;
    }
    return 0;
}

在特殊的情景下可以实现自己的streambuf类,自己实现的类必须继承streambuf 类,自定义的streambuf 必须实现overflow,underflow,uflow 等方法,其中overflow在输出缓冲区不够用时调用,underflow和uflow在输入缓冲区无数据时调用,区别是uflow 会让读取位置前进一位,而underflow不会。sreambuf 内部维护着六个指针 eback,gptr,egptr,pbase,pptr,epptr.分别指向读取缓冲区的头,当前读取位置,尾,写缓冲区的头,当前写位置,尾(实际上这几个指针指向同一段缓冲区)

自定义实现方式要注意要在该返回EOF的时候,返回EOF,underflow和uflow都有可能返回EOF,一旦返回了EOF则标志着流结束,之后对流的操作无效。

如下代码实现了一个自定义的streambuf:

#include <iostream>#include <streambuf>#include <sstream>#include <fstream>#include <string>#include <cstring>#include <memory>
using namespacestd;
class mybuf : publicstreambuf {
public:
    enum{ SIZE = 10};
    mybuf() {
        memset(buffer, 'j', 10);
        //buffer[3] = ' ';
setbuf(buffer, SIZE);
    }
    voidlog() {
        cout <<hex<<gptr() <<endl;
    }
protected:
    int_type overflow( int_type c) {
        cout << "overflow" <<endl;
        returnc;
    }
    streambuf* setbuf(char*s, streamsize n) {
        setp(s, s +n);
        setg(s, s, s +n);
        return this;
    }
    int_type underflow() override{
        cout << "here"<<endl;
        memset(buffer, 'w', 10);
        setg(buffer, buffer, buffer+10);
        return ' ';
    }
    int_type uflow() override{
        cout << "uflow" <<endl;
        memset(buffer, 'x', 10);
        setg(buffer, buffer, buffer + 10);
        returnEOF;
    }
private:
    charbuffer[SIZE];
};
intmain() {
    mybuf buf;
    char test[2000];
    memset(test, 'a', 2000);
    //buf.pubsetbuf(test, 1000);
    stringhh;
    stringxx;
    istream in(&buf);
    ostream tt(&buf);
    in>>hh;
    cout << hh <<endl;
    //tt.write(test, 9);
    in >>xx;
    in.read(test, 11);
    cout<< xx <<endl;
    cout << "end" <<endl;
    return 0;
}

免责声明:文章转载自《c++ 流对象之streambuf(可当做缓冲区使用)》仅用于学习参考。如对内容有疑问,请及时联系本站处理。

上篇几种上传文件组件的性能比较【转载】哈希表(hash)下篇

宿迁高防,2C2G15M,22元/月;香港BGP,2C5G5M,25元/月 雨云优惠码:MjYwNzM=

相关文章

java 实现基于opencv全景图合成

因项目需要,自己做了demo,从中学习很多,所以分享出来,希望有这方面需求的少走一些弯路,opencv怎么安装网上教程多多,这里不加详细说明,我安装的opencv-3.3.0  如上图所示,找到相应的jar包,这里讲一下如何这个jar如何导入Maven仓库 mvn install:install-file -Dfile=D:opencv-3.0.0ope...

C语言浮点数运算

   C语言标准C89里规定了3种浮点数,float型、double型和long double型,常见的浮点型长度为float型占4个字节,double型占8个字节,long double型长度要大于等于double型,下面将以float型为例进行介绍,double型和long double型只是比float型位数长,原理是一样的 。   float型可以...

linux高编信号-------令牌桶实现

main.c /********************************* *功能:每秒从文件读N个字节(N可控) *使用信号机制实现令牌桶:解决数据流不均匀传输 * *****************************/ #include <stdio.h> #include <stdlib.h>...

解决bind错误 bind: Address already in use

关于bind错误的处理:bind: Address already in use 原因: 操作系统没有立即释放端口 解决一: 等待一段时间运行网络程序即可 解决二:通过setsockopt进行设置,关键代码如下。 1 //解决在close之后会有一个WAIT_TIME,导致bind失败的问题 2 int val = 1; 3...

简单的 nginx 多站点配置

测试环境:基于CentOS6.8 编译安装LNMP(http://www.cnblogs.com/afee666/p/6836161.html) 一 需求 在一个 VPS 主机上配置 web 服务器,实现通过一个 IP 访问多个站点或域名。 假设: IP地址: 66.88.33.11 域名1 test1.example.com放在 /web/www/tes...

unity客户端与c++服务器之间的简单通讯_1

// 服务器 # pragma once using namespace std; # include <iostream> # include <string> # include <stdio.h> # include <winsock2.h> # pragma comment(lib,...