首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >将300+文件合并为5-8个,OutOfMemory例外

将300+文件合并为5-8个,OutOfMemory例外
EN

Stack Overflow用户
提问于 2020-02-18 01:37:40
回答 2查看 46关注 0票数 0

我有369个文件,需要格式化和合并为5-8个文件,然后才能提交到服务器。我不能提交这369个文件,因为这将使我们数据库中的元数据表不堪重负(他们可以处理它,但对于本质上是一个文件的文件来说,这将是369行,这将使查询和利用这些表成为一场噩梦),我不能将其作为一个文件处理,因为对于我们的服务器上的SSIS来说,3.6 GB的总容量太大了。

我写了以下脚本来解决这个问题:

代码语言:javascript
复制
        static void PrepPAIDCLAIMSFiles()
        {
            const string HEADER = "some long header text, trimmed for SO question";
            const string FOOTER = "some long footer text, trimmed for SO question";
            //path is defined as a static member of the containing class
            string[] files = Directory.GetFiles(path + @"split\");  
            int splitFileCount = 0, finalFileCount = 0;
            List<string> newFileContents = new List<string>();
            foreach(string file in files)
            {
                try
                {
                    var contents = File.ReadAllLines(file).ToList();
                    var fs = File.OpenRead(file);
                    if (splitFileCount == 0)
                    {
                        //Grab everything except the header
                        contents = contents.GetRange(1, contents.Count - 1);
                    }
                    else if (splitFileCount == files.Length - 1)
                    {
                        //Grab everything except the footer
                        contents = contents.GetRange(0, contents.Count - 1);
                    }
                    if (!Directory.Exists(path + @"split\formatted"))
                    {
                        Directory.CreateDirectory(path + @"split\formatted");
                    }
                    newFileContents.AddRange(contents);
                    if (splitFileCount % 50 == 0 || splitFileCount >= files.Length)
                    {
                        Console.WriteLine($"{splitFileCount} {finalFileCount}");
                        var sb = new StringBuilder(HEADER);
                        foreach (var row in newFileContents)
                        {
                            sb.Append(row);
                        }
                        sb.Append(FOOTER);
                        newFileContents = new List<string>();
                        GC.Collect();
                        string fileName = file.Split('\\').Last();
                        string baseFileName = fileName.Split('.')[0];
                        DateTime currentTime = DateTime.Now;
                        baseFileName += "." + COMPANY_NAME_SetHHMMSS(currentTime, finalFileCount) + ".TXT";
                        File.WriteAllText(path + @"split\formatted\" + baseFileName, sb.ToString());
                        finalFileCount += 1;
                    }
                    splitFileCount += 1;
                }
                catch(OutOfMemoryException OOM)
                {
                    Console.WriteLine(file);
                    Console.WriteLine(OOM.Message);
                    break;
                }
            }
        }

它的工作方式是读取拆分的文件,将其行放入字符串构建器,每次到达50个文件的倍数时,它将字符串构建器写入新文件并重新开始。COMPANY_NAME_SetHHMMSS()方法确保文件具有唯一的名称,因此它不会反复写入同一个文件(我可以通过查看输出来验证这一点,它在分解之前会写入两个文件)。

当它到达第81个文件时,它会中断。var contents = File.ReadAllLines(file).ToList();上的System.OutOfMemoryException。第81个文件没有什么特别之处,它的大小与其他文件完全相同(~10MB)。此函数提供的文件大小约为500MB。它在读取和处理第81个之前的所有文件时也没有问题,所以我不认为读取文件会耗尽内存,而是在做其他事情时会耗尽内存,而是在第81个文件内存耗尽的情况下。

应该通过用新列表覆盖newFileContents()列表来清空它,对吗?它不应该随着这个函数的每次迭代而增长。GC.Collect()在某种程度上是最后的努力。

369拆分的原始文件几天来一直是一个令人头疼的问题,导致UltraEdit崩溃,SSIS崩溃,C#崩溃,等等。通过7zip拆分它似乎是唯一可行的选择,而将它拆分成369个文件似乎是7zip唯一没有以不受欢迎的方式重新格式化或压缩文件的选择。

我是不是漏掉了什么?我的代码中有什么东西在内存中不断增长?我知道File.ReadAllLines()会打开和关闭文件,所以它应该在调用后被释放,对吧?newFileContents()每隔50个文件就会被覆盖一次,字符串生成器也是如此。我还能做什么呢?

EN

回答 2

Stack Overflow用户

发布于 2020-02-18 01:52:19

我突然想到的一件事是,你正在打开一个FileStream,从来没有使用过它,也从来没有处理过它。对于300+文件流,这可能会导致您的问题。

代码语言:javascript
复制
var fs = File.OpenRead(file);

另一件让我耳目一新的事情是,你说了3.6 is。确保您是针对64位架构进行编译的。

最后,在字符串构建器中填充千兆字节可能会引起您的悲哀。可以创建一个临时文件-每次打开新的输入文件时,您都会将其写入到临时文件中,关闭输入,而不需要将所有内容都填充到内存中。

票数 2
EN

Stack Overflow用户

发布于 2020-02-18 03:40:22

您应该循环遍历源文件中的行,并将它们附加到新文件中。你一次可以在内存中保存多达50个10MB文件的内容,再加上你正在做的任何其他事情。这可能是因为您正在为x86而不是x64编译,但是没有任何理由应该使用接近该内存的任何地方。类似于以下内容:

代码语言:javascript
复制
        var files = Directory.Getfiles(System.IO.Path.Combing(path, "split")).ToList();
        //since you were skipping the first and last file
        files.Remove(files.FirstOrDefault());
        files.Remove(files.LastOrDefault());

        string combined_file_path = "<whatever you want to call this>";
        System.IO.StreamWriter combined_file_writer = null;
        try
        {
            foreach(var file in files)
            {
                //if multiple of 50, write footer, dispose of stream, and make a new stream
                if((files.IndexOf(file)) % 50 == 0)
                {
                    combined_file_writer?.WriteLine(FOOTER);
                    combined_file_writer?.Dispose();
                    combined_file_writer = new System.IO.StreamWriter(combined_file_path + "_1"); //increment the name somewhow
                    combined_file_writer.WriteLine(Header);
                }

                using(var file_reader = new System.IO.StreamReader(file))
                {
                    while(!file_reader.EOF)
                    {
                        combined_file_writer.WriteLine(file_reader.ReadLine());
                    }
                }

            }

            //finish out the last file
            combined_file_writer?.WriteLine(FOOTER);
        }
        finally
        {
            //dispose of last file
            combined_file_writer?.Dispose();
        }
票数 0
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/60267767

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档