zoukankan      html  css  js  c++  java
  • C++:数据流和缓冲区

    (1):C++之自定义的input缓冲区

       原文链接:http://hi.baidu.com/nicker2010/item/d0c4cd2a7caf27c4ddf69aeb

    input stream用三个指针维护一个read缓冲区,这些指针可以透过eback(),gptr(),egptr()获得
        1.eback()(意思是 end back)是缓冲区的起始位置,或者回退区的尾端,如果不采取特殊措施,
          字符最多只能被回退到这个位置
        2.gptr()(意思是 get pointer)是当前的读取位置
        3.egptr()(意思是 end get pointer)是input buffer的尾端

        函数sungetc()(由input stream的unget()调用)或者
        sputbackc()(由input  stream的putback()调用)
        可以用来存储stream缓冲区最后一次读取前的状态

        函数sgetc()sbumpc()可以读取一个字符。二者的差别在于:后者会让读取指针前进,而前者不会

        如果缓冲区读取完毕(gptr() == egptr()),就不再有可用字符了,此时对缓冲区就必须重新获得补给
        这项工作可以由虚函数 underflow()完成,它会负责读取数据

        如果没有字符,函数sbumpc()会调用虚函数uflow(),而uflow()的缺省行为就是调用underflow(),
        并移动(前进)读取指针。基类basic_streambuf中对underflow()的缺省行为是返回EOF

        函数sgetn()用于一次读取多个字符,这个函数把任务交给xsgetn(),
        后者的缺省做法是多美歌字符调用sbumpc().我们可以改进xsgetn()来优化对多个字符的读取

        对input缓冲区,要么就必须建立一个缓冲区,要么就至少实现underflow()和uflow()
        任何一个具备字符读取功能的stream缓冲区,都必须实现underflow()

        成员函数setg()可以建立一个read缓冲区,它有三个参数:
        1.一个指针,指向缓冲区头部(eback())
        2.一个指针,指向当前读取位置(gptr())
        3.一个指针,指向缓冲区尾部(egptr())

        下面就实现一个我们自定义的input stream buffer:

    extern "C"
    {
        int read(int fd,char* buf,int num);
    }
    class InBuffer : public std::streambuf
    {
    protected:
        static const int bufferSize = 10; ///缓冲区大小
        char buffer[bufferSize];
    public:
        /**建立read缓冲区,完全清空*/
        InBuffer()
        {
            setg(buffer+4,buffer+4,buffer+4);
        }
    protected:
        virtual int_type underflow()
        {
            if(gptr() < egptr())
                return *gptr();
            int numPutback = gptr()-eback();
            if(numPutback > 4) numPutback=4;
            /**把缓冲区最后几个字符移到头部,便于回退*/
            std::memcpy(buffer+(4-numPutback), gptr()-numPutback, numPutback);
            int num = read(0,buffer+4,bufferSize-4); ///读取数据
            if(num <= 0) return EOF;
            setg(buffer+(4-numPutback),buffer+4,buffer+4+num);
            return *gptr();
        }
    };
    
    void testMyInputBuffer()
    {
        InBuffer buf;
        std::istream in(&buf);
    
        char c;
        for(int i=0;i<20;++i)
        {
            in.get(c);
            cout<<c<<flush;
            if(i == 10)
            {
                in.unget();  ///放回2个字符,即第9,10个字符被打印两次
                in.unget();
            }
        }
        cout<<endl;
    }

    (2):C++对缓冲区的理解

    摘自:http://www.vckbase.com/document/viewdoc/?id=1897

    什么是缓冲区
    缓冲区又称为缓存,它是内存空间的一部分。也就是说,在内存空间中预留了一定的存储空间,这些存储空间用来缓冲输入或输出的数据,这部分预留的空间就叫做缓冲区。
    缓冲区根据其对应的是输入设备还是输出设备,分为输入缓冲区和输出缓冲区。

    为什么要引入缓冲区
    我们为什么要引入缓冲区呢?
    比如我们从磁盘里取信息,我们先把读出的数据放在缓冲区,计算机再直接从缓冲区中取数据,等缓冲区的数据取完后再去磁盘中读取,这样就可以减少磁盘的读写次数,再加上计算机对缓冲区的操作大大快于对磁盘的操作,故应用缓冲区可大大提高计算机的运行速度。
    又比如,我们使用打印机打印文档,由于打印机的打印速度相对较慢,我们先把文档输出到打印机相应的缓冲区,打印机再自行逐步打印,这时我们的CPU可以处理别的事情。
    现在您基本明白了吧,缓冲区就是一块内存区,它用在输入输出设备和CPU之间,用来缓存数据。它使得低速的输入输出设备和高速的CPU能够协调工作,避免低速的输入输出设备占用CPU,解放出CPU,使其能够高效率工作。

    缓冲区的类型
    缓冲区 分为三种类型:全缓冲、行缓冲和不带缓冲。
    1、全缓冲
    在这种情况下,当填满标准I/O缓存后才进行实际I/O操作。全缓冲的典型代表是对磁盘文件的读写。
    2、行缓冲
    在这种情况下,当在输入和输出中遇到换行符时,执行真正的I/O操作。这时,我们输入的字符先存放在缓冲区,等按下回车键换行时才进行实际的I/O操作。典型代表是键盘输入数据。
    3、不带缓冲
    也就是不进行缓冲,标准出错情况stderr是典型代表,这使得出错信息可以直接尽快地显示出来。
    缓冲区的刷新
    下列情况会引发缓冲区的刷新:
    1、缓冲区满时;
    2、执行flush语句;
    3、执行endl语句;
    4、关闭文件。
    可见,缓冲区满或关闭文件时都会刷新缓冲区,进行真正的I/O操作。另外,在C++中,我们可以使用flush函数来刷新缓冲区(执行I/O操作并清空缓冲区),如:
    cout<<flush; //将显存的内容立即输出到显示器上进行显示

    endl控制符的作用是将光标移动到输出设备中下一行开头处,并且清空缓冲区。
    cout<<endl;
    相当于
    cout<<” ” <<flush;

    通过实例演示说明

    1、文件操作演示全缓冲
    创建一个控制台工程,输入如下代码:

    #include <fstream>using namespace std;int main(){    //创建文件test.txt并打开ofstream outfile("test.txt");    //向test.txt文件中写入4096个字符’a’for(int n=0;n<4096;n++){outfile<<'a';}    //暂停,按任意键继续system("PAUSE");        //继续向test.txt文件中写入字符’b’,也就是说,第4097个字符是’b’outfile<<'b';    //暂停,按任意键继续system("PAUSE");return 0;}

    上面这段代码很容易理解,已经在代码内部作了注释。
    编写这段小代码的目的是验证WindowsXP下全缓冲的大小是4096个字节,并验证缓冲区满后会刷新缓冲区,执行真正的I/O操作。

    编译并执行,运行结果如下:

    此时打开工程所在文件夹下的test.txt文件,您会发现该文件是空的,这说明4096个字符“a”还在缓冲区,并没有真正执行I/O操作。敲一下回车键,窗口变为如下:

    此时再打开test.txt文件,您就会发下该文件中已经有了4096个字符“a”。这说明全缓冲区的大小是4K(4096),缓冲区满后执行了I/O操作,而字符“b”还在缓冲区。
    再次敲一下回车键,窗口变为如下:

    此时再打开test.txt文件,您就会发现字符“b”也在其中了。这一步验证了文件关闭时刷新了缓冲区。

    2、键盘操作演示行缓冲
    先介绍getchar()函数。
    函数原型:int getchar(void);
    说明:当程序调用getchar()函数时,程序就等着用户按键,用户输入的字符被存放在键盘缓冲区中,直到用户按回车为止(回车字符也放在缓冲区中)。当用户键入回车之后,getchar()函数才开始从键盘缓冲区中每次读入一个字符。也就是说,后续的getchar()函数调用不会等待用户按键,而直接读取缓冲区中的字符,直到缓冲区中的字符读完后,才重新等待用户按键。
    不知道您明白了没有,再通俗一点讲,当程序调用getchar()函数时,程序就等着用户按键,并等用户按下回车键返回。期间按下的字符存放在缓冲区,第一个字符作为函数返回值。继续调用getchar()函数,将不再等用户按键,而是返回您刚才输入的第2个字符;继续调用,返回第3个字符,直到缓冲区中的字符读完后,才等待用户按键。
    如果您还没有明白,只能怨我表达能力有限,您可以结合以下实例体会。

    创建一个控制台工程,输入如下代码:

    #include <iostream>using namespace std;int main(){char c;//第一次调用getchar()函数//程序执行时,您可以输入一串字符并按下回车键,按下回车键后该函数才返回c=getchar();    //显示getchar()函数的返回值cout<<c<<endl;    //暂停system("PAUSE"); //循环多次调用getchar()函数//将每次调用getchar()函数的返回值显示出来//直到遇到回车符才结束while((c=getchar())!='
    '){printf("%c",c);}    //暂停system("PAUSE");return 0;}

    这段小代码也很简单,同样在代码内部都有注释。
    getchar()函数的执行就是采用了行缓冲。第一次调用getchar()函数,会让程序使用者(用户)输入一行字符并直至按下回车键 函数才返回。此时用户输入的字符和回车符都存放在行缓冲区。
    再次调用getchar()函数,会逐步输出行缓冲区的内容。
    好了,本人表达能力有限,还是编译运行程序,通过运行结果自己领会吧。

    编译运行程序,会提示您输入字符,您可以交替按下一些字符,如下:
     
    您一直按下去,您就会发现当您按到第4094个字符时,不允许您继续输入字符。这说明行缓冲区的大小也是4K。
    此时您按下回车键,返回第一个字符’a’,如下图:

    继续敲一下回车键,将缓冲区的其它的字符全部输出,如下图:

    3、标准错误输出不带缓冲
    如错误输出时使用:

    cerr<<”错误,请检查输入的参数!”;

    这条语句等效于:
    fprintf(stderr, ”错误,请检查输入的参数!”);

    好了,就说到这吧,祝您好运,希望能对您有所帮助。


    (3):内存日志系统

    原文链接:http://www.cnblogs.com/JefferyZhou/archive/2010/07/06/1771802.html

    在第一篇中主要由讨论日志的需求以及接口设计,这里阐述一下各个部分的实现过程。在设计过程中我们把整个系统分为三个部分:模块buffer, 线程buffer, 和内存日志系统接口。

    首先我们需要明确的是我们的写入是正对线程来说,不同线程需要在不同的内存段进行写入操作,而线程内的写入操作是针对模块而来的。所以正在的写入操作在模块buffer里面,这里我们直接实现 一个前面设计的模块buffer:

    //模块buffer
    template<int nMaxBuffLen = MEMLOGSYS_MAX_BUFFE_LEN>
    classIModuleBufferImp
    {
    public:
        typedefunsigned int size_t;
     
        staticconst unsigned int m_snMaxBuffLen = nMaxBuffLen;
     
        voidWriteLog( constchar* szBufferCache)                                  //写日志
        {
            WriteLog_Imp(GetTimeSting().c_str());
            WriteLog_Imp(szBufferCache);
            WriteLog_Imp("
    ");
        }
     
        constchar* GetMemLog()                                        //获取当前模块的日志信息
        {
            m_Buffer[m_snMaxBuffLen] = 0;
            returnm_Buffer;
        }
     
        constchar* GetModuleName()const
        {
            returnm_szModuleName;
        }
         
        staticIModuleBufferImp* CreateModuleBuffer(constchar* pszModuleName)
        {
            IModuleBufferImp* pModuleBuffer =new IModuleBufferImp(pszModuleName);
            returnpModuleBuffer;
        }
     
        staticvoid DestroyModuleBuffer(IModuleBufferImp* pModuleBuffer)
        {
            SAFE_DELETE(pModuleBuffer);
        }
     
        staticstd::string GetTimeSting()
        {
            tm* pTimeStruct = Time::GetLOCALTime();
     
            charszTempBuffer[128] = {0};
             
            sprintf_s(szTempBuffer,"%.2d.%.2d.%.2d:%.2d:%.2d:%.10d  ",pTimeStruct->tm_mon,pTimeStruct->tm_mday,pTimeStruct->tm_hour,pTimeStruct->tm_min,pTimeStruct->tm_sec, GetTickCount());
             
            returnstd::string(szTempBuffer);
        }
    protected:
     
        explicitIModuleBufferImp(constchar* pszModuleName)
        {
            if(pszModuleName)
            {
                sprintf_s(m_szModuleName, MEMLOGSYS_MAX_MODULENAME_LEN-1,"%s", pszModuleName);
                m_szModuleName[MEMLOGSYS_MAX_MODULENAME_LEN-1] = 0;
            }else
            {
                memset(m_szModuleName, 0, MEMLOGSYS_MAX_MODULENAME_LEN);
                sprintf_s(m_szModuleName, MEMLOGSYS_MAX_MODULENAME_LEN-1,"%s", MEMLOGSYS_DEFAULT_MODULENAME);
            }
     
            m_WriteOffSet = 0; 
            memset(m_Buffer, 0, m_snMaxBuffLen);
        }
     
        ~IModuleBufferImp()
        {
            ;
        }
     
        boolWriteLog_Imp(constchar* pszLog)
        {
            if(pszLog)
            {
                intlen = strlen(pszLog);
     
                if(len >= m_snMaxBuffLen)   //超过最大值了
                {
                    returnfalse;
                }  
     
                if(m_WriteOffSet + len > m_snMaxBuffLen)
                {  
                    memcpy(m_Buffer+m_WriteOffSet, pszLog, m_snMaxBuffLen - m_WriteOffSet);
                    memcpy(m_Buffer,pszLog + (m_snMaxBuffLen - m_WriteOffSet), len-m_snMaxBuffLen + m_WriteOffSet);
                }else
                {
                    memcpy(m_Buffer+m_WriteOffSet, pszLog, len);
                }
     
                m_WriteOffSet += len;
     
                if(m_WriteOffSet >= m_snMaxBuffLen)
                {
                    m_WriteOffSet -= m_snMaxBuffLen;
                }
     
                returntrue;
            }
            returnfalse;
        }
         
     
    private:
        IModuleBufferImp(constIModuleBufferImp& rstIModuleBuffer){;};              //禁用
        IModuleBufferImp& operator = (constIModuleBufferImp& rstIModuleBuffer) {;};//禁用
     
     
        charm_szModuleName[MEMLOGSYS_MAX_MODULENAME_LEN];
        intm_WriteOffSet;
        charm_Buffer[m_snMaxBuffLen + 1];
    };

    上面我们提供了一个简单的工厂函数来创建模块buffer, 并置拷贝构造函数和赋值构造函数为私有防止恶意拷贝。

    在上面的实现过程中,有几个小细节需要注意:

      1。 我们是需要一个线程安全的写入类,所以在类的实现中不能出现任何多线程共享的可写字段。

      2。 线程Buffer 是一个循环日志,必须保证最后记录的完整性,以及记录的循环可读性,

                必须在写入记录的时候保证字符串结尾的0不屏蔽,数组中的有效内容。也就是在Write_Imp()里面用的是memcpy, 当然也可以使用 sprintf 系列,

                (读者也可以自行改为使用sprintf系列,如果使用sprintf系列需要仔细阅读相关文档, 比如 sprintf_s 保证最后给你加一个 0, 超过缓冲区会报错获设置错误标志位 : 这里有一个小tips,sprintf_s ,不仅在你的缓冲区里面输入内容,而且在后面加一个0,不仅加一个0,还会对0后面的有效缓冲区拿来做其他用途)

         3。 为了保证不破坏任何有效的完整记录,以及循环可读性,申请的缓冲区必须比可写缓冲区多一个字节用来存字符串结束符.

         4。 把构造函数定义为保护,提供一个简单工厂函数,这里把缓冲区定义为一个数组,而不是一个动态申请的空间,然后,这个时候工厂函数全部是new出的模块buff,所有你定义任何大小的缓冲区,只要不操作当前系统最大可申请内存数量就不会出现错误。

    Sign Clown 2010.7.6 0:59 HDPY

    大概实现就是上面了。后面另外两个部分,线程buff,系统管理接口的具体实现,会陆续展示。

    [本文原创,转载请注明出处,在文章末尾提供原文链接http://www.cnblogs.com/JefferyZhou/,否则一旦发现,将按字节每人民币收费,绝不论价]


  • 相关阅读:
    Fire and Motion[转载]
    HLSL2GLSL v0.9 Released
    CEGUI Release of 0.5.0 stable by CrazyEddie 6th November 2006
    MapInfo 连接Oracle
    MapInfo连接SQLServer
    视图的创建及使用(sql server 2005)
    MapInfo 建立永久表
    MapInfo Update Feature
    MapInfo导入.TAB和.mws的方法
    触发器的创建及使用(sqlserver 2000)
  • 原文地址:https://www.cnblogs.com/wishchin/p/9200367.html
Copyright © 2011-2022 走看看