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

Tuesday, June 13, 2006

Parse CSV upload

How easy can it be done?

CSV files are commaseparated files and can be opened and saved using Microsoft Excel. However, in most locales (at least US International and Europe) Excel uses a semicolon instead of a comma to separate fields. With the correct field separator the CSV files will be opened automatically in the right rows and columns in Excel. And saving an Excel table as CSV without using any wizard will deliver CSV files with the locale default field separation.

According to the RFC 4180 spec there are 3 important rules to keep in mind (you may also read "comma" as "semicolon"):

  1. Fields are separated by a comma.
  2. If a comma occurs within a field, then the field have to be surrounded by double quotes.
  3. If a double quote occurs within a field, then the field have to be surrounded by double quotes and the double quote within the field have to be escaped by another double quote.

Valid CSV records are:

field1,field2,field3
"field1,",field2,"fie""ld3"
"""field1""",",field2,",","","","""

Which should be parsed as:

field1    field2    field3
field1,   field2    fie"ld3
"field1"  ,field2,  ,",","

Back to top

Uploading a CSV file using IBM faces

Here is a basic example of the JSF code:

<hx:scriptCollector id="scriptCollector1">
    ...
    <h:form id="csvupload">
        <hx:fileupload 
            id="csvfile"
            binding="#{myBean.csvFile}"
            accept="application/vnd.ms-excel"
        />
        <h:message for="csvfile" />
        <h:commandButton
            value="upload"
            action="#{myBean.uploadCsvFile}"
        />
    </h:form>
    ...
</hx:scriptCollector>

The h:message is a placeholder for any errormessage from the hx:fileupload if the application type of the uploaded file don't match the MIME type application/vnd.ms-excel. If you don't need the accept attribute of the hx:fileupload, then just leave this attribute and the h:message away.

The relevant java code for the backing bean MyBean.java:

package mypackage;

import java.io.ByteArrayInputStream;
import java.util.List;

import com.ibm.faces.component.html.HtmlFileupload;
import com.ibm.faces.fileupload.util.ContentElement;

import net.balusc.util.CsvUtil;

public class MyBean {

    // Init --------------------------------------------------------------------------------------

    private HtmlFileUpload csvFile;

    // Actions -----------------------------------------------------------------------------------

    public void uploadCsvFile() {

        // Get uploaded csv file.
        ContentElement csvContent = (ContentElement) csvFile.getValue();

        // Process uploaded csv file.
        if (csvContent != null) {
            ByteArrayInputStream csvInput = new ByteArrayInputStream(csvContent.getContentValue());
            List<List<String>> csvList = CsvUtil.parseCsv(csvInput, ';');
            // Now you can do your thing with the CSV List.
        } else {
            // Empty file error, do your thing.
        }
    }

    // Getters -----------------------------------------------------------------------------------

    public HtmlFileupload getCsvFile() {
        return csvFile;
    }
    
    // Setters -----------------------------------------------------------------------------------

    public void setCsvFile(HtmlFileupload csvFile) {
        this.csvFile = csvFile;
    }

}

The useful CsvUtil class is described in the next chapter:

Back to top

Parsing and formatting the CSV file

This kind of a CSV parser accepts an InputStream which can be feeded by a file upload, reading a local file or any other kind of a binary stream. And it returns a two-dimensional List. The first List contains the CSV records in the appropriate order and the second List contains the fields of each CSV record in the appropriate order.

Formatting a CSV is also relatively easy: just turn the parser around. The CSV formatter should accept a two-dimensional List and return an InputStream with which you can do anything. Saving to a file or streaming it to the response as a file download.

/*
 * net/balusc/util/CsvUtil.java
 * 
 * Copyright (C) 2006 BalusC
 * 
 * This program is free software: you can redistribute it and/or modify it under the terms of the
 * GNU Lesser General Public License as published by the Free Software Foundation, either version 3
 * of the License, or (at your option) any later version.
 * 
 * This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without
 * even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 * 
 * You should have received a copy of the GNU Lesser General Public License along with this library.
 * If not, see <http://www.gnu.org/licenses/>.
 */

package net.balusc.util;

import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

/**
 * Useful CSV utilities.
 * 
 * @author BalusC
 * @link http://balusc.blogspot.com/2006/06/parse-csv-upload.html
 */
public class CsvUtil {

    // Init ---------------------------------------------------------------------------------------

    // Defaults.
    private static final char DEFAULT_CSV_SEPARATOR = ',';
    private static final String DEFAULT_LINE_SEPARATOR = "\r\n"; // CRLF.

    private CsvUtil() {
        // Utility class, hide the constructor.
    }

    // Parsers ------------------------------------------------------------------------------------

    /**
     * CSV content parser. Convert an InputStream with the CSV contents to a two-dimensional List
     * of Strings representing the rows and columns of the CSV. Each CSV record is expected to be
     * separated by the default CSV field separator, a comma.
     * @param csvInput The InputStream with the CSV contents.
     * @return A two-dimensional List of Strings representing the rows and columns of the CSV.
     */
    public static List<List<String>> parseCsv(InputStream csvInput) {
        return parseCsv(csvInput, DEFAULT_CSV_SEPARATOR);
    }

