Monday, June 26, 2006

Bestandsbewerkingen (Dutch)

Introductie

Bij bestandsbewerkingen draait het allemaal om Input/Output datastromen (I/O streams). Met de I/O streams kun je data van een bestand, van het geheugen of van een socket (bijvoorbeeld via een URL) aflezen en/of ernaar schrijven. De klassen die hierbij zeer van nut kunnen zijn zitten in het java.io package. Hierin zullen we alleen de belangrijkste klassen behandelen die van nut kunnen zijn bij het lezen en schrijven van bestanden en directories.

java.io klasse beschrijving
File Definieert bestanden of directories.
FileReader Leest bestand uit als een char array.
FileWriter Schrijft een char array of een String naar een bestand.
BufferedReader Zet een Reader in een geheugenbuffer, dit is sneller bij grotere bestanden (>100KB).
BufferedWriter Zet een Writer in een geheugenbuffer, dit is sneller bij grotere bestanden (>100KB).
CharArrayReader Zet een ordinaire char array in een Reader.
CharArrayWriter Zet een Writer in een ordinaire char array.
FileInputStream Leest bestand uit als een byte array.
FileOutputStream Schrijft een byte array naar een bestand.
BufferedInputStream Zet een InputStream in een geheugenbuffer, dit is sneller bij grotere bestanden (>100KB).
BufferedOutputStream Zet een OutputStream in een geheugenbuffer, dit is sneller bij grotere bestanden (>100KB).
ByteArrayInputStream Zet een ordinaire byte array in een InputStream.
ByteArrayOutputStream Zet een OutputStream in een ordinaire byte array.
InputStreamReader Converteert een byte array naar een char array.
OutputStreamWriter Converteert een char array naar een byte array.
Terug naar boven

Welke moet ik gebruiken?

Dat is vrij simpel: je kunt het beste een Reader en Writer gebruiken voor platte tekstbestanden die uit Latijnse karakters bestaan (txt, csv, ini, etc.) en je kunt het beste een InputStream en OutputStream gebruiken voor binaire bestanden (exe, zip, pdf, tekstbestanden met encoded karakters, etc.). Je mag ze ook door elkaar gebruiken, maar voor de nabewerking is het het handigst wanneer het soort inhoud overeenkomt met de type stream (char array of byte array). Daarnaast biedt de BufferedReader de handige mogelijkheid om platte tekstbestanden regel voor regel te inlezen. Voor de rest werken ze allebei bijna precies hetzelfde.

Terug naar boven

Foutenafhandeling

Praktisch alle klassen van de java.io package kunnen een IOException of een subklasse daarvan afwerpen. Je bent dus verplicht om de I/O bewerkingen in een try-catch-finally statement te zetten. Anders moet je desbetreffende IOException doorgooien via de throws bepaling van de methode. Het is zeer aanbevelenswaardig om de I/O streams na het gebruik van de read() en write() acties altijd te sluiten met de close() methode om de systeembronnen te vrijgeven. Ze worden namelijk niet automatisch direct gesloten. Als je dat niet doet, dan kun je wanneer je veel I/O streams maakt na een tijdje een tekort aan systeembronnen krijgen, met alle desastreuze gevolgen van dien.

Terug naar boven

Werken op bestandsniveau

Met de File kun je een locatie aanwijzen waarvan gelezen moet worden of waarnaar geschreven moet worden. Wanneer je de locatie eenmaal hebt opgegeven, dit kan een bestand of een directory zijn, dan kun je diverse methoden gebruiken om op bestandsniveau te kunnen werken.

Hieronder staat een aardig stukje code om te laten zien wat je allemaal op bestandsniveau kunt doen. Het doornemen van de commentaar zou afdoende moeten zijn om een beeld te krijgen wat er allemaal precies gebeurt.

package test;

import java.io.File;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;

public class Test {

