1. Tiedostot

Tällää tiedostoon rivitkin

virtaan viskaile tavuset

sinne sullo saatavaksi

muitten metsästettäväksi.

 

Rivit riivi tiedostosta

ime virrasta tavuset

sieltä sieppaa saataville

levyltä lue lukuset.

 

Pistele rivit paloiksi

kunnolla katko kummajaiset

sanat sieltä sommittele

numerotkin napsi niistä.

 

 

Mitä tässä luvussa käsitellään?

·      Tiedostojen käsittely Javan tietovirroilla

·      Tiedostot joissa rivillä monta kenttää

 

Syntaksi:

Tied. avaaminen luku: f = new BufferedReader(new FileReader(nimi));

                kirj. f = new PrintWriter(new FileWriter(nimi))

                jatk. f = new PrintWriter(new FileWriter(nimi,true))

Stream          kirj: f = new PrintStream(new FileOutputStream(nimi));

                jatk: f = new PrintStream(new FileOutputStream(nimi),true);                      Lukeminen       s = f.readLine();

Kirjoittaminen  f.println(s);

Sulkeminen      f.close();  // aina finally lohkossa!

 

Luvun esimerkkikoodit:

http://www.mit.jyu.fi/~vesal/kurssit/ohj2/moniste/esim/tiedosto/

 

Pyrimme seuraavaksi lisäämään kerho–ohjelmaamme tiedostosta lukemisen ja tiedostoon tallettamisen.  Tätä varten tutustumme ensin lukemiseen mahdollisesti liittyviin ongelmiin. 

1.1 Tiedostojen käsittely

Tiedostojen käsittely ei eroa päätesyötöstä ja tulostuksesta, siis tiedostojen käyttöä ei ole mitenkään syytä vierastaa!  Itse asiassa päätesyöttö ja tulostus ovat vain stdin ja stdout –nimisten tiedostojen käsittelyä.

Tiedostoja on kahta päätyyppiä:  tekstitiedostot ja binääritiedostot.  Tekstitiedostojen etu on siinä, että ne käyttäytyvät täsmälleen samoin kuin päätesyöttökin.  Binääritiedoston etu on taas siinä, että talletus saattaa viedä vähemmän tilaa (erityisesti numeerista muotoa olevat tyypit) ja suorasaannin käyttö on järkevämpää.

Keskitymme aluksi tekstitiedostoihin, koska niitä voidaan tarvittaessa editoida tavallisella editorilla.  Näin ohjelman testaaminen helpottuu, kun tiedosto voidaan rakentaa valmiiksi ennen kuin ohjelmassa edes on päätesyöttöä lukevaa osaa.

1.1.1 Lukeminen

Muutamme hieman alkuperäistä suunnitelmaamme jäsenrekisteritiedoston sisällöstä:

Kelmien kerho ry

100

; Kenttien järjestys tiedostossa on seuraava:

id| nimi         |sotu       |katuosoite  |postinumero|postiosoite|kotipuhelin...

1|Ankka Aku      |010245–123U|Ankkakuja 6 |12345      |ANKKALINNA |12–12324   ...

2|Susi Sepe      |020347–123T|            |12555      |Perämetsä  |           ...

3|Ponteva Veli   |030455–3333|            |12555      |Perämetsä  |           ...

 

Olemme lisänneet rivin, jossa kerrotaan tiedoston maksimikoko.  Tätähän tarvittiin jäsenlistan luomisessa.  Nyt kokoa voidaan tarvittaessa muuttaa tiedostosta tekstieditorilla tarvitsematta tietää ohjelmoinnista mitään.

Tiedoston sisällössä on kuitenkin pieni ongelma:  siinä on sekaisin sekä puhtaita merkkijonoja, numeroita että tietuetyyppisiä rivejä.  Vaikka kielessä onkin työkalut sekä numeeristen tietojen lukemiseksi tiedostosta, että merkkijonojen lukemiseen, nämä työkalut eivät välttämättä toimi yksiin.  Siksi usein kannattaa käyttää lukemiseen vain yhtä työkalua, joka useimmiten on kokonaisen tiedoston rivin lukeminen.

1.2 Tiedostojen käsittely Javan tietovirroilla

