Προγραμματισμός

Χρησιμοποιήστε ένα RandomAccessFile για να δημιουργήσετε μια βάση δεδομένων χαμηλού επιπέδου

Καθώς έψαξα JavaWorldΙστότοπος για ιδέες για αυτόν τον μήνα Βήμα βήμα, Συνάντησα μόνο λίγα άρθρα που καλύπτουν πρόσβαση αρχείων χαμηλού επιπέδου. Αν και υψηλού επιπέδου API όπως το JDBC μας δίνουν την ευελιξία και την ισχύ που απαιτείται σε μεγάλες επιχειρήσεις, πολλές μικρότερες εφαρμογές απαιτούν μια πιο απλή και κομψή λύση.

Σε αυτό το άρθρο, θα δημιουργήσουμε μια επέκταση στο RandomAccessFile τάξη που μας επιτρέπει να αποθηκεύουμε και να ανακτούμε αρχεία. Αυτό το "αρχείο εγγραφών" θα είναι ισοδύναμο με ένα επίμονο hashtable, επιτρέποντας την αποθήκευση και ανάκτηση αντικειμένων με κλειδί από την αποθήκευση αρχείων.

Ένα βασικό αρχείο και αρχεία

Πριν προχωρήσουμε στο παράδειγμα, ας ξεκινήσουμε με ένα βασικό υπόβαθρο. Θα ξεκινήσουμε με τον ορισμό ορισμένων όρων που σχετίζονται με αρχεία και αρχεία, και στη συνέχεια θα συζητήσουμε εν συντομία την τάξη java.io.RandomAccessFile και εξάρτηση από πλατφόρμα.

Ορολογία

Οι ακόλουθοι ορισμοί συντονίζονται στο παράδειγμά μας, παρά στην παραδοσιακή ορολογία βάσης δεδομένων.

Ρεκόρ - Μια συλλογή σχετικών δεδομένων που είναι αποθηκευμένα σε ένα αρχείο. Μια εγγραφή έχει συνήθως πολλαπλάσια πεδία, το καθένα είναι ένα όνομα και πληκτρολογημένο στοιχείο πληροφοριών.

Κλειδί - Ένα αναγνωριστικό για μια εγγραφή. Τα κλειδιά είναι συνήθως μοναδικά.

Αρχείο - Μια διαδοχική συλλογή δεδομένων που αποθηκεύονται σε κάποιο είδος σταθερής αποθήκευσης, όπως ένας σκληρός δίσκος.

Μη απαραίτητη πρόσβαση σε αρχεία - Επιτρέπει την ανάγνωση δεδομένων από αυθαίρετες τοποθεσίες στο αρχείο.

Δείκτης αρχείου - Ένας αριθμός που κρατά τη θέση του επόμενου byte δεδομένων για ανάγνωση από ένα αρχείο.

Εγγραφή δείκτη - Ο δείκτης εγγραφής είναι ένας δείκτης αρχείου που δείχνει τη θέση όπου ξεκινά μια συγκεκριμένη εγγραφή.

Δείκτης - Ένα δευτερεύον μέσο πρόσβασης σε εγγραφές σε ένα αρχείο. δηλαδή χαρτογραφεί κλειδιά για να καταγράφει δείκτες.

Σωρός - Ένα διαδοχικό αρχείο μη ταξινομημένων και μεταβλητού μεγέθους εγγραφών. Ένας σωρός απαιτεί κάποια εξωτερική ευρετηρίαση για να έχει ουσιαστική πρόσβαση στις εγγραφές.

επιμονή - Αναφέρεται στην αποθήκευση ενός αντικειμένου ή μιας εγγραφής για συγκεκριμένο χρονικό διάστημα. Αυτό το χρονικό διάστημα είναι συνήθως μεγαλύτερο από τη διάρκεια μιας διαδικασίας, έτσι τα αντικείμενα είναι συνήθως επέμεινε σε αρχεία ή βάσεις δεδομένων.

Επισκόπηση του class java.io.RandomAccessFile

Τάξη RandomAccessFile είναι ο τρόπος της Java να παρέχει μη απαραίτητη πρόσβαση σε αρχεία. Η τάξη μας επιτρέπει να μεταβούμε σε μια συγκεκριμένη τοποθεσία στο αρχείο χρησιμοποιώντας το ψάχνω() μέθοδος. Μόλις τοποθετηθεί ο δείκτης του αρχείου, τα δεδομένα μπορούν να διαβαστούν και να εγγραφούν στο αρχείο χρησιμοποιώντας το Εισαγωγή δεδομένων και Έξοδος δεδομένων διεπαφές. Αυτές οι διεπαφές μας επιτρέπουν να διαβάζουμε και να γράφουμε δεδομένα ανεξάρτητα από την πλατφόρμα. Άλλες πρακτικές μεθόδους στο RandomAccessFile επιτρέψτε μας να ελέγξουμε και να ορίσουμε το μήκος του αρχείου.

