Εισαγωγή στην Java - κεφ. 12

...ασύγχρονα μαθήματα γλώσσας Java

Εισαγωγή στην Java - κεφ. 12

Δημοσίευσηαπό alkismavridis » 22 Απρ 2014, 20:10

Προηγούμενο: Exceptions
Επόμενο: Προσεχώς


Αρχεία


Ίσως η στιγμή που πολλοί περιμένατε... Σε αυτό το κεφάλαιο θα μάθουμε πως να μεταχειριζόμαστε αρχεία! Είναι σημαντικό να καταλάβουμε ότι όταν λέμε «αρχείο» εννοούμε δύο πράγματα: την περιγραφή του, και τα περιεχόμενά του.
Στην περιγραφή εντάσσονται διάφορες πληροφορίες που κρατά το λειτουργικό σύστημα (ο τίτλος του αρχείου και ο φάκελος στον οποίο βρίσκεται, η διεύθυνσή του στο σκληρό δίσκο, τα δικαιώματα ανάγνωσης-τροποποίησης-εκτέλεσης, ο ιδιοκτήτης του, ημερομηνία τελευταίας προσπέλασης, μέγεθος και διάφορα άλλα).
Στα περιεχόμενα εντάσσεται αυτό που βλέπουμε όταν ανοίγουμε το αρχείο (πχ με ένα δεκαεξαδικό επεξεργαστή).

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

Η εικόνα μοιάζει κάπως έτσι: Αν το πρόγραμμά θέλει να διαβάσει τα περιεχόμενα του αρχείου Χ, λέει στον πυρήνα κάτι σαν αυτό: «θα ήθελα να διαβάσω τα n πρώτα bytes του αρχείου με όνομα Χ, γράψε τα bytes αυτά στην τάδε θέση της RAM για να το διαβάσω από εκεί». Σε καμία περίπτωση το πρόγραμμά μας δεν διαβάζει μόνο του το σκληρό δίσκο!
Ο πυρήνας τώρα παίρνει το όνομα Χ, βλέπει αν υπάρχει αρχείο με αυτό το όνομα, βλέπει αν το πρόγραμμα που του ζήτησε «τη δουλειά» έχει δικαίωμα να διαβάσει το αρχείο αυτό, και αν όλα αυτά πάνε καλά γράφει τα περιεχόμενα του αρχείου στην δεδομένη θέση RAM για να τα βρεί το πρόγραμμα. Τελικά λέει στο πρόγραμμα: «εντάξει, τα bytes που ζήτησες είναι εκεί», και το πρόγραμμα συνεχίζει. Αν κάτι δεν πάει καλά, ειδοποιεί το πρόγραμμα για το τι έγινε, και εμείς λαμβάνουμε συνήθως μία Exception. Την εμπλοκή αυτή του λειτουργικού συστήματος θα την συναντήσετε με όποια γλώσσα προγραμματισμού και αν ασχοληθείτε, εκτός αν φτιάχνετε ένα λειτουργικό σύστημα, κάτι που σας εύχομαι ολόψυχα!

Από την έκδοση 7 της Java, ο τρόπος με τον οποίο εργαζόμαστε με αρχεία άλλαξε. Εδώ παρουσιάζεται ο νέος τρόπος. Αν έχετε compiler ή jvm έκδοσης 6 και κάτω... είναι καιρός να αναβαθμίσετε!

Για λόγους πληρότητας αναφέρω ότι η παλιά μέθοδος βασίζεται στην κλάση java.io.File.


Η ενασχόλησή μας με τα αρχεία θα χωριστεί σε τρία βασικά βήματα:
  • Πως να λέμε στο λειτουργικό σύστημα ποιο αρχείο θέλουμε
  • Πως να μεταχειριζόμαστε την περιγραφή του αρχείου
  • Πως να γράφουμε και να διαβάζουμε από το αρχείο

1.1 Η κλάση Path
(Η κλάση αυτή αντικαθιστά την java.io.File που χρησιμοποιούσαμε μέχρι την έκδοση 6)

Ασχολούμενοι με τα αρχεία θα συναντήσουμε πολλές νέες κλάσεις. Οι βασικότερες είναι η Path, και η Files, και οι δύο μέλη του πακέτου java.nio.file. Γενικά καλό είναι να κάνουμε import όλο αυτό το πακέτο γιατί θα εμφανίζεται συνεχώς.

Αν κοιτάξετε αυτές τις κλάσεις θα δείτε κάποιες παράξενες τρίγωνες παρενθέσεις. Περισσότερα γι αυτά θα δούμε στο κεφάλαιο 13.


Η κλάση Path συμβολίζει το «μονοπάτι» που οδηγεί στο αρχείο. Φανταστείτε το κάτι σαν ένα String με τον τίτλο του αρχείου (μαζί με τους φακέλους που το περιέχουν). Η κλάση Path περιέχει διάφορες μεθόδους. Μερικές από αυτές θα μας δούμε στην παράγραφο 1.4. Το πρώτο που μας νοιάζει είναι να καταλάβουμε πως να δημιουργήσουμε το κατάλληλο αντικείμενο Path που να περιγράφει το αρχείο μας.


1.2 Δημιουργώντας ένα αντικείμενο Path
Περιέργως δεν υπάρχουν public constructors. Άρα πως δημιουργούμε το αντικείμενο Path; Μέσω ενός «μεγαλύτερου» αντικειμένου που περιγράφει το σύστημα αρχείων μας. Το αντικείμενο αυτό ονομάζεται FileSystem. Να πως δουλεύει:
Μορφοποιημένος Κώδικας: Επιλογή όλων
FileSystem fs = FileSystems.getDefault();	//όπως βλέπετε ούτε εδώ καλούμε άμεσα κάποιον constructor!
Path p = fs.getPath("όνομα_αρχείου");

Η διαδικασία λοιπόν είναι:
  • Καλούμε τη μέθοδο FileSystems.getDefault(), η οποία μας επιστρέφει ένα αντικείμενο FileSystem.
  • Από το αντικείμενο αυτό καλούμε τη μέθοδο getPath(String str) και παίρνουμε το αντικείμενο Path
Βέβαια τα παραπάνω μπορούν να γίνουν σε μία γραμμή κώδικα:
Μορφοποιημένος Κώδικας: Επιλογή όλων
Path p = FileSystems.getDefault().getPath("όνομα_αρχείου");


1.3 Δίνοντας το σωστό όνομα
Ας μιλήσουμε τώρα για το όρισμα της μεθόδου getPath. Είναι ένα String που περιγράφει τον τίτλο του αρχείου. Ο τίτλος αρχείου έχει τη μορφη: "όνομα_φακέλου/όνομα_αρχείου".

Στις διαδρομές αρχείων, η τελεία συμβολίζει τον τρέχοντα φάκελο, και η διπλή τελεία τον πατρικό φάκελο.
Το τελικό '/' δεν έχει καμία σημασία, γιατί το τι είναι φάκελος και τι όχι το ξεχωρίζει το ίδιο το λειτουργικό.

Κατά συνέπεια αν θέλουμε να πάρουμε το Path του φακέλου που βρισκόμαστε τώρα καλούμε την getPath με όρισμα "." και αν θέλουμε να πάρουμε αυτό του πατρικού φακέλου καλούμε με όρισμα ".." ή "./..". Ορίσματα όπως "../data/JavaData", "../../SomeDir" ή και "dir1/dir2/../dir3" είναι απόλυτα σωστά (αν και το τρίτο λίγο άσκοπο).

Για το linux, αν το όρισμα ξεκινά με '/', συμβολίζει την απόλυτη διαδρομή του αρχείου, σε διαφορετική περίπτωση συμβολίζει διαδρομή σχετική με τον φάκελο από τον οποίο έτρεξε η εντολή java.
Για παράδειγμα έχω την κλάση mypac.MyClass στην τοποθεσία /home/alkis/programs/mypack/MyClass.class.
Όπως έχουμε δει σε προηγούμενο κεφάλαιο, την κλάση αυτή θα την καλούσαμε από το σημείο /home/alkis/programs.
Αν σε αυτή την κλάση καλέσουμε την getPath με όρισμα "/home/alkis/somefile", το πρόγραμμα θα έψαχνε για το αρχείο /home/alkis/somefile". Κατά λέξη δηλαδή αυτό που γράψαμε. Αυτό σημαίνει απόλυτη διεύθυνση. Αν όμως γράψουμε κάτι που δεν ξεκινά με '/', όπως: "myDirectory/myFile", αυτό θα σήμαινε το αρχείο /home/alkis/programs/myDirectory/myFile. Αυτή θα ήταν μία σχετική διεύθυνση, θα «άρχιζε» δηλαδή από την θέση /home/alkis/programs, γιατί από αυτή τη θέση καλέσαμε το jvm.

Στα Windows η διαδικασία είναι η ίδια, με τη διαφορά ότι το απόλυτο μονοπάτι συμβολίζεται με τα γνωστά ονόματα για τους δίσκους, πχ C:\\, D:\\ κτλ.


1.4 Μέθοδοι της κλάσης Path
Ας δούμε μερικές από τις πιο χρήσιμες μεθόδους:
  • String toString(): η γνωστή μας μέθοδος που όλα τα αντικείμενα έχουν... Επιστρέφει ένα String με το μονοπάτι του αρχείου.
  • Path toAbsolutePath(): επιστρέφει το απόλυτο μονοπάτι του αρχείου.
  • int getNameCount(): Μας λέει από πόσα στοιχεία αποτελείται αυτό το Path. Στοιχείο ονομάζει κάθε κομμάτι κειμένου που χωρίζεται με τον χαρακτήρα αλλαγής φακέλου. Για παράδειγμα στο Path με όνομα "dir1/dir2/../dir3/myFile" η μέθοδος αυτή θα δώσει δώσει αποτέλεσμα 5.
  • Path getName(int index): Μας δίνει μόνο ένα από τα κομμάτια, όπως τα ορίσαμε παραπάνω.
  • Path getParent(): Επιστρέφει ένα Path με όνομα ίδιο με του Path μας, έχοντας αφαιρέσει το τελευταίο στοιχείο του. Αν το Path μας έχει μόνο ένα στοιχείο, η μέθοδος αυτή επιστρέφει null.
  • Path getRoot(): Μας δίνει τη «ρίζα» του συστήματος αρχείων. Στο Linux αυτή η μέθοδος θα επιστρέψει το Path με όνομα "/".
  • boolean isAbsolute(): Μας λέει αν το μονοπάτι μας είναι απόλυτο ή σχετικό.
Ένα σημαντικό στοιχείο που πρέπει να διευκρινίσουμε είναι ότι η getParent() δεν μας δίνει τον πραγματικό πατρικό φάκελο του αρχείου μας σε σχέση με την απόλυτη (πραγματική) διαδρομή του αρχείου, αλλά σε σχέση με το όνομα που δώσαμε στο Path μας.
Για παράδειγμα αν το path1 έχει όνομα "dir1/dir2/..", η path1.getParent() θα δώσει Path με όνομα "dir1/dir2" (έστω path2). Η ειρωνεία εδώ είναι ότι στην πραγματικότητα, το path1 είναι ο πατρικός φάκελος του path2 και όχι το αντίστροφο! Αν θέλετε τον πατρικό φάκελο με την έννοια του συστήματος αρχείων σας, χρησιμοποιείστε απόλυτες διαδρομές. Φυσικά η μέθοδος toAbsolutePath() είναι πάντα στη διάθεσή μας για να μας δώσει το απόλυτο μονοπάτι που ζητάμε.

Η getAbsolutePath μπορεί να ρίξει SecurityException αν κάποιος από τους φακέλους που συνιστούν την απόλυτη διαδρομή δεν μπορεί να διαβαστεί για λόγους ασφαλείας. Επίσης η getName(int) μπορεί να ρίξει IllegalArgumentException αν δώσουμε ως όρισμα «άκυρο» νούμερο. Όπως καταλαβαίνετε αυτό συμβαίνει αν δώσουμε αρνητικό και αριθμό μεγαλύτερο ή ίσο με το getNameCount().


1.5 Ένα παράδειγμα
Ας δούμε ένα πρόγραμμα που δέχεται ως όρισμα ένα κείμενο και μας δείχνει τη χρήση των παραπάνω κλάσεων και μεθόδων:
Μορφοποιημένος Κώδικας: Επιλογή όλων
//αρχείο PathMethods.java
import java.nio.file.*;

