处理非常大的数据集并及时加载
我有用C#(.NET 4.0)编写的.NET应用程序。在这个应用程序中,我们必须从文件中读取大型数据集,并以网格状结构显示内容。所以,为了实现这一点,我在窗体上放置了一个DataGridView。它有3列,所有列数据来自文件。最初,该文件有大约600.000条记录,对应于DataGridView中的600.000行。处理非常大的数据集并及时加载
我很快发现,DataGridView崩溃了这么大的数据集,所以我切换到虚拟模式。为了实现这一点,我首先将文件完全读入3个不同的数组(对应于3列),然后触发CellValueNeeded事件,我从数组中提供正确的值。
但是,在我们很快发现的情况下,该文件中可能会有巨大(大量!)记录。当记录大小非常大时,将所有数据读入数组或列表中,似乎不可行。我们很快就会遇到内存分配错误。 (内存不足例外)。
我们被困在那里,但后来认识到,为什么首先将数据全部读入数组,为什么不在CellValueNeeded事件触发时按需读取文件?这就是我们现在所做的:我们打开文件,但不读取任何内容,并且随着CellValueNeeded事件触发,我们首先将Seek()找到文件中的正确位置,然后读取相应的数据。
这是我们可以想到的最好的,但首先这是非常缓慢的,这使得应用程序呆滞而不是用户友好。其次,我们不禁认为必须有更好的方法来实现这一目标。例如,一些二进制编辑器(如HXD)对于任何文件大小都非常快,所以我想知道如何实现这一点。
噢,在DataGridView的虚拟模式下,当我们将RowCount设置为文件中可用的行数(比如16.000.000)时,需要一段时间才能使DataGridView平均初始化自己。对于这个'问题'的任何评论也将不胜感激。
感谢
如果不能放在内存整个数据集,那么你需要一个缓冲方案。应用程序应该预测用户的操作并预读,而不是只读取填充DataGridView
所需的数据量以响应CellValueNeeded
。因此,例如,当程序第一次启动时,它应该读取前10,000条记录(或者只有1,000条或者可能是100,000条 - 在你的情况下是合理的)。然后,CellValueNeeded
请求可以立即从内存中填充。
当用户在网格中移动时,程序尽可能地保持在用户的前面一步。如果用户在你前面跳跃(比如说,想从前面跳到最后),那么可能会出现短暂的暂停,并且为了完成请求,你必须出去访问磁盘。
缓冲通常最好由单独的线程完成,但如果线程正在预读用户的下一个动作,然后用户做了一些完全意想不到的事情,如同步有时可能会成为一个问题,如跳到开始列表。
除非记录非常大,否则1600万条记录并不是真正保存在内存中的所有记录。或者如果你的服务器没有太多的内存。当然,除非T
是一个值类型(结构),否则1600万远不是List<T>
的最大值。你在这里谈论多少千兆字节的数据?
管理可以卷积,分总计,用于多列计算等的行和列会产生一系列独特的挑战;把这个问题与编辑会遇到的问题进行比较并不公平。第三方数据网格控件自VB6开始就解决了显示和处理大型数据集客户端的问题。使用按需加载或自包含的客户端Garguantuan数据集来获得真正的快速性能并非轻而易举的任务。按需加载可能会受服务器端延迟的影响;操纵客户端上的整个数据集可能会受到内存和CPU限制的限制。一些支持即时加载的第三方控件提供了客户端和服务器端逻辑,而另一些则试图在100%客户端解决问题。
嗯,这里看起来运行得更好的解决方案:
步骤0:设置dataGridView.RowCount为低值,说25(或适合您的形式/屏幕的实际数量)
步骤1:禁用dataGridView的滚动条。
第2步:添加自己的滚动条。
第3步:在您的日常CellValueNeeded,应对e.RowIndex + scrollBar.Value
步骤4:对于数据存储,我现在打开一个流,并在CellValueNeeded程序中,首先做一个寻求( )和Read()所需的数据。
通过这些步骤,我可以非常合理地通过dataGrid滚动浏览非常大的文件(测试至0.8GB)。
因此总而言之,看起来放缓的实际原因并不是我们保持Seek()ing和Read()ing的事实,而是实际的dataGridView本身。
由于.net是在本地操作系统之上构建的,因此运行时加载和管理从磁盘到内存的数据需要另一种方法。 查看原因和方法:http://www.codeproject.com/Articles/38069/Memory-Management-in-NET
要解决此问题,我建议不要一次加载所有数据。而是将数据加载到块中,并在需要时显示最相关的数据。我只是做了一个快速测试,发现设置一个DataGridView
的DataSource
属性是一个好方法,但对于大量的行也需要时间。因此,使用DataTable的Merge
函数以块加载数据并向用户显示最相关的数据。 Here我已经演示了一个可以帮助你的例子。
Hello Jim,T,是一个带有4个双精度浮点数的结构体。所以,4 * 8 * 16M = 512MB的数据。 – SomethingBetter 2011-01-27 08:07:13
我尝试使用.NET MemoryMappedFile,但只要您创建视图,它显然会尝试将文件加载到内存中,因为我的内存异常。我想可能MemoryMappedFile将内部分段的数据访问页面,只加载所需的页面内存。 – SomethingBetter 2011-01-27 14:30:10