2010-06-01 23 views
3

我需要一个快速有效的方法来读取一个空格分隔的文件,并将数字读入数组中。这些文件格式如下:快速而有效的方法来读取空格分隔的数字文件到数组中?

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 

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

的数据也可以在没有任何这样的换行格式化:

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中。第二种方法是以块的形式读取文件,然后逐个解析它们。也许别人有更好的方法来做到这一点?

+0

你会有第二种格式的额外换行符吗?例如:{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}' – AllenG 2010-06-01 20:02:02

+0

是的,在第二种格式中没有额外的换行符。 – 2010-06-01 20:04:58

回答

1

如何:

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(如果您需要其他编码,则使用阅读器)。

0

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

如果内存成为问题,您的第二种阅读块应该很好。

基本上我说的只是实现它,并衡量如果性能是一个问题。

+0

但是; 100个MB - 让我们假设它是ASCII;所以在.NET中创建的时候是这样的。现在分割它,所以*至少再加倍,加上开销和新数组。加上整数数组(每个4个字节)。在您可以自信地说它适合内存之前,它必须是x64 ... – 2010-06-01 20:31:32

2

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

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

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

关于总体原则,我会采取简单的路线,除非您的测试证明您需要走更复杂的路线(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? 
    } 
} 

编辑澄清。

+0

需要针对连续多个空格字符(或换行符)的情况进行一些调整,否则需要+1。 – dtb 2010-06-01 20:07:48

+0

是的;这并不意味着是一个全面的解决方案 - 仅仅是获得OP思想的指导。 编辑:此外,对'appendToArray()'的调用将需要是前两个数字的其他内容。 – TreDubZedd 2010-06-01 20:10:27

0

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

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个值。另外,最好尽可能在最短的时间内保持文件的打开状态。

+0

我们不能假设我们可以一次将整个文件读入内存。 – luke 2010-06-01 23:52:01

0

您想要将文件流式传输到内存中并在您进行解析时使用。

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; 
} 

这将文件解析成空间分隔的字符串(懒洋洋地)的一个集合,然后你可以阅读他们刚刚双打喜欢:

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的工作方式)。

0

这里有两种方法

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; 
} 

如果你正在处理大文件,首先很可能是preferrable。如果文件很小,那么第二个文件可以将整个文件加载到锯齿状的数组中。

+0

您不能使用ReadLine,因为该文件可能包含任意长的行(如在多个MB中),因此您可能会遇到内存不足错误 – luke 2010-06-01 23:53:15

+0

啊,我没有注意到第二个文件结构问题。 – 2010-06-02 04:52:59

相关问题