"Hi! In today's lesson, we'll continue our conversation about input and output streams in Java (Java I/O). This is not the first lesson on this topic, and it certainly won't be the last :)
As it happens, the Java language provides many ways to work with I/O. There are quite a few classes that implement this functionality, so we've divided them into several lessons — so you won't get confused from the start :)
In past lessons, we touched on
BufferedReader
, as well as the InputStream
and OutputStream
abstract classes and several descendants. Today we'll consider 3 new classes: FileInputStream
, FileOutputStream
, and BufferedInputStream
.
The FileOutputStream class
The main purpose of theFileOutputStream
class is to write bytes to a file.
Nothing complicated :) FileOutputStream
is one of the implementations of the OutputStream
abstract class.
In the constructor, objects of this class take either the path to the target file (where the bytes should be written) or a File
object.
We'll examine examples of each:
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();
}
}
When creating the File
object, we passed the desired path to the constructor. We don't need to create it in advance: if it doesn't exist, the program will create it.
You can also get by without creating an extra object, simply passing a string with the path:
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();
}
}
The result in both cases will be the same. We can open our file and see the following there:
Hi! Welcome to CodeGym — The best site for would-be programmers!
But there's one nuance here. Try running the code from the example above several times in a row. Then look in the file and answer this question: how many lines does it have?
Just one. But you ran the code several times. It turns out that the data is overwritten every time — the old is replaced by the new.
What do we do if that doesn't suit us and we need to write sequentially to the file? What if we want to write our greeting to a file three times in a row?
It's all very simple. Since the language can't know what behavior we need in each case, the FileOutputStream
contrucutor can take an additional parameter — boolean append
.
If its value is true, the data will be written to the end of the file. If it is false (and by default it is false), any old data will be erased and replaced by new data.
Let's check this by running our modified code three times:
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();
}
}
File contents:
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!
Now that's different!
Don't forget about this feature when using I/O classes. There was a time when I spent hours on tasks, racking my brains for hours, trying to understand how my data was disappearing from files :)
And of course, just as with other I/O classes, don't forget to use the close()
method to free resources.
The FileInputStream class
TheFileInputStream
has the opposite purpose — reading bytes from a file. Just as FileOutputStream
inherits OutputStream
, this class derives from the InputStream
abstract class.
We'll write a few lines of text in our "test.txt" file:
"So close no matter how far
Couldn't be much more from the heart
Forever trusting who we are
And nothing else matters"
Here what it looks like to read data from a file using 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);
}
}
}
We read one byte from the file, convert the read bytes into characters and display them on the console.
And here's the console output:
So close no matter how far
Couldn't be much more from the heart
Forever trusting who we are
And nothing else matters
The BufferedInputStream class
I think, given the knowledge from past lessons, you can easily say why we need theBufferedInputStream
class and what advantages it has compared to FileInputStream
:)
We've already encountered buffered streams, so try to guess (or remember) before you continue reading :)
Buffered streams are needed mainly to optimize I/O.
Accessing a data source, such as reading from a file, is an expensive operation in terms of performance And to access a file to read each byte is wasteful.
That's why BufferedInputStream
reads data not one byte at a time, but in blocks, and temporarily stores them in a special buffer. This lets us optimize the program by reducing the number of times we access the file.
Let's see what this looks like:
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);
}
}
}
Here we created a BufferedInputStream
object. Its constructor takes an instance of the InputStream
class or any of its descendants, so FileInputStream
will do.
As an additional argument, it takes the buffer size in bytes. Thanks to this argument, the data will now be read from the file not one byte at a time, but 200 bytes at a time! Imagine how much we've reduced the number of file accesses.
To compare performance, you can take a large text file (several megabytes of text) and compare how long it takes in milliseconds to read and output to the console using FileInputStream
and BufferedInputStream
.
Here's code that demonstrates both options:
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()));
}
}
When reading a 1.5 MB file on my computer, FileInputStream
completed the work in ~3500 milliseconds, but BufferedInputStream
managed it in ~1700 milliseconds. As you can see, the buffered stream optimized the work, cutting it in half! :)
We will continue to study I/O classes — see you soon!
GO TO FULL VERSION