(c) Krzysztof Barteczko 2014 Klasy narzędziowe (1) (c) Krzysztof Barteczko 2014
Bajtowe strumienie plikowe (c) Krzysztof Barteczko 2014
(c) Krzysztof Barteczko 2014 Operacje na plikach (c) Krzysztof Barteczko 2014
(c) Krzysztof Barteczko 2014 Przykład (c) Krzysztof Barteczko 2014
(c) Krzysztof Barteczko 2014 try-with-resources (c) Krzysztof Barteczko 2014
Pliki tekstowe i strony kodowe Każdy plik jest sekwencją bajtów. Znaczenie bajtów może być różne - liczby, znaki. W Javie znaki są przedstawiane w Unikodzie. A pliki tekstowe zapisywane są w różnych systemach kodowania, niekoniecznie w Unicodzie. Sposób kodowania znaków tekstu nazywa się stroną kodową. Np. wiele polskich dokumentów HTML zapisanych jest z wykorzystaniem strony kodowej ISO8859-2, inne - z wykorzystaniem strony Cp1250 (inaczej zwanej Windows 1250). W każdym systemie operacyjnym jest ustawiona tzw. domyślna strona kodowa, która będzie wykorzystywana np. przy wczytywaniu i zapisie plików przez systemowe edytory tekstu. Np. w systemie Windows taką domyślną stroną kodową często jest - w polskich warunkach - Cp1250 lub UTF-8. Przy wczytywaniu Java musi dokonać przekodowania plików zapisanych w domyślnej stronie kodowej na Unicode, a przy zapisie wykonać operację odwrotną - przekodowania z Unicodu do domyślnej strony kodowej. Metody klas FileInputStream i FileOutputStream - nie wykonują tego zadania (czytają i piszą bajt po bajcie), co w przypadku plików tekstowych może powodować utratę informacji. (c) Krzysztof Barteczko 2014
(c) Krzysztof Barteczko 2014 Strumienie znakowe Strat informacji nie będzie, jeśli do czytania plików wykorzystamy obiekt klasy FileReader, a do zapisywania – FileWriter Klasy te zapewniają konwersje między domyślną stroną kodową systemu operacyjnego i Unicodem. Te klasy reprezentuja tzw. strumienie znakowe, bo operacje czytania/zapisu dokonują przekształceń bajty <-> znaki. (c) Krzysztof Barteczko 2014
(c) Krzysztof Barteczko 2014 Buforowanie Operacje fizycznych odwołań do pliku (dysku) są czasochłonne. Aby je ograniczyć - stosujemy tzw. buforowanie. W pamięci operacyjnej wydzielany jest duży obszar pamięci, który zapełniany jest przez jednorazowe fizyczne odwołanie do pliku. Instrukcje czytania pliku pobierają informacje z tego bufora. Gdy bufor jest pusty - następuje kolejne jego wypełnienie poprzez fizyczne odwołanie do pliku. W ten sposób liczba fizycznych odwołań do pliku (do dysku) jest mniejsza niż liczba zapisanych w programie instrukcji czytania danych. Zapisywanie pliku „działa w odwrotną stronę” Buforowanie czytania: BufferedReader + wygodna metoda readLine() Buforowanie zapisu: BufferedWriter + wygodna metoda newLine() (c) Krzysztof Barteczko 2014
(c) Krzysztof Barteczko 2014 Przykład static void copyLines(String infn, String outfn) throws IOException { try (BufferedReader in = new BufferedReader(new FileReader(infn)); BufferedWriter out = new BufferedWriter(new FileWriter(outfn)) ) { String line; while ((line = in.readLine()) != null) { out.write(line); out.newLine(); } (c) Krzysztof Barteczko 2014
(c) Krzysztof Barteczko 2014 Przekodowanie (c) Krzysztof Barteczko 2014
Dla plików – lepsze narzędzia czytanie plików tekstowych: Scanner czytanie i pisanie dla dowolnych plików: metody klasy java.nio.Files Wymagają reprezentacji obiektów plikowych m.in. jako: File File f = new File("tekst.txt"); Path (c) Krzysztof Barteczko 2014
(c) Krzysztof Barteczko 2014 Scanner Scanner scan = new Scanner(new File("x.txt")); // domyślna strona kodowa (c) Krzysztof Barteczko 2014
Skaner – sumowanie liczb z pliku File f = new File("nums.txt"); long sum = 0; String msg; try (Scanner sc = new Scanner(f)) { while(sc.hasNextInt()) sum += sc.nextInt(); msg = "Suma: " + sum; } catch (Exception exc) { msg = exc.toString(); } System.out.println(msg); (c) Krzysztof Barteczko 2014
(c) Krzysztof Barteczko 2014 Klasa java.nio.Files Wygodnie operuje się na plikach statycznymi metodami klasy Files z pakietu java.nio.file. To jest całkiem inna klasa niż klasa File, bo: zapewnia znacznie lepszą reprezentację współczesnych systemów i obiektów plikowych (m.in. większą liczbę atrybytów obiektów plikowych, obsługę tzw. symbolicznych linków). dostarcza prostych metod wejścia-wyjścia dla plików. Większość metod klasy Files ma argumenty typu Path. (c) Krzysztof Barteczko 2014
Files – kopiowanie plików static void copyFile(String srcFn, String destFn, CopyOption ... opt) throws IOException { Path src = Paths.get(srcFn); Path dest = Paths.get(destFn); Files.copy(src, dest, opt); } public static void main(String[] args) throws IOException { copyFile("in1", "out2"); // wyjatek jesli out2 istnieje // istniejący plik out1 będzie zastąpiony copyFile("in1", "out1", REPLACE_EXISTING); // skopiowanie do katalogu Temp z zachowaniem atrybutów copyFile("in1", "/Temp/in1", COPY_ATTRIBUTES); (c) Krzysztof Barteczko 2014
Files – czytanie wierszy Metoda readAllLines(...) zwraca listę wszystkich wierszy pliku, po której możemy np. iterować. Jesli nie podamy strony kodowej – UTF-8. Podanie strony kodowej pliku przy użyciu obiektu klasy Charset (domyślną stronę kodową uzyskujemy przez Charset.defaultCharset()) int lcount = 0; // liczba wierszy w pliku int llength = 0; // łaczna długość wierszy int maxLen = 0; // dlugość najdłuższego for (String line : Files.readAllLines(Paths.get("in1"), Charset.defaultCharset())) { lcount++; int len = line.length(); llength += len; if (len > maxLen) maxLen = len; System.out.println(line); } System.out.println("--------------------------------"); System.out.println("Plik zawiera wierszy: " + lcount); System.out.println("O łącznej dlugości: " + llength); System.out.println("Najdłuższy wiersz ma długosć: " + maxLen); (c) Krzysztof Barteczko 2014
Files – czytanie i zapis bajtów Metoda Files.getAllBytes(Path) zwraca zawartość pliku jako tablicę bajtów. Tablicę bajtów możemy zapisać do pliku, używając metody File.write(bytes[]). Metody będą użyteczne, gdy chcemy działać na plikach w postaci binarnej, ale czasem również przydadzą się do szybkiego przetwarzania plików tekstowych. Kod przedstawia zamianę w pliku znaków tabulacji na spacje. void tabsToSpaces(String fname) throws IOException { Path fpath = Paths.get(fname); byte[] cont = Files.readAllBytes(fpath); for (int i = 0; i < cont.length; i++) { if (cont[i] == 0x09) cont[i] = (byte) ' '; } Files.write(fpath, cont); Operacje readAllBytes() czy readAllLines() wykonywane są "za jednym zamachem" i zamykają pliki (tak samo operacje File.write(..)). (c) Krzysztof Barteczko 2014
Files – czytanie i zapis wierszy Druga wersja metody Files.write ma jako argument listę wierszy, które mają być zapisane do pliku. Łatwo więc można zmienić kodowanie pliku. Przy okazji, do ustalania stron kodowych zastosujemy statyczną metodę forName klasy Charset. Path file = Paths.get("page.html"); Charset cpIn = Charset.forName("Cp1250"), cpOut = Charset.forName("ISO8859-2"); Files.write(file, Files.readAllLines(file, cpIn), cpOut); (c) Krzysztof Barteczko 2014
Wyrażenia regularne (1) (c) Krzysztof Barteczko 2014
Wyrażenia regularne (2) (c) Krzysztof Barteczko 2014
Wyrażenia regularne (3) (c) Krzysztof Barteczko 2014
Wyrażenia regularne (4) (c) Krzysztof Barteczko 2014
Do czego służą wyrażenia regularne? (c) Krzysztof Barteczko 2014
(c) Krzysztof Barteczko 2014 Pattern i Matcher String regex = "..."; // wyrażenie regularne // Kompilacja wzorca Pattern pattern = Pattern.compile(regex); // Tekst wejściowy String txt = "..."; // tekst do analizy // Uzyskanie matchera Matcher matcher = pattern.matcher(txt); // ... zastosowanie metod Matchera do przetwarzania tekstu (c) Krzysztof Barteczko 2014
(c) Krzysztof Barteczko 2014 Metody Matchera (c) Krzysztof Barteczko 2014
(c) Krzysztof Barteczko 2014 Przykład 1 String regex = "[0-9]+"; String txt = "123 996"; System.out.println("Tekst: \n" + "'" + txt + "'" + "\nWzorzec: " + "'" + regex + "'"); Pattern pattern = Pattern.compile(regex); Matcher matcher = pattern.matcher(txt); String result = ""; // do prezentacji wyników wyszukiwania while (matcher.find()) { result += "\nDopasowano podłańcuch '" + matcher.group() + "'" + "\nod pozycji " + matcher.start() + "\ndo pozycji " + matcher.end(); } if (result.equals("")) result = "Nie znaleziono żadnego podnapisu " + "\npasującego do wzorca"; System.out.println(result); (c) Krzysztof Barteczko 2014
(c) Krzysztof Barteczko 2014 Przykład 2 String regex = "([0-9]+)\\s+(\\p{L}+)\\s+([1-9][0-9]*)"; String txt = "1111 Odkurzacz 20"; System.out.println("Tekst: " + "'" + txt + "'" + "\nWzorzec: " + "'" + regex + "'"); Pattern pattern = Pattern.compile(regex); Matcher matcher = pattern.matcher(txt); boolean isMatching = matcher.matches(); if (isMatching) { int n = matcher.groupCount(); // ile grup for (int i = 1; i <=n; i++) { // pobranie zawartości i-ej grupy String grupa = matcher.group(i); System.out.println("Grupa " + i + " = '" + grupa + "'"); } } else System.out.println("Tekst nie pasuje do wzorca"); (c) Krzysztof Barteczko 2014
(c) Krzysztof Barteczko 2014 Przykład 3 (c) Krzysztof Barteczko 2014
Regex w metodach klas String i Scanner String: split, matches, replaceFirst, replaceAll. Scanner: useDelimiter(regex) zmienia regex, na którym skaner rozkłada teksty metodą next(), next(regex) zwracają kolejny symbol ograniczony separatorami oraz pasujący do wzorca podanego w wyrażeniu regularnym i ustawiają pozycję skanera zaraz za nim, findInLine(regex) i findWithinHorizon(regex, n) wyszukują w tekście i zwracają napis pasujący do wzorca (ignorując separatory), jednocześnie przesuwając pozycję skanera za ten napis. Skaner łączy możliwość użycia wyrażeń regularnych z łatwym pobieraniem liczb (np. metoda nextInt). Należy jednak pamiętać, że przy pobieraniu liczb rzeczywistych (z separatorem miejsc dziesiętnych) skaner przyjmuje format ich zapisu (m.in. to czy separatorem miejsc dziesiętnych jest kropka czy przecinek) zgodnie z domyślną lokalizacją (aktualnymi ustawieniami regionalnymi). Używaną przez skaner lokalizację można zmienić za pomocą metody skanera useLocale(..). (c) Krzysztof Barteczko 2014
(c) Krzysztof Barteczko 2014 Skaner – przykład 1 (c) Krzysztof Barteczko 2014
(c) Krzysztof Barteczko 2014 Skaner – przykład 2 Zadanie: wyróżnić tytuły (napisy w znacznikach <h2> w dokumencie html Scanner fScan = new Scanner(new File("Dok.html")); String h2regex = "(?s)(?i)<h2>(.+?)</h2>"; while(fScan.findWithinHorizon(h2regex, 0) != null) { // Skaner może uzyskać Matcher przez odwolanie match() // Od Matchera pobierzemy zawartość jedynej grupy String title = fScan.match().group(1); System.out.println(title); } fScan.close(); (c) Krzysztof Barteczko 2014