【问题标题】:Out of memory error archiving a log file归档日志文件时内存不足错误
【发布时间】:2016-07-30 01:09:39
【问题描述】:

我遇到了一个控制台作业的问题,该作业运行并创建了一个我在午夜存档的每日日志文件。

这会创建一个第二天的空白日志文件和一个名称为昨天日期的存档文件,以及旧文件的内容,用于调试我可能遇到但直到第二天才知道的问题。

但是,自从我启动 BOT 的工作后,我在尝试归档文件时遇到了系统内存不足错误的问题。

起初我根本无法获得存档文件,然后我想出了一种方法来至少获得最后 100,000 行,但这还远远不够。

我将所有内容都包装在 3 次尝试/捕获中

  1. I/O
  2. 系统内存不足
  3. 标准异常

但是,我得到的总是 OutOfMemoryException,例如

System.OutOfMemoryException 错误:抛出了“System.OutOfMemoryException”类型的异常。;

举个例子,100,000 行的日志大约是 11MB 文件

一个标准的完整日志文件可以是从 1/2 GB 到 2 GB 的任何文件

我需要知道的是:

a) 尝试使用 File.ReadAllText 或我调用 ReadFileString 的自定义 StreamReader 函数时,标准文本文件的大小会引发内存不足错误,例如

public static string ReadFileString(string path)
{
    // Use StreamReader to consume the entire text file.
using (StreamReader reader = new StreamReader(path))
{
    return reader.ReadToEnd();
    }
}

b) 是我的计算机内存(我有 16GB RAM - 复制时使用了 8GB)还是我在 C# 中使用的对象在打开和复制文件时失败。

存档时,我首先尝试使用我的自定义 ReadFileString 函数(见上文),如果返回 0 字节的内容,我尝试 File.ReadAllText,然后如果失败,我尝试自定义函数来获取最后 100,000 行,这真的不足以在当天早些时候调试错误。

日志文件在午夜创建新文件时开始并记录一整天。我以前从来没有出现过内存不足错误,但是自从我调高方法调用的频率后,日志记录已经扩大,这意味着文件大小也有。

这是我获取最后 100,000 行的自定义函数。我想知道如果 IT 部门不抛出内存不足错误,我可以得到多少行,而我根本没有得到最后几天日志文件的任何内容。

人们对保存 X 行所需的各种方法/内存的最大文件大小有什么建议,以及获取尽可能多的日志文件的最佳方法是什么?

例如,某种方式逐行循环,直到遇到异常,然后保存我所拥有的。

这是我的 GetHundredThousandLines 方法,它记录到一个非常小的调试文件中,因此我可以查看存档过程中发生的错误。

private bool GetHundredThousandLines(string logpath, string archivepath)
{
    bool success = false;

    int numberOfLines = 100000;


    if (!File.Exists(logpath))
    {
    this.LogDebug("GetHundredThousandLines - Cannot find path " + logpath + " to archive " + numberOfLines.ToString() + " lines");
    return false;
    }

    var queue = new Queue<string>(numberOfLines);

    using (FileStream fs = File.Open(logpath, FileMode.Open, FileAccess.Read, FileShare.Read))
    using (BufferedStream bs = new BufferedStream(fs))  // May not make much difference.
    using (StreamReader sr = new StreamReader(bs))
    {
    while (!sr.EndOfStream)
    {
        if (queue.Count == numberOfLines)
        {
        queue.Dequeue();
        }

        queue.Enqueue(sr.ReadLine() + "\r\n");
    }
    }

    // The queue now has our set of lines. So print to console, save to another file, etc.
    try
    {

    do
    {        
        File.AppendAllText(archivepath, queue.Dequeue(), Encoding.UTF8);
    } while (queue.Count > 0);


    }
    catch (IOException exception)
    {
    this.LogDebug("GetHundredThousandLines - I/O Error accessing daily log file with ReadFileString: " + exception.Message.ToString());
    }
    catch (System.OutOfMemoryException exception)
    {
    this.LogDebug("GetHundredThousandLines - Out of Memory Error accessing daily log file with ReadFileString: " + exception.Message.ToString());
    }
    catch (Exception exception)
    {
    this.LogDebug("GetHundredThousandLines - Exception accessing daily log file with ReadFileString: " + exception.Message.ToString());
    }


    if (File.Exists(archivepath))
    {
    this.LogDebug("GetHundredThousandLines - Log file exists at " + archivepath);
    success = true;
    }
    else
    {
    this.LogDebug("GetHundredThousandLines - Log file DOES NOT exist at " + archivepath);
    }

    return success;

}

