.NET Stream CopyTo 错误?
.NET Stream CopyTo bug?
我有一堆 CSV 文件,所有这些文件的第一行都是 header 行。
我需要将所有这些 CSV 文件合并到一个文件中,只复制 header 一次并将其保留为合并文件的第一行。
我写了下面的代码:
public static void Merge( string outputFile, params string[] inputFiles )
{
if( inputFiles == null || inputFiles.Length <= 1 ) return;
using( Stream outputStream = new FileStream( outputFile,
FileMode.Append, FileAccess.Write, FileShare.None ) )
{
for( int i = 0; i < inputFiles.Length; i++ )
{
var inputFile = inputFiles[ i ];
using( var inputStream = File.OpenRead( inputFile ) )
using( var textReader = new StreamReader( inputStream ) )
{
if( i != 0 )
textReader.ReadLine();
textReader.BaseStream.CopyTo( outputStream );
}
}
}
}
上面的代码正确地跳过了每个文件的第一行(第一个文件被完全复制到输出的除外),但是未能正确写入每个文件的第二行 (大约每个文件第二行的前半部分丢失了)然后从第三行开始按预期工作。
似乎是流的位置问题或 CopyTo 方法中的错误。有什么想法吗?
P.S: 这个问题很容易用下面的代码解决,但是我真的很想知道上面的代码有什么问题。谢谢
public static void Merge( string outputFile, string inputDir, string filtro )
{
if( String.IsNullOrEmpty( filtro ) )
filtro = "*.*";
var inputFiles = Directory.GetFiles( inputDir, filtro );
using( FileStream outputStream = new FileStream( outputFile,
FileMode.Append, FileAccess.Write, FileShare.None ) )
{
using( var sw = new StreamWriter( outputStream ) )
{
for( int i = 0; i < inputFiles.Length; i++ )
{
var inputFile = inputFiles[ i ];
using( var inputStream = File.OpenRead( inputFile ) )
using( var textReader = new StreamReader( inputStream ) )
{
if( i != 0 && textReader.BaseStream.Position != textReader.BaseStream.Length )
textReader.ReadLine();
while( textReader.BaseStream.Position != textReader.BaseStream.Length )
sw.WriteLine( textReader.ReadLine() );
}
}
}
}
}
问题是缓冲之一。
您使用 StreamReader 跳过 1 行实际上会跳过 1 行以上,除非您非常幸运。
如果您检查 reference source,您会看到 StreamReader 使用缓冲区,并会在需要时尝试填充缓冲区。因此很有可能它不仅抓到当前行的末尾。如果您的文件的第一行非常短,那么第一个缓冲区读取也可能会从您的文件开头抓取相当多的行。参考源的默认缓冲区大小似乎是 1024 或 4096,具体取决于您的框架类型和版本。
然后,之后,当您绕过 reader 并使用底层流时,它将定位在 reader 读取的最后一个缓冲区之后。这就是为什么它从某行的中间开始。
现在,有多种方法可以完成您想要的操作,但您可以将整个事情重写为延迟评估的 LINQ 查询并摆脱所有代码。
public static void Merge( string outputFile, string inputDir, string filtro )
{
if( String.IsNullOrEmpty( filtro ) )
filtro = "*.*";
var inputFiles = Directory.GetFiles( inputDir, filtro );
File.AppendAllLines(outputFile, inputFiles
.SelectMany((inputFile, index) =>
File.ReadLines(inputFile).Skip(index == 0 ? 0 : 1)));
}
我有一堆 CSV 文件,所有这些文件的第一行都是 header 行。 我需要将所有这些 CSV 文件合并到一个文件中,只复制 header 一次并将其保留为合并文件的第一行。
我写了下面的代码:
public static void Merge( string outputFile, params string[] inputFiles )
{
if( inputFiles == null || inputFiles.Length <= 1 ) return;
using( Stream outputStream = new FileStream( outputFile,
FileMode.Append, FileAccess.Write, FileShare.None ) )
{
for( int i = 0; i < inputFiles.Length; i++ )
{
var inputFile = inputFiles[ i ];
using( var inputStream = File.OpenRead( inputFile ) )
using( var textReader = new StreamReader( inputStream ) )
{
if( i != 0 )
textReader.ReadLine();
textReader.BaseStream.CopyTo( outputStream );
}
}
}
}
上面的代码正确地跳过了每个文件的第一行(第一个文件被完全复制到输出的除外),但是未能正确写入每个文件的第二行 (大约每个文件第二行的前半部分丢失了)然后从第三行开始按预期工作。
似乎是流的位置问题或 CopyTo 方法中的错误。有什么想法吗?
P.S: 这个问题很容易用下面的代码解决,但是我真的很想知道上面的代码有什么问题。谢谢
public static void Merge( string outputFile, string inputDir, string filtro )
{
if( String.IsNullOrEmpty( filtro ) )
filtro = "*.*";
var inputFiles = Directory.GetFiles( inputDir, filtro );
using( FileStream outputStream = new FileStream( outputFile,
FileMode.Append, FileAccess.Write, FileShare.None ) )
{
using( var sw = new StreamWriter( outputStream ) )
{
for( int i = 0; i < inputFiles.Length; i++ )
{
var inputFile = inputFiles[ i ];
using( var inputStream = File.OpenRead( inputFile ) )
using( var textReader = new StreamReader( inputStream ) )
{
if( i != 0 && textReader.BaseStream.Position != textReader.BaseStream.Length )
textReader.ReadLine();
while( textReader.BaseStream.Position != textReader.BaseStream.Length )
sw.WriteLine( textReader.ReadLine() );
}
}
}
}
}
问题是缓冲之一。
您使用 StreamReader 跳过 1 行实际上会跳过 1 行以上,除非您非常幸运。
如果您检查 reference source,您会看到 StreamReader 使用缓冲区,并会在需要时尝试填充缓冲区。因此很有可能它不仅抓到当前行的末尾。如果您的文件的第一行非常短,那么第一个缓冲区读取也可能会从您的文件开头抓取相当多的行。参考源的默认缓冲区大小似乎是 1024 或 4096,具体取决于您的框架类型和版本。
然后,之后,当您绕过 reader 并使用底层流时,它将定位在 reader 读取的最后一个缓冲区之后。这就是为什么它从某行的中间开始。
现在,有多种方法可以完成您想要的操作,但您可以将整个事情重写为延迟评估的 LINQ 查询并摆脱所有代码。
public static void Merge( string outputFile, string inputDir, string filtro )
{
if( String.IsNullOrEmpty( filtro ) )
filtro = "*.*";
var inputFiles = Directory.GetFiles( inputDir, filtro );
File.AppendAllLines(outputFile, inputFiles
.SelectMany((inputFile, index) =>
File.ReadLines(inputFile).Skip(index == 0 ? 0 : 1)));
}