public class PathMethods {

public static void main(String arg[]) {


if (arg.length<1) { System.out.print("\nPathMethods: Παρακαλώ δώστε ως όρισμα το όνομα ενός αρχείου.\n\n"); return; }

Path p = FileSystems.getDefault().getPath( arg[0] );
int i = p.getNameCount();

System.out.print("p.toString():\t\t"+p.toString()+"\n");
System.out.print("p.toAbsolutePath():\t"+p.toAbsolutePath()+"\n");
System.out.print("p.getParent():\t\t"+p.getParent()+"\n");

try { System.out.print("p.getRoot():\t\t"+p.getRoot()+"\n"); }
catch (Exception ex) { ex.printStackTrace(); }

System.out.print("p.isAbsolute():\t\t"+p.isAbsolute()+"\n\n");

try { System.out.print("p.getNameCount():\t"+i+"\n"); }
catch (Exception ex) { ex.printStackTrace(); }

for (int j=0; j<i; ++j) System.out.print(" p.getName("+j+")\t\t"+p.getName(j)+"\n");

} //main

}
Μπορείτε να δώσετε ως όρισμα και μονοπάτια που δεν υπάρχουν!

Ενδεικτικά δίνω ένα output του προγράμματος:
Κώδικας: Επιλογή όλων
alkis@Alkis:~/Programs/java/Tutorial$ java PathMethods dir1/dir2/../dir3
p.toString():      dir1/dir2/../dir3
p.toAbsolutePath():   /home/alkis/Programs/java/Tutorial/dir1/dir2/../dir3
p.getParent():      dir1/dir2/..
p.getRoot():      null
p.isAbsolute():      false

p.getNameCount():   4
  p.getName(0)      dir1
  p.getName(1)      dir2
  p.getName(2)      ..
  p.getName(3)      dir3
alkis@Alkis:~/Programs/java/Tutorial$



2. Εργασίες με αρχεία
Ωραία! Τώρα κρατάμε ένα αντικείμενο Path στα χέρια μας. Και τι χρήσιμο μπορούμε να κάνουμε με αυτό; Ας ρίξουμε μια ματιά στην κλάση Files και στις static μεθόδους της και θα δούμε πολλά ωραία:

2.1 Έλεγχος ύπαρξης
  • static boolean exists(Path p)
Στη μέθοδο getPath μπορούμε να δώσουμε όποιο όρισμα θέλουμε. Δε σημαίνει ότι το αντίστοιχο αρχείο θα υπάρχει στο σύστημα αρχείων μας! Αν θέλουμε να το μάθουμε, καλούμε τη μέθοδο exists με όρισμα το Path:
Μορφοποιημένος Κώδικας: Επιλογή όλων
Path p1 = FileSystems.getDefault().getPath("myfile");
if ( Files.exists(p1) ) System.out.print("Αρχείο υπάρχει\n");
else System.out.print("Αρχείο δεν υπάρχει\n");

Να διευκρινήσουμε ότι η exists δεν «νοιώθει» το τελικό '/' που συμβολίζει τους φακέλους. Αν υπάρχει το αρχείο "myfile", η exists με όρισμα το fs.getPath("myfile/") θα δώσει true. Άλλωστε δεν μπορούμε να έχουμε αρχείο και φάκελο με το ίδιο όνομα και έτσι δεν υπάρχει περιθώριο σύγχυσης.


2.2 Δημιουργία
Με την ίδια λογική της exists δουλεύουν οι παρακάτω μέθοδοι:
  • static Path createFile(Path p)
  • static Path createDirectory(Path p)
  • static Path createDirectories(Path p)
Στην πραγματικότητα υπάρχουν και «εκδόσεις» των παραπάνω μεθόδων και με περισσότερα ορίσματα. Εδώ αναφέρουμε την πιο απλή τους μορφή.
Και οι τρεις μέθοδοι επιστρέφουν το αρχείο που δημιουργήθηκε. Και οι τρεις μέθοδοι ρίχνουν διάφορες Exceptions αν κάτι δεν πάει καλά, όπως FileAlreadyExistsException αν το αρχείο υπάρχει ήδη, SecurityException αν το λειτουργικό σύστημα αρνηθεί για κάποιο λόγο να δημιουργήσει το αρχείο ή το φάκελο, και NoSuchFileException αν πάμε να δημιουργήσουμε σε ανύπαρκτο φάκελο.

Η createFile δημιουργεί ένα άδειο αρχείο. Δε δημιουργεί φακέλους και δε δημιουργεί τους πατρικούς φακέλους του αρχείου αν αυτοί δεν υπάρχουν. Το μόνο που μπορεί να δημιουργήσει είναι ένα αρχείο σε ήδη υπαρκτό φάκελο.
Η createDirectory κάνει την ίδια δουλειά με την createFile, με τη διαφορά ότι δημιουργεί ένα φάκελο αντί για αρχείο. Δεν δημιουργεί τους πατρικούς φακέλους του αν αυτοί δεν υπάρχουν.
Η createDirectories προχωρά ένα βήμα παραπάνω, μπορεί να φτιάξει ένα φάκελο με όσους πατρικούς φακέλους δεν υπάρχουν.

Για παράδειγμα ας υποθέσουμε ότι το παρακάτω πρόγραμμα τρέχει από τον φάκελο dir1/ ο οποίος περιέχει μόνο το class αρχείο:
Μορφοποιημένος Κώδικας: Επιλογή όλων
FileSystem fs = FileSystems.getDefault();
Path p1 = fs.getPath("myNewFile"); //σχετική διεύθυνση
if (!Files.exists(p1)) Files.createFile(p1); //βάζουμε τη δομή if για να αποφύγουμε μία πιθανή FileAlreadyExistsException

//η παρακάτω εντολή δε θα εκτελεστεί, γιατί ο φάκελος myNewDir δεν υπάρχει. Μία NoSuchFileException θα ριχθεί.
Files.createFile( fs.getPath("myNewDir/myNewFile2") );

//Ομοίως και η παρακάτω.
Files.createDirectory( fs.getPath("myNewDir/myNewDir2") );

//Αντίθετα η createDirectories θα δημιουργήσει όλους τους απαραίτητους φακέλους:
Files.createDirectories( fs.getPath("myNewDir1/myNewDir2") );

//τώρα μπορούμε να δημιουργήσουμε αρχεία σε αυτούς τους φακέλους με την createFile!
Files.createFile ( fs.getParh("myNewDir1/myNewFile2") );

//και άλλους φακέλους με την createDirectory:
Files.createDirectory( fs.getParh("myNewDir1/myNewDir3") );

Αν θέλουμε να φτιάξουμε προγράμματα που να έχουν πρόσβαση στις πιο «ιδιαίτερες» τοποθεσίες του συστήματος αρχείων μας (χωρίς να μας αρνείται το λειτουργικό μας σύστημα) μπορούμε να τρέξουμε την εντολή java με sudo.
Ωστόσο σας προειδοποιώ: αν επιλέξετε να κάνετε κάτι τέτοιο, να είστε ΠΟΛΥ προσεκτικοί!



2.3 Διαγραφή
  • static void delete(Path p)
  • static boolean deleteIfExists(Path p)
Με αυτές τις μεθόδους διαγράφουμε αρχεία και άδειους φακέλους. Δεν μπορούμε να διαγράψουμε φάκελο που περιέχει αρχεία. Αν θέλουμε να κάνουμε κάτι τέτοιο διαγράφουμε πρώτα τα περιεχόμενα αρχεία και ύστερα τον ίδιο το φάκελο. Αν εμείς επιμένουμε με κάποια από αυτές τις μεθόδους να διαγράψουμε ένα φάκελο που δεν είναι άδειος, θα λάβουμε ως δώρο μία DirectoryNotEmptyException. Επίσης αν ο πυρήνας του λειτουργικού μας συστήματος αρνηθεί να διαγράψει το αρχείο, θα λάβουμε μία SecurityException.

Η εντολή delete προσπαθεί να διαγράψει το εν λόγω αρχείο. Αν το αρχείο υπάρχει και δωθεί η άδεια από τον πυρήνα, το αρχείο θα διαγραφεί. Αν το αρχείο δεν υπάρχει, η delete θα ρίξει μία NoSuchFileException.

Αντίθετα η deleteIfExists δεν ρίχνει ποτέ NoSuchFileException. Απλώς αν διαγράψει το αρχείο επιστρέφει true, και αν το αρχείο δεν υπάρχει επιστρέφει false. Και στις δύο περιπτώσεις που επιστρέφει χωρίς Exceptions, θα είμαστε σίγουροι ότι το αρχείο δεν υπάρχει πια.


2.4 Αντιγραφή
  • static Path copy(Path from, Path to)
Δημιουργεί αρχείο ή φάκελο που ορίζεται από το δεύτερο όρισμα, και αντιγράφει τα περιεχόμενα του πρώτου. Ρίχνει διάφορες Exceptions όπως FileAlreadyExistsException αν το αρχείο-στόχος υπάρχει ήδη, και την συνηθισμένη SecurityException αν το λειτουργικό μας σύστημα αρνηθεί να «κάνει τη δουλειά».
Επιστρέφει το Path του αρχείου-στόχου.


2.5 Μετονομασία-μετακίνηση
  • static Path move(Path from, Path to)
Στην ουσία αυτό που λέμε μετακίνηση και μετονομασία είναι η ίδια διαδικασία. Η μέθοδος αυτή επιστρέφει το Path του αρχείου-στόχου και ρίχνει περίπου τις ίδιες Exceptions με την copy.


2.6 Έλεγχος στοιχείων
  • static long size(Path p)
  • static boolean isDirectory(Path p)
  • static boolean isRegularFile(Path p)
  • static boolean isHidden(Path p)
  • static boolean isReadable(Path p)
  • static boolean isWritable(Path p)
  • static boolean isExecutable(Path p)
Η μέθοδος size επιστρέφει το μέγεθος του αρχείου σε bytes. Να διευκρινίσουμε ότι όταν λέμε μέγεθος αρχείου εννοούμε το μέγεθος των περιεχομένων του. Δεν περιλαμβάνονται σε αυτό οι πληροφορίες της περιγραφής του (τίτλος, δικαιώματα κτλ). Η μέθοδος μπορεί να ρίξει NoSuchFileException αν το αρχείο δεν υπάρχει, και SecurityException αν το λειτουργικό αρνηθεί να μας πει το μέγεθος του αρχείου.

Η isDerictory επιστρέφει true αν το εν λόγω Path είναι φάκελος. Αν δεν υπάρχει, ή αν είναι αρχείο επιστρέφει false. Μπορεί να ρίξει SecurityException.

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

Οι isReadable, isWritable και isExecutable αναφέρονται στο αν το jvm μπορεί να διαβάσει, να τροποποιήσει ή να εκτελέσει τα αρχεία. Οι μέθοδοι αυτές επιστρέφουν true αν τα αρχεία έχουν όντως την ιδιότητα, false αν δεν υπάρχουν ή αν δεν έχουν την ιδιότητα. Αναμένετε SecurityException σε περίπτωση που του λειτουργικό σας αρνηθεί την πρόσβαση.

Η siHidden λειτουργεί επίσης με τον ίδιο τρόπο και μας λέει αν το αρχείο ή ο φάκελος είναι κρυφά (στο linux αυτό σημαίνει αν αρχίζουν με τελεία).


2.7 Περιεχόμενα φακέλου
  • static DirectoryStream<Path> newDirectoryStream(Path dir)
Ξέρω ότι προς το παρόν αυτή η σύνταξη σας φαίνεται εντελώς καινούρια. Ένα παράξενο αντικείμενο με όνομα DirectoryStream, και κάτι τρίγωνες παρενθέσεις... Μα τι είναι όλα αυτά;;;

Λοιπόν, μια πλήρη εικόνα του τι είναι και το πως δουλεύουν θα πάρουμε στο κεφάλαιο 13. Προς το παρόν σκεφτείτε ότι το αντικείμενο DirectoryStream ανήκει στην ευρύτερη κατηγορία των Iterable. Ένα Iterable είναι κάτι σαν πίνακας. Ο τύπος μέσα στην τρίγωνη παρένθεση ορίζει το είδος των στοιχείων αυτού του παράξενου πίνακα.
Με λίγα λόγια το DirectoryStream<Path> είναι κάτι σαν πίνακας από Path, κάτι σαν Path[]. Από το αντικείμενο αυτό δεν μπορείτε να «πάρετε» ένα στοιχείο με το γνωστό τρόπο:
Μορφοποιημένος Κώδικας: Επιλογή όλων
Path some_directory = ...;
DirectoryStream<Path> list = Files.newDirectoryStream(some_directory);

