- [Tim] All right so in the last video
we talked about or we consolidated the code now
to make it a bit more efficient
by actually putting a punch of data
putting that into the bugger before we actually wrote it out
so let's actually just run this
to make sure it does work.
And we look at our data.dat file.
We can see there that obviously
we can't read the numeric values
but we can see that the Hello World
and Nice to meet you are actually showing there.
Let's put down this data.txt.
So so far so good there.
And I think you'll agree that the code's
a little bit cleaner than the previous version was
when we used modable buffers.
So for fun now, and perhaps a mini challenge,
I'm gonna remove the flip call here
and see what happens.
So I'm gonna take that out.
And can you guess what's gonna happen
when I remove the flip?
Right, and let's try running it to see what happens
with the removal of the flip method call.
And look at that data.dat file again.
And you can see it's dramatically different.
So effectively there's nothing in there.
There's this strange character showing up on the screen.
But obviously the output that we saw
prior to having the flip method removed
has changed dramatically what the output would be.
And the point is if you don't use the flip method,
the buffer's position will be set to after
the last value you wrote to it
when you call up in channel.write.
So we'll close that down again.
But since there's nothing to write beyond that position,
nothing's actually written to the file.
So calling the flip method resets the buffer's
position to zero.
Which is why we actually need that.
So obviously we've got to uncomment that again.
Run it.
We get our result back.
So I'm just gonna move this code that we commented out
in previous videos.
Okay, it's a little bit tidier.
All right.
So now that we've written things out
in a single buffer, let's use single buffer
to read the contents of the file in one shot.
So we'll come down here right after the binWrite buffer.
And with our RandomAccessFile ra equals new.
RandomAccessFile, data.dat being the filename
and rwd being the mode.
And FileChannel, channel equals ra.getChannel.
Then we're gonna do a byteBuffer readbuffer
is equal to byteBuffer.allocate 100.
And channel.read, readBuffer.
Then readBuffer.flip.
And then we're gonna do byte array.
Of course on inputString is equal to new byte.
And we're gonna pass the outputBytes
that we actually used previously, dot length.
So we're getting the right size byte array,
having them to readBuffer.get.
And it's going to be inputString.
And let's do some output here.
So we can put inputString
equals plus new String inputString.
Then we're gonna do another one.
We're going to put int1 equals plus readBuffer.getint.
Another printout for int2 equals plus readBuffer.getint.
Then we want byte, byte array, inputString2
is equal to new byte.
And it's gonna be outputBytes2.length.
Then we want to do a readBuffer.get.
This time inputString2.
And it will print out.
inputString2 equals plus new string called inputString2.
And let's print out int3.
So int3 is equal to plus readBuffer.getint.
Now we could reuse the buffer that we used
to write the data here by the way.
But then the buffer's contents would already be set
to the contents of the file.
So you're starting fresh.
That way you can be sure that the file was actually read.
That's why we're doing it this way with a new buffer
using readBuffer.
So we're declaring a buffer at the same size as before
using the allocate method call on line 31.
And then we're actually doing a channel.read
for the readBuffer.
Now the readBuffer is smart enough
to stop reading, by the way when it hits
the end of the file.
And next we're calling flip on line 33
because we kind of now switch from writing to the buffer
to reading from the buffer.
So at this point it's just a matter
of calling the appropriate get methods
as we go through.
So I'm using get here to get the first string.
Then we're getting the two ints
and printing those out at the same time.
Moving down to get the second string
or the second set of bytes.
And then the final int as well.
All right, let's actually check
to see whether that's worked or will work.
So you can see here we've got Hello World,
Nice to meet you showing.
245, negative 98765 and int3 is 1000.
And that's obviously corresponding exactly
with what we've actually output here
when we've written the buffer to our file,
to our data.dat file.
So clearly that's actually working which is nice.
Yeah one last thing,
the put methods overturn the byteBuffer.
And that's great because that means
we can chain multiple puts together.
But I'm gonna show you an example of that.
So I'm going to come up to where we're writing.
So I'll just take a copy of this first.
We just need the various variables there
'cause we're gonna use those shortly.
So I'm taking a copy of that
then I'm gonna comment that entire block of code out
other than the buffer up here on line 16.
And don't comment it out because we're gonna need
that code again in a minute to use the unchained version.
But let me show you a chained version
of these method calls.
So I'm just going to change the position here
and put the string up at the top.
Then we can now do this.
We can do buffer.put outputBytes.
Then dot putInt 245 dot putInt negative 98765.
Dot put outputBytes2,
and dot putInt 1000.
Then we can just do a buffer.flip.
And then as we did before, the binChannel call.
But that's actually going to be
still called down on line 37.
And I'll delete that other code.
You can see, we're chaining method calls here.
And the reason again we can do that
is with these put methods,
we actually return the byteBuffer.
Thus consequently we can chain those put methods together
as you've seen me do now in line 20.
So we actually run this now.
We get exactly the same results as we did before.
And obviously the reading has returned
the right information there.
But we can confirm that also by checking the data.dat file.
And see it's still what the data in there.
So you can see it's exactly the same as before, the results.
And the code's a little bit more compact.
For that reason, less lines.
And you might well come across code in your travels
that's written that way as well.
Now we've been using a random access file
to read the file.
But only because it was convenient to get a channel from it.
Now since you're always reading
and writing data sequentially,
you could also have used an inputString.
Now other than creating the string,
the code will then be the same.
But moving on.
Now let's learn how to use random access
using java.nio.
So to jump around in a file,
you have to use a seekable byte channel
which has the notion of a current position.
Now a seekable byte channel only has
a small number of methods.
And it had actually been available since Java seven.
Before then, random access file
was the only game in town when it came
to non-sequential io.
And seekable byte channel was actually an interface.
And there's a couple, there's quite a few methods there.
And there's actually six methods
that I want to bring to your attention.
What I'm going to do, I've actually got them written out.
So let's just put them on the screen so you can see.
So I'm just gonna paste that in there.
And I'm just gonna comment it out.
So you can sort of see what they're all about.
So there's six methods from the seekable
byte channel interface.
You've got a read ByteBuffer, reads bytes
beginning at the channel's current position.
And after read, so let's just put that to the next line
so that you can read it a bit better.
Okay, we'll start again.
So read, the first read method
from the seekable byte channel
accepts an argument of byteBuffer.
And it reads bytes beginning
at the channel's current position
and after the read, updates the position accordingly.
And note that now we're talking about the channel's position
not the byte buffer's position.
So that's important there.
And of course the bytes will be placed
into the buffer starting at its current position.
So that's the read method.
The interface also defines a write method.
It also accepts a byteBuffer as an argument.
And ultimately it's the same as read
except that it writes.
But one exception to note.
And that's if the data source is opened in a pin mode
then the first write will take place
starting at the end of the data source
rather than at position zero
which you would expect from a pinned operation.
And after the write, the position
will be updated accordingly in both cases.
We've got position.
Now that method is meant to return the channel's position.
And there's a second method that's overloaded
for position which accepts a long argument.
And that sets the channel's position to the passed value.
We've got a truncate method that has an argument
of type long.
Truncates the size of the attached data source
to the passed value.
And finally a size method returns the size
of the attached data source.
So that's actually all the methods
in the seekable byte channel interface.
And here's the good news about that.
You've been using one all along
because the file channel class
implements the seekable byte channel interface.
So let's modify the code we have
so that reads the integers in backwards order.
So to do so, when we write the data,
we're gonna save the start position for each value.
Now as you saw previously when we're working
with RandomAccessFile, normally you'd have to have
something like an index in the file
that would enable you to read values on demand.
But you've already learned how to do that
and so we're gonna keep things simple here.
We're gonna go back to using the unchained version
of the write code.
Because you'll need to store the file position
after every write.
Now in this simple case, you know what the data is
and the order in which it's written.
And you could calculate the positions
before you do each read,
but in this case just to save us a bit of time
we're gonna pretend that we can't do that.
So let's have a look at doing that.
So I'm going to comment out now
the chained versions of our code.
And we're gonna uncomment down here
the longer version that we originally had
that we commented out earlier in the video.
And now we need to add those extra pieces of information
in terms of the length.
So we're gonna start by coming down here
after the outputBytes.
So outputting outputBytes, okay, that's fine.
But then we need to do a long int1Pos
is equal to outputBytes.length.
We're going to put the int on line 39, leave that as it is.
Then after that, what we're going to do
is create a second variable.
It's a long int2Pos now equals int1Pos plus integer.bytes.
And then outputting our second int
which is our negative 98765.
Coming on down, we're going to define,
we're defining outputBytes2.
Then we'll go ahead and output that below that.
We're gonna calculate intPos3.
So long int3Pos equals int2Pos
plus integer.bytes plus outputBytes2.length.
Then we're putting our putInt 1000.
We're doing our buffer.flip.
And we're still doing the writeBuffer.
So what have we done here?
Well, it's the same code as we had before
as you saw me uncomment.
Except we're now saving the start position
of each integer that we wrote to the file.
So we wrote the first integer, 235 here on line 39
right after the string.
So its start position will be the number of bytes
in the string.
Now the second time we define one
is here on line 40 when we're defining the position
for the next int.
And we're then writing the int,
negative 98765 after that.
So the start position for that int
is the length of the string plus the int
of an integer in terms of bytes.
Now the third integer, 1000.
Well that's actually written last.
So its position will be the starting position
of the second int.
You can see me defining it here on line 44.
And then to that we're adding the size of the integer
in bytes, integer.bytes plus the size
of the second string.
And then we're writing out the value and doing a flip
and then writing the contents to the buffer.
All right, so now that we've got that defined,
we want to start reading the ints back
in the reverse order we wrote them.
But let's start working on that in the next video.