这个函数中的内存泄漏在哪里?

Dan*_*tti 7 .net c# io memory-leaks

Edit2:我只是想确定我的问题是清楚的:为什么,在AppendToLog()的每次迭代中,应用程序使用15mb以上?(原始日志文件的大小)

我有一个名为AppendToLog()的函数,它接收HTML文档的文件路径,进行一些解析并将其附加到文件中.它以这种方式调用:

this.user_email = uemail;
string wanted_user = wemail;

string[] logPaths;
logPaths = this.getLogPaths(wanted_user);

foreach (string path in logPaths)
{              

    this.AppendToLog(path);                

}
Run Code Online (Sandbox Code Playgroud)

在每次迭代中,RAM使用量增加15mb左右.这是功能:(看起来很长但很简单)

public void AppendToLog(string path)
{

Encoding enc = Encoding.GetEncoding("ISO-8859-2");
StringBuilder fb = new StringBuilder();
FileStream sourcef;
string[] messages;

try
{
    sourcef = new FileStream(path, FileMode.Open);
}
catch (IOException)
{
    throw new IOException("The chat log is in use by another process."); ;
}
using (StreamReader sreader = new StreamReader(sourcef, enc))
{

    string file_buffer;
    while ((file_buffer = sreader.ReadLine()) != null)
    {
        fb.Append(file_buffer);
    }                
}

//Array of each line's content
messages = parseMessages(fb.ToString());

fb = null;

string destFileName = String.Format("{0}_log.txt",System.IO.Path.GetFileNameWithoutExtension(path));
FileStream destf = new FileStream(destFileName, FileMode.Append);
using (StreamWriter swriter = new StreamWriter(destf, enc))
{
    foreach (string message in messages)
    {
        if (message != null)
        {
            swriter.WriteLine(message);
        }
    }
}

messages = null;

sourcef.Dispose();
destf.Dispose();


sourcef = null;
destf = null;
}
Run Code Online (Sandbox Code Playgroud)

我已经有了这个日子,我不知道该做什么:(

编辑:这是ParseMessages,这是一个使用HtmlAgilityPack去除HTML日志部分的函数.

public string[] parseMessages(string what)
{
StringBuilder sb = new StringBuilder();
HtmlDocument doc = new HtmlDocument();

doc.LoadHtml(what);            

HtmlNodeCollection messageGroups = doc.DocumentNode.SelectNodes("//body/div[@class='mplsession']");
int messageCount = doc.DocumentNode.SelectNodes("//tbody/tr").Count;

doc = null;

string[] buffer = new string[messageCount];

int i = 0;

foreach (HtmlNode sessiongroup in messageGroups)
{
    HtmlNode tablegroup = sessiongroup.SelectSingleNode("table/tbody");

    string sessiontime = sessiongroup.Attributes["id"].Value;

    HtmlNodeCollection messages = tablegroup.SelectNodes("tr");
    if (messages != null)
    {
        foreach (HtmlNode htmlNode in messages)
        {
            sb.Append(
                    ParseMessageDate(
                        sessiontime,
                        htmlNode.ChildNodes[0].ChildNodes[0].InnerText
                    )
                ); //Date
            sb.Append(" ");

            try
            {
                foreach (HtmlTextNode node in htmlNode.ChildNodes[0].SelectNodes("text()"))
                {
                    sb.Append(node.Text.Trim()); //Name
                }
            }
            catch (NullReferenceException)
            {
                /*
                 * We ignore this exception, it just means there's extra text
                 * and that means that it's not a normal message
                 * but a system message instead
                 * (i.e. "John logged off")
                 * Therefore we add the "::" mark for future organizing
                 */
                sb.Append("::");
            }
            sb.Append(" ");

            string message = htmlNode.ChildNodes[1].InnerHtml;
            message = message.Replace(""", "'");
            message = message.Replace(" ", " ");
            message = RemoveMedia(message);
            sb.Append(message); //Message
            buffer[i] = sb.ToString();
            sb = new StringBuilder();
            i++;
        }
    }
}
messageGroups = null;
what = null;
return buffer;
}
Run Code Online (Sandbox Code Playgroud)

Kev*_*ock 6

正如许多人所提到的,这可能只是GC的一个工件,没有像你期望的那样快速清理内存存储.对于托管语言,例如C#,Java等,这是正常的.如果您对该用法感兴趣,您确实需要查明分配给您的程序的内存是否可用.与此相关的问题是:

  1. 你的程序运行多长时间了?它是一个连续运行的服务类型程序吗?
  2. 在执行范围内,它是否继续从操作系统分配内存或是否达到稳定状态?(你运行它的时间足够长吗?)

您的代码看起来不会有"内存泄漏".在托管语言中,您实际上不会像在C/C++中那样出现内存泄漏(除非您使用的是不安全的C/C++外部库).但是,您确实需要注意保留或隐藏的引用(如已被告知删除项目但未设置内部数组元素的Collection类null).通常,除非将对象的引用存储到对象/类变量中,否则对堆栈引用的对象(本地和参数)不能"泄漏".

您对代码的一些评论:

  1. 您可以通过预先分配StringBuilder至少适当的大小来减少内存的分配/释放.因为你知道你需要将整个文件保存在内存中,所以将它分配给文件大小(这实际上会给你一个比你需要的大一点的缓冲区,因为你没有存储换行字符序列,但文件可能是有他们):

    FileInfo fi = new FileInfo(path);
    StringBuilder fb = new StringBuilder((int) fi.Length);
    
    Run Code Online (Sandbox Code Playgroud)

    您可能希望确保文件在获取其长度之前存在,fi用于检查该文件.请注意,int根据您的问题文本,我只是将长度向下转换为无错误检查,因为您的文件小于2GB.如果不是这种情况,那么你应该在投射之前验证长度,如果文件太大,可能会抛出异常.

  2. 我建议删除variable = null代码中的所有语句.这些不是必需的,因为它们是堆栈分配的变量.同样,在这种情况下,它不会帮助GC,因为该方法将不会存在很长时间.因此,通过让它们在代码中创建额外的混乱并且更难以理解.

  3. 在您的ParseMessages方法中,您捕获NullReferenceException并假设它只是一个非文本节点.这可能会导致将来出现混乱的问题.由于数据中可能存在的某些内容通常会发生这种情况,因此您应检查代码中的条件,例如:

    if (node.Text != null)
        sb.Append(node.Text.Trim()); //Name
    
    Run Code Online (Sandbox Code Playgroud)

    例外情况是代码中的异常/意外情况.将重要意义分配给NullReferenceException存在空引用可能(可能会)try现在或将来的更改中隐藏同一块的其他部分中的错误.