Потоки и вложения файлов

Потоки и вложения файлов

Потоки и вложения файлов

Помимо базовых перегрузок Document.Open/Document.Save, Aspose.PDF FOSS для .NET предоставляет более тонкий контроль над тем, как байты перемещаются в документ и из него — расширяемые буферы в памяти для очень больших файлов и специализированный API для встраивания, инспекции и извлечения файловых вложений через FileSpecification и EmbeddedFileCollection.


Увеличение буферов в памяти более 2ГБ

MemoryStream хранит данные в едином непрерывном массиве, который ограничивается примерно 2ГБ. OptimizedMemoryStream хранит данные в фиксированных блоках, поэтому может удерживать гораздо более крупные документы, когда вы сохраняете или собираете PDF полностью в памяти.

using Aspose.Pdf;

using var doc = Document.Open("large-input.pdf");
using var buffer = new OptimizedMemoryStream();

doc.Save(buffer);

buffer.Seek(0, SeekOrigin.Begin);
byte[] allBytes = buffer.ToArray();
Console.WriteLine($"Buffer length: {buffer.Length}");

Вы также можете записывать в него напрямую, так же как и с любым Stream:

using var buffer = new OptimizedMemoryStream();
byte[] chunk = System.Text.Encoding.UTF8.GetBytes("raw content chunk");

buffer.Write(chunk, 0, chunk.Length);
buffer.WriteByte(0x0A);
buffer.SetLength(buffer.Length);

Встраивание файла напрямую из потока

FileSpecification может обернуть Stream, вместо того чтобы требовать предварительное буферизование вложения в byte[]. Это полезно, когда источник вложения сам является потоком, например открытым файловым дескриптором.

using Aspose.Pdf;

using var doc = Document.Open("input.pdf");
using var attachmentStream = File.OpenRead("report.csv");
using var spec = new FileSpecification(attachmentStream, "report.csv", "Quarterly report data");

spec.MimeType = "text/csv";
spec.AFRelationship = AFRelationship.Data;
spec.Encoding = FileEncoding.Zip;

doc.EmbeddedFiles.Add(spec);
doc.Save("with-attachment.pdf");

Встраивание файла из сырых байтов

Document.AddEmbeddedFile — это удобный метод для встраивания файла напрямую из массива байтов, без прямого создания FileSpecification.

using Aspose.Pdf;

using var doc = Document.Open("input.pdf");
byte[] fileData = File.ReadAllBytes("appendix.docx");

doc.AddEmbeddedFile(
    "appendix.docx",
    fileData,
    "Supporting appendix",
    "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
    true,
    DateTime.Now,
    DateTime.Now);

doc.Save("with-appendix.pdf");

Перечисление и извлечение встроенных файлов

Document.EmbeddedFiles — это EmbeddedFileCollection, который можно перечислять. Каждая запись представляет собой FileSpecification, а GetData() возвращает сырые байты вложения.

using Aspose.Pdf;

using var doc = Document.Open("with-attachment.pdf");

if (doc.HasEmbeddedFiles)
{
    foreach (FileSpecification file in doc.EmbeddedFiles)
    {
        Console.WriteLine($"{file.Name} ({file.Size} bytes, {file.MimeType})");
        byte[] contents = file.GetData();
        File.WriteAllBytes(file.Name, contents);
    }
}

Поиск, просмотр и удаление вложений

Используйте FindByName для поиска отдельного вложения, Params — для чтения его метаданных, а Delete/DeleteByKey — для его удаления.

using Aspose.Pdf;

using var doc = Document.Open("with-attachment.pdf");

FileSpecification? found = doc.EmbeddedFiles.FindByName("report.csv");
if (found?.Params is FileParams info)
{
    Console.WriteLine($"{found.Name}: {info.Size} bytes, checksum {info.CheckSum}, modified {info.ModDate}");
}

doc.EmbeddedFiles.Delete("report.csv");
Console.WriteLine($"Remaining attachments: {doc.EmbeddedFiles.Count}");
doc.Save("cleaned.pdf");

Советы и лучшие практики

  • Отдавайте предпочтение OptimizedMemoryStream вместо MemoryStream, когда в памяти документ может превышать 2ГБ — MemoryStream выбрасывает исключение, как только его единственный базовый массив достигает этого ограничения.
  • Освободите экземпляры FileSpecification (и любой поток, который вы открыли вручную для них) – FileSpecification реализует IDisposable.
  • Проверьте Document.HasEmbeddedFiles перед перебором EmbeddedFiles, чтобы пропустить ненужную итерацию по документам без вложений.
  • Установите MimeType и AFRelationship на каждый FileSpecification, чтобы downstream PDF consumers могли правильно классифицировать вложения.
  • Используйте EmbeddedFileCollection.FindByName для одиночного именованного поиска вместо написания ручного цикла.

Распространённые проблемы

ПроблемаПричинаИсправление
OutOfMemoryException сохранение большого документа в потокMemoryStream ограничен единым массивом размером ~2 GBСохраните в OptimizedMemoryStream вместо этого
FileSpecification.GetData() возвращает пустой массивПозиция исходного потока не была сброшена перед созданием FileSpecificationПереместите указатель потока в позицию 0 (или откройте его заново) перед передачей в FileSpecification
EmbeddedFiles.FindByName возвращает nullИмя вложения не совпадает точно (поиск основан на имени)Итерируйте doc.EmbeddedFiles и сравните значения Name, либо проверьте Keys
Удаление вложения удаляет неправильную записьНесколько вложений имеют одинаковое имяИспользуйте DeleteByKey с точным ключом из Keys для однозначного удаления

FAQ

В чём разница между MemoryStream и OptimizedMemoryStream?

MemoryStream хранит свои байты в одном непрерывном массиве, ограниченном примерно 2ГБ. OptimizedMemoryStream хранит данные в блоках фиксированного размера, поэтому может превысить этот предел – полезно для Document.Save операций, выполняемых полностью в памяти над очень большими файлами.

Могу ли я вложить файл, не загружая его сначала в byte[]?

Да. FileSpecification(stream, name, description) принимает Stream напрямую, поэтому вы можете вложить из File.OpenRead(...) или любого другого потока, не буферизуя его в памяти самостоятельно.

Как извлечь вложенный файл из документа?

Итерируйте doc.EmbeddedFiles и вызывайте GetData() для каждого FileSpecification, чтобы получить необработанные байты.

Удаляет ли EmbeddedFileCollection.Delete(name) все вложения с этим именем?

Нет — точно нацеливайтесь на одно вложение с помощью DeleteByKey и конкретного ключа из Keys, если несколько вложений имеют одинаковое имя.

Можно ли безопасно переиспользовать FileSpecification в нескольких документах?

Нет. FileSpecification реализует IDisposable и оборачивает единый поток вложенного файла; создавайте новый экземпляр для каждого вложения в каждом документе.


API Reference Сводка

Класс / МетодОписание:
OptimizedMemoryStreamРасширяемый поток в памяти, который превышает ограничение в 2ГБ MemoryStream
OptimizedMemoryStream.WriteЗаписать байты в буфер
OptimizedMemoryStream.ToArrayВернуть буферизованные байты в виде массива байтов
FileSpecificationВстроенный дескриптор вложения файла, основанный на потоке или байтовых данных
FileSpecification.GetDataПолучить сырые байты встроенного файла
FileSpecification.ParamsМетаданные (размер, контрольная сумма, даты) для встроенного файла
EmbeddedFileCollectionКоллекция FileSpecification вложений на Document
EmbeddedFileCollection.FindByNameНайти вложение по имени
EmbeddedFileCollection.DeleteУдалить вложение по имени
Document.AddEmbeddedFileУдобный метод для встраивания файла из сырых байтов
Document.EmbeddedFilesКоллекция встроенных файлов документа
Document.HasEmbeddedFilesСодержит ли документ какие-либо встроенные файлы
FileParamsОборачивает метаданные размера/контрольной суммы/даты для встроенного файлового потока

См. также:

 Русский