Ζητήματα που εξαρτώνται από την πλατφόρμα

Οι σύγχρονες βάσεις δεδομένων βασίζονται σε μονάδες δίσκου για αποθήκευση. Τα δεδομένα σε μια μονάδα δίσκου αποθηκεύονται στο μπλοκ, που διανέμονται σε όλη κομμάτια και επιφάνειες. Ο δίσκος αναζητήστε χρόνο και περιστροφική καθυστέρηση υπαγορεύστε πώς τα δεδομένα μπορούν να αποθηκευτούν και να ανακτηθούν πιο αποτελεσματικά. Ένα τυπικό σύστημα διαχείρισης βάσης δεδομένων βασίζεται στενά στα χαρακτηριστικά του δίσκου προκειμένου να βελτιώσει την απόδοση. Δυστυχώς (ή ευτυχώς, ανάλογα με το ενδιαφέρον σας για το αρχείο I / O χαμηλού επιπέδου!), Αυτές οι παράμετροι απέχουν πολύ από τη χρήση ενός API αρχείων υψηλού επιπέδου όπως java.io. Δεδομένου αυτού του γεγονότος, το παράδειγμά μας θα αγνοήσει τις βελτιστοποιήσεις που θα μπορούσε να παρέχει η γνώση των παραμέτρων του δίσκου.

Σχεδιάζοντας το παράδειγμα RecordsFile

Τώρα είμαστε έτοιμοι να σχεδιάσουμε το παράδειγμά μας. Για να ξεκινήσω, θα καθορίσω ορισμένες απαιτήσεις σχεδίασης και στόχους, θα επιλύσω ζητήματα ταυτόχρονης πρόσβασης και θα καθορίσω τη μορφή αρχείου χαμηλού επιπέδου. Πριν προχωρήσουμε στην εφαρμογή, θα εξετάσουμε επίσης τις κύριες λειτουργίες εγγραφής και τους αντίστοιχους αλγόριθμούς τους.

Απαιτήσεις και στόχοι

Ο κύριος στόχος μας σε αυτό το παράδειγμα είναι να χρησιμοποιήσουμε ένα RandomAccessFile να παρέχει έναν τρόπο αποθήκευσης και ανάκτησης δεδομένων εγγραφής. Θα συσχετίσουμε ένα κλειδί τύπου Σειρά με κάθε εγγραφή ως μέσο μοναδικής ταυτοποίησής του. Τα πλήκτρα θα περιορίζονται σε ένα μέγιστο μήκος, αν και τα δεδομένα εγγραφής δεν θα είναι περιορισμένα. Για τους σκοπούς αυτού του παραδείγματος, τα αρχεία μας θα αποτελούνται από ένα μόνο πεδίο - ένα "blob" δυαδικών δεδομένων. Ο κωδικός αρχείου δεν θα επιχειρήσει να ερμηνεύσει τα δεδομένα εγγραφής με κανέναν τρόπο.

Ως δεύτερος στόχος σχεδίασης, θα απαιτήσουμε να μην καθοριστεί ο αριθμός των εγγραφών που υποστηρίζει το αρχείο μας κατά τη δημιουργία. Θα επιτρέψουμε στο αρχείο να αναπτυχθεί και να συρρικνωθεί καθώς οι εγγραφές εισάγονται και αφαιρούνται. Επειδή τα δεδομένα ευρετηρίου και εγγραφής μας θα αποθηκευτούν στο ίδιο αρχείο, αυτός ο περιορισμός θα μας κάνει να προσθέσουμε επιπλέον λογική για να αυξήσουμε δυναμικά το χώρο ευρετηρίου αναδιοργανώνοντας τις εγγραφές.

Η πρόσβαση σε δεδομένα σε ένα αρχείο είναι παραγγελίες μεγέθους πιο αργή από την πρόσβαση σε δεδομένα στη μνήμη. Αυτό σημαίνει ότι ο αριθμός των προσβάσεων αρχείων που εκτελεί η βάση δεδομένων θα είναι ο καθοριστικός παράγοντας απόδοσης. Θα απαιτήσουμε οι κύριες λειτουργίες της βάσης δεδομένων μας να μην εξαρτώνται από τον αριθμό των εγγραφών στο αρχείο. Με άλλα λόγια, θα είναι σταθερού χρόνου παραγγελίας σε σχέση με τις προσβάσεις αρχείων.

