代码改变世界

《Effective C#》Item 16:尽量减少垃圾产生的数量

2007-09-08 22:01  Jacky_Xu  阅读(184)  评论(0编辑  收藏  举报

对于.Net所写一般程序来说,都属于托管程序,内存的释放和回收是由Garbage Collector完成。但是相对于栈上内存操作而言,GC回收堆上的内存,会消耗更多的CPU时间,这方面的内容可以参看这篇文章。

http://blog.csdn.net/knight94/archive/2006/08/05/1023352.aspx

 

因此如果让GC不停的释放和回收内存,会造成程序性能的下降。

例如对于如下这段程序而言。

    protected override void OnPaint(PaintEventArgs e)

    {

        using( Pen penBlack = new Pen( Color.Black, 2f ) )

        {

            e.Graphics.DrawLine( penBlack, 10, 10, 100,10 );//Draw a black line

        }

        base.OnPaint (e);

    }

 

虽说每次调用完OnPaint后,penBlack都调用了Dispose方法,但是Dispose不同于原来C或者C++Delete方法,还是需要GC去回收内存。那么当此函数频繁被调用,那么在内存中产生的垃圾就会越来越多,GC需要花很多时间去回收它们,这样会造成程序性能下降。

 

那么,如何避免呢,或者说如何减少垃圾的产生呢。其实对象都是通过new来产生的,只要减少用new产生对象的次数,就可以减少垃圾的产生。只要把握到问题的关键点,那么相应的方法就有如下几个。

第一个就是,使用成员而代替局部变量,对于上面的例子,可以改成如下形成。

    private Pen penBlack = new Pen( Color.Black, 2f );

    protected override void OnPaint(PaintEventArgs e)

    {

        e.Graphics.DrawLine( penBlack, 10, 10, 100,10 );//Draw a black line

        base.OnPaint (e);

    }

 

可能很多人从C或者C++转型到使用C#,按照以前C或者C++的概念,尽量少使用全局变量,多使用局部变量;由于C#无法像C或者C++去显式释放内存,那么与其对象频繁需要GC回收,不如减少这样操作,从而造成效果会更好。

 

第二个就是公用的对象,可以采用静态成员的方法。例如,对于上面例子中的penBlack可能其他类型也需要使用,防止在其他类型也去创建,可以采用静态全局成员,即按照如下来实现。

    public class MyPens

    {

        private static Pen penBlack;

        public static Pen Black

        {

            get

            {

                if( penBlack == null )

                    penBlack = new Pen( Color.Black, 2f );

                return penBlack;

            }

        }

 

        private MyPens(){}

    }

 

对于.Net系统提供的“Pens”和“Brushes”这两个类,提供了一般常用的PenBrush.Net提供这两个静态类估计也是出于此考虑的。因此在进行绘画的时候,可以先考虑这两个类型所提供的静态对象。

 

最后一个方法,主要是针对不可改变的原子类型,例如对于string类型来说,就是这种类型。

例如:

    string strValue = "Hello";

    strValue += " World";

 

对于如上的语句来说,按照一般类型来说,都会在原有对象进行处理,但是对于不可改变的原子类型而言,对于原有对象的修改,会产生新的对象。因此这两条语句会产生两个对象,第一个字符串对象是“Hello”,另一个就是“Hello World”。

 

假如这种修改操作很频繁,那么意味着产生对象也很多,也就是说等待GC回收的垃圾就很多。为了避免此类现象发生,要么选用非原子的替代类型,要么在处理手法进行修改。

 

对于string类型来说,替代类型为StringBuilder,这也就是为什么用StringBuilder替换string进行大型字符串操作效率高的原因。

至于处理手法的改进,就string而言,可以采用string.Format,例如:

    string strValue = string.Format( "Total value is {0}!", nValue );

 

方法都说完了,怎么使用在于自身灵活的掌握。不过这里要注意的一点就是,这里所说的都是针对引用类型对象,而对于在值类型来说,并不需要这样处理,因为它们分配在栈上。