- [Tutor] Alright, so let's move on to the next topic now
and that's using a FileChannel to copy one file to another.
So we're gonna try and copy the data.dat file
that we've been reading and writing
to a file called datacopy.dat.
So to do so, we're gonna use a method called transferFrom,
which is from the FileChannel class.
So I'm at the code that I'm about to go through
after the code that reads the three integers
in reverse order.
So looking up here, we've got the three integers
and I'm gonna put the code there, right after
that last call, that retrieves the first integer,
first of the three backwards in reverse order,
so we're going to create a RandomAccessFile class.
We're gonna call this one copyFile = new RandomAccessFile
and we're gonna call this one datacopy.dat
and rw is gonna be the mode
and we're gonna do a FileChannel,
CopyChannel = CopyFile.getchannel
and then we're gonna put long numTransferred = copyChannel.
This is the transferFrom method,
then we pass channel, 0, channel.size
and then we can print out a message
to put numTransferred = numTransferred
and actually what we'll do is
put some closed commands here, so we're gonna put
channel.close, ra.close
and also copyChannel.close
and we'll just comment out that rest of the code,
that we were working on in the previous video.
Alright, so you can see that when we're using
the transferFrom method on line 75,
we pass the source channel,
that's the one we're copying from
and you can see channel is the source,
which is being defined on line 74
and spaced on the filename datacopy.dat,
which we've acrowded around a file access for in line 73.
Now we also need to specify the number of bytes,
that we wanna copy, so here we've chosen the start,
which is zero and channel.size
to get the complete number of bytes, that are in that file
and notice that I'm calling the method
using the destination channel,
so obviously datacopy.dat, which is our RandomAccessFile
and then the copyChannel, that's based from that,
a channel has been created from that,
so that's our destination, so we're calling
the transferFrom on that destination channel
and we're using a channel.size here on line 75
to get the number of bytes to copy.
And the transferFrom method, as you can see there
is storing the number of transferred,
or returning the number of bytes transferred,
and we're saving that numTransferred variable
and then printing that out on line 76.
So, let's just try running it and see what happens.
Alright, number of bytes transferred equals 24.
We've got datacopy.dat, let's have a look at it
and something weird went wrong.
So obviously something went wrong there.
What is the problem here?
Well, from the source channel,
we're using the same channel we used to read the integers
in reverse order,
so we'll go back and have a look.
We're using it here on line 75.
Now the position you pass to the transfer method
isn't an absolute position,
it's actually a relative position,
so in this case, the channel's position is currently set
to one byte after the last integer you've actually read.
If you wanna copy the file standing
at absolute position zero, then you need to set
the file position before calling the transferFrom method,
so what we'll do is before that line
that does the transfer from from our course,
the transferFrom method,
we're gonna put channel, channel.position.
We're gonna set that to zero.
And now if we run it again,
number of bytes transferred this time is 40
and if we have a look there now,
that's much like we would expect it to be,
which is pretty well a mirror copy of our data.net file now.
So that's demonstrating that when working with methods
that accept channel and buffer positions as parameters,
you have to actually be careful to read
the methods documentation to see if it uses the position
as an absolute value, or a relative value.
So in this particular case,
the transferFrom method is using a relative value,
which means whatever you pass is treated
as an index relative to the current position.
And note also, there's a transferTo method,
that does exactly the same thing as transferFrom,
except you call it using the source channel,
rather than the destination channel.
So let's actually change the code to use that instead.
So the difference here would be instead of this line there,
which I'm gonna comment out,
we would do long numTransferred =
this time we'll do channel.transferredTo
and then we would do 0, channel.size,
and copyChannel
and I'll leave the print out there on the next line.
Just to confirm, we'll actually delete the datacopy.dat.
Delete that
and run our method again, or run our code again,
and have a look at our datacopy.dat
and you can see that it's still there
and it has worked nicely.
Now usually you use a method in the files class
to copy one file to another,
but if you've already got one of the files open,
using transferFrom or transferTo
may actually be a more efficient way to do it,
depending on the operating system.
Now, we're gonna see how to do it using the files class
in an upcoming video.
Now before we leave the topic of reading and writing
using Java.devIO, we're gonna take a brief look at pipes.
Now pipes are used to transfer data between threads
and they're actually a one way connexion,
so the data can only flow one way.
Now a pipe's got two channels,
the sink channel and the source channel.
One or more threads can write,
or writes to the sink channel
and the other thread, or threads
read from the source channel.
So let's actually have a look at an example.
And what we're going to do now
is we're just going to comment out all this code.
I'll actually comment out everything.
Actually, what I'll do is I'll just delete
some of this stuff, that we've got copies of
in previous videos source code.
To clean things up a little bit, requires for this,
I'll just clean all that up.
Then I'll just comment out everything else here.
A lot shorter now, right down to and including ending catch.
Alright, so we're gonna try and have a go
at one of these pipes.
I'll do try, and it's gonna be pipe,
noticing it's coming fromm Java.nio,
Pipe = Pipe.open
and then we can catch an exception,
and just an IOException
and we'll just printStackTrace if we we get an error.
Now, you'll need two threads,
one that will write to the sink channel
and the other that will read from the source channel.
So, let's actually add the writer thread first.
So, I'm going to put some more code in here
and we'll type
Runnable writer = new Runnable,
press enter there so it creates the method for us
and within the run, we're gonna do a try
and we're just going to catch any exceptions.
If we do get one, we'll just print a StackTrace.
For the actual code, we're gonna type
Pipe.SinkChannel
sinkChannel = pipe.sink
and we're gonna do a ByteBuffer
buffer = ByteBuffer.allocate
and we're using the number 56 there.
We're gonna create a loop for int i=0
i<10 i++
then we're gonna do a String currentTime =
The time is +
System.CurrentTimeMillis, short for milliseconds,
so we'll assign that to the string.
Then we're gonna do buffer.put currentTime.getBytes,
then we're gonna do buffer.flip
and then we're gonna move down and do
whilebuffer.hasremaining
and we're gonna do sinkChannel.write(buffer)
and outside of the while loop, buffer.flip
and then Thread.sleep, we'll add 100 there
and then our semi-colon down here.
Now we could have used a lander expression for the runnable,
but we've spelt it out here for clarity.
So the write method is calling the Pipe.Sink method
over here on line 18, to get the SinkChannel.
It then allocates a buffer on the next line
and we've then got a loop here
starting from lines 21 down to lines 33,
because we want the thread to terminate at some point.
Now inside the loop, you know, we're creating a string
that includes the current time.
So we're creating it there
and then putting it into the buffer,
so writing the string to the buffer
and then we're flipping the buffer,
we're then writing the string
from the buffer into the SinkChannel.
Now at this point, you have to flip the buffer again,
once we've finished that,
for the next iteration of the loop.
Then the thread sleeps for a hundred, as you can see here,
to give the reader thread the chance
to read the source channel.
So let's go ahead now and add the code
for the reader thread.
Right, so let's do that, so we come down here
and put it in our loop.
We're going to create our reader,
so that's gonna be Runnable reader = new Runnable,
same deal there
and put the semi-colon down there for starters,
then we wanna try,
and we're going to add catch there for an exception
and we'll just print a StackTrace, if we do get an error.
So the code for the reader is going to be
Pipe.SourceChannel
sourceChannel = pipe.source
and we're gonna do a ByteBuffer buffer =
ByteBuffer.allocate
56 again,
then for int i=0
i<10 i++
int bytesRead =
sourceChannel.read(buffer)
a byte array timesString = new byte
and we set that to bytesRead,
right number of characters, so it'll fit in our byte array,
then we do a buffer.flip,
then we do a buffer.get(timeString),
then we're gonna do a printout,
System.out.println and we'll put
Reader Thread = + new String(timeString)
then we do a buffer.flip
and Thread.sleep(100).
So, this is very similar to the writer,
except here we're calling the pipe.source method
to get the source channel, get to the source channel.
You can see that on line 51.
Otherwise, it's pretty much the same code,
except this time it's reading from the channel
using the buffer.get, line 54 there
and it writes what it then gets to the console, line 55.
So finally now we've done that,
we need to start both threads,
first the writer, then the reader.
So if we come down and do that, down here,
so we're gonna do new Thread,
and we'll start with the (writer).start,
then new Thread(reader).start.
Alright, so let's actually try running this now
and see what happens.
And you can see that that worked away there
and we got the time reader thread reading what the time was
an it went through 10 times, as per the for loop
and then stopped, so we can try that again
and obviously that's still working nicely.
Now if a timing problem crops up for any reason,
you could actually try increasing this number here
and here as well, if you find that there's some problem
not working on your computer, but you can see
in the case of my computer, it's actually worked fine.
So that was how to go about using a pipe
to send data from one thread to another.
Now, there are other ways for threads
to communicate and pass data, so it would be a good idea
to benchmark the different ways
and then choose the one that's the most efficient
for an application, so sometimes using a java.nio pipe
may not actually make sense
and because of the nature of the content of this course,
I've actually added this particular video.
At a later point, after I've actually created
the thread section of this course,
which confusingly isn't until later in the course.
So it's a long story that the reason
that's been designed that way,
but the point I'm making here is
if you're not understanding these thread concepts,
there's a separate section of this course
dealing with threads,
so you might well want to actually go and check that out
and once you've been through that, you could come back
and look at this particular video again
and it would probably make a lot more sense.
The point is though, that I wanted to make this section
fairly complete in that it is important
to be able to know how you can use a pipe
to send data from one thread to another.
Alright, so that's it, that's all there is
for reading and writing, using java.nio.
In the next video, what we're gonna do
is take a look at how to use java.nio
to work with the file system.
So, I'll see you in the next video.