【问题标题】:C# Generic List foreach OutofMemoryExceptionC# 通用列表 foreach OutofMemoryException
【发布时间】:2012-06-12 16:32:22
【问题描述】:

我有一个程序可以从数据库中读取大约 200 万行到一个列表中。 每行都是一个包含地理坐标等信息的位置。

将数据添加到列表后,我使用 foreach 循环并获取坐标以创建一个 kml 文件。当行数很大时,循环会遇到 OutOfMemoryException 错误(但在其他情况下可以正常工作)。

关于如何处理这个问题以便程序可以处理非常大的数据集有什么建议吗? kml 库是 SharpKML。

我还是 C# 新手,所以请放轻松!

这是循环:

            using (SqlConnection conn = new SqlConnection(connstring))
        {
            conn.Open();
            SqlCommand cmd = new SqlCommand(select, conn);

            using (cmd)
            {
                SqlDataReader reader = cmd.ExecuteReader();
                while (reader.Read())
                {
                    double lat = reader.GetDouble(1);
                    double lon = reader.GetDouble(2);
                    string country = reader.GetString(3);
                    string county = reader.GetString(4);
                    double TIV = reader.GetDouble(5);
                    double cnpshare = reader.GetDouble(6);
                    double locshare = reader.GetDouble(7);

                    //Add results to list
                    results.Add(new data(lat, lon, country, county, TIV, cnpshare, locshare));
                }
                reader.Close();
            }
            conn.Close();
        }

            int count = results.Count();
            Console.WriteLine("number of rows in results = " + count.ToString());

            //This code segment generates the kml point plot

            Document doc = new Document();
            try
            {
                foreach (data l in results)
                {
                    Point point = new Point();
                    point.Coordinate = new Vector(l.lat, l.lon);

                    Placemark placemark = new Placemark();
                    placemark.Geometry = point;
                    placemark.Name = Convert.ToString(l.tiv);

                    doc.AddFeature(placemark);

                }
            }
            catch(OutOfMemoryException e)
            {
                throw e;
            }

这是列表中使用的类

        public class data
    {
        public double lat { get; set; }
        public double lon { get; set; }
        public string country { get; set; }
        public string county { get; set; }
        public double tiv { get; set; }
        public double cnpshare { get; set; }
        public double locshare { get; set; }

        public data(double lat, double lon, string country, string county, double tiv, double cnpshare,
            double locshare)
        {
            this.lat = lat;
            this.lon = lon;
            this.country = country;
            this.county = county;
            this.tiv = tiv;
            this.cnpshare = cnpshare;
            this.locshare = locshare;
        }

    }

【问题讨论】:

  • 到底为什么需要内存中的所有 200 万行?

标签: c# foreach out-of-memory generic-list sharpkml


【解决方案1】:

为什么在写入之前需要存储所有数据?与其将每一行都添加到列表中,不如在读取每一行时对其进行处理,然后忘记它。

例如,尝试像这样将代码滚动在一起:

Document doc = new Document();
while (reader.Read())
{
    // read from db
    double lat = reader.GetDouble(1);
    double lon = reader.GetDouble(2);
    string country = reader.GetString(3);
    string county = reader.GetString(4);
    double TIV = reader.GetDouble(5);
    double cnpshare = reader.GetDouble(6);
    double locshare = reader.GetDouble(7);

    var currentData = new data(lat, lon, country, county, TIV, cnpshare, locshare));

    // write to file
    Point point = new Point();
    point.Coordinate = new Vector(currentData.lat, currentData.lon);

    Placemark placemark = new Placemark();
    placemark.Geometry = point;
    placemark.Name = Convert.ToString(currentData.tiv);

    doc.AddFeature(placemark);
}

这只有在 Document 被合理实现时才有效。

【讨论】:

  • 好主意。我试试看。
【解决方案2】:

Oliver 是对的(我赞成)。性能方面,你可以做一些其他的事情。首先不要查询您不会使用的字段。然后将所有变量声明(Oliver 的代码)移到 while 语句 (?) 之前。最后,不要等待您的 sql server 收集并发送回所有记录,而是逐步进行。例如,如果您的记录有一个 UID,并且获取它们的顺序是这个 UID,那么从一个本地 C# 变量“var lastID = 0”开始,将您的 select 语句更改为类似(pre-format)“select top 1000 ... where UID > lastID" 并重复您的查询,直到您一无所获或任何内容少于 1000 条记录。

