javascript

JavaScript. Работа с большими файлами в браузере. Часть 2/2: Создание 5Gb файлов в браузере

  • пятница, 29 мая 2026 г. в 00:00:07
https://habr.com/ru/articles/1040752/

Часть 1/2: Чтение файлов | Часть 2/2: Создание файлов

Онлайн доска DGRM.net кеширует файлы в постоянном кеше. Постоянный кеш не удаляется при закрытии вкладки. Рассказываю как создавать и хранить большие файлы в браузере.

Рис. 1. Расход памяти при полной загрузке файла в память
Рис. 1. Расход памяти при полной загрузке файла в память

Кеш файлов на origin private file system (OPFS)

Постоянный кеш файлов можно сделать с помощью cache API, indexedDB или OPFS. OPFS считается самым быстрым. OPFS это виртуальная файловая система. Можно делать файлы и папки. Другие сайты не будут иметь доступ к вашим файлам.

Запись файлов в OPFS

Получить ссылку на файл на устройстве пользователя можно с помощью HTMLInputElement. При этом данные файла не будут загружены в память.

/**
 * @param {string} [accept]
 * @returns {Promise<File>}
 */
const fileInputOpen = accept => new Promise((resolve, reject) => {
    const input = document.createElement('input');
    input.type = 'file';
    input.multiple = false;
    input.accept = accept;
    input.style.display = 'none';
    document.body.appendChild(input);

    const dispose = () => input?.remove();

    input.oncancel = () => {
        resolve(null);
        dispose();
    };
    input.onchange = () => {
        resolve((!input.files?.length) ? null : input.files[0]);
        dispose();
    };

    input.click();
});

Листинг 1. Получение ссылки на файл

Запись файла в OPFS - листинг 2.

/**
 * @param {string} fileName
 * @param {Blob} blob
 */
const fileAdd = async (fileName, blob) => {
    const dir = await navigator.storage.getDirectory();

    const writable =
        await (await dir.getFileHandle(fileName, { create: true }))
            .createWritable();

    // (1)  FireFox will load all file into memory
    //      Chrome drop exception
    // await writable.write(await blob.bytes());

    // (2)  very slow in FireFox
    // await writable.write(blob);

    // (3)  works in Chrome and FireFox.
    //      In Chrome slower then (2)
    await blob.stream().pipeTo(writable, { preventClose: true });

    await writable.close();
};

Листинг 2. Запись файла в OPFS

В листинге 2 показаны 3 варианта копирования файла в OPFS. Проверял на файле в 5 Gb на Windows 11 и Ubuntu.

Вариант (1) blob.bytes()

Вариант (1) демонстрирует загрузку всего файла в память. Расход оперативной памяти четко видно - рис 2.. FireFox на Windows загрузит весь файл в память, в Ubuntu загрузит 2 Gb в память и бросит исключение. Chrome выбросит исключение.

Рис. 2. FireFox (1) file.bytes(). Расход памяти при полной загрузке файла в память
Рис. 2. FireFox (1) file.bytes(). Расход памяти при полной загрузке файла в память

Вариант (2) writable.write(blob)

(2) Быстро работает в Chrome - 23 сек. В FireFox очень медленно - больше минуты. Файл не грузится целиком в память, копируется кусочками - рис 3 и 4. В Chrome и FireFox графики похожие, только в FireFox растянутые по времени.

Рис 3. Chrome (2) writable.write(file). Расход памяти
Рис 3. Chrome (2) writable.write(file). Расход памяти
Рис 4. Chrome (2) wr.writable(file). Обращения к диску
Рис 4. Chrome (2) wr.writable(file). Обращения к диску

Вариант(3) blob.stream().pipeTo(writable)

Хорошо работает в Chrome и FireFox, В Chrome медленней чем (2). Графики похожи на (2). Вариант (3) с pipeTo оказался лучше - таблица 1.

Метод загрузки
Файл 5 Gb

Chrome

FireFox

(1) blob.bytes()

Исключение

(2) writable.write(file)

23.484 sec

114.430 sec

(3) blob.stream().pipeTo(writable

30.391 sec

34.675 sec

Таблица 1. Сравнение методов создания файлов в OPFS

Запись части файла в OPFS

Для записи части файла в OPFS используется blob.slice(). Листиг 3 - запись части файла в OPFS без загрузки всего файла в память.

const file = await fileInputOpen(); 
if (!file) { return; } 
 
await fileAdd( 
    file.name, 
    // upload second byte 
    file.slice(1, 2));

Листинг 3. Запись части файла в OPFS без загрузки всего файла в память

Как можно хранить много разных данных в одном большом файле см в первой части статьи.

Чтение из OPFS

Файл из OPFS можно вывести в картинку, <audio>, <video> – листинг 4.

const img =
    /** @type {HTMLImageElement} */(document.getElementById('img'));

const dir = await navigator.storage.getDirectory();
img.src = URL.createObjectURL(
    await (await dir.getFileHandle('1.png')).getFile()
);

// don't forget to revoke object url when delete img
// URL.revokeObjectURL(img.src)

Листинг 4. Отображение картинки из OPFS

Скачивание файла из OPFS – листинг 5.

/**
 * @param {Blob} blob
 * @param {string} name
 */
const fileDownload = (blob, name) => {
    const link = document.createElement('a');
    link.download = name;
    link.href = URL.createObjectURL(blob);
    link.click();
    URL.revokeObjectURL(link.href);
    link.remove();
};

//
// usage

const dir = await navigator.storage.getDirectory();
fileDownload(
    await (await dir.getFileHandle('big.zip')).getFile(),
    'big.zip'
);

Листинг 5. Скачивание файла из OPFS

Google говорит что таким способом можно скачать файлы до 1 Gb. В действительности 5 Gb скачивается без проблем и в Chrome и в FireFox в Windows и Ubuntu. Потребление оперативной памяти не растет.

Создание файлов на устройстве пользователя

Chrome может писать файлы прямо в файловой системе пользователя. Писать в файл можно кусочками, API тотже самый что и в OPFS – листинг 6.

if (window['showSaveFilePicker']) {
    /** @type {FileSystemFileHandle} */
    const file =
        // @ts-ignore
        await window.showSaveFilePicker({
            suggestedName: 'my.png' });

    const writable = await file.createWritable();

    // write your data
    // await blob1.stream().pipeTo(writable, { preventClose: true });
    // await blob2.stream().pipeTo(writable, { preventClose: true });

    await writable.close();
}

Листинг 6. Создание файла в на устройстве пользователя

В других браузерах придется делать временный файл в OPFS и скачивать его с помощью ссылки – см. листинг 5.

Самореклама

Недавно добавил в DGRM.net аудио и видео. Использую для уроков по гитаре - удобно. Попробуйте.

Аудио и видео на онлайн доске DGRM.net
Аудио и видео на онлайн доске DGRM.net