【问题标题】:Remove a specific column from a delimited file从分隔文件中删除特定列
【发布时间】:2014-05-09 15:47:16
【问题描述】:

这些天来,我一直在处理一些大的分隔文本 (~1GB) 文件。它看起来有点像下面

COlumn1 #COlumn2#COlumn3#COlumn4
COlumn1#COlumn2#COlumn3 #COlumn4

其中 # 是分隔符。

如果一列无效,我可能必须将其从整个文本文件中删除。第 3 列无效时的输出文件应如下所示。

COlumn1 #COlumn2#COlumn4
COlumn1#COlumn2#COlumn4

string line = "COlumn1# COlumn2 #COlumn3# COlumn4";
int junk =3;
int columncount = line.Split(new char[] { '#' }, StringSplitOptions.None).Count();
//remove the [junk-1]th '#' and the value till [junk]th '#'
//"COlumn1# COlumn2 # COlumn4"

我无法在 SO 中找到它的 c# 版本。有没有办法我可以做到这一点?请帮忙。

编辑: 我发现自己的解决方案如下所示,它可以完成这项工作。有没有办法可以将其修改为更好的方式,以缩小在大型文本文件的情况下可能对性能产生的影响?

int junk = 3;
string line = "COlumn1#COlumn2#COlumn3#COlumn4";
int counter = 0;
int colcount = line.Split(new char[] { '#' }, StringSplitOptions.None).Length - 1;
string[] linearray = line.Split(new char[] { '#' }, StringSplitOptions.None);
List<string> linelist = linearray.ToList();
linelist.RemoveAt(junk - 1);
string finalline = string.Empty;
foreach (string s in linelist)
{
    counter++;
    finalline += s;
    if (counter < colcount)
             finalline += "#";
}

Console.WriteLine(finalline);

【问题讨论】:

标签: c# delimiter


【解决方案1】:

已编辑

这种方法可能会非常消耗内存,正如您可以在post 中看到的那样,建议应该是:

如果您需要对文件中的数据运行复杂的查询,正确的做法是将数据加载到数据库中,让 DBMS 负责数据检索和内存管理。

为避免内存消耗,您应该使用StreamReader 逐行读取文件 这可能是您的任务的开始,缺少您的 invalid 匹配逻辑

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

namespace ConsoleApplication1
{
  class Program
  {
    static void Main(string[] args)
    {

      const string fileName = "temp.txt";

      var results = FindInvalidColumns(fileName);
      using (var reader = File.OpenText(fileName))
      {
        while (!reader.EndOfStream)
        {
          var builder = new StringBuilder();
          var line = reader.ReadLine();
          if (line == null) continue;
          var split = line.Split(new[] { "#" }, 0);

          for (var i = 0; i < split.Length; i++)
            if (!results.Contains(i))
              builder.Append(split[i]);

          using (var fs = new FileStream("new.txt", FileMode.Append, FileAccess.Write))
          using (var sw = new StreamWriter(fs))
          {
            sw.WriteLine(builder.ToString());
          }
        }
      }
    }

    private static List<int> FindInvalidColumns(string fileName)
    {
      var invalidColumnIndexes = new List<int>();
      using (var reader = File.OpenText(fileName))
      {
        while (!reader.EndOfStream)
        {
          var line = reader.ReadLine();
          if (line == null) continue;

          var split = line.Split(new[] { "#" }, 0);
          for (var i = 0; i < split.Length; i++)
          {
            if (IsInvalid(split[i]) && !invalidColumnIndexes.Contains(i))
              invalidColumnIndexes.Add(i);
          }
        }
      }
      return invalidColumnIndexes;
    }

    private static bool IsInvalid(string s)
    {
      return false;
    }
  }
}

【讨论】:

  • -1 几乎肯定会导致 OOM 异常。鉴于 OP 说他们有一个 1GB 的文件要处理。
  • @Aron 是否有缓冲文件的替代方法?
  • 您将输出放入 StringBuilder。该 SB 最终应该与原始文件一样大。再加上 GC 的低效率和不断增长的“列表”,这应该很容易通过你的记忆来咀嚼。由于您在任何时候都不会在 SB 中倒退,因此您可以轻松地将字符串构建器替换为 StreamWriter。
【解决方案2】:

首先,您要做的是使用 COlumn3 的 0 长度字符串将该行重新写入文本文件。因此,正确写入后的行将如下所示:

COlumun1#COlumn2##COlumn4

如您所见,COlumn2 和 COlumn4 之间有两个分隔符。这是一个没有数据的单元格。 (“单元格”是指某一行的一列。)稍后,当其他进程使用 Split 函数读取它时,它仍会为 Column 3 创建一个新值,但在 Split 生成的数组中,第三个位置将是一个空字符串:

String[] columns = stream_reader.ReadLine().Split('#');
int lengthOfThirdItem = columns[2].Length;  // for proof
//  lengthOfThirdItem = 0

这会将无效值减少为 null 并将它们保留在文本文件中。

有关 String.Split 的更多信息,请参阅C# StreamReader save to Array with separator

当文本文件也打开以供读取时,无法写入文本文件内部的行。本文讨论了一些(simultaneous read-write a file in C#),但看起来提问者只是希望能够将行写到最后。您希望能够在内部的任何位置写线。我认为如果不以某种方式缓冲数据,这是不可能的。

缓冲数据的最简单方法是首先将文件重命名为临时文件(使用 File.CoMovepy() // http://msdn.microsoft.com/en-us/library/system.io.file.move(v=vs.110).aspx)。然后使用临时文件作为数据源。只需打开要读取可能具有损坏条目的数据的临时文件,然后使用我上面描述的方法将数据重新写入原始文件名以表示空列。完成后,您应该删除临时文件。

重要

删除临时文件可能会使您容易受到电源和数据瞬变(或软件“瞬变”)的影响。 (即,中断部分过程的断电可能会使数据处于不可用状态。)因此,您可能还希望将临时文件留在驱动器上作为紧急备份,以防出现问题。

【讨论】:

  • 如果该行已经包含一个带有空条目的字段,这会中断吗?意味着在我的行中,空列不必是无效列(对不起,我没有提到我的有效行可以在列中具有空值,例如 COlumn1 ##COlumn3#COlumn4)
  • 我不会说它真的会坏掉。如果没有有关所涉及课程的更多详细信息,我很难确定。例如,我不知道您已经有空单元格。鉴于您刚才所做的澄清,您现在有两个选择。切换到另一种格式,例如 JSON,为每一行再次标记字段,在这种情况下,无效单元格将永远不会出现,或者发明一个表示“无效”但可能永远不会出现在真实数据中的关键字;像“无效”或“i_n_v_a_l_i_d”这样的词。
  • 在标记值(JSON 或 XML)中,顺序不一定重要(在一行或记录中),空单元格不需要存在。在分隔符分隔的值中,需要顺序和存在,因为字段 id 是通过计算分隔符来完成的
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2012-09-24
  • 2018-03-17
  • 1970-01-01
  • 1970-01-01
  • 2019-05-15
  • 1970-01-01
  • 2016-07-06
相关资源
最近更新 更多