【讨论】:

    【解决方案3】:

    如果用数据库中的数据填充列表没有大的延迟,并且您没有提到用数据填充列表的问题,为什么不立即创建您的 Point 和 Placemark 对象。代码如下。

        var doc = new Document();
    
        using (SqlConnection conn = new SqlConnection(connstring))
        {
            conn.Open();
            SqlCommand cmd = new SqlCommand(select, conn);
    
            using (cmd)
            {
                var reader = cmd.ExecuteReader();
                while (reader.Read())
                {
                    double lat = reader.GetDouble(1);
                    double lon = reader.GetDouble(2);
                    string country = reader.GetString(3);
                    string county = reader.GetString(4);
                    double TIV = reader.GetDouble(5);
                    double cnpshare = reader.GetDouble(6);
                    double locshare = reader.GetDouble(7);
    
                    var point = new Point();
                    point.Coordinate = new Vector(lat , lon );
    
                    var placemark = new Placemark();
                    placemark.Geometry = point;
                    placemark.Name = Convert.ToString(TIV);
    
                    doc.AddFeature(placemark);
    
                reader.Close();
            }
            conn.Close();
        }
    

    如果没有充分的理由在内存中检索这么多数据,请尝试一些延迟加载方法。

    【讨论】:

    • 感谢您的精彩建议。理想情况下,我确实需要查询中的其他字段,因为这些字段有时会用于标记 KML 点或执行其他 KML 操作,例如多边形。今天晚上我会花几个小时来研究这里的建议并报告回来。谢谢大家。
    • 试一试。阅读器似乎像流媒体一样工作。但是,当我尝试编写 KML 文件时,我现在得到了 OutOfMemoryException。这将是一个非常大的 KML 文件(例如 50mb),但对于 OutOfMemoryException 来说仍然不够。还有另一种更有效地实现某种流的方法吗?也许我需要拆分文件,然后再加入。
    • 你真的需要这么大的KML文件吗?您可以尝试使用@drdigit 解决方案stackoverflow.com/a/11001300/1433917,在每次迭代中将 1000 行附加到现有 KML 文件。但是,根据我的 GIS 知识,Google Maps 或 Google Earth 处理 50MB 大小的 KML 文件可能会很慢,为什么不按规则将其拆分为几个较小的 KML 文件,这会降低您的查询执行速度。
    【解决方案4】:

    @drdigit,

    我会避免循环执行查询。一个查询应始终返回该时刻所需的尽可能多的数据。在这种情况下,您将有 1000 个查询返回 1000 行。也许快速显示前 1000 行会更好,但我不确定如果你在循环中执行 1000 个更快的查询,而不是只执行一个查询,它是否会更快。也许我错了......

    如果在这种情况下需要延迟加载,我认为您的方法非常适合延迟加载。

    【讨论】:

    • 数字 1000 只是一个例子。在大多数情况下(如果数据库索引适用于查询),只要在同一连接下执行查询,性能上的差异就很明显。可能影响性能的一个因素是网络延迟,但它可以(在一定程度上)与基于样本数 1000 的往返次数进行平衡。无论如何,在这种情况下,我们似乎在谈论localhost 环境意味着可能没有网络延迟。
    • 糟糕。对于没有按时看到您的第一个答案,我深表歉意。它是正确的,并且与奥利弗斯的完全一致。因此,请为您投票,因为这不是最快打字“机器”的比赛。
    • 我对 DB 不太擅长,因为我主要面向编程,没有这样的情况来测试性能问题。我的回答指出了良好的(必须具备的)编程实践——永远不要将查询放在循环中。但是,如果我们谈论良好的编程实践,查询 200 万行肯定不是唯一的。
    猜你喜欢
    • 2016-12-02
    • 1970-01-01
    • 1970-01-01
    • 2019-02-18
    • 2012-02-25
    • 1970-01-01
    • 2016-08-04
    • 2011-01-30
    • 1970-01-01
    相关资源
    最近更新 更多