在 Python 中为文件预分配磁盘空间而不改变其大小

Posted

技术标签:

【中文标题】在 Python 中为文件预分配磁盘空间而不改变其大小【英文标题】:Preallocate disk space for a file in Python without changing its size 【发布时间】:2020-11-26 06:09:17 【问题描述】:

我正在编写一个程序,它可以同时从多个不同的服务器下载多个文件(当然,每个服务器一个下载线程!)。我担心磁盘上同时增长的多个文件会导致磁盘碎片,我想通过在开始下载之前在磁盘上为整个文件的长度(如Content-Length 标头报告的那样)预先分配空间来缓解这种情况,理想情况下没有增加文件的表观长度(这样我就可以通过以附加模式打开部分下载的文件来恢复失败的下载)。

这可能以独立于平台的方式吗?

【问题讨论】:

【参考方案1】:

我做了一些谷歌搜索,发现this lovely article 带有一些 C 代码,可以完全按照您在 Windows 上的要求。这是翻译成ctypes的C代码(为了可读性而写的):

    import ctypes
    import msvcrt
    # https://docs.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-setfileinformationbyhandle
    set_file_information = ctypes.windll.kernel32.SetFileInformationByHandle

    class AllocationInfo(ctypes.Structure):
        _fields_ = [('AllocationSize', ctypes.c_longlong)]
    
    def allocate(file, length):
        """Tell the filesystem to preallocate `length` bytes on disk for the specified `file` without increasing the
        file's length.
        In other words, advise the filesystem that you intend to write at least `length` bytes to the file.
        """
        allocation_info = AllocationInfo(length)
        retval = set_file_information(ctypes.c_long(msvcrt.get_osfhandle(file.fileno())),
                                      ctypes.c_long(5),  # constant for FileAllocationInfo in the FILE_INFO_BY_HANDLE_CLASS enum
                                      ctypes.pointer(allocation_info),
                                      ctypes.sizeof(allocation_info)
                                      )
        if retval != 1:
            raise OSError('SetFileInformationByHandle failed')

这将更改文件在磁盘上的大小:如文件资源管理器中所示为您指定的长度(加上几千字节的元数据),但保持大小:不变。

但是,在我用谷歌搜索的半小时内,我还没有找到在 POSIX 上执行此操作的方法。 fallocate() 实际上与您所追求的完全相反:它将文件的表观长度设置为您给它的长度,但将其分配为磁盘上的稀疏范围,因此同时写入多个文件仍会导致碎片.具有讽刺意味的是,Windows 具有 POSIX 所缺乏的文件管理功能吗?

我只希望被证明是错误的,但我认为这是不可能的。

【讨论】:

仍然是一个有价值的答案,但是是的,我很好奇如何在 posix 上做到这一点 @juanpa.arrivillaga 在 POSIX 中,您将使用 posix_fallocate。但由于这是 Python,“简单胜于复杂”:-) @arunanshub -- fallocate() 所做的(以及您在下面的回答所做的)是创建一个稀疏范围,这基本上是文件系统说“这里有一堆零,不需要实际存储它们”。但我确实希望它存储它们——我想在磁盘上创建一个给定大小的连续文件——因为我即将写入数据,并且 1)我不想等待 FS决定把它放在哪里,2)如果我一次写入多个文件,我希望它们在磁盘上是连续的而不是交错的,这样当我一个接一个地读取它们时,它们会加载得更快。 @wallefan 为此(顺序访问),您可以使用fadvisemadvise(例如POSIX_FADV_SEQUENTIAL)。推荐使用madvise,因为它会创建内存映射。并且你需要在使用 fallocate 和friends 之前打开一个文件,否则返回EBADF。对于 python 中的顺序访问,请使用mmap.MADV_SEQUENTIAL【参考方案2】:
FILENAME = "somefile.bin"
SIZE = 4200000

with open(FILENAME, "wb") as file:
    file.seek(SIZE - 1)
    file.write(b"\0")

优点:

    可在所有平台上移植。 如果您要 mmaping(内存映射)对文件执行写入操作(如果需要顺序访问,则通过 MADV_SEQUENTIAL)非常有效。

【讨论】:

以上是关于在 Python 中为文件预分配磁盘空间而不改变其大小的主要内容,如果未能解决你的问题,请参考以下文章

使用fallocate()在Linux中快速预分配大文件

linux磁盘管理二LVM和磁盘分配

如何在python中生成文件而不将其保存到磁盘?

在python中为空字典分配了多少空间? [复制]

linux 磁盘文件预分配

对于已预先分配的虚拟机硬盘,如何压缩或者减小其所占空间