使用Directory.EnumerateFiles进行批处理

目录

介绍

例子

Directory.EnumerateFiles来救援

评估

关于批处理的几句话

关于返回类型的几句话

结论


介绍

如果想要从目录中检索文件,对于大多数情况来说,Directory.GetFiles是一个简单的答案。但是,当您处理大量数据时,您可能需要更高级的技术。

例子

假设您有一个大数据解决方案,您需要处理一个包含200000个文件的目录。对于每个文件,您提取一些基本信息。

public record FileProcessingDto
{
    public string FullPath { get; set; }
    public long Size { get; set; }
    public string FileNameWithoutExtension { get; set; }
    public string Hash { get; internal set; }
}

请注意我们如何方便地在此处为DTO使用新的C# 9记录类型

之后,我们发送提取的信息以供进一步处理。让我们用下面的代码片段来模拟它。

public class FileProcessingService
{
    public Task Process(IReadOnlyCollection<FileProcessingDto> files, 
                        CancellationToken cancellationToken = default)
    {
        files.Select(p =>
        {
            Console.WriteLine($"Processing {p.FileNameWithoutExtension} 
                              located at {p.FullPath} of size {p.Size} bytes");
            return p;
        });

        return Task.Delay(TimeSpan.FromMilliseconds(20), cancellationToken);
    }
}

现在最后一部分是提取信息并调用服务。

public class Worker
{
    public const string Path = @"path to 200k files";
    private readonly FileProcessingService _processingService;

    public Worker()
    {
        _processingService = new FileProcessingService();
    }

    private string CalculateHash(string file)
    {
        using (var md5Instance = MD5.Create())
        {
            using (var stream = File.OpenRead(file))
            {
                var hashResult = md5Instance.ComputeHash(stream);
                return BitConverter.ToString(hashResult)
                    .Replace("-", "", StringComparison.OrdinalIgnoreCase)
                    .ToLowerInvariant();
            }
        }
    }

    private FileProcessingDto MapToDto(string file)
    {
        var fileInfo = new FileInfo(file);
        return new FileProcessingDto()
        {
            FullPath = file,
            Size = fileInfo.Length,
            FileNameWithoutExtension = fileInfo.Name,
            Hash = CalculateHash(file)
        };
    }

    public Task DoWork()
    {
        var files = Directory.GetFiles(Path)
            .Select(p => MapToDto(p))
            .ToList();

        return _processingService.Process(files);
    }
}

请注意,在这里,我们以一种天真的方式行事,并通过Directory.GetFiles(Path)一次提取提取所有文件。

但是,一旦您通过以下方式运行此代码:

await new Worker().DoWork()

您会注意到结果远不能令人满意,并且应用程序正在大量消耗内存。

Directory.EnumerateFiles来救援

Directory.EnumerateFiles的作用是它返回IEnumerable<string>,从而允许我们一个一个地获取集合项。这反过来又可以防止我们在一次加载大量数据时过度使用内存。

尽管如此,正如您可能已经注意到的那样, FileProcessingService.Process包含延迟编码(我们使用简单延迟模拟的某种I/O操作)。在实际场景中,这可能是对外部 HTTP端点的调用或使用存储。这使我们得出结论,调用FileProcessingService.Process 200 000次可能效率低下。这就是为什么我们要一次性将合理批次的数据加载到内存中。

重新编写的代码如下所示:

public class WorkerImproved
{
    //omitted for brevity

    public async Task DoWork()
    {
        const int batchSize = 10000;
        var files = Directory.EnumerateFiles(Path);
        var count = 0;
        var filesToProcess = new List<FileProcessingDto>(batchSize);

        foreach (var file in files)
        {
            count++;
            filesToProcess.Add(MapToDto(file));
            if (count == batchSize)
            {
                await _processingService.Process(filesToProcess);
                count = 0;
                filesToProcess.Clear();
            }

        }
        if (filesToProcess.Any())
        {
            await _processingService.Process(filesToProcess);
        }
    }
}

在这里,我们使用foreach枚举集合,一旦达到批处理的大小,我们就会处理它并刷新集合。这里唯一有趣的时刻是在我们退出循环后最后一次调用service以刷新剩余的项目。

评估

Benchmark.NET产生的结果非常有说服力:

关于批处理的几句话

在本文中,我们浏览了软件工程中的常见模式。合理数量的批处理有助于我们克服以逐项方式工作的I/O损失和一次性将所有项目加载到内存中的过多内存消耗。

通常,在对多个项目进行I/O操作时,您应该努力使用批处理API。一旦项目数量变多,您应该考虑将这些项目分成批次。

关于返回类型的几句话

在处理代码库时,我经常看到类似于以下内容的代码:

public IEnumerable<int> Numbers => new List<int> { 1, 2, 3 };

我认为这段代码违反了Postel的原则,随之而来的事情是,作为一个属性的消费者,我无法弄清楚我是否可以一个一个地枚举项目,或者它们是否只是一次加载到内存中。

这是我建议对返回类型更具体的原因,即:

public IList<int> Numbers => new List<int> { 1, 2, 3 };

结论

批处理是一种很好的技术,可以让您优雅地处理大量数据。Directory.EnumerateFiles是允许您为包含大量文件的目录组织批处理的API

https://www.codeproject.com/Tips/5298439/Batch-Processing-with-Directory-EnumerateFiles

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值