以最小的开销重命名目录的所有内容
Posted
技术标签:
【中文标题】以最小的开销重命名目录的所有内容【英文标题】:Rename all contents of directory with a minimum of overhead 【发布时间】:2017-10-02 04:46:20 【问题描述】:我目前需要重命名目录中的所有文件。文件不更改名称的可能性很小,而旧文件名与新文件名相同的可能性很大,因此很可能发生重命名冲突。
因此,简单地循环文件并重命名 old->new 不是一种选择。
简单/明显的解决方案是将所有内容重命名为具有临时文件名:old->tempX->new。当然,这在一定程度上改变了问题,因为现在有责任检查旧名称列表中的任何内容都与临时名称列表重叠,并且临时名称列表中的任何内容都与新列表重叠。
此外,由于我正在处理速度慢的媒体和病毒扫描程序,它们喜欢减慢速度,因此我想尽量减少磁盘上的实际操作。除此之外,用户会不耐烦地等待做更多的事情。因此,如果可能的话,我想一次性处理磁盘上的所有文件(通过巧妙地重新排序重命名操作)并避免指数时间的恶作剧。
最后一点给我带来了一个“足够好”的解决方案,我首先在我的目录中创建一个临时目录,然后将所有内容移动重命名到该目录中,最后,我将所有内容移回旧文件夹并删除临时目录。这使我的磁盘和操作复杂度为 O(2n)。
如果可能,我希望将磁盘上的复杂度提高到 O(n),即使它的代价是将内存中的操作增加到 O(99999n)。毕竟内存要快很多。
我个人对图论还不够熟悉,而且我怀疑之前已经解决了整个“重命名冲突”问题,所以我希望有人能指出我的算法可以满足我的需求。 (是的,我可以尝试自己酿造,但我不够聪明,无法编写有效的算法,而且我可能会留下一个逻辑错误,它很少会抬起丑陋的脑袋,以至于无法通过我的测试。xD)
【问题讨论】:
是否可以将整个文件结构读入内存,确定内存中的新名称,然后全部写入? 我已经在内存中有一个新旧名字的列表,所以这不是问题。内存不是我担心的限制……无论如何,在合理范围内。 【参考方案1】:一种方法如下。
假设文件 A 重命名为 B,B 是一个新名称,我们可以简单地重命名 A。
假设文件 A 重命名为 B,B 重命名为 C,C 是一个新名称,我们可以逆序将 B 重命名为 C,然后将 A 重命名为 B。
一般来说,如果没有循环,这将起作用。只需列出所有依赖项,然后按相反顺序重命名即可。
如果有一个循环,我们有这样的事情:
A renames to B
B renames to C
C renames to D
D renames to A
在这种情况下,每个循环都需要一个临时文件。
将循环中的第一个 A 重命名为 ATMP。 那么我们的修改列表就变成了:
ATMP renames to B
B renames to C
C renames to D
D renames to A
此列表不再有循环,因此我们可以像以前一样以相反的顺序处理文件。
使用这种方法的文件移动总数将是 n + 重新排列中的循环数。
示例代码
所以在 Python 中这可能看起来像这样:
D=1:2,2:3,3:4,4:1,5:6,6:7,10:11 # Map from start name to final name
def rename(start,dest):
moved.add(start)
print 'Rename to '.format(start,dest)
moved = set()
filenames = set(D.keys())
tmp = 'tmp file'
for start in D.keys():
if start in moved:
continue
A = [] # List of files to rename
p = start
while True:
A.append(p)
dest = D[p]
if dest not in filenames:
break
if dest==start:
# Found a loop
D[tmp] = D[start]
rename(start,tmp)
A[0] = tmp
break
p = dest
for f in A[::-1]:
rename(f,D[f])
此代码打印:
Rename 1 to tmp file
Rename 4 to 1
Rename 3 to 4
Rename 2 to 3
Rename tmp file to 2
Rename 6 to 7
Rename 5 to 6
Rename 10 to 11
【讨论】:
我同意您的回答,因为您深入探讨了问题的所有不同方面:链以及循环的可能性得到了展示和展示。这使我能够最清楚地解决问题,同时在我自己的项目中实施适合我需求的解决方案,同时添加一个具有回滚功能的简单事务日志以防出现错误。感谢您花时间用示例代码编写如此深入的答案。 :-)【参考方案2】:看起来您正在查看Topologic sort 的子问题。 但是它更简单,因为每个文件只能依赖于另一个文件。 假设没有循环:
假设map
是从旧名称到新名称的映射:
在循环中,只需选择要重命名的任何文件,然后将其发送到一个函数:
-
如果它的目标新名称不冲突(不存在具有新名称的文件),则只需重命名它
否则(存在冲突)
2.1 先重命名冲突文件,递归发送到同一个函数
2.2 重命名此文件
一种 Java 伪代码如下所示:
// map is the map, map[oldName] = newName;
HashSet<String> oldNames = new HashSet<String>(map.keys());
while (oldNames.size() > 0)
String file = oldNames.first(); // Just selects any filename from the set;
renameFile(map, oldNames, file);
...
void renameFile (map, oldNames, file)
if (oldNames.contains(map[file])
(map, oldNames, map[file]);
OS.rename(file, map[file]); //actual renaming of file on disk
map.remove(file);
oldNames.remove(file);
【讨论】:
我认为你是对的,但需要小幅改进来检测循环。尝试重命名 1.txt->2.txt 和 2.txt->1.txt 时,您的代码将出现堆栈溢出(实际上不是双关语)【参考方案3】:我相信您对问题的图论建模感兴趣,所以这是我对此的看法:
您可以在第一阶段构建旧文件名到新文件名的双向映射。
现在,您计算旧文件名和新文件名的交集I。该集合中出现的每个目标“新文件名”都需要首先重命名“旧文件名”。这是一种可以在图中建模的依赖关系。
现在,为了构建该图,我们迭代该 I 集。对于I的每个元素e:
在图表中插入一个顶点,表示文件e如果尚不存在则需要重命名 获取必须重命名为e的“旧文件名”o 如果代表 o 的顶点不存在,则将其插入图中 在图中插入有向边(e, o)。此边表示“e 必须在 o 之前重命名”。如果该边引入了循环(*),请不要插入它并将o标记为需要移动和重命名的文件 .您现在必须遍历图的根(没有入边的顶点)并使用它们作为起点执行 BFS,并在每次发现顶点时执行重命名.重命名可以是普通重命名或移动重命名,具体取决于顶点是否被标记。
最后一步是将已移动并重命名的文件从沙盒目录移回目标目录。
C++ Live Demo 来说明图形处理。
【讨论】:
这是一个很好的解释。由于我更专注于实施另一个答案更有帮助的可行解决方案,因此我无法选择您作为“答案”。但无论哪种方式,我都想称赞您的出色帖子。谢谢。以上是关于以最小的开销重命名目录的所有内容的主要内容,如果未能解决你的问题,请参考以下文章