    public static void main(String[] args) throws IOException {

        // Geef de schijf op.
        File disk = new File("c:");
        
        // Kunnen we naar de schijf schrijven?
        if (!disk.canWrite()) {
            // Zo nee, stop het programma.
            throw new RuntimeException("Kan niet naar schijf schrijven.");
        }

        // Definieer de map.
        File dir = new File("c:/java");

        // Bestaat de map?
        if (!dir.exists()) {
            // Zo nee, aanmaken!
            dir.mkdir();
        }
        
        // Definieer het bestand.
        File file = new File("c:/java/test.txt");
        
        // Deze tekst moet in het bestand komen.
        String text = "Java forever!";

        // Bereid de writer voor.
        FileWriter writer = null;

        try {
            // Zet het bestand in de writer.
            writer = new FileWriter(file);
            
            // Schrijf de tekst naar het bestand. Let op: een
            // eventueel bestaand bestand wordt overgeschreven.
            writer.write(text);
        } catch (IOException e) {
            // Schrijven mislukt.
            System.err.println("Schrijven mislukt");

            // Werp de exceptie af, het programma moet direct stoppen.
            throw e;
        } finally {
            // Sluit de writer. Doe dit altijd in de finally blok!
            if (writer != null) {
                try {
                    writer.close();
                } catch (IOException e) {
                    // Niks aan te doen.
                    e.printStackTrace();
                }
            }
        }
        
        // Toon enkele gegevens.
        System.out.println("Is een bestand? " + file.isFile());
        System.out.println("Is een map? " + file.isDirectory());
        System.out.println("Is verborgen? " + file.isHidden());
        System.out.println("Is leesbaar? " + file.canRead());
        System.out.println("Is schrijfbaar? " + file.canWrite());
        System.out.println("Bestandsnaam: " + file.getName());
        System.out.println("Locatie: " + file.getParent());
        System.out.println("Pad: " + file.getPath());
        System.out.println("Grootte: " + file.length() + " bytes");

        // Hernoem het bestand.
        File newfile = new File("c:/java/newfile.txt");
        file.renameTo(newfile);

        // Toon bewijs van de hernoeming.
        System.out.println("Bestaat oud bestand? " + file.exists());
        System.out.println("Bestaat nieuw bestand? " + newfile.exists());
        System.out.println("Pad nieuw bestand? " + newfile.getPath());

        // Vraag de bestandsgrootte op als int.
        int length = (int) newfile.length();
        
        // Declareer en initialiseer een char array. De opgegeven grootte
        // moet in ieder geval minimaal de bestandsgrootte zijn.
        char[] content = new char[length];

        // Bereid de reader voor.
        FileReader reader = null;

        try {
            // Zet het bestand in de reader.
            reader = new FileReader(newfile);
            
            // Zet de inhoud in de char array.
            reader.read(content);
        } catch (IOException e) {
            // Lezen mislukt.
            System.err.println("Lezen mislukt");

            // Werp de exceptie af, het programma moet direct stoppen.
            throw e;
        } finally {
            // Sluit de reader. Doe dit altijd in de finally blok!
            if (reader != null) {
                try {
                    reader.close();
                } catch (IOException e) {
                    // Niks aan te doen.
                    e.printStackTrace();
                }
            }
        }

        // Converteer van char array naar String en toon de inhoud.
        System.out.println("Inhoud: " + new String(content));

        // Verwijder het bestand.
        newfile.delete();
        
        // Verwijder de map. Dit gebeurt overigens alleen wanneer
        // de map helemaal leeg is. 
        dir.delete();
    }

}

Is een bestand? true
Is een map? false
Is verborgen? false
Is leesbaar? true
Is schrijfbaar? true
Bestandsnaam: test.txt
Locatie: c:\java
Pad: c:\java\test.txt
Grootte: 13 bytes
Bestaat oud bestand? false
Bestaat nieuw bestand? true
Pad nieuw bestand: c:\java\newfile.txt
Inhoud: Java forever!

Als je goed hebt opgelet, dan zie je dat de paden worden weergeven met een forward slash / in plaats van een backward slash \. Op zich werken ze allebei prima, echter de backward slash is binnen de String een escape teken; zo kun je bijvoorbeeld doublequotes binnen een string aangeven met "\"Quoted\"". Wanneer je per-se backward slashes in de paden wilt gebruiken, wat overigens af te raden is, zou je deze dus dubbel moeten opgeven:

        // Definieer het bestand.
        File file = new File("c:\\java\\test.txt");

Daarnaast werken alleen de paden met de forward slash ook prima in linux. Wanneer je in een Windows-omgeving geen schijfletter opgeeft, zoals "/java/test.txt", dan wordt er altijd uitgegaan van de partitie waar je de JVM hebt geinstalleerd en waarvandaan de JVM wordt uitgevoerd. Standaard is dit de c: schijf.

Wanneer je een pad en een bestandsnaam in twee variabelen hebt en je er niet helemaal zeker van bent of de slashes er wel goed in zitten, voordat je ze aan elkaar wilt plakken, dan zou de andere constructeur van File zeer van pas komen:

        // Definieer bij wijze van voorbeeld de pad en de naam.
        String filePath = "c:/java";
        String fileName = "test.txt";

        // Definieer het bestand.
        File file = new File(filePath, fileName);

Dat scheelt gehannes met indexOf() en/of matches() om te controleren of de slashes er wel goed in zitten.

Terug naar boven

Werken op directory niveau

Met de listFiles() methode van de File kun je alle directories en bestanden opvragen die in de aangegeven locatie zitten. Dit levert een File[] array op. Een simpel praktijkvoorbeeldje:

package test;

import java.io.File;
import java.util.Iterator;
import java.util.Set;
import java.util.TreeSet;

public class Test {

