从一个目录复制文件到另一个目录的最快方法

4

我需要将文件从一个目录复制到另一个目录,取决于SQL数据库表中文件名是否存在。

为此,我使用以下代码:

using(SqlConnection connection = new SqlConnection("datasource or route"))
{

  connection.Open();

  using(SqlCommand cmd = new SqlCommand("SELECT idPic, namePicFile FROM DocPicFiles", connection))
  using (SqlDataReader reader = cmd.ExecuteReader())
  {

    if (reader != null)
    {
      while (reader.Read())
      {
        //picList IS AN ARRAY THAT Contains All the files names in a directory
        if (picList.Any(s => s.Contains(reader["namePicFile"].ToString())))
        {
          File.Copy("theFile  in the Directory or array picList",  "the destiny directory"+ ".jpg", false)
        }
      }
    }
  }
}

有没有更快的方法完成这个任务?对于20,876条记录,需要1小时才能完成。


2
这段时间有多少被 File.Copy 使用,有多少被循环和查找名称使用?如果你不进行测量,就无法找出问题所在并寻找可行(且已存在的)解决方案。 - Steve
1
将你的方法拆分成两部分,第一部分返回一个包含所有想要复制的文件的List<string>,然后将该列表发送到另一个执行复制操作的方法中。然后你可以测量这两个方法并找出瓶颈所在。 - Kvam
如果您可以在此处运行cmd,则我的解决方案将有所帮助。https://dev59.com/WG445IYBdhLWcg3wq8Pp - pankaj
4个回答

13

File.Copy的速度已经尽可能快了。您必须记住,您取决于硬件所规定的文件传输速度,并且在20000个文件时,数据访问的延迟也会发挥作用。如果您正在使用HDD进行此操作,则切换到SSD或其他快速介质后,您可能会看到巨大的改善。

仅针对这种情况,最有可能出现瓶颈的是硬件。

编辑:我认为保持与数据库的连接时间过长是一种不良做法。我建议您在某些内存缓存(数组、列表等)中获取所有所需数据,然后在复制文件时通过该缓存进行迭代。DB连接是宝贵的资源,在必须处理高并发性(但不仅限于此)的应用程序上,快速释放连接是必须的。


耶!连接增加了资源,我改成了一个对象列表“DocPicFiles”,这样移位显著减少,复制操作只需要35分钟!! - ger
@ger:让我惊讶的是,这么小的改变竟然带来了如此大的时间差异。如果您可以的话,请再次运行您的代码,一次保持连接打开,一次关闭连接。这样我们就能知道没有其他因素在起作用了。 - displayName

9
让我猜一下 - 没有办法更快地完成这个任务。
为什么我这么自信呢?因为文件复制需要与磁盘进行通信,而这是一个非常慢的操作。而且,如果你尝试使用多线程,结果会变得更慢,因为移动磁头的机械操作不再是顺序的,可能之前只是偶然的顺序。
请参考我之前提出的问题的答案。
如果你还没有使用固态硬盘,请切换到固态硬盘,否则你已经得到了最好的效果。
下面是一些数据,用来说明在磁盘写入时,“慢”意味着什么,与缓存相比。如果缓存访问需要10分钟,那么从磁盘读取需要2年时间。所有的访问都显示在下面的图片中。显然,当你的代码执行时,瓶颈将是磁盘写入。你能做的最好的事情就是让磁盘写入保持顺序。

enter image description here


https://dev59.com/WG445IYBdhLWcg3wq8Pp - pankaj

5

由于您的I/O子系统几乎肯定是瓶颈,使用并行任务库可能是最好的选择:

static void Main(string[] args)
{
  DirectoryInfo source      = new DirectoryInfo( args[0] ) ;
  DirectoryInfo destination = new DirectoryInfo( args[1] ) ;

  HashSet<string> filesToBeCopied = new HashSet<string>( ReadFileNamesFromDatabase() , StringComparer.OrdinalIgnoreCase ) ;

  // you'll probably have to play with MaxDegreeOfParallellism so as to avoid swamping the i/o system
  ParallelOptions options= new ParallelOptions { MaxDegreeOfParallelism = 4 } ;

  Parallel.ForEach( filesToBeCopied.SelectMany( fn => source.EnumerateFiles( fn ) ) , options , fi => {
      string destinationPath = Path.Combine( destination.FullName , Path.ChangeExtension( fi.Name , ".jpg") ) ;
      fi.CopyTo( destinationPath , false ) ;
  }) ;

}

public static IEnumerable<string> ReadFileNamesFromDatabase()
{
  using ( SqlConnection connection = new SqlConnection( "connection-string" ) )
  using ( SqlCommand cmd = connection.CreateCommand() )
  {
    cmd.CommandType = CommandType.Text ;
    cmd.CommandText = @"
      select idPic ,
             namePicFile
      from DocPicFiles
      " ;

    connection.Open() ;
    using ( SqlDataReader reader = cmd.ExecuteReader() )
    {
      while ( reader.Read() )
      {
        yield return reader.GetString(1) ;
      }
    }
    connection.Close() ;

  }
}

3
你有任何时间测量支持TPL会比OP当前的方法更快吗?我曾经看到多线程的方式比单线程的性能更慢。 - displayName
@nicholas-carey 我在这行代码上遇到了问题:Parallel.ForEach( filesToBeCopied.SelectMany( fn => source.EnumerateFiles( fn ) ) , options , fi => { string destinationPath = Path.Combine( destination.FullName , Path.ChangeExtension( fi.Name , ".jpg") ) ; fi.CopyTo( destinationPath , false ) ; - ger

1

我通过使用参数只存储文件(不压缩)来创建一个单一的压缩文件(.zip)来解决这个问题。创建单一的(.zip)文件,将该单一文件移动,然后在目标位置进行扩展,处理数千个文件时速度提高了2倍。


嗨@keith gresham,但我需要一对一而不是所有文件包!谢谢! - ger
1
@ger 我相信Keith的解决方案是指在源和目标位于不同物理驱动器时移动大量文件的问题。在这种特定情况下,他在这里提到的技术可以通过 consolidaing 目标MFT写入来加速事情,从而可以减少在该复杂活动期间的缓存未命中。 - Glenn Slayden

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接