惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

Google DeepMind News
Google DeepMind News
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
酷 壳 – CoolShell
酷 壳 – CoolShell
WordPress大学
WordPress大学
小众软件
小众软件
博客园 - 司徒正美
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
Jina AI
Jina AI
Hugging Face - Blog
Hugging Face - Blog
博客园 - Franky
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
量子位
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
雷峰网
雷峰网
云风的 BLOG
云风的 BLOG
M
MIT News - Artificial intelligence
F
Fortinet All Blogs
T
Tailwind CSS Blog
Martin Fowler
Martin Fowler
I
InfoQ
The GitHub Blog
The GitHub Blog
有赞技术团队
有赞技术团队
The Cloudflare Blog
罗磊的独立博客

Все публикации подряд на Хабре

Ловим музу за клавиатуру: как айтишнику стать автором Что умеет Midjourney в 2026? Мой немного грустный разбор этого шикарного инструмента Никто не любит писать тесты, но ИИ может исправить это IPv8 выглядит как мечта. Поэтому почти наверняка не взлетит Производители вернули в продажу материнки с DDR3. Что происходит? Управление агентом с телефона через Telegram теперь в KodaCode От координации к лидерству: как меняется роль руководителя разработки Я сделала родителям бизнес вместо пенсии: зарабатываем 70 тысяч, мама не даёт продать В три раза быстрее приемка товара и оптимизация трудозатрат на 73%: как «РСТ-Инвент» помог Gulliver Group ИИ-шечный мир победил? О влиянии искусственного интеллекта на игропром Кремль снижает давление на Телеграмм пока Европа строит интернет по паспорту Как CEO, CTO и CIO за 8 часов собрали ИИ-директора, который умеет держать позицию под давлением Как (не) потерять домен за выходные Вместо 8 разных VPS: как я организовал практику студентам на одном сервере Почему твой Open Source проект не замечают? R&D: искусство управления неопределенностью в разработке AI-дефляция: вакансий для разработчиков больше, а рост зарплат — худший за 15 лет Мы отдали управление роботами OpenClaw. Что из этого вышло Галактический ID: система идентификации для всех форм разумной жизни Шесть основ бизнес-анализа: начинаем с вопроса «Кто в игре?» Код-ревью, в котором дело не в коде Данные переехали. Команда — нет Системной подход к сдаче OSWE в 2025 Почему комната управления реактором покрашена в цвет морской пены 4 YAML-файла вместо PySpark: как аналитикам строить пайплайны без разработчиков LLM-агент для поиска свободных доменов: автоматизируем подбор Когда, зачем и как правильно начинать новую сессию в Claude Code? Как я заставил нейросеть писать макросы для FreeCAD Анатомия ИИ‑агента для подбора персонала. От тысячи резюме к топ‑10 за минуты Опыт разработчика как экономика внимания
JavaScript. Работа с большими файлами в браузере. Часть 1...
Alex_BBB · 2026-04-25 · via Все публикации подряд на Хабре

Уровень сложностиПростой

Время на прочтение3 мин

Охват и читатели282

Кейс

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

Рис 1. Онлайн доска DGRM.net открывает схемы из PNG картинок

Рис 1. Онлайн доска DGRM.net открывает схемы из PNG картинок

Формат PNG-файла

Файл PNG состоит из блоков. Блоки содержат разную информацию. Например блок tIME содержит дату редактирования.

В конце идет обязательный блок IEND. После IEND можно дописать в файл свои данные и картинка не сломается. Это использует DGRM: пишет свои данные в конец PNG файла.

Получается такой файл - рис 2.

Рис 2. Файл картинки с данными DGRM

Рис 2. Файл картинки с данными DGRM

Структура PNG блока - рис 3.

Рис 3. Структура PNG блока

Рис 3. Структура PNG блока

Хранение в DGRM Data организовано также блоками, только немного другого формата. Первый блок - JSON-фигур, потом вложения.

Чтение из файла без загрузки всего файла в память

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

/**
 * @param {string} accept
 * @param {FileCallback} callBack
 * @param {(evt:Event)=>void} cancelCallBack
 */
const fileInputOpen = (accept, callBack, cancelCallBack) => {
    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 = evt => {
        cancelCallBack(evt);
        dispose();
    };
    input.onchange = () => {
        callBack((!input.files?.length) ? null : input.files[0]);
        dispose();
    };

    input.click();
}

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

При открытии файла нужно найти где начинаются DGRM данные, т.е. блок IEND.

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

Функция pngChunkDataPositionGet последовательно читает только по 8 байт (длина + заголовок) и проматывает до следующего блока пока не найдет нужный - листинг 2.

// IEND
const PNG_CHUNK_END_NAME_UINT32 = 1229278788;


/**
 * @param {Blob} pngFile, @param {number} chankNameUint32
 * @returns {Promise<[startBytePosition:number, endBytePosition:number]>}
 */
const pngChunkDataPositionGet = async (pngFile, chankNameUint32) => {
    /** @param {number} pos */
    const uint32Get =
        async pos => uint32From4BytesBlob(pngFile.slice(pos, pos + 4));

    /** @type {number} */ let chunkPosition = 8; // 8 byte - png signature
    /** @type {number} */ let chunkLenght;
    /** @type {number} */ let chunkName;
    /** @type {number} */ let chunkDataStart;
    /** @type {number} */ let chunkDataEnd;

    do {
        chunkLenght = await uint32Get(chunkPosition);
        chunkName = await uint32Get(chunkPosition + 4);
        chunkDataStart = chunkPosition + 8;
        chunkDataEnd = chunkDataStart + chunkLenght;

        if (chunkName === chankNameUint32) {
            return [chunkDataStart, chunkDataEnd];
        }

        chunkPosition = chunkDataEnd + 4;
    } while (chunkName !== PNG_CHUNK_END_NAME_UINT32);

    // looking for end chunk
    if (chunkName === chankNameUint32) {
        return [chunkDataStart, chunkDataEnd];
    }

    return null;
};

Листинг 2. Поиск блока в PNG файле

Мотать до IEND большие PNG не быстро. Поэтому имеет смысл добавить свой блок в начало файла. В этом блоке указать кол-во байт до конца DGRM Data, т.е. размер PNG картинки без дополнительной DGRM Data.

Рис 4. Свой блок dgRp в начале PNG-файла. Указывает на начало DGRM data.

Рис 4. Свой блок dgRp в начале PNG-файла. Указывает на начало DGRM data.

В DGRM Data первый блок это JSON-фигур, потом идут блоки вложений - рис 5.

Рис 5. Блоки DGRM Data

Рис 5. Блоки DGRM Data

Блок JSON грузится в память целиком. Вложения грузятся только если их нет в кэше.
Вложения могут быть большими, поэтому их тоже не желательно грузить целиком в память. Подробнее во второй части статьи.

Во второй части статьи про формирование больших файлов в браузере.