【问题标题】:iTextSharp System.OutOfMemoryExceptioniTextSharp 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);
    }

【问题讨论】:

    标签: c# itextsharp


    【解决方案1】:

    几点说明:

    1. PdfCopy 实现了iDisposable,所以你应该尝试看看using 是否有帮助。
    2. 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
                    }
                }
            }
        }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-02-28
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多