    public static void main(String[] args) {

        // Geef de schijf op.
        File disk = new File("c:/");
        
        // Kunnen we van de schijf lezen?
        if (!disk.canRead()) {
            // Zo nee, stop het programma.
            throw new RuntimeException("Kan niet van schijf lezen.");
        }

        // Vraag de inhoud op.
        File[] files = disk.listFiles();

        // Voorbereid de collecties.
        Set diskDirs = new TreeSet();
        Set diskFiles = new TreeSet();
        
        // Scheid directories en bestanden. Ze worden namelijk
        // door elkaar in alfabetische volgorde opgevraagd.
        for (int i = 0; i < files.length; i++) {
            if (files[i].isDirectory()) {
                diskDirs.add(files[i]);
            } else if (files[i].isFile()) {
                diskFiles.add(files[i]);
            }
        }

        // Laat de directories zien.
        for (Iterator iter = diskDirs.iterator(); iter.hasNext();) {
            File diskDir = (File) iter.next();
            String name = diskDir.getName();
            System.out.println(name + " (DIR)");
        }

        // Laat de bestanden zien.
        for (Iterator iter = diskFiles.iterator(); iter.hasNext();) {
            File diskFile = (File) iter.next();
            String name = diskFile.getName();
            long size = diskFile.length();
            System.out.println(name + " (" + size + " bytes)");
        }
    }

}

Documents and Settings (DIR)
Program Files (DIR)
RECYCLER (DIR)
System Volume Information (DIR)
WINDOWS (DIR)
boot.ini (210 bytes)
Bootfont.bin (4952 bytes)
NTDETECT.COM (47564 bytes)
ntldr (251184 bytes)

Overigens kun je met de list() methode van de File direct de namen van alle directories en bestanden opvragen in de vorm van een String[] array.

Terug naar boven

FileReader en FileWriter

Met de FileReader en FileWriter kun je bestanden uitlezen en wegschrijven als een char array. Hier is een voorbeeld in de vorm van een eenvoudige bestands-kopie:

package test;

import java.io.File;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;

public class Test {

    public static void main(String[] args) {

        // Definieer het invoerbestand.
        File inputFile = new File("c:/java/input.txt");

        // Definieer het uitvoerbestand.
        File outputFile = new File("c:/java/output.txt");

        // Voorbereid de reader.
        FileReader reader = null;

        // Voorbereid de writer.
        FileWriter writer = null;

        try {
            // Laat het invoerbestand als een char array inlezen.
            reader = new FileReader(inputFile);

            // Laat het uitvoerbestand als een char array uitschrijven.
            writer = new FileWriter(outputFile);

            // Doorloop alle karakters ..
            int data = -1;
            while ((data = reader.read()) != -1) {
                // .. en kopieer elke karakter.
                writer.write(data);
            }
            
            // Klaar!
            System.out.println("Kopieren gelukt.");
        } catch (IOException e) {
            System.err.println("Kopieren mislukt.");
            e.printStackTrace();
        } finally {
            // Sluit de reader en de writer. Doe dit altijd in de finally blok!
            if (reader != null) {
                try {
                    reader.close();
                } catch (IOException e) {
                    // Niks aan te doen.
                    e.printStackTrace();
                }
            }
            if (writer != null) {
                try {
                    writer.close();
                } catch (IOException e) {
                    // Niks aan te doen.
                    e.printStackTrace();
                }
            }
        }
    }

}

De volgende manier kan trouwens ook, getuige het stukje code hierboven bij Werken op bestandsniveau:

package test;

import java.io.File;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;

public class Test {