Path p = list[3]; //Λάθος!! το αντικείμενο list μοιλαζει με πίνακα, αλλά δεν είναι ακριβώς πίνακας... Ο όρος list[3] είναι συντακτικό λάθος!

Αυτό που μπορείτε όμως να κάνετε είναι να χρησιμοποιήσετε την εντολή for όπως με ένα πίνακα. Για παράδειγμα η παρακάτω μέθοδος μας αναφέρει όλα τα περιεχόμενα ενός φακέλου:
Μορφοποιημένος Κώδικας: Επιλογή όλων
static void listEntries(Path pth) throws IOException {
DirectoryStream<Path> list = Files.newDirectoryStream( pth );
for (Path s: list) System.out.print(s+"\n");
list.close();
}

Αφού τελειώσουμε με το αντικείμενο DirectoryStream, οφείλουμε να καλέσουμε τη μέθοδο close(), όπως φαίνεται στο παραπάνω παράδειγμα. Την υποχρέωση να «κλείνουμε» αντικείμενα θα την συναντήσουμε πολλές φορές όταν δουλεύουμε με αρχεία (και όχι μόνο). Καλό είναι λοιπόν να συνηθίζετε να θυμάστε τέτοιες λεπτομέρειες!

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


2.8 Ένα ενδεικτικό πρόγραμμα
Ας δούμε λοιπόν στην πράξη όλα όσα μάθαμε παραπάνω! Το πρόγραμμα αυτό θα είναι κάτι σαν τερματικό: θα του δίνεται εντολές με όρισμα κάποιο αρχείο θα αυτό θα κάνει διάφορες δουλειές με αυτό. Μην τρομάξετε από το μέγεθός του, περιέχει μία μέθοδο που ρωτά τον χρήστη αν είναι σίγουρος για την ενέργειά του, μία για να εκτελέσει την κατάλληλη εντολή, και την main που απλά διαβάζει την εντολή και τα ορίσματά της. Καθώς θα παίζετε με το πρόγραμμα θα δείτε διάφορα αρχεία να δημιουργούνται ή να σβήνονται στο σύστημα αρχείων σας, ανάλογα με το τι του λέτε να κάνει. Καλή διασκέδαση και... προσοχή!
Μορφοποιημένος Κώδικας: Επιλογή όλων
//αρχείο FilesMethods.java
import java.nio.file.*;

public class FilesMethods {

private static final FileSystem fs = FileSystems.getDefault();
private static final Path start = fs.getPath(".").toAbsolutePath();

private static final java.util.Scanner sc = new java.util.Scanner(System.in);
private static String[] line, command = new String[]
{ "exists", "createFile", "createDirectory", "createDirectories", "delete", "deleteIfExists", "copy", "move",
"size", "isDirectory", "isRegularFile", "isHidden", "isReadable", "isWritable", "isExecutable", "listDir" };



private static boolean isSure() {
System.out.print("Είστε βέβαιος για αυτό που πάτε να κάνετε; [ναι/όχι]\t\t");
String str = sc.nextLine();

return str.equals("ναι");
} //isSure


private static void executeCommand(int num) {
/*μία μεγάλη switch που εκτελεί τη σωστή εντολή.*/

boolean b=false;
switch (num) {
case 0:
if (line.length<2) { System.out.print("Η exists απαιτεί ένα όρισμα!"); return; }

try { b = Files.exists( fs.getPath(line[1]) ); }
catch (Exception ex) { System.out.print( "Πρόβλημα: "+ex.toString() ); return; }

if (b) System.out.print("Υπάρχει");
else System.out.print("Δεν υπάρχει");
return;

case 1:
if (line.length<2) { System.out.print("Η createFile απαιτεί ένα όρισμα!"); return; }
try { Files.createFile( fs.getPath(line[1]) ); }
catch (Exception ex) { System.out.print( "Πρόβλημα: "+ex.toString() ); }
return;

case 2:
if (line.length<2) { System.out.print("Η createDirectory απαιτεί ένα όρισμα!"); return; }
try { Files.createDirectory( fs.getPath(line[1]) ); }
catch (Exception ex) { System.out.print( "Πρόβλημα: "+ex.toString() ); }
return;

case 3:
if (line.length<2) { System.out.print("Η createDirectories απαιτεί ένα όρισμα!"); return; }
try { Files.createDirectories( fs.getPath(line[1]) ); }
catch (Exception ex) { System.out.print( "Πρόβλημα: "+ex.toString() ); }
return;

case 4:
if (line.length<2) { System.out.print("Η delete απαιτεί ένα όρισμα!"); return; }
if ( !isSure() ) { System.out.print("Η διαγραφή ματαιώθηκε."); return; }

try { Files.delete( fs.getPath(line[1]) ); System.out.print("Διεγράφη!"); }
catch (Exception ex) { System.out.print( "Πρόβλημα: "+ex.toString() ); }
return;

case 5:
if (line.length<2) { System.out.print("Η deleteIfExists απαιτεί ένα όρισμα!"); return; }
if ( !isSure() ) { System.out.print("Η διαγραφή ματαιώθηκε."); return; }

try { b=Files.deleteIfExists( fs.getPath(line[1]) ); System.out.print("Διεγράφη!"); }
catch (Exception ex) { System.out.print( "Πρόβλημα: "+ex.toString() ); }

if (b) System.out.print("Διεγράφη");
else System.out.print("Το αρχείο δεν εντοπίστηκε.");
return;

case 6:
if (line.length<3) { System.out.print("Η copy απαιτεί δύο ορίσματα!"); return; }
try { Files.copy( fs.getPath(line[1]), fs.getPath(line[2]) ); }
catch (Exception ex) { System.out.print( "Πρόβλημα: "+ex.toString() ); }
return;

case 7:
if (line.length<3) { System.out.print("Η move απαιτεί δύο ορίσματα!"); return; }
try { Files.move( fs.getPath(line[1]), fs.getPath(line[2]) ); }
catch (Exception ex) { System.out.print( "Πρόβλημα: "+ex.toString() ); }
return;

case 8:
if (line.length<2) { System.out.print("Η size απαιτεί ένα ορίσματα!"); return; }
try { System.out.print("Το αρχείο είναι "+Files.size(fs.getPath(line[1]))+" bytes."); }
catch (Exception ex) { System.out.print( "Πρόβλημα: "+ex.toString() ); }
return;

case 9:
if (line.length<2) { System.out.print("Η isDirectory απαιτεί ένα ορίσματα!"); return; }
try { b = Files.isDirectory( fs.getPath(line[1]) ); }
catch (Exception ex) { System.out.print( "Πρόβλημα: "+ex.toString() ); return; }

if (b) System.out.print("Κατάλογος");
else System.out.print("Αρχείο");
return;

case 10:
if (line.length<2) { System.out.print("Η isRegularFile απαιτεί ένα ορίσματα!"); return; }
try { b = Files.isRegularFile( fs.getPath(line[1]) ); }
catch (Exception ex) { System.out.print( "Πρόβλημα: "+ex.toString() ); return; }

if (b) System.out.print("Αρχείο");
else System.out.print("Κατάλογος");
return;

case 11:
if (line.length<2) { System.out.print("Η isHidden απαιτεί ένα ορίσματα!"); return; }
try { b = Files.isHidden( fs.getPath(line[1]) ); }
catch (Exception ex) { System.out.print( "Πρόβλημα: "+ex.toString() ); return; }

if (b) System.out.print("Κρυφό");
else System.out.print("Φανερό");
return;

case 12:
if (line.length<2) { System.out.print("Η isReadable απαιτεί ένα ορίσματα!"); return; }
try { b = Files.isReadable( fs.getPath(line[1]) ); }
catch (Exception ex) { System.out.print( "Πρόβλημα: "+ex.toString() ); return; }

if (b) System.out.print("Αναγνώσιμο");
else System.out.print("Μη αναγνώσιμο");
return;

case 13:
if (line.length<2) { System.out.print("Η isWritable απαιτεί ένα ορίσματα!"); return; }
try { b = Files.isWritable( fs.getPath(line[1]) ); }
catch (Exception ex) { System.out.print( "Πρόβλημα: "+ex.toString() ); return; }

if (b) System.out.print("Εγγράψιμο");
else System.out.print("Μη εγγράψιμο");
return;

case 14:
if (line.length<2) { System.out.print("Η isExecutable απαιτεί ένα ορίσματα!"); return; }
try { b = Files.isExecutable( fs.getPath(line[1]) ); }
catch (Exception ex) { System.out.print( "Πρόβλημα: "+ex.toString() ); return; }

if (b) System.out.print("Εκτελέσιμο");
else System.out.print("Μη εκτελέσιμο");
return;

case 15:
if (line.length<2) { System.out.print("Η listDir απαιτεί ένα ορίσματα!"); return; }

DirectoryStream<Path> d;
try { d = Files.newDirectoryStream( fs.getPath(line[1]) ); }
catch (Exception ex) { System.out.print( "Πρόβλημα: "+ex.toString() ); return; }

System.out.print("Παριεχόμενα φακέλου "+fs.getPath(line[1]).toAbsolutePath()+":\n");
for (Path pp: d) System.out.print("\t"+pp+"\n");
return;

default:
return;
} //switch
} //executeCommand


public static void main(String args[]) {
int i;

//Μήνυμα προς το χρήστη
System.out.print("\n==========Καλώς ορίσατε!!==========\nΒρίσκεστε στον φάκελο: "+start);
System.out.print("\n\nΕπιτρεπόμενες εντολές:\n");
for (i=0; i<command.length; ++i) System.out.print("\t"+command[i]+"\n");
System.out.print("\tq για έξοδο\n");


A: for (;;) {
System.out.print(">");

line = sc.nextLine().split(" "); //πίνακας με λέξεις...
if ( line[0].isEmpty() ) continue;

for (i=0; i<command.length; ++i)
if (line[0].equals(command[i])) { executeCommand(i); System.out.print("\n"); continue A; }

if (line[0].equals("q")) break;

System.out.print("Η εντολή "+line[0]+" δεν αναγνωρίζεται...\n");
} //for


} //main

}
Σημείωση: όλες οι εντολές που δέχεται το πρόγραμμα είναι τα απ' ευθείας ονόματα των μεθόδων της Files, εκτός από την listDir και την q.


3. Ανάγνωση και εγγραφή
Ωραία! Μέχρι τώρα είδαμε σχεδόν τα πάντα για τα αρχεία... εκτός από το πως να τα διαβάζουμε και πως να τα γράφουμε!
Ναι, ναι, ξέρω, αυτή είναι η πιο σημαντική δουλειά. Για διδακτικούς λόγους όμως έπρεπε να την αφήσω για το τέλος. Ετοιμαστείτε λοιπόν γιατί το θέμα είναι απαιτητικό!

Κατ αρχήν να ξεκαθαρίσουμε ότι υπάρχουν δύο βασικά αντικείμενα στην Java που κάνουν όλο το «πάρε-δώσε» με τα αρχεία. Αυτά είναι τα InputStream και OutputStream, και τα δύο μέλη του πακέτου java.io (προσοχή, όχι nio). Οποιοδήποτε άλλο αντικείμενο συναντήσουμε να διαβάζει και να γράφει αρχεία, σημαίνει ότι το κάνει μέσω αυτών των δύο αντικειμένων.


3.1 Τι διαβάζουμε και τι γράφουμε
Κάθε φορά που διαβάζουμε τα περιεχόμενα ενός αρχείου, στην πραγματικότητα διαβάζουμε μία σειρά από 0 και 1. Αυτά μπορεί να αναπαριστούν αριθμούς, χαρακτήρες, ακόμα και σύνθετες δομές. Ή για να το πούμε αλλιώς, δεν υπάρχει τίποτα που να διαχωρίζει ότι «αυτό είναι αριθμός, αυτό είναι χαρακτήρας κειμένου». Από πλευράς ποιότητας δεν υπάρχει καμία διαφορά ανάμεσα σε ένα αρχείο κειμένου, ένα βίντεο και ένα εκτελέσιμο πρόγραμμα. Το μόνο που υπάρχει είναι μία θάλασσα από 0 και 1. Τη θάλασσα αυτή τη διαβάζουμε όπως καταλαβαίνουμε.
Το ίδιο το InputStream όμως δεν «καταλαβαίνει» τίποτα, απλώς διαβάζει.

