【问题标题】:Small pdf file turns into huge byte array小pdf文件变成巨大的字节数组
【发布时间】:2020-09-27 10:52:10
【问题描述】:

我目前正在开发一个将文本文件转换为 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),但这对我来说很奇怪。我错过了什么吗?考虑到当我尝试使用转换后的文件本身时,它不会对内存造成任何问题,这就更奇怪了。

【问题讨论】:

  • 您不会处理您创建的任何一次性物品。另外,例如在ConvertFile() 中,您将返回一个字节数组,因此相关的TextToPdf() 应该返回一个字节数组,而不是MemoryStream。在这里,您还必须处理您在此过程中创建的 MemoryStream 对象。 -- 更改 resultStream.Position = 0; 中的 resultStream.Seek(0, SeekOrigin.Begin); -- 可能,将这些字节数组刷新到磁盘,在 System Temp 文件夹中,只在内存中保留指针和元数据。
  • @Jimi 所以经过大量重构和更改后,我设法解决了内存问题,关闭内存流并没有太大帮助,但是将字节写入临时文件并只保留路径(FileInfo)到那个文件完全解决了这个问题。谢谢!
  • 您不必只 close MemorySteam 对象,您必须 Dispose() of ALL 这些对象,如 StreamReader可能还有一些(或全部)PdfWriter / PdfDocument things (我不知道这些是否有Dispose() 方法:如果有,你 调用它)。可能使用using 语句/嵌套using 块来声明一次性对象:使用(var ms = new MemoryStream()) using (var streamReader = new StreamReader(stream); { // your code}; 等)。即使在该代码中引发了异常,它们也会处理这些对象。
  • @Jimi 是的,你完全正确,我很笨,我什至没有意识到我需要关闭 itext7 对象,我认为 document.Close() 会为我做这件事。结果根本不是。我只是担心,所有嵌套的 using 块都可以吗?
  • 当然可以,这是标准做法。请注意,使用using 语句声明的所有对象都将自动 处理,因此不要在using 块内添加例如streamReader.Close()(与Dispose() 相同) (s)。有时,根据操作,您可能需要(或发现更好).Flush() 一个 Stream 事先。

标签: c# itext7 .net-framework-4.8


【解决方案1】:

问题的原因在于PdfToText,对于具有多页的文档,它会提取比现有更多的文本。

LocationTextExtractionStrategy 在您开始向其提供新页面时不会忘记其内容。它不是为跨页面重复使用而设计的,您应该为每个页面创建一个新实例。

代码中循环的重用原因

  • i=1将第1页的内容写入writer
  • 用于i=2将第1页和第2页的内容写入writer
  • i=3的第1、2和3页内容写入writer
  • ...

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

【讨论】:

  • 哇,在检查运行时创建的临时文件后,它们确实非常大(每个几乎 300mb!),并且生成的文本中有重复。我没有意识到这一点,因为无论如何它都是虚拟文本,我一开始就无法做出差异。谢谢!
猜你喜欢
  • 2020-04-24
  • 2020-06-04
  • 2011-04-08
  • 1970-01-01
  • 2011-11-10
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多