- Alright.
In the last video,
we finished working with Java.nio and Java.io.
In other words, we're giving the Java.nio classes
to create to create commonly used Java.io streams.
It's now time to take a look at how
to read and write text files using Java.nio only.
I said that doing I/O using Java.nio means that data
is processed in blocks rather than one byte
or character at a time.
To accomplish this, Java.nio uses channels and buffers
and also sometimes selectors.
In terms of definitions, a channel is the DataSource
you're reading from or writing to.
That can be a file or socket or any other DataSource.
To use a DataSource as a channel,
you need a class that implements
the Java.nio.channel interface
and can connect to the DataSource.
A buffer, on the other hand,
is the container for the block of data
that you want to read or write.
Buffers are typed and that means that they
can only hold one type of data.
You can actually specify the size of the buffer as well.
And finally, selectors.
They allow a single thread to manage the I/O
for multiple channels.
We won't actually be covering selectors
because they're really only generally needed
in large enterprise applications.
When working with streams, which if you recall,
can be byte-based or character-based,
and we saw that in previous videos,
you need two instances of Java.io class
if you want to both read and write a file.
You need one instance for reading
and one instance for writing.
As an example, if you wanting to read and write
to a file in a nonrandom way,
you'd need to use FileReader and FileWriter.
When using Java.nio, you only need one instance
of a channel to both read and write the same DataSource.
Also, reading and writing is always buffered
so you don't have to wrap an instance with
a buffered instance as you do when working
with Java.io.
To be used as a channel, classes must implement
the "Java.nio.channels.channel" interface.
Though the JDK contains channels for several data sources,
including files, Network I/O, as well as sockets.
At this point in time, we've pretty well beaten
the adventure games application to death
so we're going to create a new project
to get familiar with how Java.nio I/O
is typically done.
Let's create that new project.
We'll call this one Java.nio.
Java.nio.
I'll just put it in the right place
on my computer.
Okay.
What we'll do to start off with,
we're going to create a file called "data.txt"
that's got a bit of data in there.
I'm going to right-click on the Project Name,
click on New - Select File.
We'll call this one "data.txt".
We're just going to put three lines of text.
Line one, line two, line three.
Pretty basic.
Alright, now that we've got a file to work with,
we're going to create the channel that's going
to be used to read and write the file.
There are two ways to do this.
You can create a channel from an open file instance or,
alternatively, you can use the FileChannel.open method.
Let's start by creating a channel from an open file.
We can get a FileChannel from the instances
of three classes.
FileInputStream.
FileOutputStream.
And RandomAccessFile.
We're actually going to be using a FileInputStream.
I'm going to open the source folder
and open our Main.java that was created with this project.
We'll add a Try block.
We're going to Catch our exceptions.
And we'll just put the Stack Trace in as an error.
The code's going to be:
"FileInputStream
"file = new FileInputStream("data.txt");",
the folder we just now created.
And then we're going to do
"FileChannel_channel = file.getchannel();".
So you create the FileInputStream
as we're doing on Line 11
and then on Line 12, we call in
the GetChannel method to get a FileChannel.
Now I said earlier that when using channels,
you only need one channel for both reading and writing.
The FileChannel is an exception to the rule.
If you create a FileChannel from a FileInputStream,
it's actually only open for reading.
If you create a file of a FileOutputStream,
it's only open for writing.
And for a RandomAccessFile,
it would depend on the parameter you passed
to the RandomAccessFile constructor.
And if you remember that when we create
a RandomAccessFile instance,
you can specify whether the file is open to read or write
by using either R or RW.
That's the parameter that you passed to the constructor.
Your next step will be to create the buffer
that's going to be used to read from the file.
Hang on a minute.
Under the covers, FileChannel is an instance
of SeekableByteChannel.
We're actually working with text
so you might think that it should be easy
to read a string or text from a file using Java.nio
but it's not or at least it wasn't until fairly recently.
When you create a buffer to use with a channel,
you have to specify the size of the buffer
which will specify how many bytes are read
from the file at any one time.
Now what should the buffer size be for your file?
Right now, all the lines are the same length
so you could create a buffer that has a size equal
to the length of each line in the file.
But what about files that contain lines
of varying or different lengths?
For example, if the first lines is 25 bytes long
and the next line is 100 bytes long,
what should the buffer size be?
If it's 25, then on the second read,
you'll only get 25 bytes of the second line.
If it's a hundred, then the first read will return
all the first line and the first 75 bytes
of the second line.
In each read, you have to pass the buffer
for new lines.
If you only found part of the line,
you'd have to do another read to get more of
the partial line, et cetera.
It does sound complicated and this is why you'd really want
to do it that way when dealing with text files.
Fortunately, new methods were introduced to
the Java.nio.files class in Java 8
that makes it easy to read one line
of a text file at a time.
Perhaps now you're getting some idea
of why the development community hasn't embraced
Java.nio for File I/O.
It has been around for years but
the performance improvements often aren't there
and some pretty common ways of doing I/O
were in fact overlooked.
In fact, the Files Class was only introduced
to Java 7.
It contained static methods that typically
deal with File I/O.
Hurrah, finally.
Okay.
Now since the file we're working with is small,
we're going to change the code so that it reads
the entire file in one shot.
Since we're going to be using a method introduced
in Java 8, this won't work with early versions of the JDK.
It's JDK 8, specifically.
We're just going to comment out those two lines there.
We're not going to be using those.
The code is going to be:
"Path dataPath = FileSystems.getdefault().getpath
"("data.txt");".
Then we're going to do a "List".
So a list of string...
I'm creating a list of string objects, calling it:
"lines = Files.readAllLines(dataPath);".
That's reading the entire file contents.
And then we're going to go through for each line:
"for(String line : lines) {".
And we're going to do a printout of lines
and that should be it.
We're doing this area here because my new project
hasn't been set for using JDK 8
and you can see there that it's telling us that
the usage of API documented
as Since 1.7+ so we need to go back
into our Project - Open Module Settings.
Click on Project and we want to make sure
that we're specifying Visual 8,
"Lambdas, type annotations etc.".
And that fixes that error up.
Let's actually try running this now.
And, actually, what have I done?
I've actually got an error here.
I'm trying to print out the list each time
instead of trying to print out the individual element.
So let's delete that
and Print Out Line is what it should have been.
Run it again.
And we've now got our three lines of information
coming out of our data.txt file.
Very easy to do.
You can see that, essentially,
that we've got the path to the folders we've done before.
We then call the File.readAllLines method.
You can see it here on Line 16
and that returns a list of string objects
and each entry will correspond to one line in the file
so you can see when we run the applet.
We actually ran the contents.
If you want to access the folder in a random fashion,
then you get a FileChannel from an instance
of RandomAccessFile.
We'll see how to do that in an upcoming video
but if you're working with a text folder to
want to read or write sequentially,
using the methods in the FilesClass
is the way to go.
We've seen how to read data.
Let's actually add some code to write some data.
We can actually do that by putting some code
in the line after the path definition.
We're going to be using the Files.write method.
Now, a few things to note here.
Firstly, when using the FilesClass to write a text file,
each write is considered an isolated write
in the sense that you don't open a file,
call a write method a bunch of times
and then close the file.
Each call to Files.write involves opening the file,
writing to it, and then closing the file.
Because of that, if you wanted to write more than one line,
you'd typically use something like StringBuilder
to build the text up that you wanted to output
and do it all in one shot.
If the amount of output is large,
you could do it in chunks perhaps.
Something like 10 or 50 or maybe a hundred lines
at a time.
Now a Files.write method,
you write bytes not strings.
So we're going to be calling the String.getbytes method
to convert a string to bytes.
Let's see how this works.
I'm going to type in:
Files.write(dataPath,
It links to our--
Points to our file.
And in terms of the output, we're going to do:
" " to go to a new line.
"Line 4"
And then, we want to do: ".getBytes".
I'm going to pass or specify ("UTF-8").
Parantheses and then we want to put a comma.
It's going to be: ",_StandardOpenOption.APPEND);".
That's actually it.
That's the code to write some data to that file.
Let's actually run that and see if it works.
You can see we've got one, two, three.
Then we've got a space and then Line 4.
That's because we've got a text file.
I think I pressed Enter after Line 3
which created a new line.
We can actually delete that.
Come back to here and then we could comment out
the write again because we don't want it
to press us again on it.
What we could do is make it look right
on Line 5 this time.
Run it again.
Now I've got our five lines of output appearing
and our file has now got five entries.
Alright, that's working well.
Again, the only real thing there that could potentially
be confusing is the fact that we've got a string here
that we want to write but we're using the .getBytes
and passing the parameter of UTF-8
for the character set.
We're converting that string to bytes
in other words and that's because Files.write,
that method actually need bytes.
Because it actually writes bytes and doesn't
process the string directly.
We're passing UTF-8 as the character set.
But when you're actually using the .readAllLines,
UTF-8 is assumed.
If you want to write text that's
in a different character set,
you can specify the character set as a second parameter
to the .readAllLines.
So have a look at that.
You can see that, by default, it's returning:
"readAllLines(path, StandardCharsets.UTF_8".
That's an overloaded method.
So we could do something very similar to that.
We could pass that as: "StandardCharsets."
and we could something like "US_ASCII" for argument's sake.
Again, we're not doing that so UTF is assumed.
That's more of an easy first method.
By the way, the other thing is here
you may not have actually needed the leading
new line character here in the string.
If you included an new line character at the end
of Line 3 as you saw me do when you added
the existing contents to the file,
then you wouldn't need it.
Alright, last thing to note before we finish
the video is this third parameter.
This StandardOptionOpen.APPEND.
That obviously means if you want to write
to a file that already exists and you want
to append the bytes to the end of the file,
that's the one you're actually using.
That would be the right one.
If you don't specify a third parameter,
the method assumes that you, in fact,
want to start fresh.
If the file doesn't exist, it'll create it.
If it does exist, it'll truncate or wipe out
the existing contents.
You can explicitly ask for that behaviour
by passing StandardOpenOption.CREATE
as a third parameter and also
StandardOpenOption.TRUNCATE_EXISTING
as a fourth parameter.
The right method only requires two parameters.
Any others are optional and have
to be of type OpenOption.
But again, as you can see in our case,
that clearly what we've done is working.
We've now got five lines of output appearing in that file.
The other thing to note here about
this Files.readAllLines method
is that it reads the entire file into memory.
What would you do if you needed to read
a huge text file and you didn't want to read
the entire thing into memory?
Well, honestly, to be completely honest,
you'd use the Java.io classes.
You could use a ByteBuffer and go through the pain
of pausing at each line.
You could also call Files.NewBufferedReader
to create a BufferedReader that's using
a ByteChannel under the covers.
But as mentioned earlier,
some developers have benchmarked Java.io
versus Java.nio and found that Java.nio
is actually often much slower.
If you're working with a huge text file,
another thing to consider would be using
a different file format.
Perhaps you should be using a database
rather than a flat file.
Now the adventure game examples that we've worked with
use small files but since there's a relationship between
the locations and the exits,
it may well have made more sense to store
the data in a database and retrieve it on demand
as you did when using the RandomAccessFile class.
There's really no rule or best practise that dictates
using Java.nio over Java.io.
If you're considering Java.nio, really,
you should benchmark both ways and see which one wins
for your application.
The only case in which Java.nio might be
the recommended approach is when you're working
on a large application that using multiple threads
to do I/O.
Otherwise, my opinion is that Java.io
is absolutely fine to use.
As you'll see later, though,
Java.nio is an improvement when it comes
to dealing with that file system.
Let's finish the video here.
In the next one, we're going to turn our attention
to binary files and how to work with those
with Java.nio.
See you in the next video.