Javan IO-systeemi on varsin monimutkainen.  Erilaisia tietovirtoja on yli 60 kappaletta.  Alimman tason virta-luokat ovat abstrakteja luokkia määräten vain virtojen rajapinnan.  Ylemmällä tasolla hoidetaan fyysistä lukemista ja kirjoittamista.  Fyysinen lukeminen ja kirjoittaminen voi tarkoittaa levyn käyttöä, verkon käyttöä tai muiden IO-porttien käyttöä.   Seuraavaksi ylemmällä tasolla tarjotaan yksinkertaisempaa rajapintaa esimerkiksi rivien käsittelyyn.  Siksi virtoja käytettäessä niitä pitää kerrostaa.

Kun perustoimet on saatu tehtyä, on tiedostojen käsittely Javassa esimerkiksi System.in ja System.out –tietovirtoja vastaavien tietovirtojen käsittelyä. 

Olkoon meillä tiedosto nimeltä luvut.dat:

13.4

23.6

kissa

1.9

<EOF>     <–  ei aina välttämättä mikään merkki

 

Kirjoitetaan esimerkkitiedoston luvut lukeva ohjelma Javan tietovirroilla.  Tarkoitus on hylätä ne rivit, joilla ei ole pelkästään reaalilukua:

tiedosto\Tied_ka.java - Lukujen lukeminen tiedostosta

import java.io.*;

import fi.jyu.mit.ohj2.Mjonot;

/**

 * Lukujen lukeminen tiedostosta

 * @author Vesa Lappalainen

 * @version 1.0, 07.03.2003

 */

public class Tied_ka {

 

  public static void main(String[] args)  {

 

    BufferedReader fi;

 

    try// Avataan tiedosto lukemista varten

      fi = new BufferedReader(new FileReader("luvut.dat"));

    } catch (FileNotFoundException ex) {

      System.out.println("Tiedosto ei aukea!");

      return;

    }

 

    double summa=0;

    int n=0;

 

    try {

      String s; double luku;

      while ( ( s = fi.readLine() ) != null ) {

        try {

          luku = Double.parseDouble(s);

        } catch (NumberFormatException ex) {

          continue;

        }

        summa += luku;

        n++;

      }

    } catch (IOException ex) {

      System.out.println("Virhe tiedostoa luettaessa!");

    } finally// Aina ehdottomasti finally:ssa resurssien vapautus

      try {

        fi.close();  // tiedoston sulkeminen heti kun sitä ei enää tarvita

      } catch (IOException ex) {

        System.out.println("Tiedostoa ei saa suljettua!");

      }

    }

 

    double ka = 0;

    if ( n > 0 ) ka = summa/n;

    System.out.println("Lukuja oli " + n + " kappaletta.");

    System.out.println("Niiden summa oli " + Mjonot.fmt(summa,4,2));

    System.out.println("ja keskiarvo oli " + Mjonot.fmt(ka,4,2));

 

  }

}

 

Tehtävä 17.1  Tiedoston lukujen summa

1.   Muuta tiedoston Tied_ka.java –ohjelmaa siten, että väärän rivin kohdalla tulostetaan väärä rivi ja lopetetaan koko ohjelma.

2.   Muuta edelleen ohjelmaa siten, että väärät rivit tulostetaan näyttöön:

Tiedostossa oli seuraavat laittomat rivit:

kissa

Lukuja oli...

      Ilmoitusta ei tietenkään tule, mikäli tiedostossa ei ole laittomia merkkejä.  Tyhjää riviä ei tulkita vääräksi riviksi.

 

1.2.1 Tiedoston avaaminen muodostajassa

Tiedosto voidaan siis avata heti kun tiedostoa vastaava tietovirta luodaan:

new FileReader("luvut.dat")

 

Parametri ”luvut.dat” on tiedoston nimi levyllä.  Nimi voi sisältää myös hakemistopolun, mutta tätä kannattaa välttää, koska hakemistot eivät välttämättä ole samanlaisia kaikkien käyttäjien koneissa.  Jos hakemistopolkuja käyttää, niin erottimena kannattaa käyttää /-merkkiä.  Samoin kannattaa olla tarkkana isojen ja pienien kirjainten kanssa, sillä useissa käyttöjärjestelmissä kirjainten koolla on väliä.

Lukemista varten avattaessa tiedoston täytyy olla olemassa tai avaus epäonnistuu.  Avauksen epäonnistumisesta heitetään FileNotFoundException-poikkeus.

1.2.2 Tiedostosta lukeminen.

Tiedostosta lukeminen on jälleen analogista päätesyötön kanssa:

s = fi.readLine();

 

Mikäli tiedosto on loppu, saa s null-arvon.

1.2.3 Tiedoston lopun testaaminen

