【问题标题】:.NET Stream CopyTo bug?.NET Stream CopyTo 错误?
【发布时间】:2020-11-10 10:35:31
【问题描述】:

我有一堆 CSV 文件,所有这些文件的第一行都有一个标题行。 我需要将所有这些 CSV 文件合并到一个文件中,只需将标题复制一次并将其保留为合并文件的第一行。

我写了以下代码:

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 并使用它来读取第一行。但是,StreamReader 内部有一个 buffer 以避免从底层流中读取 1 个字符/字节。因此,StreamReader 实际上会从底层流中读取比第一行更多的内容,然后当您绕过阅读器并从流中读取时,您只需继续填充该缓冲区的位置,很可能是在某行的中间。跨度>
  • 您可能还想在 Google 上搜索短语 SELECT isn't broken。与您在您的代码中出错的可能性相比,您在核心框架组件中发现明显的错误的可能性微乎其微。
  • 我会改用代码的底部示例,并将核心循环重写为foreach (var line in File.ReadLines(inputFile).Skip(1)) sw.WriteLine(line);
  • @LasseV.Karlsen:或者可能只使用File.AppendAllLines 并完全摆脱sw...
  • @JonSkeet 同意,我已经在考虑这个答案了

标签: .net filestream streamreader


【解决方案1】:

问题在于缓冲。

您使用 StreamReader 跳过 1 行实际上会跳过超过 1 行,除非您非常幸运。

如果您检查reference source,您会看到 StreamReader 使用缓冲区,并会在需要时尝试填充缓冲区。因此,它很可能不仅仅抓取到当前行的末尾。如果文件的第一行很短,那么第一次读取的缓冲区也可能会从文件开头抓取很多行。参考源的默认缓冲区大小似乎是 1024 或 4096,具体取决于您的框架类型和版本。

然后,当您绕过阅读器并使用底层流时,它将定位在阅读器进行的最后一次缓冲区读取之后。这就是为什么它从某行的中间开始。

现在,有多种方法可以实现您想要的,但您可以将整个事情重写为延迟评估的 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)));
}

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-05-22
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-02-05
    • 2012-05-02
    相关资源
    最近更新 更多