我有369个文件,需要格式化和合并为5-8个文件,然后才能提交到服务器。我不能提交这369个文件,因为这将使我们数据库中的元数据表不堪重负(他们可以处理它,但对于本质上是一个文件的文件来说,这将是369行,这将使查询和利用这些表成为一场噩梦),我不能将其作为一个文件处理,因为对于我们的服务器上的SSIS来说,3.6 GB的总容量太大了。
我写了以下脚本来解决这个问题:
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个文件就会被覆盖一次,字符串生成器也是如此。我还能做什么呢?
发布于 2020-02-18 01:52:19
我突然想到的一件事是,你正在打开一个FileStream,从来没有使用过它,也从来没有处理过它。对于300+文件流,这可能会导致您的问题。
var fs = File.OpenRead(file);另一件让我耳目一新的事情是,你说了3.6 is。确保您是针对64位架构进行编译的。
最后,在字符串构建器中填充千兆字节可能会引起您的悲哀。可以创建一个临时文件-每次打开新的输入文件时,您都会将其写入到临时文件中,关闭输入,而不需要将所有内容都填充到内存中。
发布于 2020-02-18 03:40:22
您应该循环遍历源文件中的行,并将它们附加到新文件中。你一次可以在内存中保存多达50个10MB文件的内容,再加上你正在做的任何其他事情。这可能是因为您正在为x86而不是x64编译,但是没有任何理由应该使用接近该内存的任何地方。类似于以下内容:
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();
}https://stackoverflow.com/questions/60267767
复制相似问题