Helpoin ratkaisu on perustaa lukusilmukka siihen, että yritetään lukea kokonainen tiedoston rivi ja jos tämä epäonnistuu, on tiedostokin todennäköisesti loppu.

while ( ( s = fi.readLine() ) != null ) {

  ... käsittele jonoa s

}

 

1.2.4  Tiedostoon kirjoittaminen

Vastaavasti kirjoittamista varten avattuun tiedostoon kirjoitettaisiin

PrintStream fo;

...

fo = new PrintStream(new FileOutputStream("taulu.txt"));

// avataan tiedosto kirjoittamista varten

// avauksessa vanha tiedosto tuhoutuu

 

Mikäli avattaessa tiedostoa kirjoittamista varten, ei haluta tuhota vanhaa sisältöä, vaan kirjoittaa vanhan perään, käytetään avauksessa toista parametria, jolla kerrotaan halutaanko kirjoittaa edellisen tiedoston perään (append):

fo = new PrintStream(new FileOutputStream("taulu.txt",true));

// avataan perään kirjoittamista varten

 

Tiedoston jatkaminen on erittäin kätevä esimerkiksi virhelogitiedostoja kirjoitettaessa.

tiedosto\Kertotaulu.java - Tiedostoon tulostaminen

import java.io.*;

/**

 * Ohjelmalla tulostetaan kertotaulu tiedostoon.  Jos tiedosto on

 * olemassa, jatketaan vanhan tiedoston perään.

 * @author Vesa Lappalainen

 * @version 1.0, 21.02.2003

 */

public class Kertotaulu {

 

  public static void main(String[] args)  {

    PrintStream fo = null;

    try {

      fo = new PrintStream(new FileOutputStream("taulu.txt",true));

    }  catch (FileNotFoundException ex) {

      System.out.println("Tiedosto ei aukea"); return;

    }

 

    int kerroin = 5;

 

    try {

      for (int i=0; i<10; i++)

        fo.println( i + "*" + kerroin + " = " + i*kerroin);

    } finally {

      fo.close();

    }

  }

}

 

Edellä voisi käyttää PrintStream virran sijasta PrintWriter-luokkaa, joka olisi yhteensopivampi Reader-luokan kanssa:

PrintWriter fo;

...

  fo = new PrintWriter(new FileWriter(nimi,true))

 

Kuitenkin PrintStream on taas yhteensopiva System.out:in kanssa, joten joissakin tapauksissa tämä puolustaa PrintStream-luokan käyttämistä.

Useimmiten kannattaa kaikki näyttöön tulostavat aliohjelmat/metodit kirjoittaa sellaiseksi, että niille viedään parametrinä se tietovirta, johon tulostetaan.  Näin samalla aliohjelmalla voidaan helposti tulostaa sitten näyttöön tai tiedostoon tai jopa kirjoittimelle (joka on vain yksi tietovirta muiden joukossa, esim. Windowsissa PRN-niminen tiedosto).

1.2.5 Tiedoston sulkeminen close

Avattu tiedosto on aina lukemisen tai kirjoittamisen lopuksi syytä sulkea.  Tiedoston käsittely on usein puskuroitua, eli esimerkiksi kirjoittaminen tapahtuu ensin apupuskuriin, josta se kirjoittuu fyysisesti levylle vain puskurin täyttyessä tai tiedoston sulkemisen yhteydessä.  Käyttöjärjestelmä päivittää tiedoston koon levylle usein vasta sulkemisen jälkeen.  Sulkemattoman tiedoston koko saattaa näyttää 0 tavua.

Javassa tiedoston sulkeminen pitää aina varmistaa try-finally-lohkolla:

    ... avaa tiedosto 

    try {

      ... käsittele tiedostoa

    } finally// Aina ehdottomasti finally:ssa resurssien vapautus

      try {

        fi.close();  // tiedoston sulkeminen heti kun sitä ei enää tarvita

      } catch (IOException ex) {

        ... toimenpiteet jos tiedostoa ei saada suljettua

      }

    }

 

 

Tiedosto kannattaa sulkea heti kun sen käyttö on loppu.

Tehtävä 17.2  Kommentit näytölle

Kirjoita ohjelma, joka kysyy tiedoston nimen ja tämän jälkeen tulostaa tiedostosta rivien /******* ja––––––*/välisen osan näytölle.

1.3 Tiedoston yhdellä rivillä monta kenttää

Jäsenrekisterissä on tiedoston yhdellä rivillä useita kenttiä.  Kentät saattavat olla myös eri tyyppisiä.  Miten lukeminen hoidetaan varmimmin? 

1.3.1 Ongelma

