- [Tim] All right, so at the end of the last video
we finished off consolidating this code
to make it a bit more streamlined.
And then we went through setting it up
and reading the information back.
But let's actually try reading the integers back
in the reverse order that we wrote them.
So in other words you wanna read the thousand first
followed by negative 98765.
And then followed by 245 so you can see here,
it's obviously the reverse.
Now if you're feeling ambitious,
you can try this you solve as a mini challenge.
That being the case, pause the video now
and give it a go and see if you can figure out
how to actually read the integers in reverse.
So pause the video now if you're gonna do that.
Otherwise, we'll continue on now.
What we're going to do is we're going to use
the channels position method to set the far position
before each read.
And remember that you also need to call the flip method
when you have to as well
for that to be able to work.
All right, so let's look at the code
to read things in reverse order.
So I'll come up here.
We're going to leave the,
actually what we're gonna do,
it'll take a bit longer
but let's actually just comment out this code all together.
So we've got our channel defined.
We're going to create our byteBuffer.
Call up the readBuffer is equal to
it's going to be byteBuffer.allocate.
And we just need enough for an integer.
So integer.bytes.
Channel.position.
We want to start without int3Pos
'cause it's the last number.
You're reading backwards.
And I'll just put this up so you can see the code
that we wrote out.
So were' starting at int three position
which we calculated in the previous video
as being the location for the third integer.
Then we wanna do a read.
So channel.read, readBuffer.
Then we need to know readBuffer.flip.
Then just to confirm it, we'll print it out.
So we'll try int3 equals plus readBuffer.getint.
All right, so we'll continue on.
So we'll do readBuffer.flip again.
Then we're gonna do a channel.position.
This time no surprises int2Pos.
Then we're going to read that into our buffer.
Buffer, and we're gonna do a flip
so readBuffer.flip after the read.
And we'll print it out.
So it's going to be int2
is equal to plus readBuffer.getint again.
Then we need to,
well I'll just pull this guy up a little bit now.
Read buffer.
Top position.
And that's going to be int1Pos.
Then we can do a readBuffer.flip.
Now of course that should be channel.
So I readBuffer.flip again.
Then channel.read.
ReadBuffer.
ReadBuffer.flip.
And then let's print out the last one.
Int1 equals plus readBuffer.getint.
All right, so that's ultimately the code.
So for each integer, we're first calling the position method
to specify the start position in the file.
And we then call the read method with the buffer.
Now if you want to write the result to the console
as this code is doing, you have to then call flip
before calling the get method.
And also after writing the value,
you have to call flip again before reading the next integer
into the buffer.
We then repeat that process for all three integers.
So we can actually just run that.
We've got the integers returning in the right order there
from the last one int3 through to int1.
So just to clarify.
I'm just going to make a bit more space here,
try to be more clear.
So we're doing a flip before the printout.
Which we're doing in all three cases here.
Then we're doing, before we're doing a channel.position
to go to the relevant position
and we're reading it into the buffer.
We're doing a flip after reading it from the buffer.
So technically there's an extra flip here
with the channel.position from int1.
So we could probably remove that.
Maybe consistent with our code
and do it exactly what same each time.
And also I haven't got a readBuffer.flip
after the printout here from the int2.
But it would be good to be consistent
to do that as well.
So basically,
(keyboard clacking)
Oh sorry, sorry.
Again we just run it to make sure that's going to work.
And we still get our same results.
Now when reading in a random manner,
probably you would normally create a buffer
for each read that matches the size of the data.
So for example, let's think back
to the adventure application.
Now if we wanted to use java.nio
to load a location on demand,
then we already know the link that the location record
from the index.
So therefore for each read, we'd create a new buffer
of that exact link.
Now you might think it'd be more efficient
to create a buffer that's long enough
or large enough to hold the longest location record
and reuse it.
But you may never need to load the longest record.
And Java's garbage collector will take care
of the buffers you're creating.
So they won't actually clog up memory.
So now that we've done this,
now let's write data in a random fashion.
So instead of writing it sequentially,
we're gonna write all the integers first
into the same position as they've always occupied
and then the two strings.
So let's have a look at the code
and how about to go about doing that.
And I'm going to leave the code that I've got in there
for reading integers as is.
But you can comment that out if you want to.
And actually I might just delete this other code here now.
All right.
So let's start this.
We're gonna start with a byte, a byte array.
I put string equals hello.
What I'll actually do just to save a bit of time.
Let me just grab this again.
So I'll just start again, I think that'll be quicker.
So hello world.Getbytes.
Then we wanna start with long.
str1Pos, string one position.
Make that equal to zero.
Long equal int1Pos equals,
it's gonna be outputString.length.
Then long int2Pos.
We'll call this newint1Pos and newint2Pos.
That equals newint1Pos plus integer.bytes.
Then we want byte, it's gonna be outputString2,
byte array, that is.
And that's equal to nice to meet you.getbytes.
So we have long, str2Pos equals newint2Pos
plus integer.bytes.
Then we've got long newint3Pos
is equal to str2Pos plus outputstring2.length.
All right, so that's our start.
And I'll just move this up.
Then we're gonna create a byte buffer.
ByteBuffer intbuffer is equal to bytebuffer.allocate.
And we start with integer.bytes.
Then we want to do intbuffer, dot,
and we'll start with putting an int, 245.
Then we're gonna do intBuffer.flip.
Then we're gonna do binChannel.position int1Pos.
That actually should be newint1Pos,
that we've defined above, newint1Pos.
Then one again, binChannel.right, go int buffer.
Then we're gonna do intbuffer.flip.
Then write our negative integer now.
So intbuffer.putint negative 98765.
Then we're doing buffer.flip again.
And then we do a binChannel.position, newint2Pos
and binChannel.right buffer.
All right, that's two numbers done.
And next we're going to do intbuffer.flip again.
No surprises there.
And intbuffer.putint.
And we're gonna put there our value 1000.
IntBuffer.flip again.
Then binChannel.position.
(mumbling) gonna be position to newint3Pos, third integer.
Then we're gonna write out binChannel.write.
And that'll be the intBuffer again.
So that's our three integers.
And now we need to write the two strings.
So to do that, binChannel.position, str1Pos.
BinChannel.right, and it's going to be byteBuffer.wrap.
And it's going to be outputString.
And then we want to do binChannel.position to str2Pos.
And binChannel.right, byteBuffer.wrap.
That's gonna be outputString2.
I'll close it up so we could see a bit better.
So that's actually the code.
Now there are other ways to write this code.
But doing it this way does make each step clear.
So firstly, we're calculating all the start positions.
And we're doing this all up here from line 73
down to line 79.
And it should be fairly familiar with how
these are being calculated by now.
So I won't go through them again.
And then with each chunk of code here,
we're actually writing the three integers.
And for each integer, we're writing the value
to the buffer after flipping the buffer if necessary.
Then you flip the buffer to prepare it for writing
to the channel.
And you then set the channel to the start position
for the integer.
And then you write the value to the file.
And for the second and third integers,
you have to then flip the buffer
before writing the values to it.
Now the strings are simpler.
Let me get down to this code down here
from lines 99 onwards.
Because we're using the wrap method.
And that takes care of creating
and flipping the buffer for us.
So if I actually run this now.
And we see that the numbers were printed out
just the way before.
Nothing's changed there.
But if we take a look at the file, data.dat,
you see that it pretty much looks the way
that it's always looked.
We just didn't write the values sequentially this time.
So again the point here is that you have to understand
how important it is to make sure
you call the flip method when changing
from reading the buffer to writing it and vice versa.
Now often the reads and writes aren't intuitive.
For example, when writing a buffer to a channel,
the buffer is being read.
Now if you haven't called flip when you're supposed to,
you'll usually get a buffer underflow exception
when reading from the buffer.
So in the case of writing values,
you won't actually see the value appear in the file.
And that's because you didn't flip the buffer
and the buffer's position will still be positioned
after the value put.
And thus, so the channel goes to read the buffer
so it can write the value to the file
and it thinks there's nothing to read.
So if you only take one thing away from these videos
about doing io with java.nio,
and that is to remember to flip the buffer
when switching from reading to writing and vice versa.
All right, that's all there is
to randomly accessing a data source.
We've learned the concept in a previous video
and you know the methods available now
when using java.nio.
In the next video, we're gonna move on
to another topic.
And that's how you can use a file channel
to copy one file to another.
So I'll see you in the next video.