Ομοίως το OutputStream όταν γράφει σε αρχείο, γράφει μία σειρά από 0 και 1. Ο πιο θεμελιώδης τρόπος που αυτά τα 0 και 1 ομαδοποιούνται είναι το byte. Ακριβώς για αυτό το λόγο τα αντικείμενα αυτά γράφουν και διαβάζουν πίνακες από bytes!


3.2 Αποκωδικοποιώντας τα bytes που διαβάσαμε
Πολλές φορές τα bytes που διαβάσαμε από ένα αρχείο από μόνα τους δε μας λένε τίποτα! Ας δούμε το ακόλουθο παράδειγμα:

Αποθηκεύσατε σε ένα αρχείο δύο int, τους 10000 και 15000. Σε δεκαεξαδική μορφή αυτοί είναι 0x00002710 και 0x00003A98, και σε δυαδική οι 00000000_00000000_00100111_00010000 και 00000000_00000000_00111010_10011000 (ούφ!). Το αρχείο λοιπόν θα γράφει τα εξής (σε δεκαεξαδική):
Κώδικας: Επιλογή όλων
0000271000003A98

Τώρα εσείς πάτε να το διαβάσετε. Τι θα διαβάστε; Φυσικά ένα πίνακα από bytes. Δηλαδή τον πίνακα { 00, 00, 27, 10, 00, 00, 3A, 98} σε δεκαεξαδική ή σε δεκαδική τον { 0, 0, 39, 16, 0, 0, 58, -104 }.

Όπως βλέπετε, αν πάρουμε τα bytes «κατά λέξη», παίρνουμε μία πληροφορία που ουδεμία σχέση έχει με την πραγματική πληροφορία του αρχείου, που ήταν οι αριθμοί 10000 και 15000. Καταλαβαίνετε τώρα τι σημαίνει «αποκωδικοποίηση»;

Στην περίπτωση των int τα πράγματα είναι εύκολα, γιατί ξέρουμε ότι «4 byte αντιστοιχούν σε 1 int» και τέλος.

Η παραπάνω εργασία γίνεται όμως πολύ πιο δύσκολη σε πιο σύνθετες δομές από int. Ας μην πάμε μακρυά: το να θέλουμε να μετατρέψουμε αυτά τα bytes σε κείμενο. Εκεί πρέπει να ξέρουμε πως να μετατρέψουμε τα bytes που διαβάσαμε σε UTF-16 που χρησιμοποιεί η Java.
Όμως μια στιγμή! Αυτά τα bytes σε τι κωδικοποίηση γράφτηκαν από αυτόν που γράφτηκαν; Ας πούμε ότι μας λένε ότι το αρχείο που διαβάζουμε γράφτηκε σε UTF-8 (το σύνηθες στο linux).
Πως εγώ θα μετατρέψω έναν πίνακα από byte (γραμένα σε UTF-8) σε UTF-16 για να κάνω τη δουλειά μου; Πρέπει να μάθω απ' έξω τους μηχανισμούς της κάθε κωδικοποίησης χαρακτήρων;

Σκέτος πονοκέφαλος! Μήπως να τα παρατήσουμε;
Φυσικά και όχι! Γιατί μέχρι εδώ ήταν τα κακά νέα.

Τα καλά είναι ότι πριν από εμάς, άλλοι έκατσαν και υπέστησαν όλο αυτό τον πονοκέφαλο. Έφτιαξαν λοιπόν ένα κάρο αντικείμενα που κάνουν όλες τις παραπάνω δουλειές! Αντικείμενα που μετατρέπουν τα bytes που διαβάζουν σε short, int, long, αντικείμενα που διαβάζουν ένα χαρακτήρα UTF-8 (ή άλλες γνωστές κωδικοποιήσεις) και επιστρέφουν το αντίστοιχο char που αναγνωρίζει η Java, και διάφορα άλλα. Όλα αυτά τα αντικείμενα χρησιμοποιούν τα InputStream και OutputStream για να κάνουν αυτές τις δουλειές.

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

Εγώ προσωπικά χρησιμοποιώ τον jeex. Αν θέλετε να τον εγκαταστήσετε απλά πατήστε:
Κώδικας: Επιλογή όλων
sudo apt-get install jeex


Αν σας ενδιαφέρουν μόνο εργασίες με κείμενα, παραλείψτε τις παραγράφους 3.3 και 3.4



3.3 Διαβάζοντας δεδομένα, τρόπος 1
Πριν πάμε όμως στα βαθειά νερά και πριν αγγίξουμε απ' ευθείας τα InputStream και OutputStream, ας αρχίσουμε από μία μέθοδο της κλάσης Files που μας δίνει κατ' ευθείαν τα περιεχόμενα του αρχείου, σαν ένα πίνακα από bytes. Σκοπός μας είναι προς το παρόν να διαβάσουμε αυτά τα bytes. Το τι σημαίνουν το αφήνουμε για μετά. Η μέθοδος αυτή είναι η:
static byte[] readAllBytes(Path p)

Όπως μπορείτε να φανταστείτε, η μέθοδος αυτή δημιουργεί έναν πίνακα που περιέχει τα bytes του αρχείου, και επιστρέφει τον πίνακα αυτό. Να είστε προσεκτικοί: αν ανοίξετε κανένα τεράστιο αρχείο με αυτή τη μέθοδο, εκτός από το ότι το πρόγραμμά σας θα καθυστερήσει, υπάρχει κίνδυνος να «ξεμείνετε» από μνήμη! Γνωρίζετε φυσικά ότι το αρχείο βρίσκεται στον σκληρό δίσκο, και όταν το διαβάζουμε αντιγράφεται στη RAM. Το πρόβλημα είναι όμως ότι η RAM έχει πολύ μικρότερη χωρητικότητα από το σκληρό μας δίσκο, άρα καλό είναι να προσέχουμε τι φορτώνουμε εκεί.

Αν ξεμείνετε από μνήμη, το jvm θα ρίξει ένα OutOfMemoryError. Θα ρίξει επίσης μία SecurityException αν το αρχείο δεν επιτρέπεται να διαβαστεί, NoSuchFileException αν το αρχείο δεν υπάρχει και IOException αν δώσουμε ως όρισμα ένα φάκελο.

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


3.4 Γράφοντας δεδομένα, τρόπος 1
Τώρα θα γνωρίσουμε το αδερφάκι της readAllBytes, που είναι η write:
static Path write(Path p, byte[] data, OpenOption... options)

Η μέθοδος αυτή γράφει τον πίνακα data στο αρχείο p. Το τρίτο όρισμα είναι αυθαίρετου πλήθους όπως είδαμε στο κεφάλαιο για τους πίνακες. Στην πιο απλή περίπτωση μπορεί να παραληφθεί εντελώς και να καλέσουμε τη μέθοδο αυτή με δύο ορίσματα. Αν επιλέξουμε αυτό, η μέθοδος θα διαγράψει ό,τι υπήρχε πριν στο αρχείο και μετά θα γράψει. Αν αντίθετα θέλουμε απλά να προσθέσει τα bytes στο τέλος του αρχείου, χωρίς να διαγράψει αυτό που ήδη υπήρχε, προσθέτουμε το εξής όρισμα:
Μορφοποιημένος Κώδικας: Επιλογή όλων
Path p = ...;
byte data[] = ...;
Files.write(p, data, StandardOpenOption.APPEND);

Το τελευταίο όρισμα μπορείτε να το θυμάστε και να το χρησιμοποιείτε όταν μία μέθοδος θέλει όρισμα OpenOption, και εσείς θέλετε να προσθέσετε κάτι στο τέλος του αρχείου. Για όποιον θέλει να ψάξει παραπάνω, υπάρχουν και άλλες επιλογές πέραν της APPEND εδώ.

Το αρχείο θα δημιουργηθεί αν δεν υπάρχει ήδη. Αν όμως το όρισμα StandardOpenOption.APPEND είναι παρόν, τότε το αρχείο πρέπει να υπάρχει.
Όπως και η readAllBytes, έχει το ελάττωμα ότι μπορείτε να γράψετε μόνο μικρές ποσότητες δεδομένων σε αρχείο.


3.5 Διαβάζοντας αρχείο ως κείμενο, τρόπος 1
Εδώ έχουμε το τρίτο μέλος της οικογένειας! Είναι η μέθοδος readAllLines. Όπως καταλαβαίνετε, αυτή η μέθοδος θα επιστρέφει ένα πίνακα με String, το κάθε ένα από τα οποία θα περιέχει μία γραμμή του κειμένου. Όλο τον πονοκέφαλο της μετατροπής των byte σε κείμενο τον αναλαμβάνει αυτή η μέθοδος για εμάς! Εμείς παίρνουμε απλώς το ωραίο μας κείμενο.
Στην πραγματικότητα τα πράγματα είναι λιγάκι πιο δύσκολα... Κατ αρχήν γιατί στη μέθοδο αυτή πρέπει να πούμε δύο πράγματα: ποιο αρχείο να διαβάσει και με ποια κωδικοποίηση χαρακτήρων! Επίσης ο τύπος που επιστρέφει δεν είναι πίνακας με String αλλά... ένα από αυτά τα παράξενα αντικείμενα που μοιάζουν με πίνακα. Συγκεκριμένα επιστρέφει ένα List<String>, αντικείμενο του πακέτου java.util (όπως και το Scanner). Περισσότερα γι αυτά θα πούμε στο κεφάλαιο 13. Προς το παρόν ας δούμε την περιγραφή και τη χρήση της μεθόδου:
static List<String> readAllLines(Path p, Charset set)

Το πως να δώσουμε το κατάλληλο Path νομίζω το έχουμε καταλάβει. Το δεύτερο όρισμα όμως της μεθόδου είναι ένα αντικείμενο Charset, το οποίο αντιπροσωπεύει την κωδικοποίηση χαρακτήρων που θα χρησιμοποιήσουμε. Σημειώστε ότι η κλάση αυτή βρίσκεται στο πακέτο java.nio.charset.

Η συνηθέστερη επιλογή είναι η προεπιλεγμένη κωδικοποίηση του συστήματος, την οποία παίρνουμε με τη μέθοδο Charset.defaultCharset(). Για παράδειγμα στο Linux θα πάρουμε το UTF-8. Επίσης με τη μέθοδο Charset.forName(String name) μπορούμε να πάρουμε ένα άλλο από τα υπάρχοντα Charset (αρκεί να δώσουμε ένα όνομα που να υπάρχει).

Ας μιλήσουμε λίγο για το τι μπορούμε να κάνουμε με αυτό το List.
Βασικά έχει τρεις πολύ ωραίες μεθόδους:
  • String get(int index)
  • void set(int index, String value) και
  • int size()
Η πρώτη μας δίνει το στοιχείο υπ' αριθμόν index (πρώτο στοιχείο είναι το 0), η δεύτερη κάνει το στοιχείο index ίσο με value και η τρίτη μας λέει πόσα στοιχεία περιέχει η λίστα. Ας δούμε μία μέθοδο που τυπώνει στην οθόνη όλες τις γραμμές ενός αρχείου κειμένου:
Μορφοποιημένος Κώδικας: Επιλογή όλων
static void readTextFile(Path p, Charset set) throws IOException {
List<String> lst = Files.readAllLines(p, set);
int i=0, j=lst.size();

for (; i<j; ++i) System.out.print("Γραμμή " + i+":\t" + lst.get(i) + "\n");
}


//και θα την καλούσαμε κάπως έτσι (μέσα σε κάποιο try block):
Path p = ...;
readTextFile(p, Charset.defaultCharset() );

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


3.6 Γράφοντας κείμενο σε αρχείο, τρόπος 1
Το τέταρτο και τελευταίο μέλος της μικρής οικογένειας μεθόδων είναι και πάλι η write... με άλλα ορίσματα:
static Path write(Path p, Iterable<String> lines, Charset set, OpenOption... options)

Όπως καταλαβαίνετε αντί για byte[] έχουμε ένα αντικείμενο που μοιάζει με πίνακα, το Iterable. Το Iterable είναι πολύ γενικό. Όλα τα αντικείμενα αυτής της παράξενης κατηγορίας είναι Iterable. Τόσο το List όσο και το DirectoryStream που είδαμε πριν είναι Iterable. Μπορούμε λοιπόν να βάλουμε ως όρισμα οποιοδήποτε από αυτά. Το επιπλέον όρισμα Charset παίζει τον ίδιο ρόλο που έπαιζε και στην readAllLines.

