【问题标题】:Fast and efficient way to read a space separated file of numbers into an array?将空格分隔的数字文件读入数组的快速有效方法?
【发布时间】:2010-06-01 19:57:36
【问题描述】:

我需要一种快速有效的方法来将带有数字的空格分隔文件读入数组。文件格式如下:

4 6
1 2 3 4 5 6
2 5 4 3 21111 101
3 5 6234 1 2 3
4 2 33434 4 5 6

第一行是数组 [rows 列] 的维度。以下行包含数组数据。

数据也可以不带任何换行符,如下所示:

4 6
1 2 3 4 5 6 2 5 4 3 21111 101 3 5 6234 1 2 3 4 2 33434 4 5 6

我可以读取第一行并使用行值和列值初始化一个数组。然后我需要用数据值填充数组。我的第一个想法是逐行读取文件并使用拆分功能。但是列出的第二种格式让我停下来,因为整个数组数据将一次全部加载到内存中。其中一些文件在 100 MB 中。第二种方法是分块读取文件,然后逐块解析它们。也许其他人有更好的方法来做到这一点?

【问题讨论】:

  • 第二种格式是否会有额外的换行符?例如:{ 8 6 } {1 2 3 4 5 6 2 5 4 3 2 1 3 5 6 1 2 3 4 2 3 4 5 6} {2 3 4 5 6 7 3 4 5 6 7 8 4 5 6 7 8 9 5 6 7 8 9 0}
  • 是的,第二种格式没有额外的换行符。

标签: c# parsing text arrays


【解决方案1】:

加载数据后,您的数据使用模式是什么?您通常需要触摸每个数组元素还是只进行稀疏/随机访问?

如果您需要接触大多数数组元素,将其加载到内存中可能是最好的方法。

如果您只需要访问某些元素,您可能希望将需要的元素延迟加载到内存中。一种策略是确定文件使用两种布局中的哪一种(带/不带换行符),并创建一个算法以根据需要直接从磁盘加载特定元素(查找给定的文件偏移量,读取和解析)。为了有效地重新访问相同的元素,将元素在读取后保存在由偏移量索引的字典中是有意义的。在查找特定值的文件之前先检查字典。

在一般原则上,我会选择简单的路线,除非您的测试证明您需要走更复杂的路线 (avoid premature optimization)。