Olkoon meillä vaikkapa seuraavanlainen tiedosto:

tiedosto\tuotteet.dat - esimerkkitiedosto

    Volvo |  12300 | 1

    Audi  |  55700 | 2

    Saab  |   1500 | 4

    Volvo | 123400 | 1<EOF>

 

Tiedostoa voitaisiin periaatteessa niin että luetaan ensin yksi merkkijono, sitten tolppa, sitten reaaliluku, tolppa ja lopuksi kokonaisluku.

Ratkaisussa on kuitenkin seuraavia huonoja puolia:

·      mikäli tiedoston loppu ei olekaan viimeisen rivin lopussa, tulee ”ylimääräisen” rivin käsittelystä ongelmia

·      mikäli jokin rivi on väärää muotoa, menee ohjelma varsin sekaisin

Tehtävä 17.3  Ohjelman "sekoaminen"

Jos esimerkin hahmotellussa ratkaisussa olisi silmukka, joka tulostaa tiedot kunkin lukemisen jälkeen, niin mitä tulostuisi seuraavasta tiedostosta:

 

    Volvo |  12300 | 1

    Audi     55700 | 2

    Saab  |   1500 | 4

    Volvo | 123400 | 1

<EOF>

 

1.4 Merkkijonon paloittelu

Tutkitaanpa ongelmaa tarkemmin.  Tiedostosta on siis luettu rivi, joka on muotoa

 ┌─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┐

 │ │V│o│l│v│o│ │|│ │ │1│2│3│0│0│ │|│ │1│

 └─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┘

 

Jos saisimme erotettua tästä 3 merkkijonoa:

 pala1               pala2              pala3

 ┌─┬─┬─┬─┬─┬─┬─┬     ┬─┬─┬─┬─┬─┬─┬─┬─┬  ┬─┬─┬ 

 │ │V│o│l│v│o│ │     │ │ │1│2│3│0│0│ │  │ │1│

 └─┴─┴─┴─┴─┴─┴─┴     ┴─┴─┴─┴─┴─┴─┴─┴─┴  ┴─┴─┴

 

voisimme kustakin palasesta erikseen ottaa haluamme tiedot.  Esimerkiksi 1. palasesta saadaan tuotteen nimi, kun siitä poistetaan turhat välilyönnit. Hinta saataisiin 2. palasesta kutsulla

sscanf(pala2.c_str(),"%lf",&hinta);

 

1.4.1 parse

Merkkijono pitää varsin usein muuttaa reaaliluvuksi tai kokonaisluvuksi.  Java tarjoaa luokissa Integer ja Double mahdollisuuden muuttaa merkkijono vastaavaksi lukutyypiksi:

double d = Double.parseDouble(jono);

int i = Integer.parseInt(jono);

 

Edellän mainitut metodit heittävät poikkeuksen jos jono sisältää mitä tahansa muuta kuin pelkkiä lukuun kuuluvia merkkejä.

Siksi kirjoitammekin luokkaan Mjonot kaksi funktiota erotaDouble ja erotaInt:

public static double erotaDouble(String jono, double oletus) ...

public static double erotaInt(String jono, int oletus) ...

 

Jos funktio ei löydä merkkijonosta lukua, se palauttaa oletuksen. Nämä funktiot toimivat oikein myös seuraavien jonojen kanssa:

12.34 e   => 12.34

14 kpl    => 14

1.4.2 erota

Tehdään myös yleiskäyttöinen funktio erota, jonka tehtävä on ottaa merkkijonon alkuosa valittuun merkkiin saakka, poistaa valittu merkki ja palauttaa sitten funktion tuloksena tämä alkuosa.  Itse merkkijonoon jää jäljelle ensimmäisen merkin jälkeinen osa.  Funktio on kirjoitettu tiedostoon Mjonot.java:

  public static String erota(StringBuffer jono, char merkki,

                             boolean etsitakaperin) {

    if ( jono == null ) return "";

    int p;

    if ( !etsitakaperin ) p = jono.indexOf(""+merkki);

    else p = jono.lastIndexOf(""+merkki);

    String alku;

    if ( p < 0 ) {

      alku = jono.toString();

      jono.delete(0,jono.length());

      return alku;

    }

    alku = jono.substring(0,p);

    jono.delete(0,p+1);

    return alku;

  }

 

}

 

1.4.3 Esimerkki erota-funktion käytöstä

Kirjoitetaan lyhyt esimerkki, jolla demonstroidaan funktion käyttöä:

tiedosto\ErotaEsim.java - esimerkki erota-funktion käytöstä 

import fi.jyu.mit.ohj2.Mjonot;

 

/**

 * Ohjelmalla demonstroidaan erota-funktion toimintaa

 * @author Vesa Lappalainen

 * @version 1.0, 21.02.2003

 */

public class ErotaEsim {

 

  private static void tulosta(int n,String pala, StringBuffer jono)

  {

    int valeja = 10-pala.length();

    System.out.println(n + ": pala = '" + Mjonot.fmt(pala + "'",-10) +

                       " jono = '" + jono + "'");

  }

 

  public static void main(String[] args)  {

    StringBuffer jono = new StringBuffer(" Volvo |  12300 | 1");

    String pala="";                  tulosta(0,pala,jono);

    pala = Mjonot.erota(jono,'|');   tulosta(1,pala,jono);

    pala = Mjonot.erota(jono,'|');   tulosta(2,pala,jono);

    pala = Mjonot.erota(jono,'|');   tulosta(3,pala,jono);

    pala = Mjonot.erota(jono,'|');   tulosta(4,pala,jono);

 

  }

}

 

 

Ohjelma tulostaa:

0: pala = ''          jono = ' Volvo |  12300 | 1'

1: pala = ' Volvo '   jono = '  12300 | 1'

2: pala = '  12300 '  jono = ' 1'

3: pala = ' 1'        jono = ''

4: pala = ''          jono = ''

 

1.4.4 Erota funktion toiminta vaihe vaiheelta

Ennen ensimmäistä kutsua tilanne on seuraava:

 pala                   jono

┌┐                     ┌─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┐

││                     │ │V│o│l│v│o│ │|│ │ │1│2│3│0│0│ │|│ │1│

└┘                     └─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┘

 0                      0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8

 

Ensimmäisessä kutsussa erota-funktio löytää etsittävän | -merkin paikasta 7. Merkit 0-6 kopioidaan funktion paluuarvoksi ja sitten jonosta tuhotaan merkit 0-7.  Funktion paluuarvo sijoitetaan muuttujaan pala:

 pala                   jono

┌─┬─┬─┬─┬─┬─┬─┐        ┌─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┐

│ │V│o│l│v│o│ │        │ │ │1│2│3│0│0│ │|│ │1│

└─┴─┴─┴─┴─┴─┴─┘        └─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┘

 0 1 2 3 4 5 6          0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8

 

Seuraavalla kutsulla (kerta 2) |-merkki löytyy jonosta paikasta 8.  Nyt merkit jonon merkit 0-7 kopioidaan funktion paluuarvoon ja merkki 8 tuhotaan.  Kutsun jälkeen tilanne on:

 pala                   jono

┌─┬─┬─┬─┬─┬─┬─┬─┐      ┌─┬─┐

│ │ │1│2│3│0│0│ │      │ │1│

└─┴─┴─┴─┴─┴─┴─┴─┘      └─┴─┘

 0 1 2 3 4 5 6          0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8

 

Kolmannessa kutsussa merkkiä | ei enää löydy jonosta.  Tämä ilmenee siitä, että find-metodi palauttaa arvon string::npos (no position), eli ei paikkaa. Näin koko jono kopioidaan funktion paluuarvoksi ja kutsun jälkeen tilanne on:

 pala                   jono

┌─┬─┐                  ┌┐

│ │1│                  ││

└─┴─┘                  └┘

 0 1 2 3 4 5 6          0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8

 

Vastaava toistuu neljännessä kutsussa, eli koko jono sitten kopioidaan paluuarvoksi ja tilanne on neljännen kutsun jälkeen:

 pala                   jono

┌┐                     ┌┐

││                     ││

└┘                     └┘

 0 1 2 3 4 5 6          0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8

 

Tämän jälkeen tilanne pysyy samana vaikka erota-funktiota kutsuttaisiin kuinka monta kertaa tahansa.  Tästä saadaan se etu, että erota-funktiota voidaan turvallisesti kutsua kuinka monta kertaa tahansa, vaikkei jonosta enää palasia saataisikaan.  Jos kutsua tehdään silmukassa, voidaan silmukan lopetusehdoksi kirjoittaa

while ( !jono.equals("") ) {

  pala = erota(jono,'|');

  System.out.println(pala);

}

1.4.5 Luvun erottaminen

Usein samassa jonossa on sekaisin lukuja ja merkkijonoja.  Jotta käsittely saataisiin symmetrisemmäksi eri tyyppien välillä, niin luokkaan Mjonot on kirjoitettu myös polymorfiset funktiot:

StringBuffer jono = new StringBuffer("   Volvo   145  | 2000 e  | 3 kpl ")

String s = "";

double d = 0.0;

int kpl = 0;

s   = erota(jono,'|',s);      // "Volvo 145"

d   = erota(jono,'|',d);      // 2000

kpl = erota(jono,'|',kpl);    // 3

 

Idea on siinä, että jos myöhemmin huomataan vaikka että kpl pitäisi olla tyypiltään double eikä int, riittää vain muuttujan kpl tyypin vaihtaminen.

Jos halutaan käsitellä tilanteet, joissa joku kenttä onkin virheellistä muotoa, on edellisestä myös poikkeuksen heittävät muodot:

try {

  s   = erotaEx(jono,'|',s);    // "Volvo 145"

  d   = erotaEx(jono,'|',d);    // 2000

  kpl = erotaEx(jono,'|',kpl);    // 3

} catch  ( NumberFormatException ex ) {

  System.out.println(ex.getMessage());

}

 

1.5  Lukeminen ja paloittelu

Nyt voimme toteuttaa "tuotetiedoston" lukevan ohjelman Javan tietovirroilla ja funktioiden erotaEx avulla:

tiedosto\LueTuote.java - esimerkki tiedoston lukemisesta 

import fi.jyu.mit.ohj2.*;

import java.io.*;

 

/**

 * Ohjelmalla luetaan tuotetiedosto ja tulostetaan tuotteet

 * @author Vesa Lappalainen

 * @version 1.0, 21.02.2003

 */

public class LueTuote {

 

  public static boolean tulosta_tuotteet() {

    String srivi,pala;

    String nimike; double hinta; int kpl;

 

    BufferedReader fi = Tiedosto.avaa_lukemista_varten("tuotteet.dat");

    if ( fi == null ) return false;

 

    System.out.println(); System.out.println(); System.out.println();

    System.out.println("-------------------------------------------");

 

    try {

      while ( ( srivi = fi.readLine() ) != null ) {

        StringBuffer rivi = new StringBuffer(srivi);

        try {

          nimike = Mjonot.erotaEx(rivi,'|',"");

          hinta  = Mjonot.erotaEx(rivi,'|',0.0);

          kpl    = Mjonot.erotaEx(rivi,'|',0);

        } catch (NumberFormatException ex) {

          System.out.println("Virhe: " + ex.getMessage());

          continue;

        }

        System.out.println(Mjonot.fmt(nimike,-20) +" " + Mjonot.fmt(hinta,7,0) +

                           Mjonot.fmt(kpl,4));

      }

    } catch (IOException ex) {

      System.out.println("Vikaa tiedostoa luettaessa");

    } finally {

      try {

        fi.close();

      } catch (IOException ex) {

        System.out.println("Ongelmia tiedoston sulkemisessa");

      }

    }

 

    System.out.println("-------------------------------------------");

    System.out.println(); System.out.println(); System.out.println();

 

    return true;

  }

 

  public static void main(String[] args)  {

    if ( !tulosta_tuotteet() ) System.out.println("Tuotteita ei saada luetuksi");

  }

}

 

Ohjelma tulostaa:

-------------------------------------------

Volvo                  12300    1

Audi                   55700    2

Saab                    1500    4

Volvo                 123400    1

-------------------------------------------

 

1.5.1 Olio joka lukee itsensä

Muutetaan vielä tuotteiden lukua oliomaisemmaksi, eli annetaan tuotteelle kuuluvat tehtävät kokonaan Tuote–luokan vastuulle, samalla lisätään Tuotteet–luokka.

tiedosto\LueRek.java  - esimerkki oliosta joka käsittelee tiedostoa

import java.io.*;

import fi.jyu.mit.ohj2.*;

/**

 * Esimerkki oliosta joka käsittelee tiedostoa

 * @author Vesa Lappalainen

 * @version 1.0, 09.03.2003

 */

public class LueRek {

 

  static public class Tuote {

    private String nimike = "";

    private double hinta = 0.0;

    private int kpl = 0;

 

    public Tuote() {}

    public Tuote(String rivi) { parse(rivi); }

 

    public void parse(String s) throws NumberFormatException {

      StringBuffer sb = new StringBuffer(s);

      nimike = Mjonot.erotaEx(sb,'|',nimike);

      hinta  = Mjonot.erotaEx(sb,'|',hinta);

      kpl    = Mjonot.erotaEx(sb,'|',kpl);

    }

 