Αν τώρα έχουμε ένα πίνακα από String (αληθινό πίνακα) και θέλουμε να τον μετατρέψουμε σε Iterable (πχ List) απλά γράφουμε:
Μορφοποιημένος Κώδικας: Επιλογή όλων
String[] array = ...;
List<String> lst = Arrays.asList(array);

Files.write( some_path, lst, Charset.defaultCharset() );

Το τελευταίο όρισμα OpenOption παίζει ακριβώς τον ίδιο ρόλο με την προηγούμενη έκδοση της write (παράγραφος 3.4). Επίσης οι Exceptions που ρίχνει αυτή η μορφή της write είναι οι ίδιες με αυτήν της παραγράφου 3.4.

Με τις 4 προηγούμενες μεθόδους θα πρέπει να είστε σε θέση να γράφετε και να διαβάζετε bytes και κείμενο από μικρά αρχεία.
Στις επόμενες παραγράφους θα μπούμε σε πιο βαθειά νερά, αγγίζοντας απ' ευθείας (ή σχεδόν απ' ευθείας) τα αντικείμενα InputStream και OutputStream.



3.7 Διαβάζοντας δεδομένα, τρόπος 2: το InputStream
Αυτός ο τρόπος είναι στην πραγματικότητα ο βασικότερος. Θα γνωριστούμε με το αντικείμενο InputStream. Σας συνιστώ να ρίξετε μία ματιά στην περιγραφή του εδώ για να δείτε τις λίγες αλλά καλές μεθόδους του. Αυτό είναι το αντικείμενο που επικοινωνεί με το λειτουργικό μας σύστημα. Το μόνο που ξέρει να διαβάζει είναι bytes.

Το πρώτο πράγμα που θέλουμε να κάνουμε είναι να δημιουργήσουμε ένα InputStream που θα μας λέει τα byte που είναι γραμμένα σε κάποιο αρχείο. Αυτό το κάνουμε με τη μέθοδο της κλάσης Files:
static InputStream newInputStream(Path p)

Αυτή η μέθοδος μας επιστρέφει το InputStream με το οποίο θα δουλέψουμε. Βέβαια παραφυλάνε διάφορες Exceptions όπως στην περίπτωση που το λειτουργικό αρνηθεί την πρόσβαση κτλ.

Από τη στιγμή που κρατάμε το InputStream στα χέρια μας χρησιμοποιούμε τις παρακάτω μεθόδους για να διαβάσουμε το αρχείο:
  • int read()
  • int read(byte[] array)
  • int read(byte[] array, int offset, int length)
  • long skip(long bytes)
Ας αρχίσουμε από την read(), η οποία διαβάζει το επόμενο byte από το αρχείο. Επιστρέφει ωστόσο έναν int. Ο λόγος για κάτι τέτοιο είναι ότι χρειαζόμαστε έξτρα πληροφορία για να συμβολίσουμε το τέλος του αρχείου. Και η πληροφορία αυτή είναι το -1. Αν λοιπόν δεν υπάρχει άλλο byte να διαβαστεί, η read θα επιστρέψει -1, αλλιώς θα επιστρέψει την τιμή του byte, δηλαδή ένα ακέραιο μεταξύ 0 και 255.
Μία συνήθης τακτική που χρησιμοποιούμε είναι η εξής:
Μορφοποιημένος Κώδικας: Επιλογή όλων
int i;
byte b;
InputStream in = ...;
i = in.read();
if (i!=-1) b = (byte)i;
else //αρχείο τέλειωσε...

Η read(byte[] array) προσπαθεί να διαβάσει τόσα επόμενα bytes, όσα χωράει ο πίνακας array και να τα γράψει σε αυτόν. Δεν δημιουργεί νέο πίνακα, απλώς γράφει στον ήδη υπάρχων. Αν το αρχείο έχει τελειώσει πριν καλέσουμε τη μέθοδο αυτή, θα επιστρέψει -1 και ο πίνακας array θα μείνει ανέπαφος. Σε διαφορετική περίπτωση θα αρχίσει να γράφει τα bytes στον array, από τη θέση 0 και ανεβαίνοντας. Αν το αρχείο τελειώσει πριν προλάβει να γεμίσει ο array, τότε αυτός θα μείνει «μισογεμάτος» με την έννοια ότι οι ανώτερες θέσεις του, που δεν πρόλαβαν να γραφούν ακόμα, θα μείνουν ανέπαφες. Αλλιώς θα γραφεί ολόκληρος. Σε κάθε περίπτωση η μέθοδος θα επιστρέψει πόσα bytes διάβασε. Όπως καταλαβαίνετε, αν επιστρέψει το μήκος του πίνακα array σημαίνει ότι τον γέμισε, αν επιστρέψει κάτι μικρότερο (έστω i) θα ξέρουμε ότι η «νέα πληροφορία» βρίσκεται μέχρι τη θέση (i-1) του πίνακά μας.

Μία παραλλαγή της read(byte[]) είναι η read(byte[] array, int offset, int len). Διαβάζει ακριβώς τα ίδια bytes που θα διάβαζε και η read(byte[]), αλλά τα γράφει σε λίγο διαφορετικό μέρος. Δεν αρχίζει από τη θέση 0 του πίνακα, αλλά από τη θέση offset. Επίσης δεν γράφει τόσα bytes όσα για να γεμίσει τον πίνακα, αλλά μόνο len. Όπως πριν, επιστρέφει κατά τον ίδιο τρόπο είτε -1, είτε πόσα bytes τελικά διάβασε. Αν η τιμή επιστροφής είναι μικρότερη από το len καταλαβαίνουμε ότι το αρχείο τελείωσε. Η μέθοδος αυτή μας είναι πολύ χρήσιμη όταν δε θέλουμε να «χαλάσουμε» ολόκληρο τον πίνακα array, και να γράψουμε τη νέα πληροφορία σε συγκεκριμένη θέση του. Φυσικά, ο υπόλοιπος πίνακας μένει ανέπαφος.

Η skip(long l) απλά «παραβλέπει» τα l επόμενα bytes. Όπως καταλαβαίνετε εμείς μπορεί να γράψουμε skip(1000000), ενώ στο αρχείο μας απομένουν μόνο 1000 bytes. Σε μία τέτοια περίπτωση αυτό που θα επιστρέψει η skip είναι το πόσα bytes τελικά παρέβλεψε (εν προκειμένω 1000).

Οι μέθοδοι αυτές μπορούν να ρίξουν αρκετές Exceptions, κυρίως IOException αν κάτι δεν πάει καλά στην επικοινωνία με το λειτουργικό σύστημα, NullPointerException αν δώσουμε για πίνακα κενό αντικείμενο, και IndexOutOfBoundsException αν στην τρίτη εκδοχή της read δώσουμε τέτοια offset, len που να παραβιάζουν τα όρια του πίνακα.

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



3.8 Γράφοντας δεδομένα, τρόπος 2: το OutputStream
Ήρθε η ώρα να γνωρίσουμε το αδερφάκι του InputStream. Το OutputStream έχει ιδιότητες ίδιες στη λογική τους με το InputStream. Είναι το αντικείμενο που μιλά απ' ευθείας στο λειτουργικό μας σύστημα, και το μόνο που ξέρει να γράφει είναι bytes.

Ο τρόπος που δημιουργούμε ένα OutputStream είναι σαν αυτόν του InputStream, καλώντας λίγο διαφορετική μέθοδο της κλάσης Files, την:
static OutputStream newOutputStream(Path p, OpenOption... options)
η οποία μπορεί να ρίξει τις ίδιες Exceptions όπως η newInputStream

Και στη μέθοδο αυτή, μπορούμε να παραλείψουμε το τελευταίο όρισμα, ή να δώσουμε το StandardOpenOption.APPEND, αν θέλουμε τα δεδομένα να προστεθούν στο τέλος του αρχείου. Κάτι τέτοιο θα έχει ακριβώς τα ίδια αποτελέσματα όπως και με την Files.write.

Για να γράψουμε στο αρχείο χρησιμοποιούμε τις εξής μεθόδους του OutputStream:
  • void write(int b)
  • void write(byte[] array)
  • void write(byte[] array, int offset, int length)
Η πρώτη γράφει ένα byte, τα χαμηλότερα 8 bit του ορίσματος. Η δεύτερη γράφει έναν ολόκληρο πίνακα από bytes και η τρίτη ένα μέρος του πίνακα, με την ίδια λογική των μεθόδων read του InputStream.

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



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

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

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

Την ίδια εικόνα έχουμε όταν έχουμε να κάνουμε με το λειτουργικό μας και το σκληρό δίσκο. Κάθε φορά που καλούμε μία μέθοδο read ή write των InputStream και OutputStream, περιμένουμε στην «ουρά» των εργασιών του λειτουργικού συστήματος. Καταλαβαίνετε πόσο προτιμότερο είναι να φτιάξουμε ένα πίνακα με 1000 bytes και να καλέσουμε μία φορά την read(byte[]), από το να καλέσουμε 1000 φορές την read(). Ακόμα και αν προς το παρόν δε χρειαζόμαστε και τα 1000 bytes, είναι καλό να τα έχουμε στη RAM μας, και μετά να τα διαβάζουμε κατ' ευθείαν από τον πίνακα αντί από το σκληρό δίσκο ξανά και ξανά. Αν εξαντλήσουμε τα 1000 bytes και χρειαζόμαστε και άλλα, ξανακαλούμε την read(byte[]).
Ομοίως και για την εγγραφή σε αρχείο. Είναι προτιμότερο να γράφουμε αυτά που θέλουμε σε ένα byte[], και μόνο όταν αυτό γεμίσει να καλούμε την write(byte[]). Μετά ξαναγράφουμε στον ίδιο πίνακα και όταν γεμίσει ξανακαλούμε την write(byte[]) ή την write(byte[],int,int) κ.ο.κ.

Ένας τέτοιος πίνακας που γεμίζει με δεδομένα (είτε που διαβάσαμε είτε που ετοιμαζόμαστε να γράψουμε στον σκληρό δίσκο) λέγεται buffer. Να σας πω την αλήθεια, για πολύ καιρό με τρόμαζε αυτή η λέξη όταν τη συναντούσα σε διάφορα άρθρα γιατί δεν είχα καταλάβει ακριβώς το νόημά της. Στην ουσία δεν είναι τίποτα άλλο παρά ένας πίνακας από bytes. Bytes που βάζουμε προσωρινά εκεί μέχρι να γραφούν «με μιας» στον τελικό προορισμό τους, ή επειδή διαβάστηκαν «με μιάς» από κάπου και περιμένουν να ξαναχρησιμοποιηθούν.

Το μέγεθος του buffer δεν πρέπει να είναι ούτε πολύ μικρό (για να μη γεμίζει κάθε λίγο και λιγάκι), αλλά ούτε πολύ μεγάλο, για να μην πιάσει ολόκληρη τη RAM μας. Βέβαια το μεγάλο και το μικρό είναι σχετικά με το αρχείο που θέλουμε να διαβάσουμε ή να γράψουμε. Για παράδειγμα αν το αρχείο που σκοπεύουμε να γράψουμε είναι περί τα 20-30kB, τότε ένα buffer 1kB είναι υπέρ-αρκετό: θα καλέσουμε την write περίπου 30 φορές. Αν όμως θέλαμε να αντιγράψουμε ένα αρχείο 1GB, τότε θα χρειαζόμασταν ένα buffer της τάξεως των 10MB ώστε να καλέσουμε την read 100 φορές, και άλλες τόσες την write.

Η Java μας δίνει δύο αντικείμενα που επεκτείνουν τα InputStream και OutputStream, και τα οποία έχουν το δικό τους buffer. Αυτά είναι τα BufferedInputStream και BufferedOutputStream.

Όπως θα δείτε στη σελίδα περιγραφής τους, τα αντικείμενα αυτά δημιουργούνται από ήδη υπάρχοντα InputStream και OutputStream. Ένας δεύτερος constructor μας δίνει τη δυνατότητα να ορίσουμε το μέγεθος του buffer. Από τη στιγμή που θα δημιουργήσουμε ένα από αυτά τα αντικείμενα, μπορούμε να καλούμε τις μεθόδους που θα καλούσαμε και με ένα απλό InputStream ή OutputStream.