    /**
     * CSV content parser. Convert an InputStream with the CSV contents to a two-dimensional List
     * of Strings representing the rows and columns of the CSV. Each CSV record is expected to be
     * separated by the specified CSV field separator.
     * @param csvInput The InputStream with the CSV contents.
     * @param csvSeparator The CSV field separator to be used.
     * @return A two-dimensional List of Strings representing the rows and columns of the CSV.
     */
    public static List<List<String>> parseCsv(InputStream csvInput, char csvSeparator) {

        // Prepare.
        BufferedReader csvReader = null;
        List<List<String>> csvList = new ArrayList<List<String>>();
        String csvRecord = null;

        // Process records.
        try {
            csvReader = new BufferedReader(new InputStreamReader(csvInput, "UTF-8"));
            while ((csvRecord = csvReader.readLine()) != null) {
                csvList.add(parseCsvRecord(csvRecord, csvSeparator));
            }
        } catch (IOException e) {
            throw new RuntimeException("Reading CSV failed.", e);
        } finally {
            if (csvReader != null) try {
                csvReader.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }

        return csvList;
    }

    /**
     * CSV record parser. Convert a CSV record to a List of Strings representing the fields of the
     * CSV record. The CSV record is expected to be separated by the specified CSV field separator.
     * @param record The CSV record.
     * @param csvSeparator The CSV field separator to be used.
     * @return A List of Strings representing the fields of each CSV record.
     */
    private static List<String> parseCsvRecord(String record, char csvSeparator) {

        // Prepare.
        boolean quoted = false;
        StringBuilder fieldBuilder = new StringBuilder();
        List<String> fields = new ArrayList<String>();

        // Process fields.
        for (int i = 0; i < record.length(); i++) {
            char c = record.charAt(i);
            fieldBuilder.append(c);

            if (c == '"') {
                quoted = !quoted; // Detect nested quotes.
            }

            if ((!quoted && c == csvSeparator) // The separator ..
                || i + 1 == record.length()) // .. or, the end of record.
            {
                String field = fieldBuilder.toString() // Obtain the field, ..
                    .replaceAll(csvSeparator + "$", "") // .. trim ending separator, ..
                    .replaceAll("^\"|\"$", "") // .. trim surrounding quotes, ..
                    .replace("\"\"", "\""); // .. and un-escape quotes.
                fields.add(field.trim()); // Add field to List.
                fieldBuilder = new StringBuilder(); // Reset.
            }
        }

        return fields;
    }

    // Formatters --------------------------------------------------------------------------------

    /**
     * CSV content formatter. Convert a two-dimensional List of Objects to a CSV in an InputStream.
     * The value of each Object will be obtained by its toString() method. The fields of each CSV 
     * record will be separated by the default CSV field separator, a comma.
     * @param csvList A two-dimensional List of Objects representing the rows and columns of the
     * CSV.
     * @return The InputStream containing the CSV contents (actually a ByteArrayInputStream).
     */
    public static <T extends Object> InputStream formatCsv(List<List<T>> csvList) {
        return formatCsv(csvList, DEFAULT_CSV_SEPARATOR);
    }

    /**
     * CSV content formatter. Convert a two-dimensional List of Objects to a CSV in an InputStream.
     * The value of each Object will be obtained by its toString() method. The fields of each CSV
     * record will be separated by the specified CSV field separator.
     * @param csvList A two-dimensional List of Objects representing the rows and columns of the
     * CSV.
     * @param csvSeparator The CSV field separator to be used.
     * @return The InputStream containing the CSV contents (actually a ByteArrayInputStream).
     */
    public static <T extends Object> InputStream formatCsv(List<List<T>> csvList, char csvSeparator) {

        // Prepare.
        StringBuilder csvContent = new StringBuilder();

        // Process records.
        for (List<T> csvRecord : csvList) {
            if (csvRecord != null) {
                csvContent.append(formatCsvRecord(csvRecord, csvSeparator));
            }

            // Add default line separator.
            csvContent.append(DEFAULT_LINE_SEPARATOR);
        }

        return new ByteArrayInputStream(csvContent.toString().getBytes());
    }

    /**
     * CSV record formatter. Convert a List of Objects representing the fields of a CSV record to a
     * String representing the CSV record. The value of each Object will be obtained by its
     * toString() method. The fields of the CSV record will be separated by the specified CSV field
     * separator.
     * @param csvRecord A List of Objects representing the fields of a CSV reecord.
     * @param csvSeparator The CSV field separator to be used.
     * @return A String representing a CSV record.
     */
    private static <T extends Object> String formatCsvRecord(List<T> csvRecord, char csvSeparator) {

        // Prepare.
        StringBuilder fields = new StringBuilder();
        String separator = String.valueOf(csvSeparator);

        // Process fields.
        for (Iterator<T> iter = csvRecord.iterator(); iter.hasNext();) {
            T object = iter.next();

            if (object != null) {
                String field = object.toString().replace("\"", "\"\""); // Escape quotes.

                if (field.contains(separator) || field.contains("\"")) {
                    field = "\"" + field + "\""; // Surround with quotes.
                }

                fields.append(field);
            }

            if (iter.hasNext()) {
                fields.append(separator); // Add field separator.
            }
        }

        return fields.toString();
    }

}
Back to top

CSV formatting example

Here is an example how to create, format and save a CSV file quickly:

package mypackage;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

import net.balusc.util.CsvUtil;
import net.balusc.util.FileUtil;

public class Test {

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

        // Create CSV.
        List<List<String>> csvList = new ArrayList<List<String>>();
        csvList.add(Arrays.asList("field1", "field2", "field3"));
        csvList.add(Arrays.asList("field1,", "field2", "fie\"ld3"));
        csvList.add(Arrays.asList("\"field1\"", ",field2,", ",\",\",\""));

        // Format CSV.
        InputStream csvInput = CsvUtil.formatCsv(csvList, ';');

        // Save CSV.
        FileUtil.write(new File("c:/test.csv"), csvInput);
    }

}

You can find the FileUtil here by the way.

Back to top

Copyright - GNU Lesser General Public License

(C) June 2006, BalusC