PDA

View Full Version : [java][PdfReader][OutOfMemoryError]Problemi nel leggere pdf di 500 mb o più


ZanTeo
08-03-2012, 13:54
Salve, uso la classe PdfReader per leggere ed operare su dei pdf, ma dopo una certa dimensione del file mi da l'errore:
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
at java.util.Arrays.copyOf(Unknown Source)
at java.io.ByteArrayOutputStream.write(Unknown Source)
at com.itextpdf.text.pdf.RandomAccessFileOrArray.InputStreamToArray(Rand
omAccessFileOrArray.java:209)
at com.itextpdf.text.pdf.RandomAccessFileOrArray.<init>(RandomAccessFile
OrArray.java:199)
at com.itextpdf.text.pdf.PdfReader.<init>(PdfReader.java:237)
at com.itextpdf.text.pdf.PdfReader.<init>(PdfReader.java:248)
at code.Gestione.caricaPDF(Gestione.java:61)
at code.Start.main(Start.java:28)
dovuto alla sezione di codice:
public void caricaPDF(FileInputStream pdf,String titolo) throws SQLException, IOException{
boolean protetto=false;
String nome=pulisciStringa(titolo);
if(isProcessato(nome)==0){
PdfReader lettore=null;
psf.setString(1, nome);
try{
lettore=new PdfReader(pdf);
psf.setInt(2, 0);
psf.setInt(3, 1);
psf.executeUpdate();
caricaTesto(lettore,getIdPDF(nome));
}catch (InvalidPdfException e){
psf.setInt(2, 1);
psf.setInt(3, 0);
psf.executeUpdate();
protetto=true;
}finally{
if(lettore!=null)
lettore.close();
}
L'errore si genera giustamente quando creo il pdfreader, dovrei trovare un metodo per evitare tale sovraccarico, purtroppo non posso aumentare l'heap.
Conoscete qualche tecnica o metodo per evitare ciò? Che so predividendo il file o usando un particolare metodo. Io qualcosa l'ho trovata ma nulla di che.
Se serve altro codice chiedetelo, se avete proposte di ottimizzazione del metodo fatele.

banryu79
08-03-2012, 14:31
Non puoi caricare/leggere una pagina alla volta, o un tot di pagine alla volta? Non ho mai usato quella libreria, ma magari la sua API fornisca tale possibilità.
Se l'elaborazione che devi fare non richiede neccessariamente la presenza di tutto il contenuto del pdf in memoria ma puoi processarlo un po' per volta vai colpi di una, dieci, N o più pagine ;)

banryu79
08-03-2012, 15:07
@EDIT:
ho fatto una brevissima ricerca e pare che ci sia modo di fare delle "partial read" di un file pdf già esistente.
Guarda questa pagina (http://itextpdf.com/book/examples.php): contiene un sacco di esempi.
In particolare la "partial read" l'ho vista qua (http://itextpdf.com/examples/iia.php?id=108) (ma potrebbe non essere l'unico esempio ne il migliore).
Insomma: devi sguazzare nella documentazione e/o forum/blog relativi dove potresti trovare info molto precise al rigurado :)

ZanTeo
08-03-2012, 15:15
PdfReaderContentParser parser=new PdfReaderContentParser(lettore);
parser.processContent(i,new SimpleTextExtractionStrategy());
String parole=pulisciStringa(PdfTextExtractor.getTextFromPage(lettore, i));
mediante il codice che ho postato estraggo il testo pg per pg applicando una determinata strategia di estrazione/elaborazione per poi caricarlo tutto nel db.
Però il problema sta nel momento della creazione del PdfReader (il lettore) purtroppo.
Ho trovato questo link in cui una persona ha il mio stesso problema, ora vedo cosa posso ricavare, magari dacci un occhiata.
http://itext-general.2136553.n4.nabble.com/PDFReader-Exception-in-thread-quot-main-quot-java-lang-OutOfMemoryError-Java-heap-space-td2146288.html

banryu79
08-03-2012, 15:38
Come nel link che ho postato sopra, guarda l'esempio della partial read (che poi è lo stesso sistema illustarto nel link che hai postato tu).
In pratica usando il costruttore di PdfReader che prende come argomenti un RandomAccessFileOrArray e null (per l'array di byte che rappresenta la user password, presumo per accedere a pdf protetti) vengono lette dallo stream del pdf solo le entità xref [qualunque cosa siano, non conosco le specifiche del formato pdf] e solo quando richieste.
Link al javadoc (http://api.itextpdf.com/itext/com/itextpdf/text/pdf/PdfReader.html#PdfReader%28com.itextpdf.text.pdf.RandomAccessFileOrArray,%20byte[]%29)

@EDIT:
1) creare un PdfReader con un RandomAccessFileOrArray [come fin qua detto].

In most of this topic’s examples, you’ll create an instance of PdfReader using a String representing the path to the existing PDF file. Using this constructor will cause PdfReader to load plenty of PDF objects (from the file) into Java objects (in memory). This can be overkill for large documents, especially if you’re only interested in part of the document. If that’s the case, you can choose to read the PDF only partially.
...
The file size of the timetable document from topic 3 is 15 KB. The memory used by a full read is about 35 KB, but a partial read needs only 4 KB. This is a significant difference. When reading a file partially, more memory will be used as soon as you start working with the reader object, but PdfReader won’t cache unnecessary objects. That also makes a huge difference, so if you’re dealing with large documents, consider using PdfReader with a RandomAccessFileOrArray parameter constructed with a path to a file.



2) leggere solo un tot di pagine.