Ως τελική απαίτηση, θα υποθέσουμε ότι το ευρετήριό μας είναι αρκετά μικρό για να φορτωθεί στη μνήμη. Αυτό θα διευκολύνει την εφαρμογή μας να εκπληρώσει την απαίτηση που υπαγορεύει το χρόνο πρόσβασης. Θα αντικατοπτρίσουμε τον δείκτη σε ένα Hashtable, που παρέχει άμεσες αναζητήσεις κεφαλίδας εγγραφής.

Διόρθωση κώδικα

Ο κώδικας για αυτό το άρθρο έχει ένα σφάλμα που το προκαλεί να ρίξει ένα NullPointerException σε πολλές πιθανές περιπτώσεις. Υπάρχει μια ρουτίνα που ονομάζεται insureIndexSpace (int) στην αφηρημένη κλάση BaseRecordsFile. Ο κώδικας προορίζεται να μετακινήσει υπάρχουσες εγγραφές στο τέλος του αρχείου, εάν η περιοχή ευρετηρίου πρέπει να επεκταθεί. Μετά την επαναφορά της χωρητικότητας της "πρώτης" εγγραφής στο πραγματικό της μέγεθος, μετακινείται στο τέλος. Στη συνέχεια, το dataStartPtr ρυθμίζεται να οδηγεί στη δεύτερη εγγραφή στο αρχείο. Δυστυχώς, εάν υπήρχε ελεύθερος χώρος στην πρώτη εγγραφή, τα νέα δεδομέναStartPtr δεν θα δείχνουν σε έγκυρη εγγραφή, καθώς αυξήθηκε από την πρώτη εγγραφή μήκος παρά την ικανότητά του. Η τροποποιημένη πηγή Java για το BaseRecordsFile βρίσκεται στο Resources.

από τον Ron Walkup

Ανώτερος Μηχανικός Λογισμικού

bioMerieux, Inc.

Συγχρονισμός και ταυτόχρονη πρόσβαση σε αρχεία

Για απλότητα, ξεκινάμε υποστηρίζοντας μόνο ένα μοντέλο νήματος, στο οποίο απαγορεύεται η ταυτόχρονη εμφάνιση αιτημάτων αρχείων. Μπορούμε να το επιτύχουμε συγχρονίζοντας τις μεθόδους δημόσιας πρόσβασης του Αρχείο BaseRecords και Αρχείο εγγραφών τάξεις. Λάβετε υπόψη ότι μπορείτε να χαλαρώσετε αυτόν τον περιορισμό για να προσθέσετε υποστήριξη για ταυτόχρονες αναγνώσεις και εγγραφές σε μη συγκρουόμενες εγγραφές: Θα χρειαστεί να διατηρήσετε μια λίστα κλειδωμένων εγγραφών και να διαβάζετε και να γράφετε interleave για ταυτόχρονα αιτήματα.

Λεπτομέρειες για τη μορφή αρχείου

Τώρα θα ορίσουμε ρητά τη μορφή του αρχείου εγγραφών. Το αρχείο αποτελείται από τρεις περιοχές, καθεμία με τη δική της μορφή.

Η περιοχή κεφαλίδων αρχείων. Αυτή η πρώτη περιοχή περιέχει τις δύο βασικές κεφαλίδες που απαιτούνται για την πρόσβαση σε εγγραφές στο αρχείο μας. Η πρώτη κεφαλίδα, που ονομάζεται δείκτης έναρξης δεδομένων, είναι ένα μακρύς που δείχνει την αρχή των δεδομένων εγγραφής. Αυτή η τιμή μας λέει το μέγεθος της περιοχής ευρετηρίου. Η δεύτερη κεφαλίδα, που ονομάζεται num εγγραφή κεφαλίδας, είναι ένα int που δίνει τον αριθμό των εγγραφών στη βάση δεδομένων. Η περιοχή κεφαλίδων ξεκινά από το πρώτο byte του αρχείου και εκτείνεται για FILE_HEADERS_REGION_LENGTH byte. Θα χρησιμοποιήσουμε ανάγνωσηLong () και readInt () για να διαβάσετε τις κεφαλίδες και εγγραφή Long () και εγγραφή σε () για να γράψετε τις κεφαλίδες.

