Računalništvo 1 (PRA)

lekcije / Kako v datotekah hranimo podatke

Kako v datotekah hranimo podatke

V Lekciji 8 smo se naučili uporabljati osnovne opreacije na datotekah: kako se datoteko odpre, zapre, kako se prebere ena vrstica in kako se piše podatke na datoteko. V tej lekciji se bomo učili, kako shranjujemo in beremo podatke z datotek ter kako jih obdelujemo.
Če želimi v datoteko shraniti podatke, moramo najprej izbrati format zapisa, to je pravilo, ki nam pove, kako so podatki zapisani. Denimo, da želimo na datoteki hraniti podatke o študentih. Vsak študent ima ime, priimek, EMŠO, ter seznam ocen vseh izpitov, ki jih je opravil (to je samo poenostaljen primer, v resnici bi bilo podatkov več). Te podatke je smiselno hraniti v datoteki takole:
studenti.dat
1
2
3
4
5
6
Boney   M               1205980500123   6 7 7 8 9
AC      DC              0909981959879   9 9 10 10 9 8 7 6
Pink    Floyd           1203979500300   10
Laurie  Anderson        0101978050120   9 10 8 7 7 9
David   Bowie           0803920500987   6 6 7 8 6 7
Tereza  Kesovija        0304912050111   10 10 10 10 10 10 10
Format zapisa za podatke o študentih je torej:
  1. Vsaka vrstica vsebuje podatke o enem študentu.
  2. Polja v vrstici so: ime, priimek, EMŠO in seznam ocen.
  3. Polja so med seboj ločena s presledki ali tabulatorji. Med dvema poljema je lahko več presledkov in tabulatorjev.
  4. Ocene v seznamu so ločene enako kot so med seboj ločena polja, se pravi s presledki ali tabulatorji.
Kot drug primer si oglejmo, kako bi v datoteko shranili matriko celih števil. Primeren format zapisa za matrike bi bil naslednji:
  1. Prva vrstica datoteke vsebuje: število vrstic, enega ali več presledkov in število stolpcev.
  2. Nato so naštete vrstice tabele, vsaka v svoji vrstici. Elementi so med seboj ločeni s presledki.
Denimo, če bi imeli mariko
int[][] A = { {101, 102, 103, 104},
              { 17,  18,  19,  20},
              {  0,   0,   0,   1} };
bi jo zapisali na datoteko takole:
3 4
101 102 103 104
17 18 19 20
0 0 0 1
Pri formatu zapisa moramo biti precej pozorni, sicer si zlahka nakopljemo težave. Na primer, v formatu zapisa za študente iz zgornjega primer, zaidemo v težave takoj, ko se pojavi študent, ki ima preseledek v priimku ("Jean-Claude Van Damme"), saj s presledki ločimo polja. Tako ne bi bilo jasno, kateri presledki ločijo polja in kateri so del priimka ali imena.
Naloga 1
Naloga 2
Ko načrtujemo format zapisa za dane podatke, se torej držimo nekaterih nasvetov:
  1. Format zapisa mora biti načrtovan tako, da ne more priti do zmede. Če na primer podatki vsebujejo presledke, potem polj v zapisu ne smemo ločevati s presledki.
  2. Format zapisa mora biti tak, da programerju olajša pisanje podatkov iz programa na datoteko ter branje iz datoteke v program. Zapis naj vsebuje natanko tiste podatke, ki so potrebni. Dobro premislimo, v kakšnem vrstnem redu naj bodo podatki zapisani.
  3. Dobro je, če so zapisani podatki pregledni, tako da jih ljudje razumejo, ko pregledujejo datoteko, vendar je bolj pomembno, da so podatki shranjeni tako, da jih je lahko brati in pisati s programi.