3.10 Διαβάζοντας αρχείο ως κείμενο, τρόπος 2: το BufferedReader
Τα καλά νέα εδώ είναι ότι το αντικείμενο BufferedReader που θα χρησιμοποιήσουμε ξέρει πως να μετατρέπει τα bytes σε UTF-16 από όποια κωδικοποίηση του πούμε. Επίσης έχει το δικό του buffer! Δηλαδή διαβάζει με μιας πολλά byte από το σκληρό δίσκο, τα μετατρέπει σε ένα πίνακα από char τον οποίο κρατά «εσωτερικά», και κάθε φορά που εμείς του ζητάμε, μας λέει τον επόμενο char. Θα διαβάσει περαιτέρω bytes από τον σκληρό δίσκο μόνο όταν χρειαστεί (επίσης πολλά με μιας).

Ένα αντικείμενο BufferedReader μπορούμε να πάρουμε καλώντας την μέθοδο:
static BufferedReader newBufferedReader(Path p, Charset set)
της κλάσης Files.

Αφού το δημιουργήσουμε, μπορούμε να διαβάζουμε char με τις μεθόδους:
  • int read()
  • int read(char[] array, int offset, int len)
  • String readLine()
Στις δύο πρώτες μεθόδους ίσως αναγνωρίζετε τη λογική των αντίστοιχων της InputStream. Και έχετε δίκιο: λειτουργούν ακριβώς με τον ίδιο τρόπο, με τη διαφορά ότι η πρώτη επιστρέφει έναν ακέραιο στην περιοχή 0-65535 αντί στην 0-255.
Η τρίτη μέθοδος μας επιστρέφει ένα String που περιέχει την επόμενη γραμμή κειμένου. Γραμμή ορίζεται ως το κομμάτι κειμένου που χωρίζεται με τον χαρακτήρα \n, \r ή \r\n. Αν το τέλος του αρχείου έχει ήδη φτάσει, η read() θα επιστρέψει -1, ενώ η readLine() θα επιστρέψει null.

Η μέθοδος skip είναι επίσης στη διάθεσή μας.

Πάντα χρησιμοποιούμε τη μέθοδο close() όταν τελειώσουμε με τη δουλειά με το BufferedReader μας!



3.11 Γράφοντας κείμενο σε αρχείο, τρόπος 2: το BufferedWriter
Το αδερφάκι του BufferedReader το οποίο θα βρείτε εδώ. Ανήκει επίσης στο πακέτο java.io και δημιουργείται μέσω της μεθόδου:
static BufferedWriter newBufferedWriter(Path p, Charset ch, OpenOption... options)

Το τελευταίο όρισμα μπορεί να είναι StandardOpenOption.APPEND, ή να αφεθεί κενό, με τις γνωστές πλέον συνέπειες.

Γράφουμε στο αρχείο με τις μεθόδους:
  • void write(int c)
  • void write(char[] array, int offset, int len)
  • void write(String s, int offset, int len)
  • void newLine()
  • void flush()
Κατά τα γνωστά, η πρώτη γράφει ένα χαρακτήρα (αφού τον μετατρέψει στην κατάλληλη κωδικοποίηση φυσικά!), η δεύτερη γράφει ένα κομμάτι πίνακα (ή και όλον τον πίνακα με τα κατάλληλα ορίσματα), και η τρίτη κάνει την ίδια δουλειά με τη δεύτερη, παίρνοντας όμως ένα String. Η newLine() γράφει ένα χαρακτήρα αλλαγής παραγράφου, αυτόν που ορίζει το αντικείμενο Charset με το οποίο φτιάξαμε το BufferedWriter μας. Τέλος η flush() αδειάζει το buffer, γράφοντας τα περιεχόμενά του στον τελικό προορισμό τους (πχ στον σκληρό μας δίσκο).

Η κλάση BufferedWriter είναι υποκλάση της Writer, η οποία έχει μία μέθοδο που μας ενδιαφέρει, είναι η write(String s), χωρίς να χρειάζεται να πούμε από που μέχρι που, όπως με την write(String, int, int). Στην πράξη αυτή είναι και η μέθοδος που θα χρησιμοποιούμε συχνότερα.

Πάντα χρησιμοποιούμε τη μέθοδο close() όταν τελειώσουμε με τη δουλειά με το BufferedWriter μας!
Διαφορετικά ίσως να μη δούμε καν το αρχείο που νομίζουμε ότι γράψαμε!



3.12 Ανάγνωση και εγγραφή βασικών τύπων
Εν συντομία θα παραθέσω δύο χρήσιμα αντικείμενα που εκτός από bytes ξέρουν να διαβάζουν και να γράφουν int, long, float κτλ διαβάζοντας τον κατάλληλο αριθμό bytes και μετατρέποντάς τα στον αντίστοιχο τύπο (μην ξεχνάτε, int = 4 bytes, long = 8 bytes κτλ).

Η Java σε αντίθεση με τις native γλώσσες χρησιμοποιεί το σύστημα Big Endian.

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

DataInputStream: Εξαιρετικό αντικείμενο! Κάνει ό,τι και ένα απλό InputStream, συν την ανάγνωση βασικών τύπων με τις μεθόδους:
  • int readInt()
  • float readFloat()
  • long readLong() κτλ.
Όπως καταλαβαίνετε η readInt() διαβάζει 4 bytes και τα μετατρέπει στον κατάλληλο int, η readLong() διαβάζει 8 bytes κ.ο.κ.
Για να δημιουργήσετε αυτό το αντικείμενο πάρτε ένα InputStream (έστω in), και καλέστε τον constructor DataInputStream(in). Έπειτα καλέστε τις παραπάνω μεθόδους του DataInputStream σας. Το InputStream σας είναι πλέον άχρηστο μιας και το DataInputStream χρησιμοποιεί όπως εκείνο κρίνει τις μεθόδους του InputStream που του δώσατε.
Μπορείτε (αν θέλετε να ανεβάσετε την αποδοτικότητα του προγράμματός) να δώσετε για όρισμα στον constructor ένα BufferedInputStream (στο κάτω-κάτω και αυτό InputStream είναι!). Γενικά όμως να θυμάστε πως οτιδήποτε χρησιμοποιεί buffer πρέπει να κλείνει μέσω της close() όταν τελειώσει η δουλειά.
Χρησιμοποιείτε λοιπόν πάντα την close του DataInputStream σας και αυτό θα φροντίσει να κλείσει ό,τι χρειάζεται κλείσιμο! Αν τίποτα δε χρειάζεται κλείσιμο, δε θα κάνει τίποτα. Είναι αρκετά έξυπνο :-)

Το DataInputStream δεν έχει μέθοδο για να καταλάβει πότε τελείωσε το αρχείο. Αντ' αυτού χρησιμοποιεί την πολύ έξυπνη ιδέα να ρίχνει μία EOFException όταν «χτυπήσει τέλος». Χρησιμοποιείστε λοιπόν μία δομή try, και όταν «δείτε» την EOFException θα ξέρετε ότι το αρχείο τελείωσε.


DataOutputStream: το αδερφάκι του DataInputStream. Στην ίδια φιλοσοφία, με τη διαφορά ότι ο constructor παίρνει για όρισμα ένα OutputStream. Οι μέθοδοι για να γράψουμε είναι οι:
  • void writeInt(int i)
  • void writeFloat(float f)
  • void writeLong(long l) κτλ.
Όπως πριν, μπορείτε να χρησιμοποιήσετε για όρισμα του constructor ένα BufferedOutputStream για να βελτιώσετε την αποδοτικότητα του προγράμματός σας. Σε μία τέτοια περίπτωση μην ξεχάσετε τη μέθοδο close()! Αλλιώς ίσως να μη δείτε καν το αρχείο που νομίζετε πως γράψατε. Δε χάνετε τίποτα να καλείτε πάντα την close() όταν τελειώνετε με το DataOutputStream σας. Το πολύ-πολύ να μην κλείσει τίποτα, αν τίποτα δε χρειάζεται κλείσιμο.


3.13 Το παλιό καλό μας Scanner
Λοιπόν ναι! το java.util.Scanner ξέρει να διαβάζει και αρχεία! Απλά καλέστε έναν από τους εξής constructor:
Scanner(Path p)
Scanner(Path p, String charset_name)

Το Scanner που θα φτιάξει ο πρώτος θα διαβάζει το αρχείο σύμφωνα με το Charset.defaultCharset(), ενώ ο δεύτερος με το Charset που θα του πούμε εμείς.

Από τη στιγμή που θα φτιάξετε το Scanner, μπορείτε να χρησιμοποιήσετε τις γνωστές μας:
  • boolean hasNext()
  • boolean hasNextInt() κτλ.

  • int nextInt()
  • boolean nextBoolean() κτλ.
για να διαβάσετε τα περιεχόμενα του αρχείου. Στην περιγραφή της κλάσης ίσως δείτε και άλλες χρήσιμες μεθόδους. Εγώ προσωπικά πολλές φορές χρειαστηκα την:
Scanner useDelimiter(String del) η οποία καθορίζει με βάση ποιο χαρακτήρα το Scanner θα χωρίζει το κείμενο σε κομμάτια. Η μέθοδος αυτή επιστρέφει το Scanner για λόγους σύμπτυξης της ορθογραφίας.
Για παράδειγμα:
Μορφοποιημένος Κώδικας: Επιλογή όλων
Scanner sc = new Scanner(some_path);		//τώρα το Scanner χωρίζει τις «λέξεις» όταν βρει λευκό χαρακτήρα...
sc.useDelimiter("-"); //τώρα θα χωρίζει τις λέξεις κάθε φορά που θα βρίσκει -

//τα παραπάνω μπορούν να γίνουν και σε μία γραμμή:
Scanner sc = (new Scanner(some_path)).useDelimiter("-"); //γι αυτό το λόγο η useDelimiter επιστρέφει το Scanner :-)


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

Το πρώτο μας πρόγραμμα θα χρησιμοποιεί τις μεθόδους readAllLines και write της κλάσης Files, για να διαβάσει και να γράψει μικρά αρχεία κειμένου. Η δομή του προγράμματος μοιάζει πολύ με αυτή του FilesMethods που φτιάξαμε παραπάνω.
Μορφοποιημένος Κώδικας: Επιλογή όλων
//Αρχείο Files1.java

import java.nio.file.*;
import java.nio.charset.*;
import java.io.*;
import java.util.*;

