Как упаковать большой файл в .tar?

Как упаковать большой файл в Tar?

Если вы используете этот код, то используется много оперативной памяти.

byte[] buffer = ArrayPool<byte>.Shared.Rent(1024 * 1024);
string item = @"R:\Bigfile.dat";
using (FileStream _writestream = File.Create(@"R:\test.tar")
using (TarWriter tarwriter = new(_writestream, TarEntryFormat.Pax, leaveOpen: false))
using (FileStream srcFile = new(item, FileMode.Open, FileAccess.Read))
{
    string fileName = item.Remove(0, mainDir.Length + 1).Replace('\\', '/');
    PaxTarEntry te = new(TarEntryType.RegularFile, fileName)
    {
        DataStream = new MemoryStream()
    };
    
    int currentBlockSize = 0;
    while ((currentBlockSize = srcFile.Read(buffer)) > 0)
    {
        te.DataStream.Write(buffer.AsSpan(0, currentBlockSize));
    }
    te.DataStream.Position = 0;
    tarwriter.WriteEntry(te);
}
ArrayPool<byte>.Shared.Return(buffer);

Если вы используете этот код, он будет использовать много памяти. Да, возможно, потому что это будет записано в MemoryStream(). Но какие варианты лучше, если нужно написать ОЧЕНЬ большой файл? Я хочу записывать в файл .tar вместо ОЗУ и хочу контролировать размер каждой итерации цикла записи с помощью размера буфера.

Что здесь _writestream? Если бы вы могли предоставить минимально воспроизводимый пример , вам было бы легче помочь. (И вы явно создаете MemoryStream, в который пишете, поэтому неудивительно, что он занимает много памяти, IMO...)

Jon Skeet 04.07.2024 10:03

@JonSkeet Я исправил это. Но я не знаю, куда писать, как не в MemoryStream?

Raf-9600 04.07.2024 11:28

Можете ли вы просто не использовать srcFile вместо DataStream? (Это то, что я ожидал сделать — я бы просто использовал File.OpenRead вместо явного вызова конструктора FileStream, просто для простоты...)

Jon Skeet 04.07.2024 13:17

Непонятно, что имеется в виду под «Я хочу контролировать размер каждой итерации цикла записи через размер буфера» - итерацию чего? (В моем предложенном решении вам нечего будет повторять...)

Jon Skeet 04.07.2024 13:23
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
0
4
159
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

using System.IO;
using System.IO.Compression;

public void PackLargeFileIntoTar(string sourceFilePath, string tarFilePath)
{
    using (FileStream tarFileStream = new FileStream(tarFilePath, FileMode.Create, FileAccess.Write))
    using (TarArchive tarArchive = TarArchive.Create())
    {
        tarArchive.WriteTo(tarFileStream, TarEntryFormat.Pax, leaveOpen: false);

        using (FileStream srcFile = new FileStream(sourceFilePath, FileMode.Open, FileAccess.Read))
        {
            string fileName = Path.GetFileName(sourceFilePath);
            var entry = TarEntry.CreateEntryFromFile(sourceFilePath);
            tarArchive.AddEntry(fileName, srcFile);
        }
    }
}

Откуда взялся TarArchive? Насколько я знаю, встроенная реализация Tar — TarFile.

Karl-Johan Sjögren 04.07.2024 11:45

Я обновил вопрос. Кажется, ваш ответ не подходит. Он не использует буфер, и вы также используете реализацию .net поддержки .tar, которая не встроена в .net.

Raf-9600 04.07.2024 12:15

@Raf-9600: «и вы также используете .net-реализацию поддержки .tar, которая не встроена в .net» - в вашем вопросе это не оговаривалось как требование. Я согласен, что ответ мог бы быть более ясным, но если вы собираетесь отклонить любой ответ, в котором используется сторонняя библиотека, это обязательно должно быть в вопросе.

Jon Skeet 04.07.2024 13:21

@Raf-9600: Также неясно, что вы подразумеваете под «он не использует буфер» - какое именно поведение вам требуется, связанное с буферизацией? (Что вы имеете в виду в вопросе «Я хочу контролировать размер каждой итерации цикла записи через размер буфера»?

Jon Skeet 04.07.2024 13:23

@JonSkeet Я хотел бы иметь возможность отображать ход работы с пакетом .tar, обычно для этого нужно использовать буфер. Но я не знаю, как поступить в данном случае.

Raf-9600 04.07.2024 21:20

@Raf-9600: Raf-9600: Опять же, в вопросе об этом не упоминается. Потенциально вы могли бы создать оболочку потока, которая подсчитывает прочитанные байты и т. д., но это было бы относительно неудобно. Я бы взвесил, насколько сильно вы этого хотите, по сравнению с тем, насколько это будет хлопотно.

Jon Skeet 04.07.2024 23:18
Ответ принят как подходящий

Я думаю вам просто вообще не нужно делать копирование из входного потока в память. Вот короткая, но полная программа, которая создает tar-файл из каждого аргумента командной строки:

using System.Formats.Tar;

using var output = File.Create("output.tar");
using var tarWriter = new TarWriter(output, TarEntryFormat.Pax);

foreach (var arg in args)
{
    using var input = File.OpenRead(arg);
    var entry = new PaxTarEntry(TarEntryType.RegularFile, arg)
    {
        DataStream = input
    };
    tarWriter.WriteEntry(entry);
}

Я только что использовал этот код для создания tar-файла размером 20 ГБ (из нескольких файлов размером 1–3 ГБ), а диспетчер задач показал его только с использованием 3,6 МБ...

@JonSkeet Почему бы не использовать асинхронные версии? await using var outputawait using var tarWriterawait using var input = File.OpenReadawait tarWriter.WriteEntryAsync

Charlieface 04.07.2024 14:07

@Charlieface: Да, ты вполне можешь это сделать. Но учитывая, что исходный код не является асинхронным и нет никаких указаний на необходимость сделать его асинхронным, казалось, что было бы неправильно делать это в одностороннем порядке.

Jon Skeet 04.07.2024 14:26

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