小型 pdf 文件变成巨大的字节数组

Small pdf file turns into huge byte array

我目前正在开发一个将文本文件转换为 PDF 文件或相反的小应用程序。但是,我希望能够将转换后的文件保存在内存中,直到用户按下按钮保存文件(或将一组文件保存到 .zip 中),所有转换后的文件都保存在以旧路径为键的字典中和字节数组作为值。

除了出于测试目的,我使用了一个包含 12000 多行的大型文本文件并尝试在文本和 PDF 之间来回切换,现在我遇到了一个奇怪的问题。

一切正常。

当使用这个大文件从文本转换为 PDF 时,一切都很好。

但是,从该文件的 PDF 格式转换为文本会占用大量堆内存。最终超过 2 GB 导致内存不足异常。

请注意,我使用的是 Itext 7。

这是我使用的代码:

文本转 PDF

        public override byte[] ConvertFile(Stream stream, string path)
        {
            OnFileStartConverting(path);
            string ext = Path.GetExtension(path);
            TextFileType current = TextFileType.Parse(ext);
            MemoryStream resultStream = new MemoryStream();

            if (current.Extension.Equals(TextFileType.Txt.Extension))
            {
                resultStream = TextToPdf(stream, path);
            }
            else if (current.Extension.Equals(TextFileType.Word.Extension))
            {
                throw new NotImplementedException();
            }

            OnFileConverted(path);
            return resultStream.ToArray();
        }

        private MemoryStream TextToPdf(Stream stream, string path)
        {
            MemoryStream resultStream = new MemoryStream();
            StreamReader streamReader = new StreamReader(stream);
            int lineCount = GetNumberOfLines(streamReader);
            PdfWriter writer = new PdfWriter(resultStream);
            PdfDocument pdf = new PdfDocument(writer);
            Document document = new Document(pdf);

            int lineNumber = 1;
            while (!streamReader.EndOfStream)
            {
                string line = streamReader.ReadLine();
                Paragraph paragraph = new Paragraph(line);
                document.Add(paragraph);
                int percent = lineNumber * 100 / lineCount;
                OnFileConverting(path, percent, lineNumber);
                lineNumber++;
            }

            document.Close();
            return resultStream;
        }

PDF 转文本

        public override byte[] ConvertFile(Stream stream, string path)
        {
            OnFileStartConverting(path);

            string ext = Path.GetExtension(path);
            TextFileType current = TextFileType.Parse(ext);
            MemoryStream resultStream = new MemoryStream();

            if (current.Extension.Equals(TextFileType.Pdf.Extension))
            {
                resultStream = PdfToText(stream, path);
            }
            else if (current.Extension.Equals(TextFileType.Word.Extension))
            {
                throw new NotImplementedException();
            }

            resultStream.Seek(0, SeekOrigin.Begin);
            OnFileConverted(path);

            return resultStream.ToArray();
        }

        private MemoryStream PdfToText(Stream stream, string path)
        {
            MemoryStream resultStream = new MemoryStream();
            StreamWriter writer = new StreamWriter(resultStream);

            PdfReader reader = new PdfReader(stream);
            PdfDocument pdf = new PdfDocument(reader);
            FilteredEventListener listener = new FilteredEventListener();
            LocationTextExtractionStrategy extractionStrategy =
                listener.AttachEventListener(new LocationTextExtractionStrategy());
            PdfCanvasProcessor parser = new PdfCanvasProcessor(listener);
            int numberOfPages = pdf.GetNumberOfPages();

            for (int i = 1; i <= numberOfPages; i++)
            {
                parser.ProcessPageContent(pdf.GetPage(i));
                writer.WriteLine(extractionStrategy.GetResultantText());
                int percent = i * 100 / numberOfPages;
                OnFileConverting(path, percent, i);
            }

            pdf.Close();
            writer.Flush();

            return resultStream;
        }

从 PDF 到文本时的内存使用情况

PDF 文件本身甚至不到 1000 KB(它是 882 KB),但这对我来说很奇怪。我错过了什么吗?更奇怪的是,当我尝试使用转换后的文件本身时,它不会导致任何内存问题。

问题的原因在于 PdfToText,对于包含多个页面的文档,提取的文本比那里多。

LocationTextExtractionStrategy 在您开始向其提供新页面时不会忘记其内容。它并非设计为 re-used 跨页面,您应该为每个页面创建一个新实例。

Re-usage 在您的代码循环中导致

  • 为了i=1第1页的内容要写入writer;
  • 对于i=2要写入第1页和第2页的内容writer;
  • 对于i=3第1、2、3页的内容要写入writer
  • ...

因此,不要re-use跨页面的文本提取策略。而是将 FilteredEventListenerLocationTextExtractionStrategyPdfCanvasProcessor 的实例化移动到循环中,以便为每个页面重新创建它们。