"Cześć! W dzisiejszej lekcji będziemy kontynuować rozmowę o strumieniach wejściowych i wyjściowych w Javie ( Java I/O ). To nie pierwsza lekcja na ten temat i na pewno nie ostatnia :)
Jak to zdarza się, że język Java zapewnia wiele sposobów pracy z operacjami wejścia/wyjścia.Istnieje sporo klas, które implementują tę funkcjonalność, więc podzieliliśmy je na kilka lekcji — aby nie pogubić się od samego początku :) W przeszłości lekcje, o których mówiliśmy
Oto, jak wygląda odczyt danych z pliku za pomocą
Jako dodatkowy argument przyjmuje rozmiar bufora w bajtach. Dzięki temu argumentowi dane będą teraz odczytywane z pliku nie po jednym bajcie na raz, ale po 200 bajtów na raz! Wyobraź sobie, jak bardzo zmniejszyliśmy liczbę dostępów do plików. Aby porównać wydajność, możesz wziąć duży plik tekstowy (kilka megabajtów tekstu) i porównać, ile czasu zajmuje w milisekundach odczytanie i wyprowadzenie na konsolę za pomocą

BufferedReader
, a także InputStream
klasy OutputStream
abstrakcyjne i kilka potomków.Dzisiaj rozważymy 3 nowe klasy: FileInputStream
, FileOutputStream
, i BufferedInputStream
.
Klasa FileOutputStream
Głównym celem klasyFileOutputStream
jest zapisywanie bajtów do pliku. Nic skomplikowanego :) FileOutputStream
to jedna z implementacji OutputStream
klasy abstrakcyjnej. W konstruktorze obiekty tej klasy pobierają albo ścieżkę do pliku docelowego (gdzie mają być zapisane bajty), albo obiekt File
. Przeanalizujemy przykłady każdego z nich:
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 = "Hi! Welcome to CodeGym — The best site for would-be programmers!";
fileOutputStream.write(greetings.getBytes());
fileOutputStream.close();
}
}
Podczas tworzenia File
obiektu przekazaliśmy konstruktorowi pożądaną ścieżkę. Nie musimy go wcześniej tworzyć: jeśli nie istnieje, program go utworzy. Możesz także obejść się bez tworzenia dodatkowego obiektu, po prostu przekazując ciąg znaków ze ścieżką:
public class Main {
public static void main(String[] args) throws IOException {
FileOutputStream fileOutputStream = new FileOutputStream("C:\\Users\\Username\\Desktop\\test.txt");
String greetings = "Hi! Welcome to CodeGym — The best site for would-be programmers!";
fileOutputStream.write(greetings.getBytes());
fileOutputStream.close();
}
}
Wynik w obu przypadkach będzie taki sam. Możemy otworzyć nasz plik i zobaczyć tam:
Hi! Welcome to CodeGym — The best site for would-be programmers!
Ale jest tu jeden niuans. Spróbuj uruchomić kod z powyższego przykładu kilka razy z rzędu. Następnie zajrzyj do pliku i odpowiedz na pytanie: ile ma wierszy? Tylko jeden. Ale uruchomiłeś kod kilka razy. Okazuje się, że dane są za każdym razem nadpisywane — stare jest zastępowane przez nowe. Co zrobić, jeśli nam to nie odpowiada i musimy pisać sekwencyjnie do pliku? Co zrobić, jeśli chcemy trzy razy z rzędu napisać nasze powitanie do pliku? To wszystko jest bardzo proste. Ponieważ język nie może wiedzieć, jakiego zachowania potrzebujemy w każdym przypadku, konstruktor FileOutputStream
może przyjąć dodatkowy parametr —boolean append
. Jeśli jego wartość to prawda, dane zostaną zapisane na końcu pliku. Jeśli jest fałszywa (a domyślnie jest fałszywa), wszelkie stare dane zostaną usunięte i zastąpione nowymi. Sprawdźmy to, uruchamiając 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 = "Hi! Welcome to CodeGym — The best site for would-be programmers!\r\n";
fileOutputStream.write(greetings.getBytes());
fileOutputStream.close();
}
}
Zawartość pliku:
Hi! Welcome to CodeGym — The best site for would-be programmers!
Hi! Welcome to CodeGym — The best site for would-be programmers!
Hi! Welcome to CodeGym — The best site for would-be programmers!
Teraz jest inaczej! Nie zapomnij o tej funkcji podczas korzystania z klas I/O. Był czas, kiedy spędzałem godziny nad zadaniami, godzinami łamiąc sobie głowę, próbując zrozumieć, w jaki sposób moje dane znikają z plików :) I oczywiście, podobnie jak w przypadku innych klas I/O, nie zapomnij użyć close()
metody uwolnić zasoby.
Klasa FileInputStream
MaFileInputStream
odwrotny cel — odczytywanie bajtów z pliku. Podobnie jak FileOutputStream
dziedziczenie OutputStream
, ta klasa wywodzi się z InputStream
klasy abstrakcyjnej. Napiszemy kilka wierszy tekstu w naszym pliku „ 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"

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 wyświetlamy je na konsoli. A oto wyjście 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 lekcji, można łatwo powiedzieć, po co nam taBufferedInputStream
klasa i jakie ma zalety w porównaniu FileInputStream
:) Ze strumieniami buforowanymi już się zetknęliśmy, więc spróbuj zgadnąć (lub zapamiętać) zanim przeczytasz dalej :) Buforowane strumienie są potrzebne głównie do optymalizacji wejścia/wyjścia. Dostęp do źródła danych, taki jak odczyt z pliku, jest kosztowną operacją pod względem wydajności, a dostęp do pliku w celu odczytania każdego bajtu jest marnotrawstwem. Dlatego BufferedInputStream
odczytuje dane nie bajt po bajcie, ale w blokach i tymczasowo przechowuje je w specjalnym buforze. To pozwala nam zoptymalizować program, zmniejszając liczbę 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 BufferedInputStream
obiekt. Jego konstruktor bierze instancję klasy InputStream
lub któregokolwiek z jej potomków, więc to FileInputStream
zrobi. FileInputStream
i BufferedInputStream
. Oto kod demonstrujący obie opcje:
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()));
}
}
Czytając plik 1,5 MB na moim komputerze, FileInputStream
ukończyłem pracę w ~ 3500 milisekund, ale BufferedInputStream
zarządzałem nią w ~ 1700 milisekund. Jak widać, buforowany strumień zoptymalizował pracę, skracając ją o połowę! :) Będziemy kontynuować naukę klas I/O — do zobaczenia!
GO TO FULL VERSION