【讨论】:

    【解决方案2】:

    一次读取一个字符。如果是空格,则开始一个新数字。如果是数字,请使用它。

    对于多位数字,保留一个计数器变量:

    int counter = 0;
    while (fileOpen) {
        char ch = readChar(); // use your imagination to define this method.
        if (isDigit(ch)) {
            counter *= 10;
            counter += asciiToDecimal(ch);
        } else if (isWhitespace(ch)) {
            appendToArray(counter);
            counter = 0;
        } else {
            // Error?
        }
    }
    

    为澄清而编辑。

    【讨论】:

    • 需要对连续有多个空白字符(或换行符)的情况进行一些调整,否则 +1。
    • 是的;这并不是一个包罗万象的解决方案——仅仅是让 OP 思考的指南。编辑:另外,对于前两个数字,对“appendToArray()”的调用需要是别的东西。
    【解决方案3】:

    怎么样:

        static void Main()
        {
            // sample data
            File.WriteAllText("my.data", @"4 6
    1 2 3 4 5 6
    2 5 4 3 21111 101
    3 5 6234 1 2 3
    4 2 33434 4 5 6");
    
            using (Stream s = new BufferedStream(File.OpenRead("my.data")))
            {
                int rows = ReadInt32(s), cols = ReadInt32(s);
                int[,] arr = new int[rows, cols];
                for(int y = 0 ; y < rows ; y++)
                    for (int x = 0; x < cols; x++)
                    {
                        arr[y, x] = ReadInt32(s);
                    }
            }
        }
    
        private static int ReadInt32(Stream s)
        { // edited to improve handling of multiple spaces etc
            int b;
            // skip any preceeding
            while ((b = s.ReadByte()) >= 0 && (b < '0' || b > '9')) {  }
            if (b < 0) throw new EndOfStreamException();
    
            int result = b - '0';
            while ((b = s.ReadByte()) >= '0' && b <= '9')
            {
                result = result * 10 + (b - '0');
            }
            return result;
        }
    

    实际上,这对于分隔符并不是很具体——它几乎会假设任何不是整数的东西都是分隔符,并且它只支持 ASCII(如果你需要其他编码,你可以使用阅读器) .

    【讨论】:

      【解决方案4】:

      除非您解析这些文本文件的机器有限,否则几百 MB 的文件应该仍然适合内存。我建议您采用第一种按行阅读并使用拆分的方法。

      如果内存成为问题,您的第二种分块阅读方法应该可以正常工作。

      基本上我所说的只是实现它并衡量性能是否存在问题。

      【讨论】:

      • 但是; 100 MB - 假设是 ASCII;在 .NET 中创建时要翻倍。现在拆分它,所以 至少 再次加倍,加上开销和新数组。加上整数数组(每个 4 个字节)。它必须是 x64 才能自信地说它适合内存...
      【解决方案5】:

      假设我们已将整个文件读入一个字符串。
      你说前两个是行和列,所以我们肯定需要解析数字。
      之后,我们可以取前两个,创建我们的数据结构,并相应地填充它。

      var fileData = File.ReadAllText(...).Split(' ');
      var convertedToNumbers = fileData.Select(entry => int.Parse(entry));
      int rows = convertedToNumbers.First();
      int columns = convertedToNumbers.Skip(1).First();
      // Now we have the number of rows, number of columns, and the data.
      int[,] resultData = new int[rows, columns];
      // Skipping over rows and columns values.
      var indexableData = convertedToNumbers.Skip(2).ToList();
      for(int i=0; i<rows; i++)
          for(int j=0; j<columns; j++)
              resultData[i, j] = inedexableData[i*rows + j];
      

      另一种方法是从流中读取前两个,初始化数组,然后一次读取 n 个值,这会很复杂。此外,最好让文件在尽可能短的时间内保持打开状态。

      【讨论】:

      • 我们不能假设我们可以一次将整个文件读入内存。
      【解决方案6】:

      您想将文件流式传输到内存中并随时解析。

      private IEnumerable<String> StreamAsSpaceDelimited(this StreamReader reader)
      {
          StringBuilder builder = new StringBuilder();
          int v;
          while((v = reader.Read()) != -1)
          {
              char c = (char) v;
              if(Char.IsWhiteSpace(c))
              {
                  if(builder.Length >0)
                  {
                      yield return builder.ToString();
                      builder.Clear();
                  }
              }
              else
              {
                  builder.Append(c);
              }
          }
          yield break;
      }
      

      这会将文件解析为以空格分隔的字符串集合(lazily),然后您可以将它们读取为双精度,就像:

      using(StreamReader sr = new StreamReader("filename"))
      {
          var nums = sr.StreamAsSpaceDelimited().Select(s => int.Parse(s));
          var enumerator = nums.GetEnumerator();
          enumerator.MoveNext();
          int numRows = enumerator.Current;
          enumerator.MoveNext();
          int numColumns = enumerator.current;
          int r =0, c = 0;
          int[][] destArray = new int[numRows][numColumns];
          while(enumerator.MoveNext())
          {
              destArray[r][c] = enumerator.Current;
              c++;
              if(c == numColumns)
              {
                  c = 0;
                  r++;
                  if(r == numRows)
                     break;//we are done
              }
          }
      

      因为我们使用迭代器,所以一次读取的字符数不应超过几个字符。这是用于解析大文件的常用方法(例如LINQ2CSV 的工作原理)。

      【讨论】:

        【解决方案7】:

        这里有两种方法

        IEnumerable<int[]> GetArrays(string filename, bool skipFirstLine)
        {
            using (StreamReader reader = new StreamReader(filename))
            {
                if (skipFirstLine && !reader.EndOfStream)
                    reader.ReadLine();
        
                while (!reader.EndOfStream)
                {
                    string temp = reader.ReadLine();
                    int[] array = temp.Trim().Split().Select(s => int.Parse(s)).ToArray();
                    yield return array;
                }
            }
        }
        
        int[][] GetAllArrays(string filename, bool skipFirstLine)
        {
            int skipNumber = 0;
            if (skipFirstLine )
                skipNumber = 1;
            int[][] array = File.ReadAllLines(filename).Skip(skipNumber).Select(line => line.Trim().Split().Select(s => int.Parse(s)).ToArray()).ToArray();
            return array;
        }
        

        如果您要处理大文件,第一个可能更可取。如果文件很小,那么第二个可以将整个文件加载到锯齿状数组中。

        【讨论】:

        • 您不能使用 ReadLine,因为该文件可能包含任意长的行(如多个 MB),因此您可能会遇到内存不足错误
        • 啊,我没注意到第二个文件结构问题。
        猜你喜欢
        • 1970-01-01
        • 2016-02-05
        • 1970-01-01
        • 2019-10-22
        • 2012-01-19
        • 2015-12-03
        • 2013-01-16
        • 2012-03-06
        • 1970-01-01
        相关资源
        最近更新 更多