【问题标题】:Directory Enumeration Too Slow for First Time第一次目录枚举太慢
【发布时间】:2013-07-06 15:50:54
【问题描述】:

我想尝试制作自己的文件浏览器。我有一个算法来枚举所有驱动器中的所有目录。但它运行得太慢了。这是我的代码:

public ExplorerForm()
{
    InitializeComponent();
    this.SuspendLayout();//Without this, it will be far more slow again!
    string[] a = System.IO.Directory.GetLogicalDrives();// a is used for drive array
    for(int b = 0; b < a.Length; b++)//B is an enumerator. Ussually it is only one letter
    {
        //Defining the node
        TreeNode c = new TreeNode();//c i place for TreeNode
        c.Text = a[b].Substring(0,2);
        c.Tag = a[b];
        ApplyNodes(a[b], ref c);
        if(c != null) tv.Nodes.Add(a)
    }
    this.ResumeLayout(false);
}
private void ApplyNodes(string a, ref TreeNode b)//a=directory, b applied TreeNode
{
    try{
        List<string> c = new List<string>(Directory.EnumerateDirectories(a);//c = directories
        if (c.Count == 0 ) return;
        for(int d = 0; d < c.Count; d++)//d = enumerator.
        {
            TreeNode e = new TreeNode();//e = TreeNode
            var z = c[b].Split(Convert.ToChar("/"));
            e.Text = z[z.Length-1]
            e.Tag = c[b];
            ApplyNodes(c[b], e)
            if(e != null) b.Nodes.Add(e)
        }
    }catch (UnauthorizedAccessException){
    }catch (IOException){  //For test, It is removed. and my E: is not ready
    }
}

电视是我的控制。 它运行得很慢。当我删除选择的行时会显示,抛出 IOException 需要 10 多秒的时间。帮助我如何改进枚举。使用线程和部分更新除外。如果以后不能修复,请告诉我原因。

【问题讨论】:

  • Directory Enumeration too slow - 购买硬盘。
  • 错误..是的。这就是为什么您不枚举所有目录的原因。您只需在需要时显示您需要的内容。
  • 其他文件管理器使用相同的慢速界面。仔细观察它们的运作方式。
  • 帮助我如何改进枚举。除了使用线程和部分更新之外。您基本上是在说“这很慢。在没有公认的使其更快的技术的情况下使其更快。”您想要解决问题的学术解决方案还是实用解决方案?
  • 不,Windows 的文件资源管理器只需要两秒钟 打开 Windows 资源管理器并单击文件夹浏览器左侧的 C: 驱动器。按小键盘上的* 键。数到一密西西比,然后数到两密西西比。完成了吗?否。如果您想减少重绘的影响,请将窗口最小化。

标签: c# performance


【解决方案1】:

除了先前关于改善 TreeView 人口调用的答案之外,您还应该阅读 MSDN 页面How to: Enumerate Directories and Files

第一段提到了一些性能改进(使用 DirectoryInfo 的可枚举集合而不是字符串) - 请注意最后一行:

您可以使用返回的方法枚举目录和文件 他们名字的可枚举字符串集合。你也可以使用 返回 DirectoryInfo 的可枚举集合的方法, FileInfo 或 FileSystemInfo 对象。可枚举的集合提供 处理大型集合时,性能比数组更好 目录和文件。

然而,即使有了这种改进,您确实应该递归地下降 ApplyNodes 内的整个子树。只需读取一个级别,添加当前节点的条目,以大大减少您需要遍历的子目录的数量(这肯定是文件资源管理器所做的事情。)这就是提到的“延迟加载”技术的重点以上来自 ta.speot.is

如果这两项改进仍然无法为您提供所需的性能,那么您可能需要增加更多复杂性(例如,运行后台线程来执行您的遍历),但您需要对确切的您的代码的哪一部分首先是您的瓶颈(这意味着您需要添加计时代码和日志记录)

【讨论】:

  • 使用foreach?我正在尝试那个。但瓶颈是Directory.EnumerateDirectories()
  • 您正在使用 Directory 类的静态方法。尝试使用 DirectoryInfo 类代替枚举,例如stackoverflow.com/questions/6239544/… 这可能对一些人有所帮助,但最好的办法是删除目录子树的完全递归遍历,并且仅遍历当前 TreeView 状态所需的距离(即仅遍历另一个级别的子树以响应用户扩展一个节点)
【解决方案2】:

除了枚举系统上所有目录所花费的时间(如果您实施lazy loading,您可能会看到更好的性能),将项目插入TreeView 可能需要大量时间。

来自TreeView.BeginUpdate

要在将项目一次添加到 TreeView 时保持性能,请调用 BeginUpdate 方法。 BeginUpdate 方法阻止控件绘制,直到调用 EndUpdate 方法。 将项添加到树视图控件的首选方法是使用 AddRange 方法将树节点项数组添加到树视图。

...

要让控件恢复绘制,请在所有树节点都添加到树视图后调用 EndUpdate 方法。

虽然它与 .NET 不同,但 Raymond Chen 的博文 How to insert a large number of items into a treeview efficiently 提供了更多信息,这些信息可能会帮助您以一种可以提高项目插入性能的方式构建代码。

如果您需要在树视图中插入大量项目,比如数万个,那么“向后”插入它们会更有效。

编辑

这是一个将目录枚举放到线程中的示例。观察TreeView 控件的可用性(或缺少)。如果不出意外,这可能是使用延迟加载的最佳理由。

private void Form1_Load(object sender, EventArgs e)
{
    var treeNode = new TreeNode("Sea Drive");
    treeView1.Nodes.Add(treeNode);

    ThreadPool.QueueUserWorkItem(_ => TraverseDirectory("C:\\", treeNode));
}
   
private static readonly string DirectorySeparatorString = Path.DirectorySeparatorChar.ToString();

private void TraverseDirectory(string initialDirectoryPath, TreeNode initialTreeNode)
{
    var initialTuples = new[] {Tuple.Create(initialDirectoryPath, initialTreeNode)};
    var directoryQueue = new Queue<Tuple<string, TreeNode>>(initialTuples);

    while (directoryQueue.Any())
    {
        var tuple = directoryQueue.Dequeue();
        var parentDirectoryPath = tuple.Item1;
        var parentTreeNode = tuple.Item2;

        try
        {
            var treeNodes = new List<TreeNode>();
            var directories = Directory.EnumerateDirectories(parentDirectoryPath);

            foreach (var directoryPath in directories)
            {
                var lastDirectorySeparator = directoryPath.LastIndexOf(DirectorySeparatorString);
                var directoryName = directoryPath.Substring(lastDirectorySeparator + 1);

                // Add the tree node to our list of child 
                // nodes, for an eventual call to AddRange
                var treeNode = new TreeNode(directoryName);
                treeNodes.Add(treeNode);

                // We have to go deeper
                directoryQueue.Enqueue(Tuple.Create(directoryPath, treeNode));
            }

            // Run this operation on the main thread
            Invoke((Action)(() => parentTreeNode.Nodes.AddRange(treeNodes.ToArray())));
        }
        catch (Exception exception)
        {
            Trace.Write(exception);
        }
    }
}

示例不完整;您需要提供自己的 FormTreeView 控件。

【讨论】:

  • TreeView.BeginUpdate() 是否与 Form.SuspendLayout 不同?
  • 是的,因为它影响绘画而不是布局。
  • 我们为什么不read the fine manual 找出答案? Control.SuspendLayout 暂时暂停控件的布局逻辑 vs. TreeView.BeginUpdate 禁用树视图的任何重绘
  • 会有帮助的。但从测试来看,真正的问题不在于 TreeView。如果您向我发送其他方式以在开始时以较不严重的速度进行枚举,这也会有所帮助。
  • 点击第一个关于延迟加载的链接... 使用 WinForms TreeView 你需要至少有一个子节点,否则它不会显示展开 [+],但是然后您处理TreeNodeExpanded 事件以删除该虚拟节点并填充子节点。另请参阅我的评论here。您似乎已经知道解决此问题的解决方案,只是无缘无故地丢弃它们。
猜你喜欢
  • 2018-07-10
  • 2015-07-21
  • 2021-06-04
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-08-03
  • 1970-01-01
相关资源
最近更新 更多