public class Files1
{

private static final FileSystem fs = FileSystems.getDefault();
private static final java.util.Scanner sc = new java.util.Scanner(System.in);
private static String[] line, command = new String[] {"read", "write", "append"};


private static boolean isSure() {
System.out.print("Το αρχείο υπάρχει ήδη. Τα περιεχόμενά του θα αντικατασταθούν. Συνέχεια; [ναι/όχι]\t\t");
String str = sc.nextLine();

return str.equals("ναι");
} //isSure


private static void executeCommand(int i) {
Path p;

switch (i) {
case 0:
if (line.length<2) { System.out.print("Παρακαλώ δώστε όνομα αρχείου!"); return; }
p = fs.getPath(line[1]);

try {
java.util.List<String> lst = Files.readAllLines(p, Charset.defaultCharset()); //εδώ διαβάζουμε από το σκληρό δίσκο.

System.out.print("\n\n");
for (String s:lst) System.out.print(s+"\n"); //και τυπώνουμε στην οθόνη αυτό που διαβάσαμε
System.out.print("\n\n");
} //try

catch (Exception ex) { System.out.print("Σφάλμα: "+ex.toString()+"\n"); }
return;

case 1:
if (line.length<2) { System.out.print("Παρακαλώ δώστε όνομα αρχείου!"); return; }
p = fs.getPath(line[1]);

try {
if (Files.exists(p) && !isSure()) { System.out.print("Η διαδικασία ματαιώθηκε."); return; }

//δημιουργούμε το κείμενο προς εγγραφή. θα μπορούσαμε να γράψουμε οτιδήποτε.
Calendar cal = Calendar.getInstance(); //ένα ωραίο αντικείμενο που μας δίνει την τρέχουσα ημερομηνία.
String[] text = new String[] {"Αυτό το αρχείο δημιουργήθηκε αυτόματα από", "ένα πρόγραμμα Java στις",
cal.get(Calendar.YEAR)+"/"+(cal.get(Calendar.MONTH)+1)+"/"+cal.get(Calendar.DAY_OF_MONTH)+"\t"+
(cal.get(Calendar.HOUR)+12)+":"+cal.get(Calendar.MINUTE)+":"+cal.get(Calendar.SECOND)+"."};

Files.write(p, Arrays.asList(text), Charset.defaultCharset());
} //try

catch (Exception ex) { System.out.print("Σφάλμα: "+ex.toString()+"\n"); }
return;

case 2:
if (line.length<2) { System.out.print("Παρακαλώ δώστε όνομα αρχείου!"); return; }
p = fs.getPath(line[1]);

try {
//δημιουργούμε το κείμενο προς εγγραφή. θα μπορούσαμε να γράψουμε οτιδήποτε.
Calendar cal = Calendar.getInstance();
String[] text = new String[] {"\n\nΑυτό το κείμενο προσετέθη από", "ένα πρόγραμμα Java στις",
cal.get(Calendar.YEAR)+"/"+(cal.get(Calendar.MONTH)+1)+"/"+cal.get(Calendar.DAY_OF_MONTH)+"\t"+
(cal.get(Calendar.HOUR)+12)+":"+cal.get(Calendar.MINUTE)+":"+cal.get(Calendar.SECOND)+"."};

Files.write(p, Arrays.asList(text), Charset.defaultCharset(), StandardOpenOption.APPEND);
} //try

catch (Exception ex) { System.out.print("Σφάλμα: "+ex.toString()+"\n"); }

default:
return;

} //switch

} //executeCommand


public static void main(String[] args) {
int i;

//Μήνυμα προς το χρήστη
System.out.print("\n==========Καλώς ορίσατε!!==========\nΕπιτρεπόμενες εντολές:\n");
for (i=0; i<command.length; ++i) System.out.print("\t"+command[i]+"\n");
System.out.print("\tq για έξοδο\n");


A: for (;;) {
System.out.print(">");

line = sc.nextLine().split(" "); //πίνακας με λέξεις...
if ( line[0].isEmpty() ) continue;

for (i=0; i<command.length; ++i)
if (line[0].equals(command[i])) { executeCommand(i); System.out.print("\n"); continue A; }

if (line[0].equals("q")) break;

System.out.print("Η εντολή "+line[0]+" δεν αναγνωρίζεται...\n");
} //for
} //main


}


Το πρόγραμμα που ακολουθεί είναι σχεδόν ίδιο με το προηγούμενο. Η μόνη διαφορά έγκειται στο ότι αντί να χρησιμοποιεί τις μεθόδους readAllLines και write της κλάσης Files, χρησιμοποιεί τα αντικείμενα BufferedReader και BufferedWriter.
Μορφοποιημένος Κώδικας: Επιλογή όλων
//Αρχείο Files2.java

import java.nio.file.*;
import java.nio.charset.*;
import java.io.*;
import java.util.*;

public class Files2
{

private static final FileSystem fs = FileSystems.getDefault();
private static final java.util.Scanner sc = new java.util.Scanner(System.in);
private static String[] line, command = new String[] {"read", "write", "append"};


private static boolean isSure() {
System.out.print("Το αρχείο υπάρχει ήδη. Τα περιεχόμενά του θα αντικατασταθούν. Συνέχεια; [ναι/όχι]\t\t");
String str = sc.nextLine();

return str.equals("ναι");
} //isSure


private static void executeCommand(int i) {
Path p;

switch (i) {
case 0:
if (line.length<2) { System.out.print("Παρακαλώ δώστε όνομα αρχείου!"); return; }
p = fs.getPath(line[1]);

try {
BufferedReader rd = Files.newBufferedReader(p, Charset.defaultCharset());
String str = rd.readLine();

System.out.print("\n\n");
while (str!=null) { System.out.print(str+"\n"); str=rd.readLine(); }
System.out.print("\n\n");
rd.close();
} //try

catch (Exception ex) { System.out.print("Σφάλμα: "+ex.toString()+"\n"); }
return;

case 1:
if (line.length<2) { System.out.print("Παρακαλώ δώστε όνομα αρχείου!"); return; }
p = fs.getPath(line[1]);

try {
BufferedWriter wr = Files.newBufferedWriter(p, Charset.defaultCharset());
Calendar cal = Calendar.getInstance();

wr.write("Αυτό το αρχείο δημιουργήθηκε αυτόματα από\n");
wr.write("ένα πρόγραμμα Java στις\n");
wr.write(cal.get(Calendar.YEAR)+"/"+(cal.get(Calendar.MONTH)+1)+"/"+cal.get(Calendar.DAY_OF_MONTH)+"\t"+
(cal.get(Calendar.HOUR)+12)+":"+cal.get(Calendar.MINUTE)+":"+cal.get(Calendar.SECOND)+".");
wr.close();
} //try

catch (Exception ex) { System.out.print("Σφάλμα: "+ex.toString()+"\n"); }
return;

case 2:
if (line.length<2) { System.out.print("Παρακαλώ δώστε όνομα αρχείου!"); return; }
p = fs.getPath(line[1]);

try {
BufferedWriter wr = Files.newBufferedWriter(p, Charset.defaultCharset(), StandardOpenOption.APPEND);
Calendar cal = Calendar.getInstance();

wr.write("\n\nΑυτό το κείμενο προσετέθη από\n");
wr.write("ένα πρόγραμμα Java στις\n");
wr.write(cal.get(Calendar.YEAR)+"/"+(cal.get(Calendar.MONTH)+1)+"/"+cal.get(Calendar.DAY_OF_MONTH)+"\t"+
(cal.get(Calendar.HOUR)+12)+":"+cal.get(Calendar.MINUTE)+":"+cal.get(Calendar.SECOND)+".");
wr.close();
} //try

catch (Exception ex) { System.out.print("Σφάλμα: "+ex.toString()+"\n"); }

default:
return;

} //switch

} //executeCommand


public static void main(String[] args) {
int i;

//Μήνυμα προς το χρήστη
System.out.print("\n==========Καλως ορίσατε!!==========\nΕπιτρεπόμενες εντολές:\n");
for (i=0; i<command.length; ++i) System.out.print("\t"+command[i]+"\n");
System.out.print("\tq για έξοδο\n");


A: for (;;) {
System.out.print(">");

line = sc.nextLine().split(" "); //πίνακας με λέξεις...
if ( line[0].isEmpty() ) continue;

for (i=0; i<command.length; ++i)
if (line[0].equals(command[i])) { executeCommand(i); System.out.print("\n"); continue A; }

if (line[0].equals("q")) break;

System.out.print("Η εντολή "+line[0]+" δεν αναγνωρίζεται...\n");
} //for
} //main


}


Στο επόμενο παράδειγμά μας θα δούμε σε λειτουργία τα αντικείμενα InputStream και OutputStream. Μέσω ενός αντικειμένου InputStream θα διαβάζουμε τα byte από ένα οποιοδήποτε αρχείο, 100 τη φορά, θα προσθέτουμε 1 σε κάθε byte, και θα γράφουμε τα τροποποιημένα byte σε νέο αρχείο μέσω ενός OutputStream. Το αρχείο προς ανάγνωση το δίνουμε σαν όρισμα, και το νέο αρχείο θα έχει ίδιο όνομα με το αρχικό, με την κατάληξη _plusOne. Μπορείτε να δώσετε οποιοδήποτε αρχείο σαν όρισμα, αρχείο κειμένου, φωτογραφία, εκτελέσιμο κτλ. Μην περιμένετε το νέο αρχείο να είναι λειτουργικό όμως! Το μόνο που θα μπορέσετε να κάνετε είναι να ανοίξετε τα δύο αρχεία με ένα δεκαεξαδικό επεξεργαστή για να δείτε την ομοιότητα με το αρχικό.

Μορφοποιημένος Κώδικας: Επιλογή όλων
import java.nio.file.*;
import java.io.*;

public class FakeClone {

public static void main(String args[]) {

if (args.length<1) { System.out.print("Χρήση: java FakeClone όνομα_αρχείου\n"); System.exit(0); }
try {
Path p = FileSystems.getDefault().getPath(args[0]), p2 = FileSystems.getDefault().getPath(args[0]+"_plusOne");
InputStream in = Files.newInputStream(p);
OutputStream out = Files.newOutputStream(p2);
byte[] buf = new byte[100];
int j, i = in.read(buf); //ζητάμε από το λειτουργικό να μας πει τα επόμενα 100 byte, δηλαδή να γεμίσει τον πίνακα buf.

while(i!=-1) {
for (j=0; j<i; ++j) buf[j]++; //προσθέτουμε ένα σε κάθε byte
out.write(buf,0,i); //γράφουμε το αποτέλεσμα στο αρχείο p2.

i = in.read(buf);
} //while

in.close();
out.close();
} //try
catch (Exception ex) { System.out.print("Σφάλμα: "+ex.toString()+"\n"); }
} //main



}


Προηγούμενο: Exceptions
Επόμενο: Προσεχώς

Creative Commons License
Η εργασία υπάγεται στην άδεια Creative Commons Αναφορά-Παρόμοια διανομή 3.0 Ελλάδα
Τελευταία επεξεργασία από alkismavridis και 23 Απρ 2014, 12:18, έχει επεξεργασθεί 2 φορά/ες συνολικά
Γνώσεις ⇛ Linux: Μέτριο┃ Προγραμματισμός: Java, Assembly, Fortran, μαθαίνω C/X11┃ Αγγλικά: Μέτρια
Λειτουργικό σε Η/Υ ϰ μοντέλο: Ubuntu 14.04 64-bit ┃ Τρόπος εγκατάστασης: Live USB
Προδιαγραφές ⇛ Desktop: Intel i5 2320 3.00GHz.┃ MotherBoard: Asus p8h61 -m pro
Προδιαγραφές ⇛ RAM: 4GB ┃ Τροφοδοτικό Corsair CX430

GPU: Intel 2nd Generation Core Processor Family Integrated Graphics Controller [8086:0102] {i915}
5 eth0: Realtek RTL8111/8168B PCI Express Gigabit Ethernet controller [10ec:8168] (rev 06) ⋮ wlan0: 0b05:1723 ASUS WL-167G v2 802.11g Adapter [Ralink RT2571W]
Οθόνη Schaub Lorenz (Tv)
alkismavridis
punkTUX
punkTUX
 
Δημοσιεύσεις: 273
Εγγραφή: 18 Μαρ 2009, 18:46
Εκτύπωση

Re: Εισαγωγή στην Java - κεφ. 12

Δημοσίευσηαπό alkismavridis » 22 Απρ 2014, 20:18

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

Δεν είμαι σίγουρος όμως αν τα κατάφερα καλά.. Οπότε κάθε παρατήρηση, απορία, ή υπόδειξη, ώστε να διαλευκάνουμε σκοτεινά σημεία ή να κάνουμε το κεφάλαιο αυτό καλύτερο είναι ευπρόσδεκτη!

Νομίζω με άλλα δύο κεφάλαια θα έχουμε τελειώσει το βασικό κορμό της Java.
Γνώσεις ⇛ Linux: Μέτριο┃ Προγραμματισμός: Java, Assembly, Fortran, μαθαίνω C/X11┃ Αγγλικά: Μέτρια
Λειτουργικό σε Η/Υ ϰ μοντέλο: Ubuntu 14.04 64-bit ┃ Τρόπος εγκατάστασης: Live USB
Προδιαγραφές ⇛ Desktop: Intel i5 2320 3.00GHz.┃ MotherBoard: Asus p8h61 -m pro
Προδιαγραφές ⇛ RAM: 4GB ┃ Τροφοδοτικό Corsair CX430

GPU: Intel 2nd Generation Core Processor Family Integrated Graphics Controller [8086:0102] {i915}
5 eth0: Realtek RTL8111/8168B PCI Express Gigabit Ethernet controller [10ec:8168] (rev 06) ⋮ wlan0: 0b05:1723 ASUS WL-167G v2 802.11g Adapter [Ralink RT2571W]
Οθόνη Schaub Lorenz (Tv)
alkismavridis
punkTUX
punkTUX
 
Δημοσιεύσεις: 273
Εγγραφή: 18 Μαρ 2009, 18:46
Εκτύπωση

Re: Εισαγωγή στην Java - κεφ. 12

Δημοσίευσηαπό georgek1984 » 28 Μάιος 2015, 19:24

