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:
Vsaka vrstica vsebuje podatke o enem študentu.
Polja v vrstici so: ime, priimek, EMŠO in seznam ocen.
Polja so med seboj ločena s presledki ali tabulatorji. Med dvema
poljema je lahko več presledkov in tabulatorjev.
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:
Prva vrstica datoteke vsebuje: število vrstic, enega ali več
presledkov in število stolpcev.
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
Kako bi popravili format zapisa za študente tako, da bi lahko zapisali
tudi imena in priimke, ki vsebujejo presledke?
Naloga 2
V kakšne težave bi zašli, če v formatu zapisa za matrike v zgornjem
primeru ne bi zapisali v prvo vrstico, koliko vrstic in
stolpcev ima matrika?
Ko načrtujemo format zapisa za dane podatke, se torej držimo nekaterih
nasvetov:
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.
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.
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
Kaj bi bilo narobe, če bi zapisali podatke o študentih v zgornjem
primeru takole:
6 7 7 8 9 Boney M 1205980500123
9 9 10 10 9 8 7 6 AC DC 0909981959879
10 Pink Floyd 1203979500300
9 10 8 7 7 9 Laurie Anderson 0101978050120
6 6 7 8 6 7 David Bowie 0803920500987
10 10 10 10 10 10 10 Tereza Kesovija 0304912050111 |
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
Napiši program Primerjaj
, ki z ukazne vrstice
sprejme imeni dveh datotek in primerja njuno vsebino. Če sta
datoteki enaki, program na zaslon izpiše enaki
,
sicer izpiše različni
.
Naloga 5
Napiši program
BrisiKomentarje
, ki z ukazne vrstice
sprejme imeni vhodne in izhodne datoteke. Nato na izhodno
datoteko prepiše tiste vrstice iz vhodne datoteke, ki se
ne začnejo z znakom
'%'
. Na primer, če
je vhodna datoteka enaka
% To je primer ene datoteke
% Nekatere vrstice se zacnejo z
znakom %, druge pa ne.
Debel kmet ima neumen krompri.
Zasluzili smo 10%.
Prazne vrste se ohranijo.
% KONEC |
potem je izhodna datoteka enaka
znakom %, druge pa ne.
Debel kmet ima neumen krompri.
Zasluzili smo 10%.
Prazne vrste se ohranijo. |
Naloga 6
Rezultati kolokvija so podani v datoteki:
Prva vrstica je naslov kolokvija.
Druga vrstica vsebuje datum, ko so študenti pisali kolokvij.
Tretja vrstica vsebuje število točk, ki jih je bilo
možno doseči pri vsaki nalogi. Števila so ločena s presledki.
Preostale vrstice vsebujejo rezultate, vsaka vrstica
vsebuje rezultate o enem študentu. Polja so ločena s
presledki in so:
Primer datoteke z rezultati:
rezultati.txt |
---|
1
2
3
4
5
6
7
| 1. kolokvij iz računalnistva (PRA)
19.1.2004
25 25 25 25
27004396 23 20 10 18
27004695 25 25 25 24
27004703 20 22 10 25
27004712 0 25 25 25 |
Iz tretje vrstice razberemo, da so bile na kolokviju 4
naloge, vsaka je bila vredna 25 točk. Iz 6. vrstice
razberemo, da je študent z vpisno številko 27004703 dosegel
pri 1. nalogi 20 točk, pri 2. je dosegel 22, pri 3. je
dosegel 10 in pri 4. nalogi 25 točk.
Napiši program
Povprecje
, ki iz ukazne vrstice
sprejme ime vhodne datoteke z rezultati kolokvija in na
zaslon izpiše
skupno povprečno oceno. Za
zgornji primer je povprečna ocena enaka
80.5, ker je skupno
število vseh doseženih točk enako
((23 + 20 + 10 + 18) + (25 + 25 + 25 + 24) + (20 + 22 +
10 + 25) + (0 + 25 + 25 + 25)) = 322
in je
322/4 = 80.5.
Pozor: ne smeš predpostaviti, da so bile na kolokviju
štiri naloge. Koliko nalog je bilo, je razvidno iz tretje
vrstice.