Naloga 3
Oglejmo si sedaj na primeru, kako napišemo program, ki prebere in piše podatke na datoteke. Denimo, da želimo napisati program, ki iz vhodne datoteke prebere celoštevilsko matriko A, izračuna njen kvadrat A2, in nato zapiše rezultat v izhodno datoteko. Ime vhodne in izhodne datoteke sta podana v ukazni vrstici. Kar takoj lahko napišemo glavno metodo:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public static void main(String[] args) throws IOException {
    // odpremo vhodno datoteko
    String ime_vhodna = args[0];
    BufferedReader vhodna =
        new BufferedReader(new FileReader(ime_vhodna));

    // odpremo izhodno datoteko
    String ime_izhodna = args[1];
    PrintWriter izhodna =
        new PrintWriter(new FileWriter(ime_izhodna));

    // preberemo matriko
    int[][] A = preberi_matriko(vhodna);

    // izracunamo njen kvadrat
    int[][] B = produkt(A, A);

    // izpisemo B
    zapisi_matriko(izhodna, B);

    // zapremo datoteki
    vhodna.close();
    izhodna.close();
}
Da bo program popoln, je treba napisati še metode preberi_matriko, produkt in zapisi_matriko.
Najprej napišimo metodo produkt, ki zmnoži dve matriki:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// produkt matrik A in B
public static int[][] produkt(int[][] A, int[][] B) {
    int m = A.length; // vrstice A
    int n = A[0].length; // stolpci A (= vrstice B)
    int p = B[0].length; // stolpci B
    int[][] C = new int[m][p];
    for (int i = 0; i < m; i = i + 1) {
        for (int j = 0; j < p; j = j + 1) {
    	    C[i][j] = 0;
    	    for (int k = 0; k < n; k = k + 1) {
    	        C[i][j] = C[i][j] + A[i][k] * B[k][j];
    	    }
        }
    }
    return C;
}
Metoda zapisi_matriko je dokaj preprosta (običajno so metode, ki pišejo podatke v datoteko, bolj preproste od tistih, ki podatke preberejo z datoteke):
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public static void zapisi_matriko(PrintWriter izhod, int[][] A)
                                                 throws IOException {
    int vr = A.length; // stevilo vrstic
    int st = A[0].length; // stevilo stolpcev
    // najprej izpisemo veliko matrike
    izhod.println(vr + " " + st);

    // nato izpisemo matriko
    for (int i = 0; i < vr; i = i + 1) {
        for (int j = 0; j < st; j = j + 1) {
            izhod.print(A[i][j]);
            // ce to ni zadnji elemnt vrstice, potem izpisemo presledek
            if (j < st - 1) { izhod.print(" "); }
        }
        // izpisemo konec vrstice
        izhod.println();
    }
}
Nazadnje napišemo še metodo preberi_matriko, ki vzame kot argument objekt BufferedReader vhod in vrne matriko, ko jo prebere iz vhod. Metoda ima dva dela. Najprej prebere prvo vrstico, iz katere ugotovi število vrstic in stolpec matrike. Nato naredi novo tabelo prave velikosti in prebere še ostale podatke.
Ko metoda prebere prvo vrstico, dobi niz znakov, na primer "3 4", iz katerega mora izluščiti število vrstic in stolpcev. Niz torej želimo razkosati na posamična polja. V Javi to naredimo z razredom StringTokenizer. Ko naredimo nov StringTokenizer objekt, mu podamo niz, ki ga naj razkosa na posamična polja in niz, ki vsebuje vse znake, ki ločujejo polja. V našem primeru so polja ločena s presledki ali tabulatorji. Nato kličemo metodo nextToken(), ki nam po vrsti da vsa polja. Naslednji primer kaže, kako deluje StringTokenizer:
Razkosaj.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import java.util.*;

public class Razkosaj {
    public static void main(String[] args) {

	String vrstica = "David   Bowie           0803920500987   6 6 7 8 6 7";

	StringTokenizer polja = new StringTokenizer(vrstica, " \t");

	int k = 1;

	while (polja.hasMoreTokens()) {
	    String p = polja.nextToken();
	    System.out.println(k + ". polje: " + p);
	    k = k + 1;
	}
    }
}
> javac Razkosaj.java
> java Razkosaj
1. polje: David
2. polje: Bowie
3. polje: 0803920500987
4. polje: 6
5. polje: 6
6. polje: 7
7. polje: 8
8. polje: 6
9. polje: 7
Sedaj lahko napišemo metodo preberi_matriko:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
public static int[][] preberi_matriko(BufferedReader vhod)
                                                    throws IOException {

    // Najprej ugotovimo število vrstic in stolpcev
    StringTokenizer s = new StringTokenizer(vhod.readLine(), " \t");

    int vrstice = Integer.parseInt(s.nextToken());
    int stolpci = Integer.parseInt(s.nextToken());

    // naredimo tabelo prave velikosti
    int[][] A = new int[vrstice][stolpci];

    // preberemo preostale podatke
    for (int i = 0; i < vrstice(A); i = i + 1) {

        StringTokenizer v = new StringTokenizer(vhod.readLine(), " \t");

        for (int j = 0; j < stolpci(A); j = j + 1) {
    	A[i][j] = Integer.parseInt(v.nextToken());
        }
    }

    // vrnemo matriko
    return A;
}
Poglejmo si končni izdelek:
KvadratMatrike.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
import java.io.*;
import java.util.*;