Another way to reduce the memory usage of PdfReader up front is to reduce the number of pages before you start working with it.

SELECTING PAGES
Next, you’ll read the timetable from example 3 once again, but you’ll immediately tell PdfReader that you’re only interested in pages 4 to 8.
Listing 6.3 SelectPages.java
...[image]

Listing 6.3 SelectPages.java
...[image]

The general syntax for the range that’s used in the selectPages() method looks like this:
...[image]

You can have multiple ranges separated by commas, and the ! modifier removes pages from what is already selected. The range changes are incremental; numbers are added or deleted as the range appears. The start or the end can be omitted; if you omit both, you need at least o (odd; selects all odd pages) or e (even; selects all even pages).

If you ask the reader object for the number of pages before selectPages() in listing 6.3, it will tell you that the document has 8 pages. If you do the same after making the page selection, it will tell you that there are only 5 pages: pages 4, 5, 6, 7, and 8. The old page 4 will be the new page 1. Be careful not to try getting information about pages that are outside the new range. Don’t add the following line to listing 6.3:
...[image]

This line will throw a NullPointerException because there are no longer 6 pages in the reader object.

Now that you’ve had a short introduction to PdfReader, you’re ready to start manipulating existing PDF documents.

Estratti pescati da questa pagina (http://what-when-how.com/itext-5/accessing-an-existing-pdf-with-pdfreader-itext-5/)

ZanTeo
09-03-2012, 10:44
con questa soluzione:
public void caricaPDF(File f) throws SQLException, IOException {
boolean protetto=false;
String nome=pulisciStringa(f.getName());
if(isProcessato(nome)==0){
PdfReader lettore=null;
psf.setString(1, nome);
try{
lettore=new PdfReader(new RandomAccessFileOrArray(new FileInputStream(f)),null);
psf.setInt(2, 0);
psf.setInt(3, 1);
psf.executeUpdate();
caricaTesto(lettore,getIdPDF(nome));
}catch (InvalidPdfException e){
psf.setInt(2, 1);
psf.setInt(3, 0);
psf.executeUpdate();
protetto=true;
}finally{
if(lettore!=null)
lettore.close();
}
System.out.println("> Caricato nel database "+nome);
System.out.println("-Protetto= "+protetto);
System.out.println("-Pagine del pdf -> protette= "+protette+" vuote= "+vuote+" non inserite= "+nonInserite+" non convertite= "+nonConvertite);
}
else
System.out.println("> Preesistente nel database "+nome);
protette=0;
vuote=0;
nonInserite=0;
nonConvertite=0;
}

mi da l'errore:
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
at java.util.Arrays.copyOf(Unknown Source)
at java.io.ByteArrayOutputStream.write(Unknown Source)
at com.itextpdf.text.pdf.RandomAccessFileOrArray.InputStreamToArray(Rand
omAccessFileOrArray.java:209)
at com.itextpdf.text.pdf.RandomAccessFileOrArray.<init>(RandomAccessFile
OrArray.java:199)
at code.Gestione.caricaPDF(Gestione.java:66)
at code.Start.main(Start.java:33)
con questo tentativo non ho risolto molto, hai qualche proposta?

banryu79
09-03-2012, 11:15
con questo tentativo non ho risolto molto, hai qualche proposta?

No, come ho già detto non ho mai usato questa libreria.
Il problema potrebbe essere nel modo in cui costruisci il RandomAccessFileOrArray: tu passi un FileInputStream, ma negli esempi io ho visto che viene passata solo la stringa che rappresenta il path del file.
Oppure il problema è quello che ci fa con lettore il metodo caricaTesto, magari cerca di caricarlo tutto in memoria prima di fare quello che deve fare.
Visto che tu già usi questa libreria ne saprai sicuramente più di chi non l'ha mai presa in mano: sono sicuro che se ti documenti a dovere sarai in grado di implementare la soluzione (la soluzione l'abbiamo già trovata: leggere e processare il file a pezzetti, non tutto intero).

ZanTeo
09-03-2012, 14:28
(la soluzione l'abbiamo già trovata: leggere e processare il file a pezzetti, non tutto intero).
Attualmente i creo il PdfReader e poi lo leggo e lo carico pagina per pagina. Devo trovare un metodo (come dicevi tu) funzionale per creare un reader di parte del file in modo tale da evitare l'eccezione, magari metto un controllo e nel momento in cui si verifica l'outofmemory frazionare il reader in parti. Ora vedo cosa trovo e ne posto i risultati in modo che possiate valutarli.

rootshooter
09-03-2012, 15:48
No, come ho già detto non ho mai usato questa libreria.
Il problema potrebbe essere nel modo in cui costruisci il RandomAccessFileOrArray: tu passi un FileInputStream, ma negli esempi io ho visto che viene passata solo la stringa che rappresenta il path del file.



Guardando i costruttori sembra che sia proprio così come hai detto. Se si passa un InputStream viene caricato tutto in memoria, se si passa solo il path di default non viene caricato, oppure si può scegliere di sì :

http://www.docjar.com/html/api/com/lowagie/text/pdf/RandomAccessFileOrArray.java.html