使用c#有效识别CSV文件中已更改的字段

事实certificate这比我想象的要困难得多。 基本上,每天系统会将客户主列表的快照转储为CSV。 它包含大约120000条记录和60个字段。 大约25mb。 无论如何,我想报告一个快照与另一个快照之间发生变化的值。 它不是计划文件差异,因为它必须匹配包含客户唯一编号的最左侧列值。 可以插入/删除行等。所有字段都是字符串,包括参考编号。

我已经用LINQ编写了一个解决方案,但它随着更大的数据集而死亡。 对于10000条记录,需要17秒。 对于120000,比较这两个文件需要将近2个小时。 现在它使用优秀且免费的’filehelpers’http: //www.filehelpers.com/来加载数据,这只需要几秒钟。 但是检测哪些记录已经改变更成问题。 以下是2小时查询:

var changednames = from f in fffiltered from s in sffiltered where f.CustomerRef == s.CustomerRef && f.Customer_Name != s.Customer_Name select new { f, s }; 

你会推荐什么方法? 我想立即将列表“修剪”给那些有某种变化的人,然后将我更具体的比较应用于那个小子集。 我的一些想法是:

a)使用字典或Hashsets-虽然早期的测试并没有真正显示出改进

b)区分操作 – 使用客户参考字段中的第一个字符,并仅与具有相同字符的字符匹配。 这可能涉及创建许多单独的集合,但似乎非常不优雅。

c)远离类型化数据安排并使用数组进行操作。 再次,利益不确定。

有什么想法吗?

谢谢!

出于以下讨论的目的,我假设您可以通过某种方式将CSV文件读入类中。 我会把那个叫MyRecord

将文件加载到单独的列表中,将其NewListOldList

 List NewList = LoadFile("newFilename"); List OldList = LoadFile("oldFilename"); 

使用LINQ可能有一种更优雅的方式,但想法是直接合并。 首先,您必须对两个列表进行排序。 您的MyRecord类可以实现IComparable ,也可以提供自己的比较委托:

 NewList.Sort(/* delegate here */); OldList.Sort(/* delegate here */); 

如果MyRecord实现IComparable您可以跳过委托。

现在它是直接合并。

 int ixNew = 0; int ixOld = 0; while (ixNew < NewList.Count && ixOld < OldList.Count) { // Again with the comparison delegate. // I'll assume that MyRecord implements IComparable int cmpRslt = OldList[ixOld].CompareTo(NewList[ixNew]); if (cmpRslt == 0) { // records have the same customer id. // compare for changes. ++ixNew; ++ixOld; } else if (cmpRslt < 0) { // this old record is not in the new file. It's been deleted. ++ixOld; } else { // this new record is not in the old file. It was added. ++ixNew; } } // At this point, one of the lists might still have items. while (ixNew < NewList.Count) { // NewList[ixNew] is an added record ++ixNew; } while (ixOld < OldList.Count) { // OldList[ixOld] is a deleted record } 

只有120,000条记录,应该可以非常快速地执行。 如果进行合并只需从磁盘加载数据,我会感到非常惊讶。

编辑:LINQ解决方案

我在思考如何用LINQ做到这一点。 我不能完成与上面的合并完全相同的事情,但我可以在单独的集合中获取添加,删除和更改的项目。
为此, MyRecord必须实现IEquatable并覆盖GetHashCode

 var AddedItems = NewList.Except(OldList); var RemovedItems = OldList.Except(NewList); var OldListLookup = OldList.ToLookup(t => t.Id); var ItemsInBothLists = from newThing in NewList let oldThing = OldListLookup[newThing.Id].FirstOrDefault() where oldThing != null select new { oldThing = oldThing, newThing = newThing }; 

在上面,我假设MyRecord有一个唯一的Id属性。

如果您只想要更改的项目而不是两个列表中的所有项目:

 var ChangedItems = from newThing in NewList let oldThing = OldListLookup[newThing.Id].FirstOrDefault() where oldThing != null && CompareItems(oldThing, newThing) != 0 select new { oldThing = oldThing, newThing = newThing }; 

假设CompareItems方法将对这两个项进行深度比较,如果比较等于或非零则返回0,如果有什么变化的话。

这可能最好在数据库而不是代码中完成:创建两个表(当前和旧),将CSV文件中的数据导入到正确的表中,并使用SQL查询的组合来生成输出。

你从哪里导出那个CSV?

您的原始来源是数据库吗? 如果是这样,为什么不能对数据库运行查询? 它将比任何LINQ实现更高效。

扩展Jims的答案,一个基本的例子:

 public class MyRecord { public MyRecord(int id) { Id = id; Fields = new int[60]; } public int Id; public int[] Fields; } 

然后测试代码:

 var recordsOld = new List(); var recordsNew = new List(); for (int i = 0; i < 120000; i++) { recordsOld.Add(new MyRecord(i)); recordsNew.Add(new MyRecord(i)); } var watch = new System.Diagnostics.Stopwatch(); int j = 0; watch.Start(); for (int i = 0; i < recordsOld.Count; i++) { while (recordsOld[i].Id != recordsNew[j].Id) { j++; } for (int k = 0; k < recordsOld[i].Fields.Length; k++) { if (recordsOld[i].Fields[k] != recordsNew[j].Fields[k]) { // do your stuff here } } } watch.Stop(); string time = watch.ToString(); 

假设列表有序,需要200ms才能运行。 现在,我确信代码中有大量的错误,但从最基本的意义上说,处理器不需要花费数百万次迭代。 您要么进行一些复杂的比较检查,要么某些代码非常低效。

另一个已经提供了很好的答案,我只是提供一些不同的东西供你考虑。

伪代码:

 Read 1000 from each source. Compare the records. If changed, store in list of changed records. If not changed, discard from list. If not exists, keep in list. Repeat until all records are exhausted. 

此代码假定记录未排序。

另一种方法是:

 Read all the records and determine what are all the first characters. Then for each character, Read and find records starting with that character. Perform comparison as necessary 

如果使用的记录超过某个阈值,则对上述内容的改进是写入新文件。 例如:

 Read all the records and determine what are all the first characters and the number of occurrence. Sort by characters with the highest occurrence. Then for each character, Read and find records starting with that character. If number of occurrence exceed a certain limit, write records that doesn't start with the character into a new file. // this reduces the amount of data that must be read from file Perform comparison as necessary