    public static void main(String[] args) {

        File inputFile = new File("c:/java/input.txt");
        File outputFile = new File("c:/java/output.txt");
        FileReader reader = null;
        FileWriter writer = null;

        try {
            reader = new FileReader(inputFile);
            writer = new FileWriter(outputFile);

            // Downcast de hoeveelheid karakters van long naar int.
            int length = (int) inputFile.length();

            // Declareer en initialiseer een char array. De array
            // index accepteert alleen een int, vandaar de downcast.
            char[] content = new char[length];
            
            // Zet de inhoud van het invoerbestand in de char array.
            reader.read(content);

            // Zet de char array in het uitvoerbestand.
            writer.write(content);

            System.out.println("Kopieren gelukt.");
        } catch (IOException e) {
            System.err.println("Kopieren mislukt.");
            e.printStackTrace();
        } finally {
            if (reader != null) {
                try {
                    reader.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if (writer != null) {
                try {
                    writer.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }

}

Deze manier is eigenlijk niet zo netjes, want je zou geheugenproblemen kunnen krijgen bij het initialiseren van de char array wanneer de length groter is dan de hoeveelheid beschikbare geheugen voor de JVM. Dit is echter wel véél sneller dan de andere methoden.

Daarnaast zou je ook problemen kunnen krijgen bij het downcasten van long naar int wanneer een bestand meer dan 2147483647 karakters bevat (de maximumwaarde van int). Aangezien 1 karakter gelijk staat aan 1 byte, mag de invoerbestand dus voor deze methode maximaal 2147483647 / 1024 / 1024 / 1024 = 2TB groot zijn (om precies te zijn: 2TB - 1 byte). Dat is in de meeste gevallen nog altijd een aardige hoeveelheid.

Indien je veel met grote datablokken werkt, dan kun je dus beter zélf een geheugenbuffer definiëren. In dit geval nemen we als voorbeeld 10KB.

package test;

import java.io.File;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;

public class Test {

    public static void main(String[] args) {

        File inputFile = new File("c:/java/input.txt");
        File outputFile = new File("c:/java/output.txt");
        FileReader reader = null;
        FileWriter writer = null;

        try {
            reader = new FileReader(inputFile);
            writer = new FileWriter(outputFile);

            // Voorbereid de hoeveelheid karakters.
            int length = 0;

            // Declareer en initialiseer een char array met een buffer van 10KB.
            char[] buffer = new char[10240];

            // Zet de inhoud van het invoerbestand in de char array buffer.
            // Er wordt elke keer 10KB van het invoerbestand in de buffer gezet.
            // Dit gaat zolang door tot er niks meer van het invoerbestand over is.
            while ((length = reader.read(buffer)) > 0) {
                // Zet de char array buffer in het uitvoerbestand.
                writer.write(buffer, 0, length);
            } 

            System.out.println("Kopieren gelukt.");
        } catch (IOException e) {
            System.err.println("Kopieren mislukt.");
            e.printStackTrace();
        } finally {
            if (reader != null) {
                try {
                    reader.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if (writer != null) {
                try {
                    writer.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }

}

In plaats daarvan zou je ook gewoon BufferedReader en BufferedWriter kunnen gebruiken wanneer je graag grote bestanden wilt bufferen. Deze klassen regelen de buffering allemaal automatisch voor jou binnen de geheugenruimte van de JVM. Zie ook het volgende hoofdstuk hoe deze te gebruiken.

Overigens, als je verder niks met de bestanden hoeft te doen (gegevens opvragen, hernoemen, verwijderen, etc), dan kun je het bestand ook gewoon direct in de constructeur van FileReader en FileWriter definiëren in plaats van in de vorm van een File object:

            ...

            // Laat het invoerbestand als een char array inlezen.
            reader = new FileReader("c:/java/input.txt");

            // Laat het uitvoerbestand als een char array uitschrijven.
            writer = new FileWriter("c:/java/output.txt");

            ...
Terug naar boven

BufferedReader en BufferedWriter

Met BufferedReader en BufferedWriter kun je de inhoud van een willekeurige Reader respectievelijk Writer direct in een geheugenbuffer zetten en daarop te werken. Dit is sneller dan elke keer direct op het opslagmedium te werken (harde schijf, USB stick, CD/DVD, etc), met name bij grote bestanden en weinig geheugen.

package test;

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;

public class Test {

    public static void main(String[] args) {

        // Voorbereid de gebufferde reader.
        BufferedReader reader = null;

        // Voorbereid de gebufferde writer.
        BufferedWriter writer = null;
        
        try {
            // Laat het invoerbestand als een gebufferde char array inlezen.
            reader = new BufferedReader(new FileReader("c:/java/input.txt"));

            // Laat het uitvoerbestand als een gebufferde char array uitschrijven.
            writer = new BufferedWriter(new FileWriter("c:/java/output.txt"));

            // Definieer een lege regel.
            String line;
            
            // Omdat het bestand nu in een geheugenbuffer zit kunnen we
            // volledige regels eruit halen. Dat gaan we nu ook doen:
            // Doorloop alle regels ..
            while ((line = reader.readLine()) != null) {
                // .. en kopieer elke regel.
                writer.write(line);
            }
            
            // Klaar!
            System.out.println("Kopieren gelukt.");
        } catch (IOException e) {
            System.err.println("Kopieren mislukt.");
            e.printStackTrace();
        } finally {
            // Sluit de reader en de writer. Doe dit altijd in de finally blok!
            if (reader != null) {
                try {
                    reader.close();
                } catch (IOException e) {
                    // Niks aan te doen.
                    e.printStackTrace();
                }
            }
            if (writer != null) {
                try {
                    writer.close();
                } catch (IOException e) {
                    // Niks aan te doen.
                    e.printStackTrace();
                }
            }
        }
    }

}
Terug naar boven

CharArrayReader en CharArrayWriter

Je kunt een voorgedefinieerde char array dus zó in de FileWriter#write() zetten:

package test;

import java.io.FileWriter;
import java.io.IOException;

public class Test {

    public static void main(String[] args) {

        // Definieer de char array.
        char[] chars =
            new char[] {'J', 'a', 'v', 'a', ' ', 'F', 'o', 'r', 'e', 'v', 'e', 'r', '!'};

        // Zo kan het trouwens ook:
        // String string = "Java forever!";
        // char[] chars = string.toCharArray();
        // Je kunt trouwens ook een volledige String in de writer stoppen.

        // Voorbereid de writer.
        FileWriter writer = null;

        try {
            // Laat het uitvoerbestand als een char array uitschrijven.
            writer = new FileWriter("c:/java/output.txt");

            // Zet de char array in het uitvoerbestand.
            writer.write(chars);

            // Klaar!
            System.out.println("Uitschrijven gelukt.");
        } catch (IOException e) {
            System.err.println("Uitschrijven mislukt.");
            e.printStackTrace();
        } finally {
            // Sluit de writer. Doe dit altijd in de finally blok!
            if (writer != null) {
                try {
                    writer.close();
                } catch (IOException e) {
                    // Niks aan te doen.
                    e.printStackTrace();
                }
            }
        }
    }

}

Deze manier is eigenlijk niet zo netjes, want je zou geheugenproblemen kunnen krijgen bij het aanroepen van FileWriter#write() wanneer de char array groter is dan de hoeveelheid beschikbare geheugen voor de JVM. Dit is echter wel extreem snel wanneer je voldoende geheugen hebt.

Indien je veel met grote datablokken werkt, dan kun je dus beter een BufferedWriter gebruiken, maar dan moet je de voorgedefinieerde char array wel eerst in een automatisch gebufferde CharArrayReader zetten.

package test;

import java.io.BufferedWriter;
import java.io.CharArrayReader;
import java.io.FileWriter;
import java.io.IOException;

public class Test {

    public static void main(String[] args) {

        // Definieer de char array.
        char[] chars =
            new char[] {'J', 'a', 'v', 'a', ' ', 'F', 'o', 'r', 'e', 'v', 'e', 'r', '!'};

        // Zo kan het trouwens ook:
        // String string = "Java forever!";
        // char[] chars = string.toCharArray();
        // Je kunt trouwens ook een volledige String in de writer stoppen.

        // Voorbereid de gebufferde writer.
        BufferedWriter writer = null;

        try {
            // Voorbereid de char array reader.
            CharArrayReader reader = new CharArrayReader(chars);

            // Laat het uitvoerbestand als een gebufferde char array uitschrijven.
            writer = new BufferedWriter(new FileWriter("c:/java/output.txt"));
            
            // Doorloop alle karakters ..
            int data = -1;
            while ((data = reader.read()) != -1) {
                // .. en schrijf elke karakter uit.
                writer.write(data);
            }

            // Klaar!
            System.out.println("Uitschrijven gelukt.");
        } catch (IOException e) {
            System.err.println("Uitschrijven mislukt.");
            e.printStackTrace();
        } finally {
            // Sluit de writer. Doe dit altijd in de finally blok!
            if (writer != null) {
                try {
                    writer.close();
                } catch (IOException e) {
                    // Niks aan te doen.
                    e.printStackTrace();
                }
            }
        }
    }

}

Hieronder volgt het omgekeerde voorbeeld met een CharArrayWriter:

package test;

import java.io.BufferedReader;
import java.io.CharArrayWriter;
import java.io.FileReader;
import java.io.IOException;

public class Test {

    public static void main(String[] args) {

        // Voorbereid de gebufferde reader.
        BufferedReader reader = null;

        // Voorbereid de char array.
        char[] chars = null;

        try {
            // Laat het invoerbestand als een gebufferde char array inlezen.
            reader = new BufferedReader(new FileReader("c:/java/input.txt"));

            // Voorbereid de char array writer.
            CharArrayWriter writer = new CharArrayWriter();
            
            // Definieer een lege regel.
            String line;
            
            // Doorloop alle regels ..
            while ((line = reader.readLine()) != null) {
                // .. en kopieer elke regel.
                writer.write(line);
            }

            // Zet het resultaat in de byte array.
            chars = writer.toCharArray();

            // Klaar!
            System.out.println("Inlezen gelukt. Resultaat: " + new String(chars));
        } catch (IOException e) {
            System.err.println("Inlezen mislukt.");
            e.printStackTrace();
        } finally {
            // Sluit de reader. Doe dit altijd in de finally blok!
            if (reader != null) {
                try {
                    reader.close();
                } catch (IOException e) {
                    // Niks aan te doen.
                    e.printStackTrace();
                }
            }
        }
    }

}

Let op: de CharArrayReader en CharArrayWriter zijn dus automatisch gebufferd en het is derhalve volstrekt nutteloos om deze in een BufferedReader respectievelijk BufferedWriter te zetten.

Terug naar boven

FileInputStream en FileOutputStream

Met de FileInputStream en FileOutputStream kun je bestanden uitlezen en wegschrijven als een byte array. Hier is een voorbeeld in de vorm van een eenvoudige bestands-kopie:

package test;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;

public class Test {

    public static void main(String[] args) {

        // Definieer het invoerbestand.
        File inputFile = new File("c:/java/input.txt");

        // Definieer het uitvoerbestand.
        File outputFile = new File("c:/java/output.txt");

        // Voorbereid de inputstream.
        FileInputStream input = null;

        // Voorbereid de outputstream.
        FileOutputStream output = null;

        try {
            // Laat het invoerbestand als een byte array inlezen.
            input = new FileInputStream(inputFile);

            // Laat het uitvoerbestand als een byte array uitschrijven.
            output = new FileOutputStream(outputFile);

            // Doorloop alle bytes ..
            int data = -1;
            while ((data = input.read()) != -1) {
                // .. en kopieer elke byte.
                output.write(data);
            }

            // Klaar!
            System.out.println("Kopieren gelukt.");
        } catch (IOException e) {
            System.err.println("Kopieren mislukt.");
            e.printStackTrace();
        } finally {
            // Sluit de input en de output. Doe dit altijd in de finally blok!
            if (input != null) {
                try {
                    input.close();
                } catch (IOException e) {
                    // Niks aan te doen.
                    e.printStackTrace();
                }
            }
            if (output != null) {
                try {
                    output.close();
                } catch (IOException e) {
                    // Niks aan te doen.
                    e.printStackTrace();
                }
            }
        }
    }

}

De volgende manier kan trouwens ook:

package test;

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;

public class Test {

    public static void main(String[] args) {

        FileInputStream input = null;
        FileOutputStream output = null;

        try {
            input = new FileInputStream("c:/java/input.txt");
            output = new FileOutputStream("c:/java/output.txt");

            // Declareer en initialiseer een byte array. De available()
            // methode van een InputStream retourneert een int, dus je
            // kunt deze direct gebruiken als de array index.
            byte[] content = new byte[input.available()];
            
            // Zet de inhoud van het invoerbestand in de byte array.
            input.read(content);

            // Zet de byte array in het uitvoerbestand.
            output.write(content);

            System.out.println("Kopieren gelukt.");
        } catch (IOException e) {
            System.err.println("Kopieren mislukt.");
            e.printStackTrace();
        } finally {
            if (input != null) {
                try {
                    input.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if (output != null) {
                try {
                    output.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }

}

Deze manier is eigenlijk niet zo netjes, want je zou geheugenproblemen kunnen krijgen bij het initialiseren van de byte array wanneer de input.available() groter is dan de hoeveelheid beschikbare geheugen voor de JVM. Dit is echter wel véél sneller dan de andere methoden.

Indien je veel met grote datablokken werkt, dan kun je dus beter zélf een geheugenbuffer definiëren. In dit geval nemen we als voorbeeld 10KB.

package test;

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;

public class Test {

    public static void main(String[] args) {

        FileInputStream input = null;
        FileOutputStream output = null;

        try {
            input = new FileInputStream("c:/java/input.txt");
            output = new FileOutputStream("c:/java/output.txt");

            // Voorbereid de hoeveelheid bytes.
            int length = 0;

            // Declareer en initialiseer een byte array met een buffer van 10KB.
            byte[] buffer = new byte[10240];

            // Zet de inhoud van het invoerbestand in de byte array buffer.
            // Er wordt elke keer 10KB van het invoerbestand in de buffer gezet.
            // Dit gaat zolang door tot er niks meer van het invoerbestand over is.
            while ((length = input.read(buffer)) > 0) {
                // Zet de byte array buffer in het uitvoerbestand.
                output.write(buffer, 0, length);
            } 

            System.out.println("Kopieren gelukt.");
        } catch (IOException e) {
            System.err.println("Kopieren mislukt.");
            e.printStackTrace();
        } finally {
            if (input != null) {
                try {
                    input.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if (output != null) {
                try {
                    output.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }

}

In plaats daarvan zou je ook gewoon BufferedInputStream en BufferedOutputStream kunnen gebruiken wanneer je graag grote bestanden wilt bufferen. Deze klassen regelen de buffering allemaal automatisch voor jou binnen de geheugenruimte van de JVM. Zie ook het volgende hoofdstuk hoe deze te gebruiken.

Terug naar boven

BufferedInputStream en BufferedOutputStream

Met BufferedInputStream en BufferedOutputStream kun je de inhoud van een willekeurige InputStream respectievelijk OutputStream direct in een geheugenbuffer zetten en daarop te werken. Dit is sneller dan elke keer direct op het opslagmedium te werken (harde schijf, USB stick, CD/DVD, etc), met name bij grote bestanden en weinig geheugen.

package test;

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;

public class Test {

    public static void main(String[] args) {

        // Voorbereid de gebufferde inputstream.
        BufferedInputStream input = null;

        // Voorbereid de gebufferde outputstream.
        BufferedOutputStream output = null;

        try {
            // Laat het invoerbestand als een gebufferde byte array inlezen.
            input = new BufferedInputStream(new FileInputStream("c:/java/input.txt"));

            // Laat het uitvoerbestand als een gebufferde byte array uitschrijven.
            output = new BufferedOutputStream(new FileOutputStream("c:/java/output.txt"));
            
            // Doorloop alle bytes ..
            int data = -1;
            while ((data = input.read()) != -1) {
                // .. en kopieer elke byte.
                output.write(data);
            }

            // Klaar!
            System.out.println("Kopieren gelukt.");
        } catch (IOException e) {
            System.err.println("Kopieren mislukt.");
            e.printStackTrace();
        } finally {
            // Sluit de input en de output. Doe dit altijd in de finally blok!
            if (input != null) {
                try {
                    input.close();
                } catch (IOException e) {
                    // Niks aan te doen.
                    e.printStackTrace();
                }
            }
            if (output != null) {
                try {
                    output.close();
                } catch (IOException e) {
                    // Niks aan te doen.
                    e.printStackTrace();
                }
            }
        }
    }

}
Terug naar boven

ByteArrayInputStream en ByteArrayOutputStream

Je kunt een voorgedefinieerde byte array dus zó in de FileOutputStream#write() zetten:

package test;

import java.io.FileOutputStream;
import java.io.IOException;

public class Test {

    public static void main(String[] args) {

        // Definieer de byte array.
        byte[] bytes = new byte[] {74, 97, 118, 97, 32, 102, 111, 114, 101, 118, 101, 114, 33};

        // Zo kan het trouwens ook:
        // String string = "Java forever!";
        // byte[] bytes = string.getBytes();

        // Voorbereid de outputstream.
        FileOutputStream output = null;

        try {
            // Laat het uitvoerbestand als een byte array uitschrijven.
            output = new FileOutputStream("c:/java/output.txt");

            // Zet de byte array in het uitvoerbestand.
            output.write(bytes);

            // Klaar!
            System.out.println("Uitschrijven gelukt.");
        } catch (IOException e) {
            System.err.println("Uitschrijven mislukt.");
            e.printStackTrace();
        } finally {
            // Sluit de output. Doe dit altijd in de finally blok!
            if (output != null) {
                try {
                    output.close();
                } catch (IOException e) {
                    // Niks aan te doen.
                    e.printStackTrace();
                }
            }
        }
    }

}

Deze manier is eigenlijk niet zo netjes, want je zou geheugenproblemen kunnen krijgen bij het aanroepen van FileOutputStream#write() wanneer de byte array groter is dan de hoeveelheid beschikbare geheugen voor de JVM. Dit is echter wel extreem snel wanneer je voldoende geheugen hebt.

Indien je veel met grote datablokken werkt, dan kun je dus beter een BufferedOutputStream gebruiken, maar dan moet je de voorgedefinieerde byte array wel eerst in een automatisch gebufferde ByteArrayInputStream zetten.

package test;

import java.io.BufferedOutputStream;
import java.io.ByteArrayInputStream;
import java.io.FileOutputStream;
import java.io.IOException;

public class Test {

    public static void main(String[] args) {

        // Definieer de byte array.
        byte[] bytes = new byte[] {74, 97, 118, 97, 32, 102, 111, 114, 101, 118, 101, 114, 33};

        // Zo kan het trouwens ook:
        // String string = "Java forever!";
        // byte[] bytes = string.getBytes();

        // Voorbereid de gebufferde outputstream.
        BufferedOutputStream output = null;

        try {
            // Voorbereid de byte array inputstream.
            ByteArrayInputStream input = new ByteArrayInputStream(bytes);

            // Laat het uitvoerbestand als een gebufferde byte array uitschrijven.
            output = new BufferedOutputStream(new FileOutputStream("c:/java/output.txt"));
            
            // Doorloop alle bytes ..
            int data = -1;
            while ((data = input.read()) != -1) {
                // .. en kopieer elke byte.
                output.write(data);
            }

            // Klaar!
            System.out.println("Uitschrijven gelukt.");
        } catch (IOException e) {
            System.err.println("Uitschrijven mislukt.");
            e.printStackTrace();
        } finally {
            // Sluit de output. Doe dit altijd in de finally blok!
            if (output != null) {
                try {
                    output.close();
                } catch (IOException e) {
                    // Niks aan te doen.
                    e.printStackTrace();
                }
            }
        }
    }

}

Hieronder volgt het omgekeerde voorbeeld met een ByteArrayOutputStream:

package test;

import java.io.BufferedInputStream;
import java.io.ByteArrayOutputStream;
import java.io.FileInputStream;
import java.io.IOException;

public class Test {

    public static void main(String[] args) {

        // Voorbereid de gebufferde inputstream.
        BufferedInputStream input = null;

        // Voorbereid de byte array.
        byte[] bytes = null;

        try {
            // Laat het invoerbestand als een gebufferde byte array inlezen.
            input = new BufferedInputStream(new FileInputStream("c:/java/input.txt"));

            // Vraag de hoeveelheid bytes op.
            int length = input.available();

            // Voorbereid de byte array outputstream.
            ByteArrayOutputStream output = new ByteArrayOutputStream(length);

            // Doorloop alle bytes ..
            int data = -1;
            while ((data = input.read()) != -1) {
                // .. en kopieer elke byte.
                output.write(data);
            }

            // Zet het resultaat in de byte array.
            bytes = output.toByteArray();

            // Klaar!
            System.out.println("Inlezen gelukt. Resultaat: " + new String(bytes));
        } catch (IOException e) {
            System.err.println("Inlezen mislukt.");
            e.printStackTrace();
        } finally {
            // Sluit de input. Doe dit altijd in de finally blok!
            if (input != null) {
                try {
                    input.close();
                } catch (IOException e) {
                    // Niks aan te doen.
                    e.printStackTrace();
                }
            }
        }
    }

}

Let op: de ByteArrayInputStream en ByteArrayOutputStream zijn dus automatisch gebufferd en het is derhalve volstrekt nutteloos om deze in een BufferedInputStream respectievelijk BufferedOutputStream te zetten.

Terug naar boven

Converteren van InputStream naar Reader

Met InputStreamReader kun je de inhoud van een willekeurige byte array InputStream converteren naar een char array Reader. Dit is handig wanneer je platte tekstbestanden in vorm van een byte array krijgt aangeleverd (uit een ander programma, uit het geheugen, uit een file-upload element op een Java EE webapplicatie, etcetera).

package test;

import java.io.FileInputStream;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStreamReader;

public class Test {

    public static void main(String[] args) {

        // Voorbereid de reader.
        InputStreamReader reader = null;

        // Voorbereid de writer.
        FileWriter writer = null;

        try {
            // Voorbereid het invoerbestand als een byte array.
            FileInputStream input = new FileInputStream("c:/java/input.txt");

            // Laat het invoerbestand als een char array inlezen.
            reader = new InputStreamReader(input);

            // Laat het uitvoerbestand als een char array uitschrijven.
            writer = new FileWriter("c:/java/output.txt");

            // Doorloop alle karakters ..
            int data = -1;
            while ((data = reader.read()) != -1) {
                // .. en kopieer elke karakter.
                writer.write(data);
            }

            // Klaar!
            System.out.println("Kopieren gelukt.");
        } catch (IOException e) {
            System.err.println("Kopieren mislukt.");
            e.printStackTrace();
        } finally {
            // Sluit de reader en de writer. Doe dit altijd in de finally blok!
            if (reader != null) {
                try {
                    reader.close();
                } catch (IOException e) {
                    // Niks aan te doen.
                    e.printStackTrace();
                }
            }
            if (writer != null) {
                try {
                    writer.close();
                } catch (IOException e) {
                    // Niks aan te doen.
                    e.printStackTrace();
                }
            }
        }
    }

}
Terug naar boven

Converteren van Writer naar OutputStream

Met OutputStreamWriter kun je een byte array OutputStream voorbereiden voor de invoer van een char array. Het doet zich dus voor als een Writer, maar het schrijft weg in vorm van een byte array. Dit is bijvoorbeeld handig wanneer je zelf geschreven binaire bestanden wilt wegschrijven.

package test;

import java.io.File;
import java.io.FileOutputStream;
import java.io.FileReader;
import java.io.IOException;
import java.io.OutputStreamWriter;

public class Test {

    public static void main(String[] args) {

        // Definieer het bestand.
        File inputFile = new File("c:/java/input.txt");

        // Voorbereid de reader.
        FileReader reader = null;

        // Voorbereid de writer.
        OutputStreamWriter writer = null;

        try {
            // Laat het invoerbestand als een char array inlezen.
            reader = new FileReader(inputFile);

            // Voorbereid het uitvoerbestand als een byte array.
            FileOutputStream output = new FileOutputStream("c:/java/output.txt");

            // Laat het uitvoerbestand als een char array uitschrijven.
            writer = new OutputStreamWriter(output);

            // Doorloop alle karakters ..
            int data = -1;
            while ((data = reader.read()) != -1) {
                // .. en kopieer elke karakter.
                writer.write(data);
            }

            // Klaar!
            System.out.println("Kopieren gelukt.");
        } catch (IOException e) {
            System.err.println("Kopieren mislukt.");
            e.printStackTrace();
        } finally {
            // Sluit de reader en de writer. Doe dit altijd in de finally blok!
            if (reader != null) {
                try {
                    reader.close();
                } catch (IOException e) {
                    // Niks aan te doen.
                    e.printStackTrace();
                }
            }
            if (writer != null) {
                try {
                    writer.close();
                } catch (IOException e) {
                    // Niks aan te doen.
                    e.printStackTrace();
                }
            }
        }
    }

}
Terug naar boven

De bestanden worden overgeschreven?

Inderdaad, het overschrijven van eventueel bestaande bestanden is de standaard gedrag van de Writer en de OutputStream. Maar deze interfaces kennen een tweede constructeur, waarbij je met een boolean true kunt aangeven dat het te schrijven bestand niet overgeschreven mag worden. De geleverde data wordt dan gewoon achteraan toegevoegd.

Dit kun je dus als volgt aangeven:

        // Writer: geef aan dat een bestaand bestand behouden moet worden.
        FileWriter writer = new FileWriter("c:/java/output.txt", true);

        // OutputStream: geef aan dat een bestaand bestand behouden moet worden.
        FileOutputStream output = new FileOutputStream("c:/java/output.txt", true);
Terug naar boven

Copyright - Er is geen copyright op de code. Je kunt het naar believen overnemen, aanpassen danwel verspreiden.

(C) Juni 2006, BalusC

No comments: