【问题标题】:How to remove contents of one csv file from another in C#如何在 C# 中从另一个 csv 文件中删除内容
【发布时间】:2014-07-19 05:01:52
【问题描述】:

我有 2 个 csv 文件,file1.csv 和 file2.csv。每个文件中的某些行将是相同的。我希望创建一个基于 file2.csv 的第三个 csv 文件,但从中删除 file1.csv 中存在的任何行。实际上,我希望从 file2.csv 中减去 file1.csv,忽略 file1 中存在但不在 file2 中的任何行。 我知道我可以使用streamreader 读取file2.csv 中的每一行并在file1.csv 中搜索它。如果它在 file1.csv 中不存在,我可以将其写入 file3.csv。但是,这些文件非常大(超过 30000 行),我相信这将花费大量的处理时间。 我怀疑可能有更好的方法将每个 csv 加载到数组中,然后对它们执行简单的减法函数以获得所需的结果。我将不胜感激有关代码或解决此问题的方法的一些帮助。

文件内容示例:

file1.csv

dt97861.jpg,149954,c1714ee1,\folder1\folderA\,
dt97862.jpg,149955,c1714ee0,\folder1\folderA\,
dt97863.jpg,59368,cd23f223,\folder2\folderA\,
dt97864.jpg,57881,0835be4a,\folder2\folderB\,
dt97865.jpg,57882,0835be4b,\folder2\folderB\,

file2.csv

dt97862.jpg,149955,c1714ee0,\folder1\folderA\,
dt97863.jpg,59368,cd23f223,\folder2\folderA\,
dt97864.jpg,57881,0835be4a,\folder2\folderB\,
dt97865.jpg,57882,0835be4b,\folder2\folderB\,
dt97866.jpg,57883,0835be4c,\folder2\folderB\,
dt97867.jpg,57884,0835be4d,\folder3\folderA\,
dt97868.jpg,57885,0835be4e,\folder3\folderA\,

我需要的结果是:

file3.csv

dt97866.jpg,57883,0835be4c,\folder2\folderB\,
dt97867.jpg,57884,0835be4d,\folder3\folderA\,
dt97868.jpg,57885,0835be4e,\folder3\folderA\,

编辑: 在下面的帮助下,我得出了以下我认为不错且优雅的解决方案:

public static IEnumerable<string> ReadFile(string path)
        {
            string line;

            using (var reader = File.OpenText(path))
                while ((line = reader.ReadLine()) != null)
                    yield return line;
        }

然后:

var file2 = ReadFile(file2FilePath);
var file1 = ReadFile(file1FilePath);
var file3 = file2.Except(file1);
File.WriteAllLines(file3FilePath, file3);

【问题讨论】:

  • 你应该展示你尝试过的东西。
  • 我通常会,但在这种情况下,我不确定该采取什么方法。我已经描述了一种我认为可行的方法,但我希望有人会提出更有效的建议。
  • 两个 CSV 文件是否已按字母顺序排列? (看起来是这样。)如果不是,输出的顺序重要吗? 30,000 行长度约 50 个字符只有几兆字节,所以我建议将两个文件都拉入、排序并使用二进制搜索搜索重复项。
  • 我会使用 EPPlus 之类的东西,将两个文件都加载到内存集合中(如果文件不是那么大)。然后你可以使用一些 LINQ 或其他东西来解析不在文件 1 中的行。
  • 如果两个文件的排序方式始终相同,您可以使用external merge

标签: c# csv


【解决方案1】:

假设该行完全相同,您可以将两个文件读入两个IEnumerable&lt;string&gt; 并使用IEnumerable.Except&lt;T&gt; 提取。无论排序如何,这都会产生相同的结果~

例子:

var file1 = new List<string>{
        @"dt97861.jpg,149954,c1714ee1,\folder1\folderA\,",
        @"dt97862.jpg,149955,c1714ee0,\folder1\folderA\,",
        @"dt97863.jpg,59368,cd23f223,\folder2\folderA\,",
        @"dt97864.jpg,57881,0835be4a,\folder2\folderB\,",
        @"dt97865.jpg,57882,0835be4b,\folder2\folderB\,",
    };

var file2 = new List<string>{
        @"dt97862.jpg,149955,c1714ee0,\folder1\folderA\,",
        @"dt97863.jpg,59368,cd23f223,\folder2\folderA\,",
        @"dt97864.jpg,57881,0835be4a,\folder2\folderB\,",
        @"dt97865.jpg,57882,0835be4b,\folder2\folderB\,",
        @"dt97866.jpg,57883,0835be4c,\folder2\folderB\,",
        @"dt97867.jpg,57884,0835be4d,\folder3\folderA\,",
        @"dt97868.jpg,57885,0835be4e,\folder3\folderA\,",
    };

file2.Except(file1).Dump();

输出:

dt97866.jpg,57883,0835be4c,\folder2\folderB\, 
dt97867.jpg,57884,0835be4d,\folder3\folderA\, 
dt97868.jpg,57885,0835be4e,\folder3\folderA\, 

这是将任何文件加载到IEnumerable&lt;string&gt; 的函数。只是不要忘记using System.IO;

public static IEnumerable<string> ReadFile(string path)
{
    string line;

    using(var reader = File.OpenText(path))
        while((line = reader.ReadLine()) != null)
            yield return line;
}

将结果写入文件:

 //using System.IO; is required
File.WriteAllLines("file3.csv", file2.Except(file1))

备注:File.WriteAllLines 将创建或覆盖文件。

【讨论】:

  • 这看起来不错。请你告诉我如何将 csv 文件加载到 file1 和 file2 var 中,因为 csv 文件的位置是 file1Path 和 file2Path。
  • 谢谢。所以现在我有: var myFullCsv = ReadFile(myFullCsvFilePath); var masterCsv = ReadFile(csvFilePath); //如果文件不在masterCsv中,则将其放入extras.csv var extraFilesCsv = myFullCsv.Except(masterCsv);我应该使用 StreamWriter 将其写入新的 csv,还是将所有数据作为一个 var 后有更好的方法?
  • 是的,使用File.WriteAllLines
  • 搞定了。用 StreamWriter 写出结果,速度超级快。谢谢。
  • File.WriteAllLines 用一行代码来创建我的 csv 文件就更好了。谢谢你的帮助。这正是我希望的那种解决方案,但担心有些人可能会觉得我没有做出足够的努力,而没有发布我自己尝试的一些代码。
【解决方案2】:

虽然这可能不是最好的方法,但这是我过去使用过的方法。这有点肮脏,但是...

  1. 将两个 CSV 文件导入数据表(这样您将有两个数据表 - 如果您打算使用 excel 类型格式,我个人更喜欢 closed xml,否则只需使用普通文件读/写 - 我的示例使用常规读/写)
  2. 将数据从数据表移动到列表中(我的示例假定逗号分隔值,每行一个。)
  3. 在列表之间查找唯一值并合并
  4. 将合并的列表导出到 csv 文件

*[实际处理代码后的编辑步骤]

根据 Bit 的请求,我添加了一个使用来自 Some Random Website 的示例数据的示例 - 这是在 VS2008 中针对 .NET 3.5 编写的,但它应该适用于 3.5+。我将 us-500 复制成 2 个版本,原始和修改 1 行以创建唯一值进行测试。该项目针对 x86 平台。我使用了一个新的 windows 窗体进行测试

using System.Data;
using System.Data.OleDb;
using System.IO;
using System.Linq;
using System.Windows.Forms;

namespace TestSandbox
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            var file1 = new DataTable();
            var file2 = new DataTable();

            InitializeComponent();
            //Gets data from csv file, select allows for filtering
            using (var conn = new OleDbConnection(@"Provider=Microsoft.Jet.OLEDB.4.0;Data Source=c:\;Extended Properties=""text;HDR=Yes;FMT=Delimited"";"))
            {
                conn.Open();
                using (var adapter = new OleDbDataAdapter(@"select * from [us-500.csv]", conn))
                {
                    adapter.Fill(file1);
                }
            }

            using (var conn = new OleDbConnection(@"Provider=Microsoft.Jet.OLEDB.4.0;Data Source=c:\;Extended Properties=""text;HDR=Yes;FMT=Delimited"";"))
            {
                conn.Open();
                using (var adapter = new OleDbDataAdapter(@"select * from [us-500-2.csv]", conn))
                {
                    adapter.Fill(file2);
                }
            }
            //Moves datatable information to lists for comparison
            var file1List = (from DataRow row in file1.Rows select row.ItemArray.Select(field => field.ToString()).ToArray() into fields select string.Join(",", fields)).ToList();
            var file2List = (from DataRow row in file2.Rows select row.ItemArray.Select(field => field.ToString()).ToArray() into fields select string.Join(",", fields)).ToList();
            //Adds all data from file2 into file1 list, except for data that already exists in file1
            file1List.AddRange(file2List.Except(file1List));
            //Exports all results to c:\results.csv
            File.WriteAllLines(@"C:\Results.csv", file1List.ToArray());
        }
    }
}

*注意:查看代码后,直接导入列表看起来会更有效,但我暂时保留它,因为它并不太复杂。

【讨论】:

  • 这更适合作为评论,除非您展开并提供示例。
【解决方案3】:

第 1 步。使用 System.IO,我们将使用 FileStream 读取两个文件并使用 StreamWriter 创建第三个文件。

第 2 步。使用 FileStream 读取文件 #1。例如

using (var FS = new System.IO.FileStream(file1, System.IO.FileMode.Open, System.IO.FileAccess.Read)) { ...<insert next steps in here>...}

第 3 步。嵌套另一个 FileStream 以读取文件 #2。该流将被多次读取,因此最好将较小的文件放在嵌套的这一部分。您可以通过在跳入这些循环之前检查文件的大小来做到这一点。

步骤 4. 从我们最大的文件 File#1 中读取一行,然后我们将它与 File#2 中的所有行顺序进行比较。如果找到匹配项,则将布尔值设置为 TRUE,表示在文件 #2 中找到匹配行。

第 5 步。一旦我们在文件 #2 的末尾,检查布尔值的真/假条件。如果它为假,则将我们从文件#1 中读取的字符串保存到文件#3 中。这是您的输出文件。

步骤 6. 将文件 #2 的流指针重置为文件的开头,例如FS.Seek(0, System.IO.SeekOrigin.Begin)

第 7 步。从第 4 步开始重复,直到我们到达文件 #1 的末尾。文件 #3 的内容应仅代表文件 #1 中不属于文件 #2 的唯一条目

【讨论】:

    猜你喜欢
    • 2013-01-12
    • 2013-04-05
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-12-17
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多