none
关于数据存储问题 RRS feed

  • 问题

  • 我想存放一些字符串的数据,有两种选择。举例字符串“CHINA”
    (1)直接Archive CString 进去,最后Archive 出来,最省事。
    (2)先把长度 5 写进去,再把一个个 char给写进去,共5个char。

    我想问一下,这两种方法有什么区别,优缺点?哪个节省存储空间。

    2009年9月18日 3:21

答案

  • 在深入这前,通过上述的演示,我们应该知道在string类中,要实现写时才拷贝,需要解决两个问题,一个是内存共享,一个是Copy-On-Wirte,这两个主题会让我们产生许多疑问,还是让我们带着这样几个问题来学习吧:
    1、 Copy-On-Write的原理是什么?
    2、 string类在什么情况下才共享内存的?
    3、 string类在什么情况下触发写时才拷贝(Copy-On-Write)?
    4、 Copy-On-Write时,发生了什么?
    5、 Copy-On-Write的具体实现是怎么样的?
    喔,你说只要看一看STL中stirng的源码你就可以找到答案了。当然,当然,我也是参考了string的父 模板类basic_string的源码。但是,如果你感到看STL的源码就好像看机器码,并严重打击你对C++自信心,乃至产生了自己是否懂C++的疑 问,如果你有这样的感觉,那么还是继续往下看我的这篇文章吧。
    OK,让我们一个问题一个问题地探讨吧,慢慢地所有的技术细节都会浮出水面的。
    2.3、         Copy-On-Write的原理是什么?
    有一定经验的程序员一定知道,Copy-On-Write一定使用了“引用计数”,是的,必然有一个变量类似于 RefCnt。当第一个类构造时,string的构造函数会根据传入的参数从堆上分配内存,当有其它类需要这块内存时,这个计数为自动累加,当有类析构 时,这个计数会减一,直到最后一个类析构时,此时的RefCnt为1或是0,此时,程序才会真正的Free这块从堆上分配的内存。
    是的,引用计数就是string类中写时才拷贝的原理!
    不过,问题又来了,这个RefCnt该存在在哪里呢?如果存放在string类中,那么每个string的实例 都有各自的一套,根本不能共有一个RefCnt,如果是声明成全局变量,或是静态成员,那就是所有的string类共享一个了,这也不行,我们需要的是一 个“民主和集中”的一个解决方法。这是如何做到的呢?呵呵,人生就是一个糊涂后去探知,知道后和又糊涂的循环过程。别急别急,在后面我会给你一一道来的。
    2.3.1、      string类在什么情况下才共享内存的?
    这个问题的答案应该是明显的,根据常理和逻辑,如果一个类要用另一个类的数据,那就可以共享被使用类的内存了。这是很合理的,如果你不用我的,那就不用共享,只有你使用我的,才发生共享。
    使用别的类的数据时,无非有两种情况,1)以别的类构造自己,2)以别的类赋值。第一种情况时会触发拷贝构造函 数,第二种情况会触发赋值操作符。这两种情况我们都可以在类中实现其对应的方法。对于第一种情况,只需要在string类的拷贝构造函数中做点处理,让其 引用计数累加;同样,对于第二种情况,只需要重载string类的赋值操作符,同样在其中加上一点处理。

     

    唠叨几句:
    1)构造和赋值的差别
    对于前面那个例程中的这两句:
           string str1 = "hello world";
           string str2 = str1;
    不要以为有“=”就是赋值操作,其实,这两条语句等价于:
           string str1 ("hello world");   //调用的是构造函数
           string str2 (str1);            //调用的是拷贝构造函数
    如果str2是下面的这样情况:
        string str2;      //调用参数默认为空串的构造函数:string str2(“”);
    str2 = str1;     //调用str2的赋值操作:str2.operator=(str1);
    2) 另一种情况
           char tmp[]=”hello world”;
         string str1 = tmp;
           string str2 = tmp;
        这种情况下会触发内存的共享吗?想当然的,应该要共享。可是根据我们前面所说的共享内存的情况,两个string类的声明和初始语句并不符合我前述的两种情况,所以其并不发生内存共享。而且,C++现有特性也无法让我们做到对这种情况进行类的内存共享。
    2.3.2、      string类在什么情况下触发写时才拷贝(Copy-On-Write)?
    哦,什么时候会发现写时才拷贝?很显然,当然是在共享同一块内存的类发生内容改变时,才会发生Copy-On- Write。比如string类的[]、=、+=、+、操作符赋值,还有一些string类中诸如insert、replace、append等成员函 数,包括类的析构时。
    修改数据才会触发Copy-On-Write,不修改当然就不会改啦。这就是托延战术的真谛,非到要做的时候才去做。
    2.3.3、      Copy-On-Write时,发生了什么?
    我们可能根据那个访问计数来决定是否需要拷贝,参看下面的代码:

     

    If ( RefCnt>0 ) {
        char* tmp = (char*) malloc(strlen(_Ptr)+1);
        strcpy(tmp, _Ptr);
        _Ptr = tmp;
    }
    上面的代码是一个假想的拷贝方法,如果有别的类在引用(检查引用计数来获知)这块内存,那么就需要把更改类进行“拷贝”这个动作。
    我们可以把这个拷的运行封装成一个函数,供那些改变内容的成员函数使用。

    麻烦把正确答案设为解答。
    • 已标记为答案 Tim Li 2009年9月25日 10:29
    2009年9月23日 1:12
    版主
  • 保存一个字符串值有两种方法,要么前面写长度,要么连最后的'\0'一起写进去。如果你是unicode的话最后的应该是L'\0',两个字节长。我不知道Archive的原理是什么,不过你如何不同其实最后也就节省了3个字节(sizeof(int) - sizeof('\0'))而已。我觉得这是无所谓的。最大的区别是你在读的时候代码会截然不同,你觉得哪种你读起来方便你就用哪种吧。

    • 已标记为答案 Tim Li 2009年9月25日 10:29
    2009年9月23日 1:34

