iTextSharp System.OutOfMemoryException

Posted

技术标签:

【中文标题】iTextSharp System.OutOfMemoryException【英文标题】: 【发布时间】:2016-08-31 13:55:42 【问题描述】:

我在尝试创建大型 PDF 文件时遇到问题。基本上我有一个字节数组列表,每个数组都包含一个字节数组形式的 PDF。我想将字节数组合并到一个 PDF 中。这对于较小的文件(2000 页以下)非常有用,但是当我尝试创建一个 12,00 页的文件时,它被炸毁了)。最初我使用的是 MemoryStream,但经过一些研究,一个常见的解决方案是改用 FileStream。所以我尝试了一种文件流方法,但是得到了类似的结果。该列表包含 3,800 条记录,每条记录包含 4 页。 MemoryStream 在大约 570 条后爆炸。 FileStream 在大约 680 条记录后。代码崩溃后的当前文件大小为 60MB。我究竟做错了什么?这是我的代码,代码在“copy.AddPage(curPg);”上崩溃指令,在 "for(" 循环内。

    private byte[] MergePDFs(List<byte[]> PDFs)
    
        iTextSharp.text.Document doc = new iTextSharp.text.Document();
        byte[] completePDF;
        Guid uniqueId = Guid.NewGuid();
        string tempFileName = Server.MapPath("~/" + uniqueId.ToString() + ".pdf");

        //using (MemoryStream ms = new MemoryStream())
        using(FileStream ms = new FileStream(tempFileName, FileMode.Create, FileAccess.Write, FileShare.Read))
        
            iTextSharp.text.pdf.PdfCopy copy = new iTextSharp.text.pdf.PdfCopy(doc, ms);
            doc.Open();

            int i = 0;
            foreach (byte[] PDF in PDFs)
            
                i++;
                // Create a reader
                iTextSharp.text.pdf.PdfReader reader = new iTextSharp.text.pdf.PdfReader(PDF);

                // Cycle through all the pages
                for (int currentPageNumber = 1; currentPageNumber <= reader.NumberOfPages; ++currentPageNumber)
                
                    // Read a page
                    iTextSharp.text.pdf.PdfImportedPage curPg = copy.GetImportedPage(reader, currentPageNumber);

                    // Add the page over to the rest of them
                    copy.AddPage(curPg);
                

                // Close the reader
                reader.Close();
            

            // Close the document
            doc.Close();

            // Close the copier
            copy.Close();

            // Convert the memorystream to a byte array
            //completePDF = ms.ToArray();
        

        //return completePDF;
        return GetPDFsByteArray(tempFileName);
    

【问题讨论】:

【参考方案1】:

几点说明:

    PdfCopy 实现了iDisposable,所以你应该尝试看看using 是否有帮助。 PdfCopy.FreeReader() 会有所帮助。

无论如何,不​​确定您使用的是 MVC 还是 WebForms,但这里有一个简单的工作 HTTP handler 使用在我的工作站上运行的 15 页 125KB 测试文件进行测试:

<%@ WebHandler Language="C#" Class="MergeFiles" %>
using System;
using System.Collections.Generic;
using System.Web;
using System.IO; 
using iTextSharp.text; 
using iTextSharp.text.pdf; 

public class MergeFiles : IHttpHandler

    public void ProcessRequest(HttpContext context)
    
        List<byte[]> pdfs = new List<byte[]>();
        var pdf = File.ReadAllBytes(context.Server.MapPath("~/app_data/test.pdf"));
        for (int i = 0; i < 4000; ++i) pdfs.Add(pdf);

        var Response = context.Response;
        Response.ContentType = "application/pdf";
        Response.AddHeader(
            "content-disposition",
            "attachment; filename=MergeLotsOfPdfs.pdf"
        );
        Response.BinaryWrite(MergeLotsOfPdfs(pdfs));
    

    byte[] MergeLotsOfPdfs(List<byte[]> pdfs)
    
        using (var ms = new MemoryStream())
        
            using (Document document = new Document())
            
                using (PdfCopy copy = new PdfCopy(document, ms))
                
                    document.Open();
                    for (int i = 0; i < pdfs.Count; ++i)
                    
                        using (PdfReader reader = new PdfReader(
                            new RandomAccessFileOrArray(pdfs[i]), null))
                        
                            copy.AddDocument(reader);
                            copy.FreeReader(reader);
                        
                    
                
            
            return ms.ToArray();
        
    

    public bool IsReusable  get  return false;  

