如何使用.NET读取包含2900万行数据的巨大CSV文件

11

我有一个非常大的.csv文件,具体来说是一个包含2900万行的.TAB文件,文件大小约为600MB。我需要将其读入一个IEnumerable集合中。

我尝试过使用CsvHelperGenericParser和其他一些解决方案,但最终都遇到了内存不足的异常。

请提供一种方法来解决这个问题。

我已经尝试过:

var deliveryPoints = new List<Point>();

using (TextReader csvreader1 = File.OpenText(@"C:\testfile\Prod\PCDP1705.TAB")) //StreamReader csvreader1 = new StreamReader(@"C:\testfile\Prod\PCDP1705.TAB"))
using (var csvR1 = new CsvReader(csvreader1, csvconfig))
{
     csvR1.Configuration.RegisterClassMap<DeliveryMap>();
     deliveryPoints = csvR1.GetRecords<Point>().ToList();
}

using (GenericParser parser = new GenericParser())
{
     parser.SetDataSource(@"C:\testfile\Prod\PCDP1705.TAB");

     parser.ColumnDelimiter = '\t';
     parser.FirstRowHasHeader = false;
     //parser.SkipStartingDataRows = 10;
     //parser.MaxBufferSize = 4096;
     //parser.MaxRows = 500;
     parser.TextQualifier = '\"';

     while (parser.Read())
     {
         var address = new Point();
         address.PostCodeID = int.Parse(parser[0]);
         address.DPS = parser[1];
         address.OrganisationFlag = parser[2];
         deliveryPoints.Add(address);
     }
}

并且
var deliveryPoints = new List<Point>();
csvreader = new StreamReader(@"C:\testfile\Prod\PCDP1705.TAB");
csv = new CsvReader(csvreader, csvconfig);

while (csv.Read())
{
     var address = new Point();
     address.PostCodeID = int.Parse(csv.GetField(0));
     address.DPS = csv.GetField(1);                
     deliveryPoints.Add(address);
}

7
在所有这些情况下,您确定正在流式传输结果而不是将它们全部放入列表或类似的内存数据结构中吗?请展示您使用的CsvHelper代码,例如。 - Jon Skeet
var Points = new List<Point>(); 使用(TextReader csvreader1 = File.OpenText(@"C:\testfile\Prod\PCDP1705.TAB")) 使用(var csvR1 = new CsvReader(csvreader1, csvconfig)) { csvR1.Configuration.RegisterClassMap<DeliveryMap>(); deliveryPoints = csvR1.GetRecords<Point>().ToList(); } - Lee
4
编辑问题而不是将代码放在问题中。但正如我所怀疑的那样,你试图一次性加载所有数据。这与“我需要将其读入IEnumerable集合”并不相同,后者意味着你能够流式传输。 - Jon Skeet
可能是如何在.NET中读取大型(1 GB)txt文件?的重复问题。 - pcdev
你真的需要同时在内存中拥有2900万行数据吗?不如使用SqlBulk插入将数据插入到正确索引的表中,然后智能地查询所需的实际行数,这是一个更高效的方案。 - Mark Kram
当我在处理邮政地址文件时(巧合的是,它有2900万行!),我发现先减少列数会更容易降低整体内存占用。一种方法是使用StreamReader和StreamWriter配合使用,这样您可以逐行读取,减少列数,然后将该行写出。最终,我将PAF压缩成了100 MB的数据(可搜索),并将其放入iPhone的内存中,其中包括邮政编码、街道名称和号码。 - David Bolton
3个回答

15

问题在于你将整个文件加载到了内存中。你可以将代码编译为x64,这将大幅增加程序的内存限制,但如果可以避免将整个文件加载到内存中则不建议使用。

请注意,调用ToList()会强制CsvReader一次性将整个文件加载到内存中:

csvR1.GetRecords<Point>().ToList();

但是这样只会每次加载一行:

foreach(var record in csvR1.GetRecords<Point>())
{
    //do whatever with the single record
}

这样你就可以处理无限大小的文件


11

不需要使用第三方软件。使用 .Net 库方法即可。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System.Data;

namespace ConsoleApplication1
{
    class Program
    {
        static void Main(string[] args)
        {
            StreamReader csvreader = new StreamReader(@"C:\testfile\Prod\PCDP1705.TAB");
            string inputLine = "";
            while ((inputLine = csvreader.ReadLine()) != null)
            {
                var address = new Point();
                string[] csvArray = inputLine.Split(new char[] { ',' });
                address.postCodeID = int.Parse(csvArray[0]);
                address.DPS = csvArray[1];
                Point.deliveryPoints.Add(address);
            }

            //add data to datatable
            DataTable dt = new DataTable();
            dt.Columns.Add("Post Code", typeof(int));
            dt.Columns.Add("DPS", typeof(string));

            foreach (Point point in Point.deliveryPoints)
            {
                dt.Rows.Add(new object[] { point.postCodeID, point.DPS });
            }

        }
    }
    public class Point
    {
        public static List<Point> deliveryPoints = new List<Point>();
        public int postCodeID { get; set; }
        public string DPS { get; set; }

    }
}

谢谢jdweng。我已经尝试了上面的解决方案,但在1600万条记录中出现了内存不足异常。 - Lee
以前从未见过这种情况。这是非常基础的代码。你的电脑多大了?尝试打开任务管理器运行,看看是这个应用程序占用内存还是你电脑上其他东西。 - jdweng
Point类中的哪个组件将被转储到数据表中?最终,我需要进入DataGridView。 - user1493382
这个解决方案也解决了我的问题。我有大约260万行数据,CSV文件大小为300MB。我也尝试了CSVHelper,在处理了约90万行后崩溃了。无需设置<gcAllowVeryLargeObjects enabled="true" />。 - Lei Shi

-1

它可以在x64模式下运行,并通过在app.config中添加<gcAllowVeryLargeObjects enabled="true" />来实现。


2
你没有解决问题的根本原因,只是暂时绕过了它!你真的应该考虑一下@jdweng的答案 - 它能很好地扩展,并且不会消耗太多资源! - Monza
你需要指定: <runtime> <gcAllowVeryLargeObjects enabled="true" /> </runtime> - user1493382

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接