全部回复

  • 整体赋值,CString是具有CopyOnWrite特性的。
    麻烦把正确答案设为解答。
    2009年9月18日 7:07
    版主
  • 不明白,咋回事?
    哪个节省存储空间?
    2009年9月19日 13:00
  • 在深入这前,通过上述的演示,我们应该知道在string类中,要实现写时才拷贝,需要解决两个问题,一个是内存共享,一个是Copy-On-Wirte,这两个主题会让我们产生许多疑问,还是让我们带着这样几个问题来学习吧:
    1、 Copy-On-Write的原理是什么?
    2、 string类在什么情况下才共享内存的?
    3、 string类在什么情况下触发写时才拷贝(Copy-On-Write)?
    4、 Copy-On-Write时,发生了什么?
    5、 Copy-On-Write的具体实现是怎么样的?
    喔,你说只要看一看STL中stirng的源码你就可以找到答案了。当然,当然,我也是参考了string的父 模板类basic_string的源码。但是,如果你感到看STL的源码就好像看机器码,并严重打击你对C++自信心,乃至产生了自己是否懂C++的疑 问,如果你有这样的感觉,那么还是继续往下看我的这篇文章吧。
    OK,让我们一个问题一个问题地探讨吧,慢慢地所有的技术细节都会浮出水面的。
    2.3、         Copy-On-Write的原理是什么?
    有一定经验的程序员一定知道,Copy-On-Write一定使用了“引用计数”,是的,必然有一个变量类似于 RefCnt。当第一个类构造时,string的构造函数会根据传入的参数从堆上分配内存,当有其它类需要这块内存时,这个计数为自动累加,当有类析构 时,这个计数会减一,直到最后一个类析构时,此时的RefCnt为1或是0,此时,程序才会真正的Free这块从堆上分配的内存。
    是的,引用计数就是string类中写时才拷贝的原理!
    不过,问题又来了,这个RefCnt该存在在哪里呢?如果存放在string类中,那么每个string的实例 都有各自的一套,根本不能共有一个RefCnt,如果是声明成全局变量,或是静态成员,那就是所有的string类共享一个了,这也不行,我们需要的是一 个“民主和集中”的一个解决方法。这是如何做到的呢?呵呵,人生就是一个糊涂后去探知,知道后和又糊涂的循环过程。别急别急,在后面我会给你一一道来的。
    2.3.1、      string类在什么情况下才共享内存的?
    这个问题的答案应该是明显的,根据常理和逻辑,如果一个类要用另一个类的数据,那就可以共享被使用类的内存了。这是很合理的,如果你不用我的,那就不用共享,只有你使用我的,才发生共享。
    使用别的类的数据时,无非有两种情况,1)以别的类构造自己,2)以别的类赋值。第一种情况时会触发拷贝构造函 数,第二种情况会触发赋值操作符。这两种情况我们都可以在类中实现其对应的方法。对于第一种情况,只需要在string类的拷贝构造函数中做点处理,让其 引用计数累加;同样,对于第二种情况,只需要重载string类的赋值操作符,同样在其中加上一点处理。

     

    唠叨几句:
    1)构造和赋值的差别
    对于前面那个例程中的这两句:
           string str1 = "hello world";
           string str2 = str1;
    不要以为有“=”就是赋值操作,其实,这两条语句等价于:
           string str1 ("hello world");   //调用的是构造函数
           string str2 (str1);            //调用的是拷贝构造函数
    如果str2是下面的这样情况:
        string str2;      //调用参数默认为空串的构造函数:string str2(“”);
    str2 = str1;     //调用str2的赋值操作:str2.operator=(str1);
    2) 另一种情况
           char tmp[]=”hello world”;
         string str1 = tmp;
           string str2 = tmp;
        这种情况下会触发内存的共享吗?想当然的,应该要共享。可是根据我们前面所说的共享内存的情况,两个string类的声明和初始语句并不符合我前述的两种情况,所以其并不发生内存共享。而且,C++现有特性也无法让我们做到对这种情况进行类的内存共享。
    2.3.2、      string类在什么情况下触发写时才拷贝(Copy-On-Write)?
    哦,什么时候会发现写时才拷贝?很显然,当然是在共享同一块内存的类发生内容改变时,才会发生Copy-On- Write。比如string类的[]、=、+=、+、操作符赋值,还有一些string类中诸如insert、replace、append等成员函 数,包括类的析构时。
    修改数据才会触发Copy-On-Write,不修改当然就不会改啦。这就是托延战术的真谛,非到要做的时候才去做。
    2.3.3、      Copy-On-Write时,发生了什么?
    我们可能根据那个访问计数来决定是否需要拷贝,参看下面的代码:

     

    If ( RefCnt>0 ) {
        char* tmp = (char*) malloc(strlen(_Ptr)+1);
        strcpy(tmp, _Ptr);
        _Ptr = tmp;
    }
    上面的代码是一个假想的拷贝方法,如果有别的类在引用(检查引用计数来获知)这块内存,那么就需要把更改类进行“拷贝”这个动作。
    我们可以把这个拷的运行封装成一个函数,供那些改变内容的成员函数使用。

    麻烦把正确答案设为解答。
    • 已标记为答案 Tim Li 2009年9月25日 10:29
    2009年9月23日 1:12
    版主
  • 保存一个字符串值有两种方法,要么前面写长度,要么连最后的'\0'一起写进去。如果你是unicode的话最后的应该是L'\0',两个字节长。我不知道Archive的原理是什么,不过你如何不同其实最后也就节省了3个字节(sizeof(int) - sizeof('\0'))而已。我觉得这是无所谓的。最大的区别是你在读的时候代码会截然不同,你觉得哪种你读起来方便你就用哪种吧。

    • 已标记为答案 Tim Li 2009年9月25日 10:29
    2009年9月23日 1:34