C# 列举100000+;文件夹层次结构需要几个小时
为什么此代码需要几个小时才能完成:C# 列举100000+;文件夹层次结构需要几个小时,c#,pinvoke,C#,Pinvoke,为什么此代码需要几个小时才能完成: public void SetRoot(string path) { Tag = path; BeginUpdate(); AddFolderRecursive(Nodes, path); EndUpdate(); } private void AddFolderRecursive(TreeNodeCollection nodes, string path) { try { var dirs =
public void SetRoot(string path)
{
Tag = path;
BeginUpdate();
AddFolderRecursive(Nodes, path);
EndUpdate();
}
private void AddFolderRecursive(TreeNodeCollection nodes, string path)
{
try
{
var dirs = Directory.EnumerateDirectories(path).OrderBy(d => d).Select(d => d.Split(Path.DirectorySeparatorChar).Last());
TreeNode node;
ShellFileGetInfo.FolderIcons fi;
foreach (var d in dirs)
{
node = nodes.Add(Path.Combine(path, d), d, ImageList.Images.Count);
node.Tag = Path.Combine(path, d);
node.SelectedImageIndex = ImageList.Images.Count + 1;
fi = ShellFileGetInfo.GetFolderIcon((string)node.Tag, false);
// ImageList.Images.Add(fi.closed);
// ImageList.Images.Add(fi.open);
AddFolderRecursive(node.Nodes, (string)node.Tag);
}
}
catch (UnauthorizedAccessException)
{
}
}
我已经让这段代码运行了14个小时了,但在像SetRoot(@“c:\”)那样调用它时,它仍然没有完成获取所有文件夹的列表代码>。代码正在运行,它正在添加到树中,但这太荒谬了
基本上,我想用我驱动器上的所有文件夹来流行treeview(因此treeview是可搜索的),并使用实际的文件夹图标(我使用的是SHGetFileInfo
p/invoke和必要的参数)。即使没有得到图标(我遇到的另一个问题是,尽管图标图像本身可能是相同的,但获取文件夹的图标在每个文件夹中都是唯一的。我找不到一种方法来快速确定,如果我已经将该图像保存在我的树视图的ImageList
-aka中,“c:\windows”的文件夹图标是相同的但是,作为“c:\windows\system32”,句柄等都返回不同的信息,因此似乎没有任何东西可以对它们进行唯一索引。)
我的代码中有什么可以调整以使这个过程更快,同时仍然保留系统中的文件夹图标?请记住,我还希望所有文件夹都不跳过空文件夹
(我甚至无法显示TreeView的图片,因为在它完成显示之前14小时,我停止了循环的运行)
为了测试TreeView控件的速度,我编写了以下代码:
DateTime start = DateTime.UtcNow;
treeView1.BeginUpdate();
await Task.Run(() =>
{
int x = 0;
while (x < 100000)
{
x++;
if (treeView1.InvokeRequired)
{
treeView1.Invoke((MethodInvoker)delegate { treeView1.Nodes.Add("Node - " + x); } );
}
}
});
treeView1.EndUpdate();
Text = start.ToLongTimeString() + " - " + DateTime.UtcNow.ToLongTimeString();
DateTime start=DateTime.UtcNow;
treeView1.BeginUpdate();
等待任务。运行(()=>
{
int x=0;
而(x<100000)
{
x++;
if(treeView1.invokererequired)
{
Invoke((MethodInvoker)委托{treeView1.Nodes.Add(“Node-”+x);});
}
}
});
treeView1.EndUpdate();
Text=start.ToLongTimeString()+“-”+DateTime.UtcNow.ToLongTimeString();
下面是结果的屏幕截图:
如您所见,如果您使用BeginUpdate
和EndUpdate
来防止控件在每个项目上绘制或刷新,则在大约2分钟的时间内用100000个项目填充TreeView会非常快。这也表明,绝对不是TreeView控件在拖我的后腿——14个小时是必要的过度-即使是1996年的驱动器,14小时枚举100000个文件夹也太长了。我敢打赌代码不是原因,它可能是两件事之一:
你的硬盘乱七八糟,你可以试试碎片分割法
(我也遇到了这种情况)windows没有为您的文件夹编制索引(索引-)若要修复此问题,您需要转到您正在处理的主文件夹,让windows为文件夹及其所有子文件夹(文件夹框上的某个位置)编制索引,此过程大约需要一天时间,但在此之后,您的程序应该可以正常(快速)运行
我敢打赌代码不是原因,它可能是两件事之一:
你的硬盘乱七八糟,你可以试试碎片分割法
(我也遇到了这种情况)windows没有为您的文件夹编制索引(索引-)若要修复此问题,您需要转到您正在处理的主文件夹,让windows为文件夹及其所有子文件夹(文件夹框上的某个位置)编制索引,此过程大约需要一天时间,但在此之后,您的程序应该可以正常(快速)运行
Windows树视图控件的设计根本不适合容纳这么多节点。您根本不可能在实际时间内用数千个节点填充此控件。此外,即使在短时间内枚举所有这些项,也肯定是不现实的。更糟糕的是,试图提取树中每个对象的图标时间的广告
前进的方向是,您不必尝试用所有项填充控件。只需填充父节点。然后,当它们打开时,枚举并添加子节点。这是所有shell程序的操作方式。Windows树视图控件的设计不是为了容纳这么多节点。您根本不可能填充此控件ol在实际时间内有数千个节点。此外,即使在短时间内枚举所有这些项目也肯定是不现实的。更尴尬的是,试图提前提取树中每个对象的图标
前进的方向是,您不必尝试用所有项填充控件。只需填充父节点。然后当它们打开时,枚举并添加子节点。这就是所有shell程序的操作方式。进一步研究后,我发现问题在于,可用于枚举文件和文件夹的方法非常慢,并且t当文件夹不可访问时,会抛出一个UnauthorizedAccessException
,每次事件的固有延迟约为200ms。这些异常会叠加并导致较大的延迟
此外,关于向TreeView添加项时的二次指数的Davids陈述也会导致进一步的延迟,但是在这种情况下,额外的延迟仅与TreeView节点添加的比例不成比例
为了解决这个问题,我能够将其缩小到3个问题,其中两个问题我已经完全解决,以便在合理的时间范围内控制功能的这些部分。分解它,下面是导致OP问题延迟的3个问题:
- 树越深,添加的节点越多,添加的TreeView节点的速度就越慢
- 文件系统访问不会访问NTFS可用的本机日志系统,因此每次调用都会单独获取每个文件或目录。此外,如果文件夹被标记为受限,则每次遇到
UnauthorizedAccessException
会造成约200ms的人为延迟
- 检索自定义文件夹图标
#region TreeViewFast
private readonly Dictionary<ulong, TreeNode> _treeNodes = new Dictionary<ulong, TreeNode>();
/// <summary>
/// Load the TreeView with items.
/// </summary>
/// <typeparam name="T">Item type</typeparam>
/// <param name="items">Collection of items</param>
/// <param name="getId">Function to parse Id value from item object</param>
/// <param name="getParentId">Function to parse parentId value from item object</param>
/// <param name="getDisplayName">Function to parse display name value from item object. This is used as node text.</param>
public void LoadItems<T>(IEnumerable<T> items, Func<T, ulong> getId, Func<T, ulong?> getParentId, Func<T, string> getDisplayName)
{
// Clear view and internal dictionary
Nodes.Clear();
_treeNodes.Clear();
// Load internal dictionary with nodes
foreach (var item in items)
{
var id = getId(item);
var displayName = getDisplayName(item);
var node = new TreeNode { Name = id.ToString(), Text = displayName, Tag = item };
_treeNodes.Add(getId(item), node);
}
// Create hierarchy and load into view
foreach (var id in _treeNodes.Keys)
{
var node = GetNode(id);
var obj = (T)node.Tag;
var parentId = getParentId(obj);
if (parentId.HasValue)
{
var parentNode = GetNode(parentId.Value);
if(parentNode == null)
{
Nodes.Add(node);
} else
{
parentNode.Nodes.Add(node);
}
}
else
{
Nodes.Add(node);
}
}
}
/// <summary>
/// Get a handle to the object collection.
/// This is convenient if you want to search the object collection.
/// </summary>
public IQueryable<T> GetItems<T>()
{
return _treeNodes.Values.Select(x => (T)x.Tag).AsQueryable();
}
/// <summary>
/// Retrieve TreeNode by Id.
/// Useful when you want to select a specific node.
/// </summary>
/// <param name="id">Item id</param>
public TreeNode GetNode(ulong id)
{
try
{
return _treeNodes[id];
} catch (KeyNotFoundException)
{
return null;
}
}
/// <summary>
/// Retrieve item object by Id.
/// Useful when you want to get hold of object for reading or further manipulating.
/// </summary>
/// <typeparam name="T">Item type</typeparam>
/// <param name="id">Item id</param>
/// <returns>Item object</returns>
public T GetItem<T>(ulong id)
{
return (T)GetNode(id).Tag;
}
/// <summary>
/// Get parent item.
/// Will return NULL if item is at top level.
/// </summary>
/// <typeparam name="T">Item type</typeparam>
/// <param name="id">Item id</param>
/// <returns>Item object</returns>
public T GetParent<T>(ulong id) where T : class
{
var parentNode = GetNode(id).Parent;
return parentNode == null ? null : (T)Parent.Tag;
}
/// <summary>
/// Retrieve descendants to specified item.
/// </summary>
/// <typeparam name="T">Item type</typeparam>
/// <param name="id">Item id</param>
/// <param name="deepLimit">Number of generations to traverse down. 1 means only direct children. Null means no limit.</param>
/// <returns>List of item objects</returns>
public List<T> GetDescendants<T>(ulong id, int? deepLimit = null)
{
var node = GetNode(id);
var enumerator = node.Nodes.GetEnumerator();
var items = new List<T>();
if (deepLimit.HasValue && deepLimit.Value <= 0)
return items;
while (enumerator.MoveNext())
{
// Add child
var childNode = (TreeNode)enumerator.Current;
var childItem = (T)childNode.Tag;
items.Add(childItem);
// If requested add grandchildren recursively
var childDeepLimit = deepLimit.HasValue ? deepLimit.Value - 1 : (int?)null;
if (!deepLimit.HasValue || childDeepLimit > 0)
{
var childId = ulong.Parse(childNode.Name);
var descendants = GetDescendants<T>(childId, childDeepLimit);
items.AddRange(descendants);
}
}
return items;
}
#endregion
public void PopulateTree(string path)
{
Tag = path;
using (NtfsUsnJournal ntfs = new NtfsUsnJournal(new DriveInfo(path)))
{
List<NtfsUsnJournal.UsnEntry> folders;
ntfs.GetNtfsVolumeFolders(out folders);
Func<NtfsUsnJournal.UsnEntry, ulong> getId = (x => x.FileReferenceNumber);
Func<NtfsUsnJournal.UsnEntry, ulong?> getParentId = (x => x.ParentFileReferenceNumber);
Func<NtfsUsnJournal.UsnEntry, string> getDisplayName = (x => x.Name);
LoadItems(folders, getId, getParentId, getDisplayName);
}
}
private List<string> _expandedCache;
protected override void OnBeforeExpand(TreeViewCancelEventArgs e)
{
if (!_expandedCache.Contains(e.Node.FullPath))
{
BeginUpdate();
ShellFileGetInfo.FolderIcons fi;
_expandedCache.Add(e.Node.FullPath);
string curPath;
foreach(TreeNode n in e.Node.Nodes)
{
curPath = Path.Combine((string)Tag, n.FullPath.Replace('/', Path.DirectorySeparatorChar));
if (File.Exists(Path.Combine(curPath, "desktop.ini")) == true)
{
fi = ShellFileGetInfo.GetFolderIcon(curPath, false);
if(fi.closed != null || fi.open != null)
{
ImageList.Images.Add(fi.closed);
ImageList.Images.Add(fi.open);
n.SelectedImageIndex = ImageList.Images.Count - 1;
n.ImageIndex = ImageList.Images.Count - 2;
}
}
}
EndUpdate();
}
base.OnBeforeExpand(e);
}