Osa 4

Tiedon lukeminen ja tiedostot

Merkittävä osa ohjelmistoista perustuu tavalla tai toisella tiedon käsittelyyn. Musiikin toistoon tarkoitetut ohjelmistot käsittelevät musiikkitiedostoja, kuvankäsittelyohjelmat käsittelevät kuvatiedostoja. Verkossa ja mobiililaitteissa toimivat sovellukset kuten Facebook, WhatsApp ja Telegram taas käsittelevät tiedostoihin perustuviin tietokantoihin tallennettuja henkilötietoja. Kaikissa näistä sovelluksista on yhteistä tiedon lukeminen, tiedon käsitteleminen tavalla tai toisella sekä se, että käsiteltävä tieto on loppujenlopulta tallennettu jonkinlaisessa muodossa yhteen tai useampaan tiedostoon.

Lukeminen näppäimistöltä

Olemme käyttäneet Scanner-luokkaa käyttäjän kirjoittaman syötteen lukemiseen kurssin alusta lähtien. Tiedon lukemiseen käytetty runko on while-true -toistolause, missä lukeminen lopetetaan tietynmuotoiseen syötteeseen.

Scanner lukija = new Scanner(System.in);

while (true) {
    String rivi = lukija.nextLine();

    if (rivi.equals("loppu")) {
        break;
    }

    // lisää luettu rivi listalle myöhempää käsittelyä
    // varten tai käsittele rivi heti

}

Yllä Scanner-luokan konstruktorille annetaan parametrina järjestelmän syöte (System.in). Tekstikäyttöliittymissä käyttäjän kirjoittama tieto ohjataan syötevirtaan rivi kerrallaan, eli tieto lähetetään käsiteltäväksi aina kun käyttäjä painaa rivinvaihtoa.

Loading

Käyttäjän syöttämä syöte luetaan merkkijonomuotoisena. Mikäli syöte halutaan käsitellä esimerkiksi kokonaislukuina, tulee käyttäjän syöte muuntaa toiseen muotoon. Alla olevassa esimerkissä ohjelma lukee käyttäjältä syötettä kunnes käyttäjä syöttää merkkijonon "loppu". Mikäli käyttäjän syöte ei ole "loppu", käsitellään syöte lukuna — tässä tapauksessa luku vain tulostetaan.

Scanner lukija = new Scanner(System.in);

while (true) {
    String rivi = lukija.nextLine();

    if (rivi.equals("loppu")) {
        break;
    }

    int luku = Integer.valueOf(rivi);
    System.out.println(luku);
}
Loading

Tiedosto ja tiedostojärjestelmä

Tiedostot ovat tietokoneella sijaitsevia tietokokoelmia, jotka voivat sisältää vaikkapa tekstiä, kuvia, musiikkia tai niiden yhdistelmiä. Tiedoston tallennusmuoto määrittelee tiedoston sisällön sekä tallennusmuodon lukemiseen tarvittavan ohjelman. Esimerkiksi PDF-tiedostoja luetaan PDF-tiedostojen lukemiseen soveltuvalla ohjelmalla ja musiikkitiedostoja luetaan musiikkitiedostojen lukemiseen soveltuvalla ohjelmalla. Jokainen näistä ohjelmista on ihmisen luoma, ja ohjelman luoja tai luojat — eli ohjelmoijat — ovat osana työtään myös määritelleet tiedoston tallennusmuodon.

Tietokoneissa on useampia ohjelmia tiedostojen selaamiseen ja nämä ohjelmistot ovat käyttöjärjestelmäkohtaisia. Kaikki tiedostojen selaamiseen käytettävistä ohjelmista käyttävät tavalla tai toisella tietokoneen tiedostojärjestelmää.

Käyttämämme ohjelmointiympäristö tarjoaa mahdollisuuden projektien sisältämien tiedostojen selaamiseen. Voit käydä tarkastelemassa NetBeansissa kaikkia projektiin liittyviä tiedostoja valitsemalla Files-välilehden, joka löytyy Projects-välilehden kanssa samasta paikasta. Mikäli Files-välilehteä ei löydy, saa sen auki myös Window-valikosta. Klikkaamalla projektin auki, näet kaikki siihen liittyvät tiedostot.

Loading

Lukeminen tiedostosta

Tiedoston lukeminen tapahtuu Scanner-luokan avulla. Kun Scanner-luokan avulla halutaan lukea tiedosto, annetaan luokan konstruktorille parametrina polku luettavaan tiedostoon. Polku saadaan Javan valmiilla Paths.get-komennolla, jolle annetaan parametrina merkkijonomuotoinen tiedoston nimi: Paths.get("tiedostonnimi.paate").

Kun tiedostoa lukeva Scanner-olio on luotu, tiedoston lukeminen tapahtuu while-toistolauseella. Lukemista jatketaan kunnes kaikki tiedoston rivit on luettu, eli kunnes tiedostossa ei ole enää luettavia rivejä. Tiedostoja lukiessa voidaan kohdata virhetilanne, joten tiedoston lukeminen vaatii erillisen "yrittämisen" (try) sekä mahdollisen virheen kiinnioton (catch). Palaamme virhetilanteiden käsittelyyn kurssilla myöhemmin.