    public String toPrintString() {

      return Mjonot.fmt(nimike,-20) + " " + Mjonot.fmt(hinta,7,0) +

             Mjonot.fmt(kpl,4);

    }

 

  }

 

  static public class Tuotteet {

    private String nimi = "";

 

    public Tuotteet(String nimi) { this.nimi = nimi; }

 

    public boolean tulosta(OutputStream os) {

      PrintStream out = Tiedosto.getPrintStream(os);

      BufferedReader fi = Tiedosto.avaa_lukemista_varten("tuotteet.dat");

      if ( fi == null ) return false;

 

      out.println(); out.println(); out.println();

      out.println("-------------------------------------------");

 

      try {

        String rivi;  Tuote tuote;

        while ( ( rivi = fi.readLine() ) != null ) {

          try {

            tuote = new Tuote(rivi);

          } catch (NumberFormatException ex) {

            System.err.println("Virhe: " + ex.getMessage());

            continue;

          }

          out.println(tuote.toPrintString());

        }

      } catch (IOException ex) {

        System.err.println("Vikaa tiedostoa luettaessa");

      } finally {

        try {

          fi.close();

        } catch (IOException ex) {

          System.err.println("Ongelmia tiedoston sulkemisessa");

        }

      }

 

      out.println("-------------------------------------------");

      out.println(); System.out.println(); System.out.println();

 

      return true;

    }

 

  }

 

  public static void main(String[] args)  {

    Tuotteet tuotteet = new Tuotteet("tuotteet.dat");

    if ( !tuotteet.tulosta(System.out) ) {

      System.err.println("Tuotteita ei saada luetuksi");

    }

  }

}

 

1.6 Esimerkki tiedoston lukemisesta

Seuraavaksi kirjoitamme ohjelman, jossa tulee esiin varsin yleinen ongelma:  tietueen etsiminen joukosta.  Kirjoitamme edellisiä esimerkkejä vastaavan ohjelman, jossa tavallisen tulostuksen sijasta tulostetaan kunkin tuoteluokan yhteistilanne.

tiedosto\LueTRek.java - esimerkki tiedoston lukemisesta

import java.io.*;

import fi.jyu.mit.ohj2.*;

/**

 * Ohjelma lukee tiedostoa tuotteet.dat, joka on muotoa:

 * <pre>

 *   Volvo |  12300 | 1

 *   Audi  |  55700 | 2

 *   Saab  |   1500 | 4

 *   Volvo | 123400 | 1

 * </pre>

 * Ohjelma tulostaa kuhunkin tuoteluokkaan kuuluvien tuotteiden

 * yhteishinnat ja kappalemäärät sekä koko varaston yhteishinnan

 * ja kappalemäärän.  Eli em. tiedostosta tulostetaan:

 * <pre>

 * -------------------------------------------

 * Volvo                 135700    2

 * Audi                  111400    2

 * Saab                    6000    4

 * -------------------------------------------

 * Yhteensä              253100    8

 * -------------------------------------------

 *</pre>

 * @author Vesa Lappalainen

 * @version 1.0, 09.03.2003

 */

public class LueTRek {

 

  /****************************************************************************/

  /**

   * Luokka yhden tuotteen tiedoille.

   */

  static public class Tuote {

    private String nimike = "";

    private double hinta = 0.0;

    private int kpl = 0;

 

    public Tuote() {}

    public Tuote(String rivi) {

      try {

        parse(rivi);

      } catch (NumberFormatException ex) {

      }

    }

 

    public void parse(String s) throws NumberFormatException {

      StringBuffer sb = new StringBuffer(s);

      nimike = Mjonot.erotaEx(sb,'|',nimike);

      hinta  = Mjonot.erotaEx(sb,'|',hinta);

      kpl    = Mjonot.erotaEx(sb,'|',kpl);

    }

 

    public String toPrintString() {

      return Mjonot.fmt(nimike,-20) + " " + Mjonot.fmt(hinta,7,0) +

             Mjonot.fmt(kpl,4);

    }

 

    public void ynnaa(Tuote tuote) {

      hinta += tuote.hinta * tuote.kpl;

      kpl   += tuote.kpl;

    }

 

    public String getNimike() { return nimike; }

    public void setNimike(String nimike) { this.nimike = nimike; }

 

  }

 

  /****************************************************************************/

  /**

   * Luokka joka säilyttää kunkin ero tuotteen yhteissumman ja lukumäär'n

   * sekä kaikkien tuotteiden yhteissumman ja lukumäärän

   */

  static public class Tuotteet {