尝试使输出文件类似于您在问题中描述的内容,但 YMMV,具体取决于您处理的单个 PDF 的大小。这是我运行的测试输出:

【讨论】:

我使用的是旧版本的 iTextSharp,它不允许我对 Document、PdfCopy 和 PdfReader 使用“使用”,但是,如果你有 60,000 个页面,其中将近 1/2 GB数据为你工作,那么你的代码就是我希望人们首先看到的,而不是我的。给你复选标记和投票。谢谢kuujinbo! @Lukas - 很遗憾听到您不能使用此解决方案,但感谢您的好评。 :) 在此之前,从未尝试生成大于约 2000 页的任何内容,因此您的问题很有趣,并且得到了我的支持。【参考方案2】:

因此,经过一番折腾,我意识到没有办法解决它。但是,我确实设法找到了解决方法。我没有返回字节数组,而是返回一个临时文件路径,然后我将其传输并删除。

    private string MergeLotsOfPDFs(List<byte[]> PDFs)
    
        Document doc = new Document();
        Guid uniqueId = Guid.NewGuid();
        string tempFileName = Server.MapPath("~/__" + uniqueId.ToString() + ".pdf");

        using (FileStream ms = new FileStream(tempFileName, FileMode.Create, FileAccess.Write, FileShare.Read))
        
            PdfCopy copy = new PdfCopy(doc, ms);
            doc.Open();

            int i = 0;
            foreach (byte[] PDF in PDFs)
            
                i++;
                // Create a reader
                PdfReader reader = new PdfReader(new RandomAccessFileOrArray(PDF), null);

                // Cycle through all the pages
                for (int currentPageNumber = 1; currentPageNumber <= reader.NumberOfPages; ++currentPageNumber)
                
                    // Read a page
                    PdfImportedPage curPg = copy.GetImportedPage(reader, currentPageNumber);

                    // Add the page over to the rest of them
                    copy.AddPage(curPg);

                    // This is a lie, it still costs money, hue hue hue :)~
                    copy.FreeReader(reader);
                
                reader.Close();
            

            // Close the document
            doc.Close();

            // Close the document
            copy.Close();
        

        // Return temp file path
        return tempFileName;
    

这是我将数据发送到客户端的方式。

        // Send the merged PDF file to the user.
        System.Web.HttpResponse response = System.Web.HttpContext.Current.Response;
        response.ClearContent();
        Response.ClearHeaders();
        response.ContentType = "application/pdf";
        response.AddHeader("Content-Disposition", "attachment; filename=1094C.pdf;");
        response.WriteFile(tempFileName);
        HttpContext.Current.Response.Flush(); // Sends all currently buffered output to the client.
        DeleteFile(tempFileName); // Call right after flush but before close
        HttpContext.Current.Response.SuppressContent = true;  // Gets or sets a value indicating whether to send HTTP content to the client.
        HttpContext.Current.ApplicationInstance.CompleteRequest(); // Causes ASP.NET to bypass all events and filtering in the HTTP pipeline chain of execution and directly execute the EndRequest event.

最后,这是一个花哨的 DeleteFile 方法

    private void DeleteFile(string fileName)
    
        if (File.Exists(fileName))
        
            try
            
                File.Delete(fileName);
            
            catch (Exception ex)
            
                //Could not delete the file, wait and try again
                try
                
                    System.GC.Collect();
                    System.GC.WaitForPendingFinalizers();
                    File.Delete(fileName);
                
                catch
                
                    //Could not delete the file still
                
            
        
    

【讨论】:

以上是关于iTextSharp System.OutOfMemoryException的主要内容,如果未能解决你的问题,请参考以下文章

iTextSharp 设置文档横向(横向)A4

C#工具类:使用iTextSharp操作PDF文档

使用 iTextSharp 在系统中使用字体

c#中带有html的itextsharp [重复]

iTextSharp - 非常大的表内存泄漏

使用 itextsharp 获取 PDF 页面的缩略图