Programy pracujące z danymi zazwyczaj muszą mieć możliwość ich odczytywania i zapisywania na dysku. Najbardziej podstawowym sposobem zapisu danych są odpowiednio sformatowane pliki tekstowe. Mają one także tą zaletę, że są uniwersalne, można je odczytać na wielu programach, włączając w to edytory tekstu.
Zapis prostej informacji do pliku tekstowego
Najpierw pokażę jak zapisać kilka linijek tekstu do pliku a następnie je odczytać. Sposobów zapisu informacji do plików jest kilka, zaczniemy od „tradycyjnego”. Poniższy program zawiera metodę, w której znajduje się kod zapisujący trzy linijki mające po jednym słowie (możesz dopisać ich więcej) do pliku, metodę która ten plik odczytuje i metodę main()
, która wywołuje te metody a także wypisuje na ekranie odczytany tekst. Komentarze w kodzie wyjaśniają kolejne operacje.
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 |
package pliki; import java.io.File; import java.io.FileNotFoundException; import java.io.PrintWriter; import java.util.Scanner; public class Pliki { public static void main(String[] args) { // Nazwa pliku do którego zapiszemy i z którego odczytamy tekst String nazwaPliku = "tekst.txt"; // Zapis pliku zapiszPlik(nazwaPliku); // Odczyt pliku String odczytanyTekst = odczytajPlik(nazwaPliku); // Drukowanie treści odczytanej z pliku System.out.println("Odczytany tekst:\n" + odczytanyTekst); } // Metoda zapisuje tekst w pliku tekstowym public static void zapiszPlik(String nazwaPliku) { // spróbuj... try { // Tworzenie obiektu typu PrintWriter, jako argument // zostaje podana nazwa pliku PrintWriter out = new PrintWriter(nazwaPliku); // po kolei zapisywane są kolejne linijki tekstu out.println("Raz"); out.println("Dwa"); out.println("Trzy"); // po zapisaniu danych plik należy zamknąć out.close(); // jeśli się nie udało utworzyć pliku.. } catch (FileNotFoundException ex) { System.out.println("Niestety, nie mogę utworzyć pliku!"); } } // Metoda odczytuje tekst z pliku tekstowego, public static String odczytajPlik(String nazwaPliku) { // Deklarowanie i tworzenie obiektu typu File File plikDane = new File(nazwaPliku); // Utworzenie obiektu typu String, który będzie // przechowywał odczytany tekst String odczyt = ""; try { // Utworzenie obiektu typu String Scanner skaner = new Scanner(plikDane); // Odczytywanie kolejnych linii pliku dopóki są kolejne linie while (skaner.hasNextLine()) { // Do łańcucha znaków dodawana jest zawartość kolejnej linii // oraz znak \n oznaczający następną linię odczyt = odczyt + skaner.nextLine() + "\n"; } // Jeśli nie udało się odczytać pliku } catch (FileNotFoundException e) { System.out.println("Brak Pliku do odczytania!"); } return odczyt; } } |
Zauważ, że w powyższym kodzie użyliśmy obiekt typu PrintWriter
, który po linijce zapisywał tekst do pliku, natomiast do odczytu posłużył nam stary znajomy Scanner
, który tym razem jako argument konstruktora otrzymał obiekt typu File
, w efekcie odczytywał dane z pliku zamiast z klawiatury (jak robił to w poprzednich programach). Po zapisie danych do pliku plik należało zamknąć. Nie jest to konieczne jeśli wykorzystamy nowsze rozwiązania dostępne w Javie, co pokażę w kolejnym przykładzie.
Zapis danych w formacie CSV
W drugim przykładzie pokażę jak użyć nowszych mechanizmów zapisu plików a przy okazji zapoznamy się z formatem CSV
. Jest to chyba najprostszy format służący zapisaniu danych. W standardowej formie są to rzędy danych poprzedzielanych przecinkami, stąd nazwa CSV
(ang. Comma Separated Values) i zwyczajowe przedłużenie nazw pliku: .csv
. Na przykład taki plik może wyglądać tak:
2.3, 2.2, 2.1, 2.0, 1.8
2.2, 2.1, 2.1, 2.3, 2.0
2.2, 2.2, 2.1, 1.9, 1.8
Jak widać, te dane można by łatwo przedstawić w formie tabelarycznej, gdzie rząd danych odpowiadałby rzędowi w tabeli a kolejna liczba w rzędzie byłaby przyporządkowana do kolejnej kolumny. W praktyce można zamiast przecinka wybrać inny znak, np. średnik czy hasz, jeśli chcemy przechowywać liczby zmiennoprzecinkowe w przyjętej m. in. w Polsce notacji z przecinkiem zamiast kropki. Często stosuje się znak tabulatora co dodatkowo może zwiększać czytelność pliku. W tym ostatnim przypadku mówimy raczej o plikach TSV
(ang. Tab Separated Values). Jeśli dane pole zawiera tekst, zwłaszcza jeśli jest on dłuższy i zawiera znaki separatora to można go zamknąć w parze cudzysłowów. Trzeba oczywiście wtedy zadbać, żeby program, który odczytuje taki plik to uwzględnił i potraktował np. przecinek w obrębie tekstu jako jego element a nie znak separatora.
Poniższy kod tworzy tablicę dwuwymiarową liczb double
z powyższymi danymi, zapisuje je w pliku dane.csv
, który znajdziesz po uruchomieniu programu w katalogu z projektem a następnie odczytuje ten plik i odtwarza tablicę z liczbami typu double
. Oczywiście dane w pliku tekstowym zapisywane są jako łańcuchy znaków a muszą przy odczycie zostać przekonwertowane na liczby double
.
W tym przykładzie pokazałem ja użyć sposobu zapisu pliku dostępnych w nowszych wydaniach Javy. Wykorzystują one m. in. klasy Path
i Files
, które oferują wiele użytecznych metod statycznych służących do pracy z plikami i katalogami, ale nie będziemy ich tu szerzej omawiać, przynajmniej na razie.
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 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 |
package danewpliku; import java.io.IOException; import java.nio.file.*; import java.util.ArrayList; import java.util.Arrays; public class DaneWPliku { public static void main(String[] args) { // Nazwa pliku do którego zapiszemy i z którego odczytamy tekst String nazwaPliku = "dane.csv"; // Tablica z danymi - 3 serie w rzędach po 5 liczb (np. pomiarów) double[][] dane = {{2.3, 2.2, 2.1, 2.0, 1.8}, {2.2, 2.1, 2.1, 2.3, 2.0}, {2.2, 2.2, 2.1, 1.9, 1.8} }; // Zapis pliku zapiszPlik(nazwaPliku, dane); // Odczyt pliku i konwersja do tablicy double[][] odczyt = odczytajPlik(nazwaPliku); // Coś robimy z odczytanymi danymi obliczenia(odczyt); } // Metoda zapisuje tekst w pliku tekstowym public static void zapiszPlik(String nazwaPliku, double[][] dane) { // Tworzymy obiekt typu Path Path sciezka = Paths.get(nazwaPliku); ArrayList out = new ArrayList<>(); // Pobranie kolejnego "rzędu" danych for (double[] seria : dane) { String s = Arrays.toString(seria); // Arrays.toString(seria) zwraca dane w postaci: // [2.3, 2.2, 2.1, 2.0, 1.8] // trzeba usunąć znaki [ i ] s = s.replace("[", ""); s = s.replace("]", ""); // dodanie linijki z danymi do listy out.add(s); } try { Files.write(sciezka, out); } catch (IOException ex) { System.out.println("Nie mogę zapisać pliku!"); } } // Metoda odczytuje tekst z pliku tekstowego, public static double[][] odczytajPlik(String nazwaPliku) { // Tworzymy obiekt typu Path Path sciezka = Paths.get(nazwaPliku); // Lista będzie przechowywała kolejne linie odczytane z pliku jako String ArrayList odczyt = new ArrayList(); try { // Od razu odczytane zostają wszystkie linie pliku // i umieszczone w liście odczyt = (ArrayList) Files.readAllLines(sciezka); } catch (IOException ex) { System.out.println("Brak pliku!"); } // Teraz trzeba łańcuchy znaków dla każdej linii przekonwertować // na dane liczbowe i umieścić je w tablicy // Tablica dla odczytanych danych // Na razie wiemy tylko ile będzie "rzędów" double[][] daneOdczytane = new double[odczyt.size()][]; // Będziemy potrzebowali indeksu linii int nrLinii = 0; // Pobranie kolejnych linii z listy for (String linia : odczyt) { // Rozbijamy linię na poszczególne linie (przedzielone przecinkami) String[] liniaDaneString = linia.split(","); // Tablica do przechowania danych w fomie liczb double double[] liniaDouble = new double[liniaDaneString.length]; // Pętla, która pobiera z tablicy String-ów po jednej liczbie, // konwertuje ją na liczbę double i zapisuje w tablicy typu double[] for (int i = 0; i < liniaDouble.length; i++) { liniaDouble[i] = Double.parseDouble(liniaDaneString[i]); } // Dodajemy tablicę z serią danych do tablicy z wszystkimi danymi daneOdczytane[nrLinii] = liniaDouble; // kolejna linia... nrLinii++; } return daneOdczytane; } // Robimy coś z danymi public static void obliczenia(double[][] dane) { double suma = 0.0; // Pobranie kolejnej serii danych for (double[] rzad : dane) { // pobranie kolejnej danej i dodanie do sumy for (double dana : rzad) { suma += dana; } // Wydruk danych, sumy i średniej dla serii System.out.println("Dane: " + Arrays.toString(rzad) + " suma: " + suma + " średnia: " + suma / rzad.length); } } } |
Bardziej złożony przykład danych zapisanych w pliku CSV
Plik CSV
może zawierać dane różnego rodzaju, niekoniecznie liczbowe a także może zawierać linię z nagłówkami, co zobaczymy w kolejnym przykładzie. Tym razem dane będą miały bardziej złożony charakter, tym razem punktem wyjścia będzie istniejący plik z danymi zawierającymi liczby chromosomów roślin.
Plik zawiera numer porządkowy, nazwę rodzaju, nazwę gatunkową, liczbę 2n chromosomów i liczbę x chromosomów. Pierwsza linia zawiera nagłówki.
Projekt będzie się składał z dwu plików: głównego, zawierającą metodę main()
pliku DaneZlozone.java
oraz klasy Takson.java
. Obiekty typu Takson
będą przechowywały informacje dotyczące poszczególnych taksonów. Dane odczytane z jednej linii, będą umieszczane w poszczególnych polach obiektu. Kolejne obiekty Takson
będą umieszczane w liście (w klasie DaneZlozone
).
Klasa DaneZlozone.java
, poza metodą main()
, zawiera metody służące do odczytu danych, wyświetlania menu i pobrania decyzji użytkownika, dodania nowego rekordu, usunięcia rekordu, wypisania danych oraz oddzielną metodę, która będzie dokonywać konwersji danych z listy String
-ów do listy obiektów. Będzie on pobierać po linii z listy a następnie oddzielać poszczególne pola, dokonywać ich konwersji do liczb kiedy to koniecznie a w końcu ustawiać je jako wartości pól w obiektach, które skolekcjonuje w liście obiektów Takson
.
Plik: dane_chromosomy.csv
1 2 3 4 5 6 7 8 9 10 |
nr,rodzaj,gatunek,2n,x 1,Acer,negundo,26,13 2,Acer,saccharinum,52,13 3,Amaranthus,bouchonii,32,8 4,Amaranthus,chlorostachys,32,16 5,Amaranthus,lividus,34,17 6,Amaranthus,retroflexus,34,17 7,Anethum,graveolens,22,11 8,Heracleum,mantegazzianum,22,11 9,Heracleum,sosnovskii,22,11 |
Umieść powyższą treść w pliku i zapisz go np. pod nazwą dane_chromosomy.csv
na dysku, najlepiej w miejscu do którego nie prowadzi zbyt długa ścieżka (np. bezpośrednio w katalogu użytkownika), ponieważ będzie trzeba ją ręcznie wpisać po uruchomieniu programu.
Pobierz plik:
dane_chromosomy.csv (1177 pobrań )
Plik: Takson.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 |
package danezlozone; public class Takson { private int nr; private String rodzaj; private String gatunek; private int n2; private int x; public Takson(int nr, String rodzaj, String gatunek, int n2, int x) { this.nr = nr; this.rodzaj = rodzaj; this.gatunek = gatunek; this.n2 = n2; this.x = x; } public Takson(int nr){ this.nr = nr; } public String toString(String separator) { return getNr() + separator + getRodzaj() + separator + getGatunek() + separator + getN2() + separator + getX(); } public int getNr() { return nr; } public void setNr(int nr) { this.nr = nr; } public String getRodzaj() { return rodzaj; } public void setRodzaj(String rodzaj) { this.rodzaj = rodzaj; } public String getGatunek() { return gatunek; } public void setGatunek(String gatunek) { this.gatunek = gatunek; } public int getN2() { return n2; } public void setN2(int n2) { this.n2 = n2; } public int getX() { return x; } public void setX(int x) { this.x = x; } } |
Plik: DaneZlozone.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 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 |
package danezlozone; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.util.ArrayList; import java.util.Scanner; public class DaneZlozone { // Lista przechowująca taksony static ArrayList listaTaksonow = new ArrayList<>(); static String sciezka; static String naglowki; public static void main(String[] args) { Character opcja = 'x'; // Główna pętla programu while (!opcja.equals('k')) { // Jeżeli nie ma danych, to nalezy je najpierw wczytać if (listaTaksonow.isEmpty()) { listaTaksonow = czytajPlik(); } // wydruk menu i pobranie decyzji użytkownika opcja = menu(); switch (opcja) { case 'o': listaTaksonow = czytajPlik(); break; case 'w': wypiszDane(); break; case 'd': dodajDane(); break; case 'u': usunDane(); break; case 'z': zapiszDane(); break; case 'k': System.out.println("KONIEC"); break; default: System.out.println("Nie rozumiem"); } } } // Metoda wczytująca plik z danymi public static ArrayList czytajPlik() { Scanner skaner = new Scanner(System.in); System.out.println("Podaj ścieżkę do pliku: "); System.out.print("> "); sciezka = skaner.nextLine(); Path sciezkaDoPliku = Paths.get(sciezka); // Lista będzie przechowywała kolejne linie odczytane z pliku jako String ArrayList odczyt = new ArrayList(); try { // Wczytanie wszystkich linii do listy odczyt = (ArrayList) Files.readAllLines(sciezkaDoPliku); } catch (IOException ex) { System.out.println("Brak pliku!"); } // Pobranie pierwszej linii z nagłówkami... naglowki = odczyt.get(0); // ... następnie usunięcie jej odczyt.remove(0); // Wywołanie metody tworzącej obiekty i wypełniające je danymi z pliku ArrayList taksony = doObiektow(odczyt); return taksony; } // Metoda pobiera po serii danych, odpowiadających linii w pliku // a następnie tworzy obiekty typu Takson i wypełnia je danymi public static ArrayList doObiektow(ArrayList odczyt) { ArrayList taksony = new ArrayList<>(); for (String linia : odczyt) { // Dzielenie na poszczególne pola (oodzielone przecinkami) String[] l = linia.split(","); // Tworzenie obiektu i wywołanie konstruktora, który przyjmuje // jako argumenty dane Takson takson = new Takson( Integer.parseInt(l[0]), // nr l[1], // rodzaj l[2], // gatunek Integer.parseInt(l[3]), // n2 Integer.parseInt(l[4]) // x ); taksony.add(takson); } return taksony; } // Wypisanie danych public static void wypiszDane() { // Zamiana przecinków na tabulatory w nagłówku - bardziej przejrzyste System.out.println(naglowki.replace(",", "\t")); System.out.println("==============================================="); // Pobieranie każdego taksonu i wywołanie metody toString przyjmującej // jako argument rodzaj reparatora do wyświetlenia, tu jest to tabulator for (Takson takson : listaTaksonow) { System.out.println(takson.toString("\t")); } } // Dodane nowego taksonu public static void dodajDane() { // numer nowego taksonu powinien być o 1 większy niż // najwyższy obecnie istniejący int nowyId = znajdzMaxId() + 1; // Tworzenie obiektu, wykorzystanie konstruktora przyjmującego // jako argument numer Takson nowy = new Takson(nowyId); Scanner skaner = new Scanner(System.in); System.out.println("Podaj rodzaj: "); nowy.setRodzaj(skaner.nextLine()); System.out.println("Podaj gatunek: "); nowy.setGatunek(skaner.nextLine()); System.out.println("Podaj 2n: "); nowy.setN2(Integer.parseInt(skaner.nextLine())); System.out.println("Podaj x: "); nowy.setX(Integer.parseInt(skaner.nextLine())); // Dodanie taksonu do listy listaTaksonow.add(nowy); } // Metoda wyszukuje największy numer, przyda się do wyznaczenia numeru // dla nowego taksonu public static int znajdzMaxId() { int max = 0; for (Takson t : listaTaksonow) { if (t.getNr() > max) { max = t.getNr(); } } return max; } // Wyświetlenie menu i pobranie decyzji od użytkownika public static Character menu() { Scanner skaner = new Scanner(System.in); Character opcja = 'x'; // Pętla działa dopóki uzytkownik nie wybierze zrozumiałej opcji while ((!opcja.equals('o') && !opcja.equals('w') && !opcja.equals('d') && !opcja.equals('u')) && !opcja.equals('z') && !opcja.equals('k')) { System.out.println(" *** Menu *** "); System.out.println("o - otwórz plik"); System.out.println("w - wypisz dane"); System.out.println("d - dodaj takson"); System.out.println("u - usuń takson"); System.out.println("z - zapisz dane"); System.out.println("k - koniec"); System.out.print("> "); opcja = skaner.next().charAt(0); } return opcja; } // Usuwanie taksonu z listy private static void usunDane() { wypiszDane(); System.out.println("Podaj numer taksonu, który chcesz usunąć"); Scanner skaner = new Scanner(System.in); int numer = Integer.parseInt(skaner.nextLine()); boolean usuniety = false; // Wyszukanie taksonu for (int i = 0; i < listaTaksonow.size(); i++) { Takson takson = listaTaksonow.get(i); int numerTaksonu = takson.getNr(); if (numerTaksonu == numer) { listaTaksonow.remove(i); // zaznacz jeśli dokonane zostało usunięcie usuniety = true; break; } } if (usuniety) { System.out.println("Takson nr. " + numer + "usuniety"); } else { System.out.println("Nie ma taksonu o numerze " + numer); } } // Zapis danych private static void zapiszDane() { Path sciezkaDoPliku = Paths.get(sciezka); System.out.println("Podaj ścieżkę i plik do zapisu \n" + "Jeśli chcesz zapisać do bieżącego pliku: \n" + sciezka + "\n wciśnij enter"); Scanner skaner = new Scanner(System.in); String sc = skaner.nextLine(); // Jeśli użytkownik wpisał ścieżkę if (sc.length() > 0) { sciezkaDoPliku = Paths.get(sc); } ArrayList out = new ArrayList(); out.add(naglowki); for (Takson takson : listaTaksonow) { out.add(takson.toString(",")); } try { Files.write(sciezkaDoPliku, out); } catch (IOException ex) { System.out.println("Niestety, nie mogę utworzyć pliku!"); } } } |
Zadanie
Uzupełnij powyższy program o metodę, która umożliwi wyszukiwanie rodzajów lub gatunków w zestawie danych.
Leave a Reply
You must be logged in to post a comment.