任何帮助将不胜感激。

谢谢

【问题讨论】:

  • 你为什么要阅读日志来归档它?只需重命名它,将其复制到存档文件夹,为第二天创建一个新的空白文件,瞧!不会出现内存不足,不会在不需要时不必要地读取整个文件,也不会再头疼了。
  • 为什么不重命名旧文件并为第二天创建一个新文件?这似乎可以省去很多努力 - 抱歉,刚刚看到 ken white 击败了我。我同意他的看法。
  • 为什么不用日期和时间来命名您的日志文件。永远不会重命名
  • 如果您正在归档日志文件,您可以同时压缩它们以节省大量磁盘空间。 SharpZipLib 允许您使用use a buffer 创建 zip 文件,因此您可以将使用的内存限制为 32KB。我希望其他文件压缩实用程序具有相同的功能。
  • 好的,是的,但是根据我对 ASP 经典的经验,幕后移动与 File.Copy 然后 File.Delete 一样,因此内容仍然以某种方式复制出来。如果没有足够的内存来复制文件,那么它将无法工作。我确信几年前我确实尝试过使用这种更简单的方法,但由于内存问题它一直失败,这就是为什么我继续这个 2(现在是 3 阶段)过程。但是我可以回来看看 FIle.Move 是否会再次工作,但我确信它没有,这就是为什么我必须在第一名取得进展。

标签: c# .net memory io readfile


【解决方案1】:

尝试: 将队列和流位置保持在类范围内,在内存不足异常时尝试 GC.Collect() 并再次调用函数。寻找流到最后一个位置并继续。 要么: 使用像 sqlite 这样的一个数据库,并在每个表中保留最新的 100000 条记录。

【讨论】:

  • 你能解释一下 GC.Collect 的作用吗(我认为它是垃圾收集),如果我没有内存来打开文件,这有什么帮助。另外我想要一种方法来获取整个文件,而不仅仅是 100,000 行。那么,即使它抛出内存错误,获取整个文件的最佳方法是什么?在数据库中循环存储 100,000 个文件,清理内存(如何?GC.Collect()?)然后继续,最后将它们组合成块?
  • 顺便说一句,昨晚由于某种原因,我的第一种方法有效。该文件的大小为 25828836 字节。我想知道是否有一些已知大小的文件太大而无法通过任何方法打开和读取。如果是这样,这个限制是多少,因为我的 PC 上有足够的 RAM 可以在编辑器中打开文件。那么 C# 对每种文件读取方法的限制是什么?
  • 还有我在这里列出的 3 种读取文件的方法,包括 File.ReadAllText、Stream 函数和 Queue 方法,这是提取文件内容的最佳性能/内存/IO ?
  • @MonkeyMagix 一次读取整个文件意味着您需要有足够的 连续 虚拟内存 - 这不是您可以影响或预测的东西,真的。流媒体通常是一个更好的主意。另请注意,.NET 字符串是 unicode,因此如果您正在读取 ASCII/ANSI 文件,请将文件大小加倍以满足内存要求。
  • 只是让你知道我重写了它以使用 CopyTo 方法,目前它似乎工作正常。所以我删除了 GC.Collect()
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-04-23
  • 1970-01-01
  • 1970-01-01
  • 2010-10-07
  • 1970-01-01
相关资源
最近更新 更多