Η περιοχή ευρετηρίου. Κάθε καταχώριση στο ευρετήριο αποτελείται από ένα κλειδί και μια κεφαλίδα εγγραφής. Το ευρετήριο ξεκινά στο πρώτο byte μετά την περιοχή κεφαλίδων αρχείων και εκτείνεται μέχρι το byte πριν από το δείκτη έναρξης των δεδομένων. Από αυτές τις πληροφορίες, μπορούμε να υπολογίσουμε ένα δείκτη αρχείου έως την αρχή οποιουδήποτε από τα ν καταχωρήσεις στο ευρετήριο. Οι καταχωρίσεις έχουν σταθερό μήκος - τα βασικά δεδομένα ξεκινούν από το πρώτο byte στην καταχώρηση ευρετηρίου και επεκτείνονται MAX_KEY_LENGTH byte. Η αντίστοιχη κεφαλίδα εγγραφής για ένα δεδομένο κλειδί ακολουθεί αμέσως μετά το κλειδί στο ευρετήριο. Η κεφαλίδα της εγγραφής μας λέει πού βρίσκονται τα δεδομένα, πόσα bytes μπορεί να κρατήσει η εγγραφή και πόσα bytes κατέχει στην πραγματικότητα. Οι καταχωρίσεις ευρετηρίου στο ευρετήριο αρχείων δεν έχουν συγκεκριμένη σειρά και δεν αντιστοιχίζονται στη σειρά με την οποία αποθηκεύονται οι εγγραφές στο αρχείο.

Εγγραφή περιοχής δεδομένων. Η περιοχή δεδομένων εγγραφής ξεκινά από τη θέση που υποδεικνύεται από το δείκτη έναρξης δεδομένων και εκτείνεται μέχρι το τέλος του αρχείου. Οι εγγραφές τοποθετούνται πλάτη-πλάτη στο αρχείο χωρίς να επιτρέπεται ελεύθερος χώρος μεταξύ των εγγραφών. Αυτό το μέρος του αρχείου αποτελείται από ανεπεξέργαστα δεδομένα χωρίς κεφαλίδες ή βασικές πληροφορίες. Το αρχείο βάσης δεδομένων τελειώνει στο τελευταίο μπλοκ της τελευταίας εγγραφής στο αρχείο, οπότε δεν υπάρχει επιπλέον χώρος στο τέλος του αρχείου. Το αρχείο μεγαλώνει και συρρικνώνεται καθώς προστίθενται και διαγράφονται εγγραφές.

Το μέγεθος που εκχωρείται σε μια εγγραφή δεν αντιστοιχεί πάντα στην πραγματική ποσότητα δεδομένων που περιέχει η εγγραφή. Η εγγραφή μπορεί να θεωρηθεί ως κοντέινερ - μπορεί να είναι μόνο εν μέρει γεμάτη. Τα έγκυρα δεδομένα εγγραφής τοποθετούνται στην αρχή της εγγραφής.

Υποστηριζόμενες λειτουργίες και οι αλγόριθμοί τους

ο Αρχείο εγγραφών θα υποστηρίξει τις ακόλουθες κύριες λειτουργίες:

  • Εισαγωγή - Προσθέτει μια νέα εγγραφή στο αρχείο

  • Ανάγνωση - Διαβάζει μια εγγραφή από το αρχείο

  • Ενημέρωση - Ενημερώνει μια εγγραφή

  • Διαγραφή - Διαγράφει μια εγγραφή

  • Διασφάλιση χωρητικότητας - Αυξάνει την περιοχή ευρετηρίου για να φιλοξενήσει νέες εγγραφές

Πριν προχωρήσουμε στον πηγαίο κώδικα, ας δούμε τους επιλεγμένους αλγόριθμους για καθεμία από αυτές τις λειτουργίες:

Εισάγετε. Αυτή η λειτουργία εισάγει μια νέα εγγραφή στο αρχείο. Για να εισαγάγουμε, εμείς:

  1. Βεβαιωθείτε ότι το κλειδί που εισάγετε δεν υπάρχει ήδη στο αρχείο
  2. Βεβαιωθείτε ότι η περιοχή ευρετηρίου είναι αρκετά μεγάλη για την πρόσθετη καταχώριση
  3. Βρείτε ελεύθερο χώρο στο αρχείο αρκετά μεγάλο για να κρατάτε την εγγραφή
  4. Γράψτε τα δεδομένα εγγραφής στο αρχείο
  5. Προσθέστε την κεφαλίδα εγγραφής στο ευρετήριο

Ανάγνωση. Αυτή η λειτουργία ανακτά μια ζητούμενη εγγραφή από το αρχείο με βάση ένα κλειδί. Για να ανακτήσετε μια εγγραφή, εμείς:

  1. Χρησιμοποιήστε το ευρετήριο για να αντιστοιχίσετε το δεδομένο κλειδί στην κεφαλίδα εγγραφής
  2. Αναζητήστε μέχρι την αρχή των δεδομένων (χρησιμοποιώντας το δείκτη για την εγγραφή δεδομένων που είναι αποθηκευμένα στην κεφαλίδα)
  3. Διαβάστε τα δεδομένα της εγγραφής από το αρχείο

