.NET下的强制垃圾回收办法

首先谈谈其他高手的见解:

.Net程序员在编程时应该怎么做,有没有一种既简单又有有效的方法来处理内存回收。愚人作以下建议,望各路高手不吝赐教:

       1,对于不包涵或没有引用(直接或间接)非托管资源的类,特别是作用如同Struct的实体类,析构、终结器、Dispose均不采用。

       2,对于包涵非托管资源的类,如数据库连接对象,文件句柄等,应继承IDispose接口,在Dispose方法中清理非托管对象。

            客户代码用using(…){ }格式显示调用Dispose。

            如果继承了IDispose接口,Dispose方法就不要留空,这样没有任何意义。除了构造器,任何方法体留空都有害无益。

       3,所有自定义类一般均不建议显式声明析构函数、Finalize方法。 

今天来谈谈C#的GC,也就是垃圾回收机制,非常的受教,总结如下

首先:谈谈托管,什么叫托管,我的理解就是托付C#运行环境帮我们去管理,在这个运行环境中可以帮助我们开辟内存和释放内存,开辟内存一般用new,内存是随机分配的,释放主要靠的是GC也就是垃圾回收机制。哪么有两个大问题1.GC可以回收任何对象吗?2.GC什么时候来回收对象?回收那些对象?

对于第一个问题,GC可以回收任何对象吗?我是这样理解的,首先要明白一点,C#在强大也管不到非托管代码?哪么什么是非托管代码呢?比如stream(文件),connection(数据库连接),COM(组件)等等。。哪么这些对象是需要进行连接的,比如说我们写这样一句话FileStream fs = new FileStream(“d://a.txt”,FileMode.Open);实际上已经创建了和d://a.txt的连接,如果重复两次就会报错。哪么fs这个对象叫做非托管对象,也就是说C#

不能自动去释放和d://a.txt的连接。哪么对于非托管的代码怎么办,一会我来说。

    对于第二个问题,GC什么时候来回收,回收什么对象?我想后面的就不用我说了,当然是回收托管对象了。但是GC什么时候回收?是这样的:GC是随机的,没有人知道他什么时候来,哪么我写了一个例子,证明这一点

private void button1_Click(object sender, EventArgs e)

{           

AA a = new AA();

AA b = new AA();

AA c = new AA();

AA d = new AA(); 

}

public class AA{}

在讲这个例子之前,要明白什么被称之为垃圾,垃圾就是一个内存区域,没有被任何引用指向,或者不再会被用到。哪么在第一次点击按钮的时候会生成4个对象,第二次点击按钮的时候也会生成4个对象,但是第一次生成的4个对象就已经是垃圾了,因为,第一次生成的4个对象随着button1_Click函数的结束而不会再被调用(或者说不能再被调用),哪么这个时候GC就会来回收吗?不是的!我说了GC是随机的,哪么你只管点你的,不一会GC就会来回收的(这里我们可以认为,内存中存在一定数量的垃圾之后,GC会来),要证明GC来过我们把AA类改成

public class AA{

~AA(){

        MessageBox.Show("析构函数被执行了");

}

}

要明白,GC清理垃圾,实际上是调用析构函数,但是这些代码是托管代码(因为里面没有涉及到Steam,Connection等。。)所以在析构函数中,我们可以只写一个MsgBox来证明刚的想法;这个时候,运行你的程序,一直点击按钮,不一会就会出现一大堆的“析构函数被执行了”…

好了,然后让我们看看能不能改变GC这种为所欲为的天性,答案是可以的,我们可以通过调用GC.Collect();来强制GC进行垃圾回收,哪么button1_Click修改如下

private void button1_Click(object sender, EventArgs e)

{           

AA a = new AA();

AA b = new AA();

AA c = new AA();

AA d = new AA();

GC.Collect();

}

哪么在点击第一次按钮的时候,生成四个对象,然后强制垃圾回收,这个时候,会回收吗?当然不会,因为,这四个对象还在执行中(方法还没结束),当点第二次按钮的时候,会出现四次"析构函数被执行了",这是在释放第一次点击按钮的四个对象,然后以后每次点击都会出现四次"析构函数被执行了",哪么最后一次的对象什么时候释放的,在关闭程序的时候释放(因为关闭程序要释放所有的内存)。

好了,现在来谈谈非托管代码,刚才说过,非托管代码不能由垃圾回收释放,我们把AA类改成如下

public class AA

{

     FileStream fs = new FileStream("D://a.txt",FileMode.Open);

     ~AA()     {            MessageBox.Show("析构函数被执行了");    }

}

private void button1_Click(object sender, EventArgs e) {

            AA a = new AA();

}

如果是这样一种情况,哪么第二次点击的时候就会报错,原因是一个文件只能创建一个连接。哪么一定要释放掉第一个资源,才可以进行第二次的连接。哪么首先我们想到用GC.Collect(),来强制释放闲置的资源,修改代码如下:

private void button1_Click(object sender, EventArgs e){

            GC.Collect();

            AA a = new AA();

}

哪么可以看到,第二次点按钮的时候,确实出现了“析构函数被执行了“,但是程序仍然错了,原因前面我说过,因为Stream不是托管代码,所以C#不能帮我们回收,哪怎么办?

