Cześć! Na dzisiejszym wykładzie będziemy kontynuować dyskusję na temat strumieni wejściowych i wyjściowych w Javie, czyli w skrócie Java I/O („input-output”). To nie pierwszy wykład na ten temat i nie ostatni :) Tak się składa, że Java jako język daje wiele możliwości pracy z wejściem/wyjściem. Klas realizujących tę funkcjonalność jest całkiem sporo, dlatego podzieliliśmy je na kilka wykładów, żebyście się na początku nie pomylili :) W poprzednich wykładach poruszaliśmy BufferedReader , a także klasy abstrakcyjne InputStream i OutputStream oraz kilka potomków. Dzisiaj przyjrzymy się 3 nowym klasom: FileInputStream , FileOutputStream i BufferedInputStream .
Klasa FileOutputStream
Głównym celem klasy FileOutputStream jest zapisywanie bajtów do pliku. Nic skomplikowanego :) FileOutputStream jest jedną z implementacji abstrakcyjnej klasy OutputStream . W konstruktorze obiekty tej klasy przyjmują albo ścieżkę do pliku docelowego (do którego mają zostać zapisane bajty), albo obiekt klasyFile
. Spójrzmy na oba przykłady:
public class Main {
public static void main(String[] args) throws IOException {
File file = new File("C:\\Users\\Username\\Desktop\\test.txt");
FileOutputStream fileOutputStream = new FileOutputStream(file);
String greetings = "Cześć! Witamy w JavaRush - najlepszej witrynie dla tych, którzy chcą zostać programistami!";
fileOutputStream.write(greetings.getBytes());
fileOutputStream.close();
}
}
Tworząc obiekt, File
określiliśmy w konstruktorze ścieżkę, w której ma się on znajdować. Nie ma potrzeby tworzenia go z wyprzedzeniem: jeśli nie istnieje, program sam go utworzy. Możesz obejść się bez tworzenia dodatkowego obiektu i po prostu przekazać ciąg znaków z adresem:
public class Main {
public static void main(String[] args) throws IOException {
FileOutputStream fileOutputStream = new FileOutputStream("C:\\Users\\Username\\Desktop\\test.txt");
String greetings = "Cześć! Witamy w JavaRush - najlepszej witrynie dla tych, którzy chcą zostać programistami!";
fileOutputStream.write(greetings.getBytes());
fileOutputStream.close();
}
}
Wynik w obu przypadkach będzie taki sam. Możemy otworzyć nasz plik i zobaczyć tam:
Cześć! Добро пожаловать на JavaRush — лучший сайт для тех, кто хочет стать программистом!
Jednak jest tu jedno zastrzeżenie. Spróbuj uruchomić kod z powyższego przykładu kilka razy z rzędu, a następnie spójrz na plik i odpowiedz na pytanie: ile linii widzisz w nim zapisanych? Tylko jeden. Ale uruchomiłeś kod kilka razy. Okazuje się jednak, że za każdym razem dane były nadpisywane, zastępując stare. A co jeśli nam to nie odpowiada i potrzebujemy nagrywania sekwencyjnego? A co jeśli będziemy chcieli zapisać nasze powitanie do pliku trzy razy z rzędu? Tutaj wszystko jest proste. Ponieważ sam język nie może wiedzieć, jakiego rodzaju zachowania potrzebujemy w każdym przypadku, FileOutputStream
możesz przekazać konstruktorowi dodatkowy parametr - boolean append
. Jeśli jego wartość to true , dane zostaną zapisane na końcu pliku. Jeśli false (a wartością domyślną jest false ), stare dane zostaną usunięte i zapisane zostaną nowe. Przetestujmy i uruchommy nasz zmodyfikowany kod trzy razy:
public class Main {
public static void main(String[] args) throws IOException {
FileOutputStream fileOutputStream = new FileOutputStream("C:\\Users\\Username\\Desktop\\test.txt", true);
String greetings = "Cześć! Witamy w JavaRush — najlepszej witrynie dla tych, którzy chcą zostać programistami!\r\n";
fileOutputStream.write(greetings.getBytes());
fileOutputStream.close();
}
}
Wynik w pliku:
Cześć! Добро пожаловать на JavaRush - лучший сайт для тех, кто хочет стать программистом!
Cześć! Добро пожаловать на JavaRush - лучший сайт для тех, кто хочет стать программистом!
Cześć! Добро пожаловать на JavaRush - лучший сайт для тех, кто хочет стать программистом!
Inna rzecz! Należy pamiętać o tej funkcji podczas korzystania z klas we/wy. Kiedyś musiałem godzinami siedzieć nad zadaniami, żeby zrozumieć, dokąd poszły moje stare dane z plików :) I oczywiście, podobnie jak w przypadku innych klas I/O, nie zapomnij o zwolnieniu zasobów przez klasę close()
.
Klasa FileInputStream
Klasa maFileInputStream
odwrotny cel - odczytywanie bajtów z pliku. Podobnie jak FileOutputStream
dziedziczy OutputStream
, ta klasa wywodzi się z klasy abstrakcyjnej InputStream
. Napiszmy kilka linijek tekstu w naszym tekście „ test.txt ”:
«So close no matter how far
Couldn't be much more from the heart
Forever trusting who we are
And nothing else matters»
Tak wygląda implementacja odczytu danych z pliku za pomocą FileInputStream
:
public class Main {
public static void main(String[] args) throws IOException {
FileInputStream fileInputStream = new FileInputStream("C:\\Users\\Username\\Desktop\\test.txt");
int i;
while((i=fileInputStream.read())!= -1){
System.out.print((char)i);
}
}
}
Odczytujemy jeden bajt z pliku, konwertujemy odczytane bajty na znaki i wysyłamy je do konsoli. A oto wynik w konsoli:
So close no matter how far
Couldn't be much more from the heart
Forever trusting who we are
And nothing else matters
Klasa BufferedInputStream
Myślę, że biorąc pod uwagę wiedzę z poprzednich wykładów, można łatwo stwierdzić, po co te zajęcia są potrzebneBufferedInputStream
i jakie mają nad nimi zalety FileInputStream
:) Spotkaliśmy się już ze strumieniami buforowanymi, więc spróbuj zgadnąć (lub zapamiętać) przed dalszą lekturą :) Strumienie buforowane są potrzebne przede wszystkim do optymalizacji operacji we/wy. Uzyskiwanie dostępu do źródła danych, na przykład odczytywanie z pliku, jest operacją wymagającą dużej wydajności. A dostęp do pliku w celu odczytania jednego bajtu za każdym razem jest marnotrawstwem. Dlatego BufferedInputStream
odczytuje dane nie po bajcie, ale blokami i tymczasowo przechowuje je w specjalnym buforze. Dzięki temu możemy zoptymalizować działanie programu poprzez zmniejszenie ilości dostępów do pliku. Zobaczmy jak to wygląda:
public class Main {
public static void main(String[] args) throws IOException {
FileInputStream fileInputStream = new FileInputStream("C:\\Users\\Username\\Desktop\\test.txt");
BufferedInputStream bufferedInputStream = new BufferedInputStream(fileInputStream, 200);
int i;
while((i = bufferedInputStream.read())!= -1){
System.out.print((char)i);
}
}
}
Tutaj stworzyliśmy obiekt BufferedInputStream
. Akceptuje obiekt lub dowolny z jego następców jako dane wejściowe InputStream
, więc poprzedni FileInputStream
wystarczy. Jako dodatkowy parametr przyjmuje rozmiar bufora w bajtach. Teraz dzięki temu dane będą odczytywane z pliku nie po bajcie, a po 200! Wyobraź sobie, jak bardzo zmniejszyliśmy liczbę dostępów do plików. Aby porównać wydajność, możesz wziąć duży plik tekstowy o rozmiarze kilku megabajtów i porównać czas potrzebny na jego odczytanie i wyświetlenie na konsoli w milisekundach, używając FileInputStream
i BufferedInputStream
. Oto oba przykłady kodu:
public class Main {
public static void main(String[] args) throws IOException {
Date date = new Date();
FileInputStream fileInputStream = new FileInputStream("C:\\Users\\Username\\Desktop\\textBook.rtf");
BufferedInputStream bufferedInputStream = new BufferedInputStream(fileInputStream);
int i;
while((i = bufferedInputStream.read())!= -1){
System.out.print((char)i);
}
Date date1 = new Date();
System.out.println((date1.getTime() - date.getTime()));
}
}
public class Main {
public static void main(String[] args) throws IOException {
Date date = new Date();
FileInputStream fileInputStream = new FileInputStream("C:\\Users\\Username\\Desktop\\26951280.rtf");
int i;
while((i = fileInputStream.read())!= -1){
System.out.print((char)i);
}
Date date1 = new Date();
System.out.println((date1.getTime() - date.getTime()));
}
}
Podczas czytania pliku o wielkości 1,5 MB na moim komputerze FileInputStream
wykonał on zadanie w ~ 3500 milisekund, ale tutaj BufferedInputStream
wykonał to zadanie w ~ 1700 milisekund. Jak widać, buforowany strumień zoptymalizował wydajność programu 2 razy! :) Będziemy kontynuować naukę klas I/O - do zobaczenia wkrótce!
GO TO FULL VERSION