    private String nimi = "";

    private int lkm;

    private Tuote alkiot[];

    private Tuote yhteensa = new Tuote("Yhteensä");

 

    public Tuotteet(String nimi) {

      this.nimi = nimi;

      alkiot = new Tuote[10];

    }

 

    public int etsi(String tnimi) {

      for (int i=0; i<lkm; i++)

        if ( alkiot[i].getNimike().equalsIgnoreCase(tnimi) ) return i;

      return -1;

    }

 

    public int lisaa(String tnimi) {

      if ( alkiot.length <= lkm ) return -1;

      alkiot[lkm] = new Tuote(tnimi);

      return lkm++;

    }

 

    public boolean ynnaa(Tuote tuote) {

      if ( tuote.getNimike().equals("") ) return false;

      int i = etsi(tuote.getNimike());

      if ( i < 0 ) i = lisaa(tuote.getNimike());

      if ( i < 0 ) return false;

 

      alkiot[i].ynnaa(tuote);

      yhteensa.ynnaa(tuote);

      return true;

    }

 

 

    public boolean lue() {

      BufferedReader fi = Tiedosto.avaa_lukemista_varten("tuotteet.dat");

      if ( fi == null ) return false;

 

      try {

        String rivi;  Tuote tuote = new Tuote();

        while ( ( rivi = fi.readLine() ) != null ) {

          try {

            tuote.parse(rivi);

            ynnaa(tuote);

          } catch (NumberFormatException ex) {

            System.err.println("Rivillä jotakin pielessä " + rivi + "  " +

                                ex.getMessage());

            continue;

          }

        }

      } catch (IOException ex) {

        System.err.println("Vikaa tiedostoa luettaessa");

      } finally {

        try {

          fi.close();

        } catch (IOException ex) {

          System.err.println("Ongelmia tiedoston sulkemisessa");

        }

      }

 

      return true;

    }

 

    public void tulosta(OutputStream os) {

      PrintStream out = Tiedosto.getPrintStream(os);

 

      out.println(); out.println(); out.println();

      out.println("-------------------------------------------");

 

      for (int i=0; i<lkm; i++)

        out.println(alkiot[i].toPrintString());

 

      out.println("-------------------------------------------");

      out.println(yhteensa.toPrintString());

      out.println("-------------------------------------------");

      out.println(); System.out.println(); System.out.println();

    }

 

  }

 

  /****************************************************************************/

  public static void main(String[] args)  {

    Tuotteet varasto = new Tuotteet("tuotteet.dat");

    if ( !varasto.lue() ) {

      System.err.println("Tuotteita ei saada luetuksi");

      return;

    }

    varasto.tulosta(System.out);

  }

}

 

Tehtävä 17.4  Tietorakenne

Piirrä kuva Tuotteet –luokan tietorakenteesta.

Tehtävä 17.5  Perintä

Miten voisit perinnän avulla saada tiedoston luerek.cpp luokasta Tuote tiedoston LueTRek.java vastaavan luokan (tietysti eri nimelle, esim. RekTuote).  Mitä muutoksia olisi hyvä tehdä alkuperäisessä Tuote–luokassa.

Tehtävä 17.6  Tunnistenumero

Lisää LueTRek.java–ohjelmaan tunnistenumeron käsittely mahdollista tulevaa relaatiokäyttöä varten.

Tehtävä 17.7  Mittakaava

Kirjoita mittakaavaohjelma, jossa on vakiotaulukko

yks

mm

mm

cm 

dm 

m  

inch

1.0

  10.0

 100.0

1000.0

  25.4

 

ja jonka toiminta näyttäisi seuraavalta:

...

Mittakaava ja matka>1:10000 10 cm[RET]

Matka maastossa on 1.00 km.

Mittakaava ja matka>1:200000 20[RET]

Matka maastossa on 4.00 km.

Mittakaava ja matka>loppu[RET]

Kiitos!

 

Muuta ohjelmaa siten, että yksiköiden muunnostaulukko luetaan ohjelman aluksi tiedostosta muunnos.dat.

 

Muuta ohjelmaa vielä siten, että mikäli mittakaava jätetään antamatta, käytetään edellisellä kerralla annettua mittakaavaa ja ensimmäinen luku onkin matka.

 

...

Mittakaava ja matka>1:10000 10 cm[RET]

Matka maastossa on 1.00 km.

Mittakaava ja matka>0.20 dm[RET]

Matka maastossa on 0.20 km.

Mittakaava ja matka>loppu[RET]

Kiitos!