自己写一个Dispose方法;去释放我们的内存。代码如下:

public class AA:IDisposable    {

        FileStream fs = new FileStream("D://a.txt",FileMode.Open);

        ~AA()       {

            MessageBox.Show("析构函数被执行了"); 

        } 

        public void Dispose()        {

            fs.Dispose();

            MessageBox.Show("dispose执行了");

        }

    }

好了,我们看到了,继承IDisposable接口以后会有一个Dispose方法(当然了,你不想继承也可以,但是接口给我们提供一种规则,你不愿意遵守这个规则,就永远无法融入整个团队,你的代码只有你一个人能看懂),好了闲话不说,这样一来我们的button1_Click改为private void button1_Click(object sender, EventArgs e) {

            AA a = new AA();

           a.Dispose();

}

我们每次点击之后,都会发现执行了“dispose执行了”,在关闭程序的时候仍然执行了“析构函数被执行了”这意味了,GC还是工作了,哪么如果程序改为:

private void button1_Click(object sender, EventArgs e){

            AA a = new AA();

            a.Dispose();

           GC.Collect();

}

每次都既有“dispose执行了又有”“析构函数被执行了”,这意味着GC又来捣乱了,哪么像这样包含Stream connection的对象,就不用GC来清理了,只需要我们加上最后一句话GC.SuppressFinalize(this)来告诉GC,让它不用再调用对象的析构函数中。那么改写后的AA的dispose方法如下: 

        public void Dispose()        {

            fs.Dispose();

            MessageBox.Show("dispose执行了");

            GC.SuppressFinalize(this);

        }

 

给一个实际例子:

using System;
using System.Collections.Generic;
using System.Text;
using System.IO;
using System.Security.Cryptography;

namespace BtMaker.Frm
{
    class MakeIdx : IDisposable
    {
        List<String> MyInfo = new List<string>();
        StreamWriter sw = new StreamWriter(@"C:\aa.txt");
        string selectedFolder;

        public MakeIdx(string folder)
        {
            this.selectedFolder = folder;
        }

        /// <summary>
        /// *符合分割字符串,写入idx文件
        /// </summary>
        /// <param name="folder"></param>
        public void RWDirectoryInfo(string folder)
        {
            string[] filenames = Directory.GetFileSystemEntries(folder);

            foreach (string file in filenames)// 遍历所有的文件和目录
            {
                if (Directory.Exists(file))// 目录处理如果存在这个目录就直接写入idx文件
                {
                    //当前目录名称写入idx
                    var s = file.Replace(selectedFolder, "");
                    WriteFileInfo(s);

                    RWDirectoryInfo(file);
                }
                else // 否则获取文件信息 写入idx
                {
                    string fileAllInfo = GetFileMd5(file);//md5
                    fileAllInfo += "*" + GetFileLen(file);   //len
                    fileAllInfo += "*" + GetFileDate(file); //date

                    string s = file.Replace(selectedFolder, "");
                    s += "*" + fileAllInfo;

                    WriteFileInfo(s);
                }
            }//foreach          
        }

        /// <summary>
        /// 得到文件MD5值
        /// </summary>
        /// <param name="file">文件名</param>
        /// <returns></returns>
        private string GetFileMd5(string file)
        {
            FileStream fs = new FileStream(file, FileMode.Open, FileAccess.Read);
            MD5CryptoServiceProvider md5 = new MD5CryptoServiceProvider();
            byte[] bytHash = md5.ComputeHash(fs);

            StringBuilder sBuilder = new StringBuilder();

            // Loop through each byte of the hashed data and format each one as a hexadecimal string.
            for (int i = 0; i < bytHash.Length; i++)
            {
                sBuilder.Append(bytHash[i].ToString("x2"));
            }

            fs.Close();
            // Return the hexadecimal string.
            return sBuilder.ToString();
        }


        /// <summary>
        /// 获取指定文件的尺寸大小
        /// </summary>
        /// <param name="file">文件名</param>
        /// <returns></returns>
        private string GetFileLen(string file)
        {
            var MyFileInfo = new System.IO.FileInfo(file);
            return MyFileInfo.Length.ToString();
        }

        /// <summary>
        /// 获取和设置指定文件的时间
        /// </summary>
        /// <param name="file">文件名</param>
        /// <returns></returns>
        private string GetFileDate(string file)
        {
            var MyFileInfo = new System.IO.FileInfo(file);
            return MyFileInfo.LastWriteTime.ToString();
        }

        /// <summary>
        /// 写如文件一行
        /// </summary>
        /// <param name="fileInfo"></param>
        private void WriteFileInfo(string fileInfo)
        {
            sw.WriteLine(fileInfo);
            sw.Flush();
        }

        void IDisposable.Dispose()
        {
            sw.Dispose();
            sw.Close();
        }
    }

使用:
 private void button1_Click_1(object sender, EventArgs e)
        {
            string s = @"C:\gtk";
            using(MakeIdx mi = new MakeIdx(s))
            {
                mi.RWDirectoryInfo(s);               
            }
           
        }
这个例子 是读取指定文件夹的所有子目录和子文件 ,子文件的md5值 大小 最后读写日期 ,然后文本方式 逐行写入指定的txt文件。
有些地方不是很规范,还请大家见谅!只为说明问题而用。
  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值