Предварительно выделить дисковое пространство для файла в Python без изменения его размера

Я пишу программу, которая загружает несколько файлов одновременно с нескольких разных серверов (разумеется, один поток загрузки на сервер!). Меня беспокоит, что на диске одновременно растет несколько файлов, вызывающих фрагментацию диска, и я хотел бы смягчить это, предварительно выделив место на диске для полной длины файла (как сообщаетContent-Length заголовок) перед началом загрузки, в идеале без увеличения видимой длины файла (так что я могу возобновить неудачные загрузки, просто открыв частично загруженный файл в режиме добавления).

Возможно ли это независимо от платформы?

2 ответа

      FILENAME = "somefile.bin"
SIZE = 4200000

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

Преимущества:

  1. Переносится на все платформы.
  2. Очень эффективно, если бы ты был mmaping (отображение памяти) файлов для выполнения записи в них.

Я немного погуглил и нашел эту прекрасную статью с некоторым кодом C, чтобы делать именно то, что вы просите в Windows. Вот этот код C, переведенный наctypes (написано для удобства чтения):

    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')

Это изменит размер файла на диске: как показано в проводнике файлов, на указанную вами длину (плюс несколько килобайт для метаданных), но оставьте размер: без изменений.

Однако за полчаса, которые я потратил на поиск в Google, я не нашел способа сделать это в POSIX. fallocate()на самом деле делает прямо противоположное тому, что вам нужно: он устанавливает кажущуюся длину файла равной длине, которую вы ему даете, но выделяет его как разреженный экстент на диске, поэтому одновременная запись в несколько файлов все равно приведет к фрагментации. Иронично, не правда ли, в Windows есть функция управления файлами, которой не хватает в POSIX?

Мне бы очень хотелось, чтобы меня доказали, что он неправ, но я не думаю, что это возможно.

Другие вопросы по тегам