Εκσυγχρονίζω. Αυτή η λειτουργία ενημερώνει μια υπάρχουσα εγγραφή με νέα δεδομένα, αντικαθιστώντας τα νέα δεδομένα με τα παλιά. Τα βήματα για την ενημέρωσή μας διαφέρουν, ανάλογα με το μέγεθος των νέων δεδομένων εγγραφής. Εάν τα νέα δεδομένα ταιριάζουν στην υπάρχουσα εγγραφή, εμείς:

  1. Γράψτε τα δεδομένα εγγραφής στο αρχείο, αντικαθιστώντας τα προηγούμενα δεδομένα
  2. Ενημερώστε το χαρακτηριστικό που κρατά το μήκος των δεδομένων στην κεφαλίδα της εγγραφής

Διαφορετικά, εάν τα δεδομένα είναι πολύ μεγάλα για την εγγραφή, εμείς:

  1. Εκτελέστε μια λειτουργία διαγραφής στην υπάρχουσα εγγραφή
  2. Εκτελέστε ένα ένθετο των νέων δεδομένων

Διαγράφω. Αυτή η λειτουργία αφαιρεί μια εγγραφή από το αρχείο. Για να διαγράψετε μια εγγραφή, εμείς:

  1. Ανακτήστε το χώρο που έχει εκχωρηθεί στην εγγραφή που αφαιρείται είτε συρρικνώνοντας το αρχείο, εάν η εγγραφή είναι η τελευταία στο αρχείο, είτε προσθέτοντας το χώρο της σε μια γειτονική εγγραφή

  2. Αφαιρέστε την κεφαλίδα της εγγραφής από το ευρετήριο αντικαθιστώντας την καταχώριση που διαγράφηκε με την τελευταία καταχώρηση στο ευρετήριο. Αυτό διασφαλίζει ότι το ευρετήριο είναι πάντα γεμάτο, χωρίς κενά διαστήματα μεταξύ των καταχωρήσεων

Εξασφαλίστε χωρητικότητα. Αυτή η λειτουργία διασφαλίζει ότι η περιοχή ευρετηρίου είναι αρκετά μεγάλη για να φιλοξενήσει επιπλέον καταχωρήσεις. Σε έναν βρόχο, μεταφέρουμε τις εγγραφές από το μπροστινό στο τέλος του αρχείου μέχρι να υπάρχει αρκετός χώρος. Για να μετακινήσετε ένα δίσκο εμείς:

  1. Εντοπίστε την κεφαλίδα εγγραφής της πρώτης εγγραφής στο αρχείο. Σημειώστε ότι αυτή είναι η εγγραφή με δεδομένα στην κορυφή της περιοχής δεδομένων εγγραφής - όχι η εγγραφή με την πρώτη κεφαλίδα στο ευρετήριο

  2. Διαβάστε τα δεδομένα της εγγραφής στόχου

  3. Αναπτύξτε το αρχείο με το μέγεθος των δεδομένων της εγγραφής προορισμού χρησιμοποιώντας το setLength (μακρύ) μέθοδος στο RandomAccessFile

  4. Γράψτε τα δεδομένα εγγραφής στο κάτω μέρος του αρχείου

  5. Ενημερώστε το δείκτη δεδομένων στην εγγραφή που μετακινήθηκε

  6. Ενημερώστε την καθολική κεφαλίδα που δείχνει τα δεδομένα της πρώτης εγγραφής

Λεπτομέρειες εφαρμογής - προχωρώντας μέσω του πηγαίου κώδικα

Είμαστε τώρα έτοιμοι να βρώσουμε τα χέρια μας και να επεξεργαστούμε τον κώδικα για το παράδειγμα. Μπορείτε να κατεβάσετε την πλήρη πηγή από τους πόρους.

Σημείωση: Πρέπει να χρησιμοποιήσετε την πλατφόρμα Java 2 (παλαιότερα γνωστή ως JDK 1.2) για να μεταγλωττίσετε την πηγή.

File BaseRecordsFile

Αρχείο BaseRecords είναι μια αφηρημένη τάξη και είναι η κύρια εφαρμογή του παραδείγματος μας. Καθορίζει τις κύριες μεθόδους πρόσβασης καθώς και μια σειρά από μεθόδους χρησιμότητας για το χειρισμό εγγραφών και καταχωρίσεων ευρετηρίου.