- [Instructor] Alright, so in the previous videos
we talked about and looked at the files class
to read and write text files.
Let's now turn our attention to binary files.
So we're gonna start by writing to a binary file
so that we've got something we can read.
So we're gonna start by writing the string hello world,
to a file in binary format.
So I'm gonna comment out this code.
And at the top, we're going to start
by typing file output stream.
Bin file is equal to new, file output stream.
We're gonna call the file data dot DAT,
call it anything you literally like there.
Then we need a file channel, so file channel,
bin channel, is equal to bin file, dot get channel.
Alright, so far so good.
But the next thing we need is a buffer,
because we're writing to a file, we've got the data,
so you can create a byte buffer that matches
the length of the data.
In the read cases, you don't know the length
of the data, so what you can do is create
a buffer of a given length, and then parse what it receives.
But note that you don't have to use the same buffer
to do all your reads and writes,
you can create as many buffers as you like.
So before I type some more code, let's get some
terminology out of the way.
Firstly, a buffer's capacity, is the number
of elements it can contain.
Now a buffer's position is the index of the next
element that should be read or written.
It can't be greater than the buffer's capacity.
A buffer's mark is used by the buffer dot reset method,
when this method is called, the buffer's position
is reset to it's mark.
Now if you know you wanna rewind the buffer
to a certain point, you'll mark the point,
and then later you can call the reset method
to reset the position to the mark.
But you'll more of how that works as we proceed.
So with that out of the way, let's actually now write
this hello world string to a file in binary format.
The first thing we need to create a byte array
for the string that we want to output,
so it's gonna be byte array output bytes, we'll call it,
is equal to, and we'll just type in hello world,
exclamation mark, dot bytes, so there's out byte array.
And then we need a byte buffer,
so we type byte buffer, you can see that's
a java dot NIO input, buffer is equal to,
and it's byte buffer, dot wrap,
note that we're using a static method there,
then output bytes.
So we've created our buffer now.
So let me first call this string dot get bytes,
on line 14, but we do call it to create the byte array,
then we're calling this byte buffer dot wrap static method,
to create a byte buffer from the byte array.
The actual wrap method itself, does the following,
first it wraps the bite array into the buffer.
What that means is that the buffer
is backed by the byte array.
Now this is actually important.
Modifications to the buffer will actually change
the array, and modifications to the array,
will change the buffer.
Now if you use the buffer to write out our string
or our bytes, and then you use it to read from a file,
the output byte array will be changed,
in other words it'll no longer contain hello world.
So essentially, when you call the wrap method,
you're telling the run time, that you wanna
use the byte array as the buffer.
And since you've used wrap, the buffer is already filled.
May not be what you're expecting to happen there.
Secondly, it sets the position of the buffer to zero,
and when you use the buffer the read-write
will start at position zero.
Thirdly, the buffers capacity will be set
to the byte array's length, which you'd expect.
And fourthly, the buffers mark, will be undefined.
And that can be set later.
So now that we've got our buffer, it contains the bytes,
which was the string object that we wanted to write,
we can use the channel to write it to a file.
So we'll do that, int channel dot write, buffer.
And if you wanna know how many bytes are actually written,
you can get that from the write method's return value,
so we could change that to be int, num bytes, equals,
byte channel dot write buffer.
And then we can print that out,
num bytes, written, was, plus num bytes.
Finally then you'd have to close the input stream
and the file channel, because of that,
you can use try with resources instead of
creating the instances with the try block.
So it's probably better to do it that way,
so let's actually change that,
to be a try with resources, so it's gonna read,
try file, output stream, bin file, equals new,
file output stream, I guess we could've just copied the
definitions there, actually let's do that.
It'll be much easier to do that,
so we'll put those both in here.
Like that, and I'm going to put another parentheses there
and then open a code block, and that should fix that.
So now that we've done that, we'll find that both
the channel and the output stream will be closed
once we've finished execution because
we're using try with resources.
I think that's preferable to creating them within
the try block, so let's actually try, no pun intended,
to run this application.
You can see we've got 12 bytes written,
which would be consistent with hello world,
which is exactly 12 bytes, that we can see over here
our data dot DAT file, is actually written,
and we can actually see the text
is actually appearing in that file.
Now by the way, it looks like a text file,
because we've used the UTF eight character set,
and as I mentioned you can see the 12 bytes
were actually written in the console
window down at the bottom.
Alright, so let's go back to our code.
And actually change it, or add to it,
to actually write an integer.
Now we could reuse the buffer, but instead
what we're gonna do is create an int buffer,
that you'll use whenever you want to write an integer.
So we'll actually do that instead.
We'd go, byte buffer, int buffer is equal to
byte buffer, dot allocate, integer dot bytes.
So we're calling the byte buffer dot allocate method,
and we're passing to it the size that we want
the buffer to be, which is this case is,
because we mentioned we want to write an integer,
this is the number of bytes in a integer.
So now that we've got the buffer,
we can write an int to the buffer,
so let's write an integer, number, pick a number,
say 245, so we go int, buffer dot put int, 245.
Let's write it to our bin channel,
so num bytes, is equal to bin channel, dot write,
it's gonna be int buffer this time.
And then we'll print that out again.
And let's just copy that line up there.
Alright, so let's actually run it and see what happens.
And look at our file.
Well that's a bit weird, as you can see,
the integer's not appearing there,
in the data file and then down here under the second write,
num bytes written was actually zero.
So what actually happened, why isn't the integer in
the file, and why are we getting zero as the output?
When trying to write out the integer.
Well here's the deal in a nutshell.
When you created the buffer, the position of the buffer
was set to zero, now when you call the put int method,
on line 21 here, you wrote the int into the buffer,
which changed the buffer's position.
Now in the bin channel dot write method,
which we're using on the next line, line 22,
goes to read the buffer's contents,
it'll start the read at the buffer's position.
So you have to explicitly reset the position to zero
if you want the bin channel dot write method
to start reading the buffer from the beginning.
So to do this, we're gonna call
the byte buffer dot flip method.
And the flip method resets the position to zero,
and discards any mark that have been set previously.
Now, it'd be obviously fantastic if the IO methods
did this for us, so that developers didn't have
to explicitly reset our buffer's position to zero,
but alas, that's not the case, that was
a design decision, because you may wanna do
modable writes or reads to a buffer,
before writing to the channel, in which case
you wouldn't want to reset the position
to zero after every write.
Now, we didn't have to use, we don't have to use flip
when we use the wrap method, and that's because
it resets the position to zero.
The wrap method up here, on line 16,
in case you're wondering.
So by using the wrap method, it's automatically
resetting the position to zero.
And, I listed three things the wrap method does,
the second on that list is that it
sets the position to zero.
So that's one method that does take care of this for us,
it's a convenience method to use when writing byte arrays.
But when writing numerics, and that's an int,
a long and a short, using the put int,
put long and put short methods, et cetera,
you do have to take care of resetting the buffer yourself.
And as mentioned, you may not wanna do so after every
read or write, and that's why they've actually got
that in there, so if you had 20 integers to write,
for arguments sake, you could create a buffer
large enough to accept 20 integers having
called the put int method 20 times.
Each write would start at the buffer's
position in that case.
For the first put int method, the buffer's position
would be zero, the second caller would be four,
the third it would be eight, given that four
is the number of bytes for an integer.
Now also note that you can write to an explicit index
in the buffer, to do so, you pass a second parameter
to the put method, that specifies the start index
for the read or write.
But let's flip the buffer before using it,
to write to the channel, so we need to put that code
before the write and after the put,
so right down here, we're going to put int buffer,
dot flip, to reset the position to zero,
and if we run it now, we should find that
the integers written to the file
and we get to four back us and the right bytes written.
Open data dot date, we've now got this message
which is consistent with the fact that
we're now writing binary data, and we've got
the integers showing there, you can see
there's four bytes been out of there,
that's the representation of our integer,
but importantly down here, number bytes
are written was four, so that actually worked.
Alright, so let's actually write another integer this time,
we'll use a negative number.
And let's actually pretend, I'm gonna actually
just copy these three lines, actually copy these four lines,
but I'm going to get rid of the flip,
and the number I'm gonna write,
I'll use a negative number, negative 98765.
So we're pretending that, that we don't understand
that we have to call the flip method
again to reset the buffer, both before you write
negative 98765 to the buffer, and then before
you write the buffer to a file.
So if we do that now, in this scenario, and run it,
we actually get an exception, you can see here
we've got java dot NIO dot buffer overflow exception,
and the actual error is on line 26,
and line 26 is this put int negative 98765,
and the reason for that is, after we wrote the last
integer, 245 to the file, the buffer's position
was set to four, but buffer is only four bytes long,
and that's because when we used the buffer,
we set it up using the buffer dot allocate method
to integer drop bytes, which is a length of four
on a computer, and that's usual with java
the position is zero-based.
So four is actually outside of our bounds,
so the buffer's capacity, there's no room
at position four to write any more bytes,
and then hence we're getting an exception for that reason.
So calling flip will obviously solve the problem,
so we need to do two flips here, we need to do a flip,
and then I'll just copy the code,
flip before our call, to the put int,
and then also, we need to do a flip after the put int,
prior to the call to the write method.
We'll just run this to confirm that it works.
This time you can see that it was written,
we've got calls, two bytes, lots of bytes written,
integers written I should say,
and you can see, although we can't understand it,
we can see that the extra bytes actually
were written to the file.
So the point here, always remember to call the flip method
whenever you wanna reset a buffer's position to zero.
It's a common error to forget to do it,
and then wonder why the data hasn't been written
to the data source.
Now if you're going to do multiple writes to a buffer
before writing the buffer to a channel,
you don't have to call flip on after each write,
but you do have to call it whenever
you flip from reading to writing, or writing to reading,
and hence the name flip.
When you've filled a buffer, and you're ready
to write it to a channel, or to the channel,
you'll actually be reading from the buffer at that point,
and then read from the buffer and write to the channel,
and hence the call to flip is required.
Alright, so that's it, I'm going to finish
the video here now, in the next one,
I'm gonna start taking a look at reading files
using channels and buffers, and other things
along those lines, so I'll see you in the next video.