public class KvadratMatrike {

    public static void main(String[] args) throws IOException {
	// odpremo vhodno datoteko
	String ime_vhodna = args[0];
	BufferedReader vhodna =
	    new BufferedReader(new FileReader(ime_vhodna));

	// odpremo izhodno datoteko
	String ime_izhodna = args[1];
	PrintWriter izhodna =
	    new PrintWriter(new FileWriter(ime_izhodna));

	// preberemo matriko
	int[][] A = preberi_matriko(vhodna);

	// izracunamo njen kvadrat
	int[][] B = produkt(A, A);

	// izpisemo B
	zapisi_matriko(izhodna, B);

	// zapremo datoteki
	vhodna.close();
	izhodna.close();
    }


    public static int[][] preberi_matriko(BufferedReader vhod) throws IOException {
	// Najprej ugotovimo število vrstic in stolpcev
	StringTokenizer s = new StringTokenizer(vhod.readLine(), " \t");
	int vrstice = Integer.parseInt(s.nextToken());
	int stolpci = Integer.parseInt(s.nextToken());
	// naredimo tabelo prave velikosti
	int[][] A = new int[vrstice][stolpci];
	// preberemo preostale podatke
	for (int i = 0; i < A.length; i = i + 1) {
	    StringTokenizer v = new StringTokenizer(vhod.readLine(), " \t");
	    for (int j = 0; j < A[0].length; j = j + 1) {
		A[i][j] = Integer.parseInt(v.nextToken());
	    }
	}
	// vrnemo matriko
	return A;
    }


    public static void zapisi_matriko(PrintWriter izhod, int[][] A) throws IOException {
	int vr = A.length; // stevilo vrstic
	int st = A[0].length; // stevilo stolpcev
	// najprej izpisemo veliko matrike
	izhod.println(vr + " " + st);
	
	// nato izpisemo matriko
	for (int i = 0; i < vr; i = i + 1) {
	    for (int j = 0; j < st; j = j + 1) {
		izhod.print(A[i][j]);
		// ce to ni zadnji elemnt vrstice, potem izpisemo presledek
		if (j < st - 1) { izhod.print(" "); }
	    }
	    // izpisemo konec vrstice
	    izhod.println();
	}
    }

    // produkt matrik A in B
    public static int[][] produkt(int[][] A, int[][] B) {
	int m = A.length; // vrstice A
	int n = A[0].length; // stolpci A (= vrstice B)
	int p = B[0].length; // stolpci B
	int[][] C = new int[m][p];
	for (int i = 0; i < m; i = i + 1) {
	    for (int j = 0; j < p; j = j + 1) {
		C[i][j] = 0;
		for (int k = 0; k < n; k = k + 1) {
		    C[i][j] = C[i][j] + A[i][k] * B[k][j];
		}
	    }
	}
	return C;
    }
    
}
matrika.dat
1
2
3
4
5
6
5 5
1 1 0 0 0
0 1 1 0 0
0 0 1 1 0
0 0 0 1 1
0 0 0 0 1
> javac KvadratMatrike.java
> java KvadratMatrike matrika.dat bla.dat
bla.dat
1
2
3
4
5
6
5 5
1 2 1 0 0
0 1 2 1 0
0 0 1 2 1
0 0 0 1 2
0 0 0 0 1
Poskusi, kako hitro program prebere, kvadrira in izpiše matriko velikosti 100 x 100.
Naloga 4
Naloga 5
Naloga 6