// alkuun
import java.util.Scanner;
import java.nio.file.Paths;

// ohjelmassa:

// luodaan lukija tiedoston lukemista varten
try (Scanner tiedostonLukija = new Scanner(Paths.get("tiedosto.txt"))) {

    // luetaan tiedostoja kunnes kaikki rivit on luettu
    while (tiedostonLukija.hasNextLine()) {
        // luetaan yksi rivi
        String rivi = tiedostonLukija.nextLine();
        // tulostetaan luettu rivi
        System.out.println(rivi);
    }
} catch (Exception e) {
    System.out.println("Virhe: " + e.getMessage());
}

Oletuksena (eli kutsuttaessa new Scanner(Paths.get("tiedosto.txt"))) tiedosto luetaan projektin juuresta eli kansiosta, joka sisältää kansion src sekä tiedoston pom.xml (ja mahdollisesti myös muita tiedostoja). Tämän kansion sisältöä voi tarkastella NetBeansin Files-välilehdeltä.

Loading
Loading

Alla olevassa esimerkissä luetaan tiedoston "tiedosto.txt" kaikki rivit, jotka lisätään ArrayList-listaan.

ArrayList<String> rivit = new ArrayList<>();

// luodaan lukija tiedoston lukemista varten
try (Scanner tiedostonLukija = new Scanner(Paths.get("tiedosto.txt"))) {

    // luetaan kaikki tiedoston rivit
    while (tiedostonLukija.hasNextLine()) {
        rivit.add(tiedostonLukija.nextLine());
    }
} catch (Exception e) {
    System.out.println("Virhe: " + e.getMessage());
}

// tulostetaan rivien lukumäärä
System.out.println("Rivejä yhteensä: " + rivit.size());
Loading
Loading
Loading

Määrämuotoisen tiedon lukeminen tiedostosta

Maailma on täynnä tietoa, joka liittyy muuhun tietoon — tieto muodostaa kokonaisuuksia. Esimerkiksi henkilön tietoihin kuuluu nimi, syntymäaika, puhelinnumero, osoitetietoihin kuuluu maa, kaupunki, katuosoite, postinumero ja niin edelleen.

Tieto tallennetaan usein tiedostoihin määrämuotoisessa muodossa. Eräs tällainen muoto on kurssilla jo tutuksi tullut comma-separated values (CSV)-muoto, eli pilkuilla erotetut tiedot.

Alla olevassa esimerkissä on nimiä ja ikiä määrämuotoisessa muodossa lukeva ohjelma. Ohjelma tulostaa lukemansa pilkottuina omille riveilleen.

Scanner lukija = new Scanner(System.in);

while (true) {
    System.out.print("Syötä nimi: ");
    String rivi = lukija.nextLine();

    if (rivi.equals("")) {
        break;
    }

    String[] palat = rivi.split(",");
    String nimi = palat[0];
    int ika = Integer.valueOf(palat[1]);

    System.out.println("Nimi: " + nimi);
    System.out.println("Ikä: " + ika);
}

Ohjelman toiminta on seuraava:

Esimerkkitulostus

virpi,19 Nimi: virpi Ikä: 19 jenna,21 Nimi: jenna Ikä: 21 ada,20 Nimi: ada Ikä: 20

Tiedostosta tiedot.txt vastaavat tiedot lukeva ohjelma näyttäisi seuraavalta.

try (Scanner lukija = new Scanner(Paths.get("tiedot.txt"))) {

    while (lukija.hasNextLine()) {
        String rivi = lukija.nextLine();

        String[] palat = rivi.split(",");
        String nimi = palat[0];
        int ika = Integer.valueOf(palat[1]);

        System.out.println("Nimi: " + nimi);
        System.out.println("Ikä: " + ika);
    }
}
Loading

Olioiden lukeminen tiedostosta

Olioiden luominen tiedostosta luetusta datasta on suoraviivaista. Oletetaan, että käytössämme on seuraava luokka Henkilo sekä aiemmin käyttämämme data.

Olioiden lukeminen onnistuu seuraavasti:

ArrayList<Henkilo> henkilot = new ArrayList<>();

try (Scanner lukija = new Scanner(Paths.get("tiedot.txt"))) {

    while (lukija.hasNextLine()) {
        String rivi = lukija.nextLine();

        String[] palat = rivi.split(",");
        String nimi = palat[0];
        int ika = Integer.valueOf(palat[1]);

        henkilot.add(new Henkilo(nimi, ika));
    }
}

System.out.println("Luettuja henkilöitä yhteensä: " + henkilot.size());

Olioiden lukeminen tiedostosta on selkeä oma kokonaisuutensa, joka kannattaa eriyttää omaan metodiinsa. Näin tehdään myös seuraavassa tehtävässä.

Loading
Loading
Pääsit aliluvun loppuun! Jatka tästä seuraavaan osaan:

Muistathan tarkistaa pistetilanteesi materiaalin oikeassa alareunassa olevasta pallosta!