zoukankan      html  css  js  c++  java
  • string中的CopyonWrite技术

        在谈这项技术之前,我们先来了解一下string类内存分配。string类有一个私有成员,其类型是一个char*,记录用户从堆上分配内存的地址,其在构造时分配内存,在析构时释放内存。因为是从堆上分配内存,所以string类在维护这块内存上是格外小心的,string类在返回这块内存地址时,只返回const char*,也就是只读的,如果你要写,你只能通过string提供的方法进行数据的改写。

        由表及里,由感性到理性,我们先来看一看string类的Copy-On-Write的表面特征。我写了下面一段程序: 

     1class Program
     2    {
     3        static void Main(string[] args)
     4        {
     5            Console.WriteLine("Sharing the Memory:");
     6            string str1 = "Hello World";
     7            string str2 = str1;
     8            Console.WriteLine(str1);
     9            Console.WriteLine(str2);
    10
    11            Console.WriteLine("After Copy on Write:");
    12            str1 = str1.Replace('H', 'C');
    13            Console.WriteLine(str1);
    14            Console.WriteLine(str2);
    15
    16            Console.ReadLine();
    17        }
    18    }


        执行结果:Sharing the Memory:
                Hello World
                Hello World
                After Copy on Write:
                Cello World
                Hello World
        从执行结果我们看到,在没有修改之前,str1和str2的值是一样的,也就是说他们在内存中存放数据的地址是一样的。str1修改之后,他的值发生了变化,即存放数据的地址改变了。而str2没有发生变化。在这里我们就看到了string的Copy-on-Write技术的影子。
           我们再深入的分析,在string中实现的写时拷贝,要解决两个问题:一个是内存共享,一个是Copy-on-Write。
           Copy-on-Write的原理:它使用的是“引用计数”,当第一个类构造时,string类的构造函数根据传入的参数在堆上分配内存,当其它类需要这块内存时,这个计数自动累加。当灯析构时,这个计数减一,直到最后一个类析构时,这个计数为0,这时,程序才会真正释放这块内存。“引用计数”就是string类的Copy-on-Write的原理。
           内存共享:如果一个类要用另一个类的数据,那就可以共享被使用类的内存了。这是很合理的,如果你不用我的,那就不用共享,只有你使用我的,才发生共享。 
           使用别的类的数据时,无非两种情况:(1)用别的类构造自己。(2)用别的类赋值。第一种情况是在构造函数中引用计数累加,第二种情况是在赋值操作中引用计数累加。
           那么这个引用计数应该存放在哪里呢?它是存放在共享内存中的,在这儿存放着所有的引用。这样一来,所有共享一块内存区的类都有同样的一个引用计数,而这个变量的地址既然是在共享区上的,那么所有共享这块内存的类都可以访问到,也就知道这块内存的引用者有多少了。

        有了这样一个机制,每当我们为string分配内存时,我们总是要多分配一个空间用来存放这个引用计数的值,只要发生拷贝构造或是赋值时,这个内存的值就会加一。而在内容修改时,string类为查看这个引用计数是否为0,如果不为零,表示有人在共享这块内存,那么自己需要先做一份拷贝,然后把引用计数减去一,再把数据拷贝过来。
        就我们上面的那个例子来说:当string str1="Hello World"时,在分配内存时就多分配一个空间用来存放这个引用计数的值,当string str2=str1时,str1发现有人引用我,它就得共享自己。并将引用计数加1。当str1=str1.replace('H','C')时,它首先去查看引用计数,一看大于0,表示有人在共享,那么自己需要先做一份拷贝,然后把引用计数减去一,再把数据拷贝过来。所以,我们在修改之后,再看他们的值时,str1变了,而str2并没有变。
        现在我们再来回头说说这个Copy-on-Write,我们可以把它看作是一个虚拟代理,它代理的真正的对象是共享内存,而代理完成任务就是控制要引用共享数据对象,它来判断是否有在共享这个数据,即看看引用计数是否0.若为0,表示数据没有人在共享,可以直接修改;若不为0,表示有人在共享这个数据,它就不能直接修改,需要Copy一个副本,在副本的基础上去修改。

          

              

               再来看看stringbuilder
     
               StringBuilder str = new StringBuilder();   //这里得到一个新实例
               str.Append("123");
               str.Append("1234");
               这个时候str已经变成了1231234。
               这里也使用了copy-on-write.
               先把"123"copy到缓存区。 然后在write那个地方的123。
               最后将它改成1231234。把新实例的地址直接赋str
     
              其实stringbuilder是使用了一种代理的模式处理的。
              我们是使用了stringbuilder这个代理类来访问string的。


     
  • 相关阅读:
    安卓跑马灯
    utf-8加密用于汉字加密(避免乱码)
    设置将edittext的光标到最后
    自定义UIProgressView,实现渐变色进度条,带动画的
    UIImageView帧动画,包含暂停和继续功能
    UIImageView关键帧动画,监听动画结束的回调
    自定义UICollectionViewFlowLayout实现横向滚动时,离中心点越近,item越大,离中心店越远,item越小的效果
    iOS中UIView的Pan手势和UIScrollView滚动手势的冲突解决方案
    使用Cocoapods创建私有库的流程
    git代码仓库迁移的步骤
  • 原文地址:https://www.cnblogs.com/chjw8016/p/1057190.html
Copyright © 2011-2022 走看看