Η καταμέτρηση των σελίδων σε ένα σαρωμένο αρχείο 1,4 GB θα έπρεπε να είναι φθηνή. Καλέστε το LoadFromFile σε αυτό το αρχείο και παύει να είναι φθηνή: το HotPDF αναλύει τα δεδομένα cross-reference και χτίζει ένα αντικείμενο στη μνήμη για καθένα από τα αρκετές εκατοντάδες χιλιάδες έμμεσα αντικείμενα του εγγράφου, και ένας 32-bit worker χτυπά το όριο χώρου διευθύνσεων των 2 GB κάπου στη μέση αυτής της ανάλυσης. Η λειτουργία που θέλατε, η καταμέτρηση σελίδων, ποτέ δεν χρειάστηκε κανένα από αυτά τα αντικείμενα. Χρειαζόταν το δέντρο σελίδων και τίποτα άλλο. Αυτό το χάσμα, ανάμεσα σε αυτό που ζητά μια εργασία και σε αυτό που παραδίδει μια πλήρης φόρτωση, είναι όλος ο λόγος ύπαρξης του Direct File API
Το Direct File API δίνει στο Delphi και το C++Builder πρόσβαση σε επίπεδο αρχείου σε ένα PDF: καταμέτρηση σελίδων, αντίγραφα, αποκρυπτογράφηση, αυξητικές προσαρτήσεις, όλα διαβάζοντας από τον δίσκο μόνο ό,τι πράγματι χρειάζονται αντί να ανασυνθέτουν ολόκληρο το μοντέλο εγγράφου στη RAM. Η δεξιότητα είναι να ταιριάζετε κάθε εργασία με το ελαφρύτερο επίπεδο που μπορεί να την απαντήσει. Πετύχετε αυτό το ταίριασμα και μια υπηρεσία διατηρεί επίπεδη μνήμη ανεξάρτητα από το μέγεθος εισόδου. Κάντε το λάθος και το πρώτο υπερμεγέθες αρχείο ρίχνει τον worker
Τι σας κοστίζει μια πλήρης φόρτωση
Το LoadFromFile δεν είναι ο εχθρός. Δικαιολογεί τη μνήμη του: μόλις το δέντρο βρίσκεται στη RAM έχετε τυχαία πρόσβαση σε κάθε σελίδα και κάθε αντικείμενο, που είναι ακριβώς αυτό που απαιτούν το InsertPagesFromDocument, το MovePage, και η επαναδιαδοχοποίηση μέσω του SaveLoadedDocument. Δεν υπάρχει συντόμευση για γνήσια αναδιάρθρωση· πρέπει να κρατάτε το έγγραφο για να το αναδιατάξετε
Το πρόβλημα ξεκινά όταν τα μεγέθη εισόδου δεν είναι δικά σας να τα ελέγξετε. Τα ανεβάσματα πελατών, η έξοδος σαρωτή, και τα αρχεία από πριν μια δεκαετία αγνοούν ό,τι υπέθεσε το test corpus σας. Φορτώστε κάθε είσοδο ανεξαιρέτως και το όριο μνήμης σας καθορίζεται από το μεγαλύτερο μεμονωμένο αρχείο που θα υποβάλει ποτέ κανείς. Ο χρόνος ανάλυσης ακολουθεί τον αριθμό αντικειμένων, και η μνήμη που παραμένει σε χρήση σταθεροποιείται σε πολλαπλάσιο του μεγέθους αρχείου αφού μετρηθούν οι δομές αντικειμένων και τα αποκωδικοποιημένα streams, οπότε ένα gigabyte στον δίσκο μπορεί να σημαίνει πολλαπλά gigabyte σε χρήση
Η επαναμεταγλώττιση για 64-bit αίρει το όριο χώρου διευθύνσεων αλλά αφήνει τον λογαριασμό ανέπαφο. Ο worker εξακολουθεί να καίει δευτερόλεπτα CPU και ένα πολλαπλάσιο του αρχείου στη RAM για να απαντήσει σε μια ερώτηση που η ίδια η δομή του αρχείου θα μπορούσε να απαντήσει σε χιλιοστά του δευτερολέπτου. Υπό ταυτοχρονισμό τα μαθηματικά γίνονται εχθρικά: τέσσερις μεγάλες φορτώσεις που τρέχουν ταυτόχρονα μοιράζονται έναν προϋπολογισμό μνήμης, και η απόδοση καταρρέει ακριβώς όταν η ουρά είναι βαθύτερη και το αντέχετε λιγότερο
Ανάγνωση ενός αρχείου μέσω handle
Το επίπεδο μόνο ανάγνωσης ανοίγει ένα αρχείο ως handle, απαντά σε δομικές ερωτήσεις γι' αυτό, και το κλείνει. Χωρίς δέντρο αντικειμένων, χωρίς απόδοση σελίδας, χωρίς μνήμη που μεγαλώνει με την είσοδο
var
Pdf: THotPDF;
Handle, PageCount: Integer;
begin
Pdf := THotPDF.Create(nil);
try
Handle := Pdf.DAOpenFileReadOnly('archive-2026-06.pdf', '');
if Handle > 0 then
try
PageCount := Pdf.DAGetPageCount(Handle);
RouteByPageCount('archive-2026-06.pdf', PageCount);
finally
Pdf.DACloseFile(Handle);
end;
finally
Pdf.Free;
end;
end;
Τρεις συνήθειες κρατούν αυτό το επίπεδο τίμιο. Πρώτον, ελέγξτε την τιμή επιστροφής. Ένα μη θετικό handle σημαίνει ότι το άνοιγμα απέτυχε, και το να πυροδοτήσετε το DAGetPageCount σε ένα νεκρό handle είναι το είδος του σφάλματος που παραμένει κρυφό μέχρι την ημέρα που ένας πελάτης στέλνει ένα κακοδιατυπωμένο αρχείο. Δεύτερον, συνδυάστε κάθε επιτυχημένο άνοιγμα με το DACloseFile μέσα σε ένα block finally· μια υπηρεσία που διαρρέει handles δεν καταρρέει, απλώς σαπίζει, κάτι που είναι χειρότερο. Τρίτον, σεβαστείτε τι κάνει στην πραγματικότητα η παράμετρος κωδικού πρόσβασης. Το DAOpenFileReadOnly δέχεται μία, αλλά για κρυπτογραφημένες εισόδους πέφτει σιωπηλά σε πλήρη ανάλυση για να διαβάσει την καταμέτρηση σελίδων, οπότε η εγγύηση επίπεδης μνήμης εξατμίζεται. Δρομολογήστε τα προστατευμένα αρχεία πρώτα μέσω του DecryptFile και ο υπόλοιπος αγωγός παραμένει φθηνός
Η ίδια δοκιμή λειτουργεί επίσης ως πύλη διαλογής. Αρχεία εμφανίζονται λανθασμένα επισημασμένα, μισοανεβασμένα, ή μετονομασμένα από κάποιο εντελώς άλλο format, και ένας έλεγχος DAOpenFileReadOnly τα απορρίπτει όλα στην είσοδο μέσα σε χιλιοστά του δευτερολέπτου, με το σφάλμα καρφωμένο στο ένοχο αρχείο. Η εναλλακτική είναι να αφήσετε ένα άχρηστο αρχείο να ταξιδέψει βαθιά μέσα σε έναν worker ουράς και να εκραγεί εκεί, όπου το ξεδιάλυμα του ποια είσοδος το προκάλεσε μπορεί να κοστίσει ένα απόγευμα
Αντιγραφή, αποκρυπτογράφηση και κρυπτογράφηση ολόκληρων αρχείων
Το δεύτερο επίπεδο μετακινεί και μετασχηματίζει ολόκληρα αρχεία χωρίς ποτέ να εκθέτει τα εσωτερικά τους. Αυτές είναι οι κλήσεις στις οποίες στηρίζονται περισσότερο οι αγωγοί εισαγωγής
// Δομική αντιγραφή: επικύρωση-και-μετακίνηση χωρίς ανάλυση του δέντρου αντικειμένων
Status := Pdf.DACopyFile('incoming\statement.pdf', 'verified\statement.pdf');
LogDirectFileStatus('copy', Status);
// Αποκρυπτογράφηση κατά την αντιγραφή: η διαδρομή Direct File για προστατευμένες εισόδους
Status := Pdf.DecryptFile('incoming\protected.pdf',
'verified\plain.pdf', 'batch-password');
LogDirectFileStatus('decrypt-copy', Status);
// Κρυπτογράφηση κατά την αντιγραφή: προστασία μιας εξόδου χωρίς πλήρη φόρτωση
Status := Pdf.EncryptFile('verified\statement.pdf',
'outbound\statement.pdf', 'owner-secret', '', aes256, [prPrint]);
LogDirectFileStatus('encrypt-copy', Status);
Κάθε κλήση δικαιολογεί τη θέση της. Το DACopyFile είναι το επικυρωμένο αντίγραφο από έναν φάκελο καραντίνας προς διαχειριζόμενη αποθήκευση: ανοίγει και ευρετηριάζει τη δομή PDF καθώς προχωρά, οπότε μια περικομμένη ή μη-PDF είσοδος αποτυγχάνει ακριβώς εδώ αντί για τρία στάδια παρακάτω στη ροή. Το DecryptFile γράφει ένα αποκρυπτογραφημένο αντίγραφο ακολουθώντας μια απευθείας διαδρομή επανεγγραφής AES-256 που παρακάμπτει το δέντρο αντικειμένων όποτε το επιτρέπει η είσοδος, το αντίστοιχο για μεγάλα αρχεία της ροής αποκρυπτογράφησης φόρτωσης-και-επανααποθήκευσης που καλύπτεται στο άρθρο για την κρυπτογράφηση AES-256. Το EncryptFile εκτελεί την ίδια κίνηση αντίστροφα, εφαρμόζοντας προστασία κωδικού πρόσβασης κατά τη διάρκεια μιας αντιγραφής σε επίπεδο αρχείου με τις παραμέτρους τύπου κλειδιού και δικαιωμάτων που χρησιμοποιεί ήδη η διαδρομή στη μνήμη
Προσάρτηση αλλαγών αντί επανεγγραφής
Η αυξητική ενημέρωση, όπως ορίζεται στο ISO 32000-1 §7.5.6, είναι το τρίτο επίπεδο. Τα αρχικά bytes παραμένουν εκεί που βρίσκονται στον δίσκο, και οποιαδήποτε νέα ή τροποποιημένα αντικείμενα προσαρτώνται μετά από αυτά, ακολουθούμενα από μια νέα ενότητα cross-reference που αλυσιδώνεται πίσω στο αρχικό. Για ένα αρχείο 900 MB που χρειάζεται την προσθήκη μίας μόνο σελίδας, το κόστος εγγραφής είναι το delta, όχι ολόκληρο το αρχείο
// Προσάρτηση μιας σελίδας ελέγχου σε ένα μεγάλο αρχείο χωρίς επανεγγραφή του
Pdf.BeginIncrementalUpdate('archive-2026-06.pdf');
Pdf.AddPage;
Pdf.CurrentPage.SetFont('Arial', [], 10);
Pdf.CurrentPage.TextOut(50, 760, 0, 'Processed by intake service 2026-06-11');
Pdf.SaveIncrementalUpdate('archive-2026-06-stamped.pdf'); // αρχικά bytes + delta
Δύο σημεία πειθαρχίας έχουν σημασία εδώ. Το BeginIncrementalUpdate πρέπει να δείχνει στο αρχικό αρχείο, καθώς τα προσαρτημένα δεδομένα cross-reference αλυσιδώνονται πίσω σε offsets byte μέσα σε αυτό. Και το μοντέλο είναι σχεδιασμένο μόνο-για-προσάρτηση: κάθε αυξητική αποθήκευση μεγαλώνει το αρχείο, ποτέ δεν το συρρικνώνει. Ένα έγγραφο που σφραγίζεται κάθε βράδυ θα διογκώνεται απεριόριστα μέχρι μια περιοδική επαναδιαδοχοποίηση, φορτώνοντάς το και ξαναγράφοντάς το μέσω του SaveLoadedDocument, να το συμπτύξει. Αυτή η ίδια μόνο-για-προσάρτηση φύση είναι αυτό που κάνει την αυξητική ενημέρωση τον μόνο ασφαλή τρόπο να αγγίξετε ένα ψηφιακά υπογεγραμμένο έγγραφο, έναν περιορισμό που εξετάζεται στο άρθρο για τις ψηφιακές υπογραφές και το PAdES. Ο υποκείμενος μηχανισμός cross-reference λαμβάνει τη δική του πραγμάτευση στο άρθρο για τα object streams και τις αυξητικές ενημερώσεις
Υπάρχει μια παγίδα στις αποθηκεύσεις μόνο-για-προσάρτηση που ξεφεύγει από τις περισσότερες αναθεωρήσεις. Τα αρχικά bytes παραμένουν στο αρχείο, ευανάγνωστα από όποιον θέλει να κοιτάξει. Μια αυξητική ενημέρωση που "αντικαθιστά" μια σελίδα δεν διαγράφει την παλιά· την υπερκαλύπτει στην τρέχουσα αναθεώρηση ενώ η προηγούμενη αναθεώρηση παραμένει εκεί, πλήρως ανακτήσιμη. Έτσι οι αυξητικές ενημερώσεις είναι το λάθος εργαλείο για την αφαίρεση ευαίσθητου περιεχομένου. Για να αποβάλετε πραγματικά ιστορικό που ένας παραλήπτης δεν πρέπει ποτέ να δει, χρειάζεστε μια πλήρη επαναδιαδοχοποίηση: LoadFromFile ακολουθούμενο από SaveLoadedDocument, που γράφει μόνο την τρέχουσα κατάσταση και αφήνει πίσω τις θαμμένες αναθεωρήσεις
Αντιστοίχιση του επιπέδου στη λειτουργία
Η λογική επιλογής είναι αρκετά σύντομη ώστε να τη θυμάστε, και αξίζει να την κωδικοποιήσετε ως ρητή απόφαση δρομολόγησης στην κορυφή ενός αγωγού αντί να αφήσετε κάθε εργασία να αυτοσχεδιάσει τη δική της διαδρομή. Η λειτουργία που χρειάζεστε καθορίζει το επίπεδο:
- Καταμέτρηση, επιθεώρηση ή ταξινόμηση ανοίγει ένα handle:
DAOpenFileReadOnly,DAGetPageCount,DACloseFile - Μετακίνηση, αποκρυπτογράφηση ή κρυπτογράφηση ολόκληρου αρχείου παραμένει σε επίπεδο αρχείου με
DACopyFile,DecryptFile, ήEncryptFile - Αναδιάρθρωση σελίδων ή συγχώνευση εγγράφων χρειάζεται την πλήρη φόρτωση:
LoadFromFile, έπειταInsertPagesFromDocumentήMovePage, έπειταSaveLoadedDocument - Προσθήκη ενός μικρού delta σε ένα τεράστιο ή υπογεγραμμένο αρχείο καλεί το
BeginIncrementalUpdateκαι αποθηκεύει
Οι μικτοί αγωγοί κάνουν καλά να τοποθετούν ένα κατώφλι μεγέθους μπροστά από τη διαδρομή πλήρους φόρτωσης. Στείλτε οτιδήποτε ξεπερνά μερικές εκατοντάδες megabyte μέσα από τα επίπεδα Direct File, και κρατήστε την πλήρη φόρτωση για γνήσια αναδιάρθρωση σε έναν 64-bit worker με πραγματικό προϋπολογισμό μνήμης. Το κατώφλι μετατρέπει μια κατάρρευση εξάντλησης μνήμης σε μια απόφαση δρομολόγησης που μπορείτε να δείτε και να ρυθμίσετε
Όποιο επίπεδο κι αν χειρίζεται μια εργασία, γράψτε την έξοδό της σε ένα προσωρινό όνομα και μετονομάστε την στη θέση της μόνο αφού το αποτέλεσμα επικυρωθεί. Ένα μισογραμμένο αρχείο που κάθεται κάτω από το τελικό όνομα φαίνεται ακριβώς σαν ένα καλό στο επόμενο στάδιο του αγωγού, και οι κλήσεις Direct File κάνουν τον έλεγχο φθηνό: η επιβεβαίωση μιας εξόδου είναι μια δοκιμή handle μίας γραμμής
Το Direct File API κυκλοφορεί ως μέρος του HotPDF Delphi Component για Delphi και C++Builder. Η σελίδα προϊόντος συνδέει την πλήρη αναφορά συναρτήσεων, συμπεριλαμβανομένων των κλήσεων αυξητικής ενημέρωσης που παρουσιάζονται εδώ