将整数添加到列表时抛出System.OutOfMemoryException

时间:2018-02-06 14:29:35

标签: c# out-of-memory

我是一个非常业余的C#开发人员,他试图使用Visual Studio在macOS上创建这个控制台程序。我是在学校里做的,但是我是自学成才,并且已经做了不到两周的工作,所以我完全有可能错过一些简单的解决方案。

我制作了一个程序,用于读取填充了素数的文本文件并将其转换为列表,然后在将它们添加到列表和文件时开始生成素数,并在每次找到时同时报告信息一个新的。

这是我的代码:

String fileLocation = "Prime Number List.txt"; //sets the file location to the root of where the program is stored

if (!File.Exists(fileLocation)) //tests if the file has already been created
{
    using (FileStream fs = File.Create(fileLocation))
    {
        Byte[] info = new UTF8Encoding(true).GetBytes("2"); //if not, it creates the file and creates the initial prime number of 2
        fs.Write(info, 0, info.Length);
    }
}


List<string> fileContents = File.ReadAllLines(fileLocation).ToList(); //imports the list of prime numbers from the file
List<int> listOfPrimeNumbers = fileContents.ConvertAll(s => Int32.Parse(s)); //converts the list into the integer variable type


int currentNumber = listOfPrimeNumbers[listOfPrimeNumbers.Count() - 1]; //sets the current number to the most recent prime number
bool isPrime; //initializing the primality test variable
int numbersGeneratedThisSession = 0; //initializing the variable for the amount of primes found in this session
var loopStart = DateTime.Now; //initializes the program start time, ignoring the time taken to load the file list

while (true)
{
    isPrime = true; //defaults the number to prime
    currentNumber++; //repeats the cycle for the next number
    double currentNumberRoot = Math.Sqrt(System.Convert.ToDouble(currentNumber));
    for (int i = 0; i < listOfPrimeNumbers.Count; i++) //cyles through all of the primes in the list. no reason to divide by composites, as any number divisible by a
                                                            //composite would be divisible by the prime factors of that composite anyway, thus if we were to divide by
                                                            //every number it would slow down the program
    {
        if (listOfPrimeNumbers[i] < Math.Sqrt(System.Convert.ToDouble(currentNumber))) //filters out any prime numbers greater than the square root of the current number, as any potential
                                                                                            //factor pair would have one of the values less than or equal to the square root
        {
            if (currentNumber % listOfPrimeNumbers[i] == 0) //checks for the even division of the current number by the current prime
            {
                isPrime = false; //if an even division is found, it reports that the number isn't false and breaks the loop
                break;
            }
        }

        else
            break; //if no even divisons are found, then it reaches this point with the primality test variable still true, and breaks the loop



    }
    if (isPrime) //this section of the code activates when the primality test variable is true
    {
        listOfPrimeNumbers.Add(currentNumber); //adds the new prime to the list
        File.AppendAllText(fileLocation, Environment.NewLine + currentNumber); //adds the new prime to the file on a new line
        numbersGeneratedThisSession++; //raises the counter for the prime numbers generated in this session
        var runtime = DateTime.Now - loopStart; //calculates the runtime of the program, excluding the time taken to load the file into the list
        int runtimeInSecs = (runtime.Milliseconds / 1000) + runtime.Seconds + (runtime.Minutes * 60) + (runtime.Hours * 360) + (runtime.Days * 86400); //converts the datetime var into an int of seconds
        int generationSpeed = runtimeInSecs == 0 ? 0 : numbersGeneratedThisSession / runtimeInSecs;
        Console.WriteLine("\nI've generated {0} prime numbers, {1} of those being in the current session." +
                          "\nI've been running for {2}, which means I've been generating numbers at a speed of {3} primes per second. " +
                          "\nThe largest prime I've generated so far is {4}, which is {5} digits long.", 
                          listOfPrimeNumbers.Count(), numbersGeneratedThisSession, runtime, generationSpeed, currentNumber, currentNumber.ToString().Length);
    }
}

我一直在“listOfPrimeNumbers.Add(currentNumber);”上获得异常。部分。我已经阅读了类似的问题,而对其他人的问题最常见的解决方案是将gcAllowVeryLargeObjects设置为true,以打破2GB的限制。这对我来说只是暂时的解决方案,但是随着时间的推移,列表会不断变大,当它达到计算机功能的极限而不是视觉工作室的上限时,会有一个点。

我想知道是否有某种技术可供更有经验的开发人员用来规避这个问题,例如将数据分成多个列表,做一些不同于我简化代码的事情等等。我知道由于我的程序的本质是不可避免的,最终数据会变得太大,但我试图推迟尽可能长的时间,因为现在的文件不到半个演出,这是一个不合理的少量内存崩溃了。

我还要注意的是,在我处理统计反馈(意味着文件读取,写入和生成代码本身在此期间基本未被触及)时,我每天运行此程序大约一个小时过去一周。我在任何时候启动都没有问题,最后一次运行顺利(由于内存不足异常没有崩溃)。我今天尝试重新启动时才遇到这个问题。

2 个答案:

答案 0 :(得分:8)

.NET中的各个数组或列表受以下所有绑定:

  • 2GiB对象限制(除非启用gcAllowVeryLargeObjects
  • 可用的进程内存(尤其与32位进程相关)
  • 每个维度2,146,435,071个项目(单字节值为2,147,483,591)

如果你附近这些问题,那么是:你需要另一种方法。移动到您视为复合块的多个单独列表应该作为一个间隙,但是......我不认为这最终是一种非常可扩展的计算素数的方法。

答案 1 :(得分:1)

由于您正在使用他的程序搜索质数,如果您只是尝试将其存储在所述内存中,那么您将会耗尽内存。

如上所述,拆分您的列表会有所帮助,但最终您会遇到同样的问题; 5组3项是15项,分组或不分组。你要快速填满你的记忆。

我认为你的问题可能在这里:

List<string> fileContents = File.ReadAllLines(fileLocation).ToList(); //imports the list of prime numbers from the file
List<int> listOfPrimeNumbers = fileContents.ConvertAll(s => Int32.Parse(s)); //converts the list into the integer variable type

这两个Lists<T>都是不必要的。您的文件中包含回车符(您在条目中插入Environment.NewLine),因此假设您想要从上次停止的位置继续,您需要该文件中只有一个值:

//note that I used ReadLines, not ReadAllLines
 int lastNumber;
 if(!int.TryParse(File.ReadLines(fileLocation).ToList().Last(), out lastNumber))
 {
      //last value wasn't a valid integer.  Start over. 
      lastNumber = 1;
 }

然后,使用lastNumber执行所有逻辑,在文件处于初级状态时写入文件,并且根本不将集合存储在内存中。这将使您的新限制因素成为目标计算机上的存储空间。如果加载文件并获取其最后一个字符串的内存不足,您需要整理一些涉及向后读取文件的代码,但由于这是一个更具学术性的项目,我怀疑您需要采取到目前为止。