Καλησπέρα αντιμετωπίζω το εξής θεματάκι: Έχω εγκατεστημένο Netbeans 8.02 και θέλω να ανοίξω ένα συγκεκριμένο αρχείο χρυσιμοποιόντας τον παρακάτω κώδικα λαμβάνω αυτό το σφάλμα
Κώδικας: Επιλογή όλων
java.io.IOException: Cannot run program "/home/georgekaranastasis/Έγγραφα/test/test1": error=13, Άρνηση πρόσβασης

Ο κώδικας που χρυσιμοποιώ για να ανοίξω το έγγραφο που θέλω είναι ο εξής:
Κώδικας: Επιλογή όλων
try {
            Runtime rt = Runtime.getRuntime();
            String file = "/home/georgekaranastasis/Έγγραφα/test/test1";
            Process p = rt.exec( file);
        } catch (IOException ex) {
            Logger.getLogger(NewJFrame.class.getName()).log(Level.SEVERE, null, ex);
        }
       
    }                   

Μήπως μπορεί να με βοηθήσει κάποιος να καταλάβω γιατί δεν μου λειτουργεί;
Γνώσεις Linux: Καλά πάμε ┃ Προγραμματισμού: Προχωράω ┃ Αγγλικών: Ικανοποιητικά
DEBIAN 8.1
Intel Core i5-3210M CPU @ 2.50GHz ‖ RAM 7825 MiB ‖ Acer VA50_HC_CR - Acer Aspire V3-571G
Intel 3rd Gen Core processor Graphics Controller [8086:0166] {i915} ⋮ nVidia GF108M [GeForce GT 630M] [10de:0de9] {nvidia}
wlan0: Qualcomm Atheros AR9462 Wireless Network Adapter [168c:0034] (rev 01) ⋮ eth0: Broadcom NetLink BCM57785 Gigabit Ethernet PCIe [14e4:16b5] (rev 10)
Άβαταρ μέλους
georgek1984
Ubuntistas
Ubuntistas
 
Δημοσιεύσεις: 35
Εγγραφή: 23 Οκτ 2012, 14:47
Τοποθεσία: Κως
Εκτύπωση

Re: Εισαγωγή στην Java - κεφ. 12

Δημοσίευσηαπό the_eye » 28 Μάιος 2015, 19:37

Δοκίμασε να το βάλεις σε φάκελο που να μην έχει ελληνικό όνομα
Όσο λιγότερο κλειστό λογισμικό έχεις, τόσα λιγότερα προβλήματα.
1 Γνώσεις ⇛ Linux: Καλό ┃ Προγραμματισμός: Ναι PHP, MySQL ┃ Αγγλικά: Καλά
2 Ubuntu 22.04 Jammy Jellyfish 5.15.0-58-generic 64bit (el_GR.UTF-8, ubuntu:GNOME ubuntu)
3 Intel Core i3-6100 CPU @ 3.70GHz ‖ RAM 7836 MiB ‖ Gigabyte B150M-HD3 DDR3-CF - Gigabyte B150M-HD3 DDR3
4 Intel HD Graphics 530 [8086:1912] {i915}
5 enp1s0: Realtek RTL8111/8168/8411 PCI Express Gigabit Ethernet Controller [10ec:8168] (rev 15)
Οδηγοί Ubuntu Βίντεο Οδηγοί
Άβαταρ μέλους
the_eye
Διαχειριστής
Διαχειριστής
 
Δημοσιεύσεις: 11671
Εγγραφή: 16 Μαρ 2010, 17:19
Launchpad: ntoulasd
IRC: the_eye_
Εκτύπωση

Re: Εισαγωγή στην Java - κεφ. 12

Δημοσίευσηαπό georgek1984 » 28 Μάιος 2015, 19:41

Ευχαριστώ για την απάντηση. Έκανα αυτό που μου είπες αλλά τώρα λέει πως δεν υπάρχει καν το αρχείο.
Κώδικας: Επιλογή όλων
java.io.IOException: Cannot run program "/home/georgekaranastasis/Java/test1": error=2, Δεν υπάρχει τέτοιο αρχείο ή κατάλογος

Κώδικας: Επιλογή όλων
try {
            Runtime rt = Runtime.getRuntime();
            String file;
            file = "/home/georgekaranastasis/Java/test1";
            Process p = rt.exec( file);
        } catch (IOException ex) {
            Logger.getLogger(NewJFrame.class.getName()).log(Level.SEVERE, null, ex);
        }
       
    }                           
Γνώσεις Linux: Καλά πάμε ┃ Προγραμματισμού: Προχωράω ┃ Αγγλικών: Ικανοποιητικά
DEBIAN 8.1
Intel Core i5-3210M CPU @ 2.50GHz ‖ RAM 7825 MiB ‖ Acer VA50_HC_CR - Acer Aspire V3-571G
Intel 3rd Gen Core processor Graphics Controller [8086:0166] {i915} ⋮ nVidia GF108M [GeForce GT 630M] [10de:0de9] {nvidia}
wlan0: Qualcomm Atheros AR9462 Wireless Network Adapter [168c:0034] (rev 01) ⋮ eth0: Broadcom NetLink BCM57785 Gigabit Ethernet PCIe [14e4:16b5] (rev 10)
Άβαταρ μέλους
georgek1984
Ubuntistas
Ubuntistas
 
Δημοσιεύσεις: 35
Εγγραφή: 23 Οκτ 2012, 14:47
Τοποθεσία: Κως
Εκτύπωση

Re: Εισαγωγή στην Java - κεφ. 12

Δημοσίευσηαπό the_eye » 28 Μάιος 2015, 19:46

Για δώσε
Κώδικας: Επιλογή όλων
ls -l /home/georgekaranastasis/Java/
Όσο λιγότερο κλειστό λογισμικό έχεις, τόσα λιγότερα προβλήματα.
1 Γνώσεις ⇛ Linux: Καλό ┃ Προγραμματισμός: Ναι PHP, MySQL ┃ Αγγλικά: Καλά
2 Ubuntu 22.04 Jammy Jellyfish 5.15.0-58-generic 64bit (el_GR.UTF-8, ubuntu:GNOME ubuntu)
3 Intel Core i3-6100 CPU @ 3.70GHz ‖ RAM 7836 MiB ‖ Gigabyte B150M-HD3 DDR3-CF - Gigabyte B150M-HD3 DDR3
4 Intel HD Graphics 530 [8086:1912] {i915}
5 enp1s0: Realtek RTL8111/8168/8411 PCI Express Gigabit Ethernet Controller [10ec:8168] (rev 15)
Οδηγοί Ubuntu Βίντεο Οδηγοί
Άβαταρ μέλους
the_eye
Διαχειριστής
Διαχειριστής
 
Δημοσιεύσεις: 11671
Εγγραφή: 16 Μαρ 2010, 17:19
Launchpad: ntoulasd
IRC: the_eye_
Εκτύπωση

Re: Εισαγωγή στην Java - κεφ. 12

Δημοσίευσηαπό georgek1984 » 28 Μάιος 2015, 19:51

Μου βγάζει τα εξής στο τερματικό:
Κώδικας: Επιλογή όλων
σύνολο 4
drwxrwxr-x 2 georgekaranastasis georgekaranastasis 4096 Μάι  28 19:46 test
Γνώσεις Linux: Καλά πάμε ┃ Προγραμματισμού: Προχωράω ┃ Αγγλικών: Ικανοποιητικά
DEBIAN 8.1
Intel Core i5-3210M CPU @ 2.50GHz ‖ RAM 7825 MiB ‖ Acer VA50_HC_CR - Acer Aspire V3-571G
Intel 3rd Gen Core processor Graphics Controller [8086:0166] {i915} ⋮ nVidia GF108M [GeForce GT 630M] [10de:0de9] {nvidia}
wlan0: Qualcomm Atheros AR9462 Wireless Network Adapter [168c:0034] (rev 01) ⋮ eth0: Broadcom NetLink BCM57785 Gigabit Ethernet PCIe [14e4:16b5] (rev 10)
Άβαταρ μέλους
georgek1984
Ubuntistas
Ubuntistas
 
Δημοσιεύσεις: 35
Εγγραφή: 23 Οκτ 2012, 14:47
Τοποθεσία: Κως
Εκτύπωση

Re: Εισαγωγή στην Java - κεφ. 12

Δημοσίευσηαπό georgek1984 » 28 Μάιος 2015, 19:53

Δεν μπορώ ν καταλάβω γιατί μου βγάζει άρνηση πρόσβασης..
Γνώσεις Linux: Καλά πάμε ┃ Προγραμματισμού: Προχωράω ┃ Αγγλικών: Ικανοποιητικά
DEBIAN 8.1
Intel Core i5-3210M CPU @ 2.50GHz ‖ RAM 7825 MiB ‖ Acer VA50_HC_CR - Acer Aspire V3-571G
Intel 3rd Gen Core processor Graphics Controller [8086:0166] {i915} ⋮ nVidia GF108M [GeForce GT 630M] [10de:0de9] {nvidia}
wlan0: Qualcomm Atheros AR9462 Wireless Network Adapter [168c:0034] (rev 01) ⋮ eth0: Broadcom NetLink BCM57785 Gigabit Ethernet PCIe [14e4:16b5] (rev 10)
Άβαταρ μέλους
georgek1984
Ubuntistas
Ubuntistas
 
Δημοσιεύσεις: 35
Εγγραφή: 23 Οκτ 2012, 14:47
Τοποθεσία: Κως
Εκτύπωση

Re: Εισαγωγή στην Java - κεφ. 12

Δημοσίευσηαπό the_eye » 28 Μάιος 2015, 20:35

Τώρα έχεις άλλο σφάλμα δεν βγάζει άρνηση πρόσβασης
Όσο λιγότερο κλειστό λογισμικό έχεις, τόσα λιγότερα προβλήματα.
1 Γνώσεις ⇛ Linux: Καλό ┃ Προγραμματισμός: Ναι PHP, MySQL ┃ Αγγλικά: Καλά
2 Ubuntu 22.04 Jammy Jellyfish 5.15.0-58-generic 64bit (el_GR.UTF-8, ubuntu:GNOME ubuntu)
3 Intel Core i3-6100 CPU @ 3.70GHz ‖ RAM 7836 MiB ‖ Gigabyte B150M-HD3 DDR3-CF - Gigabyte B150M-HD3 DDR3
4 Intel HD Graphics 530 [8086:1912] {i915}
5 enp1s0: Realtek RTL8111/8168/8411 PCI Express Gigabit Ethernet Controller [10ec:8168] (rev 15)
Οδηγοί Ubuntu Βίντεο Οδηγοί
Άβαταρ μέλους
the_eye
Διαχειριστής
Διαχειριστής
 
Δημοσιεύσεις: 11671
Εγγραφή: 16 Μαρ 2010, 17:19
Launchpad: ntoulasd
IRC: the_eye_
Εκτύπωση

Re: Εισαγωγή στην Java - κεφ. 12

Δημοσίευσηαπό georgek1984 » 28 Μάιος 2015, 22:11

περίεργα πράγματα.... δεν ξέρω τι φταίει. Τι συμβαίνει δηλαδή?
Γνώσεις Linux: Καλά πάμε ┃ Προγραμματισμού: Προχωράω ┃ Αγγλικών: Ικανοποιητικά
DEBIAN 8.1
Intel Core i5-3210M CPU @ 2.50GHz ‖ RAM 7825 MiB ‖ Acer VA50_HC_CR - Acer Aspire V3-571G
Intel 3rd Gen Core processor Graphics Controller [8086:0166] {i915} ⋮ nVidia GF108M [GeForce GT 630M] [10de:0de9] {nvidia}
wlan0: Qualcomm Atheros AR9462 Wireless Network Adapter [168c:0034] (rev 01) ⋮ eth0: Broadcom NetLink BCM57785 Gigabit Ethernet PCIe [14e4:16b5] (rev 10)
Άβαταρ μέλους
georgek1984
Ubuntistas
Ubuntistas
 
Δημοσιεύσεις: 35
Εγγραφή: 23 Οκτ 2012, 14:47
Τοποθεσία: Κως
Εκτύπωση

Επόμενο

  • ΣΧΕΤΙΚΑ ΘΕΜΑΤΑ
    ΑΠΑΝΤΗΣΕΙΣ
    ΠΡΟΒΟΛΕΣ
    ΣΥΓΓΡΑΦΕΑΣ

Επιστροφή στο Μαθήματα Java