Channel

vs 流

Java NIO Channel通道和流非常相似,主要有以下几点区别:

  1. 通道可以读也可以写,流一般来说是单向的(只能读或者写)。

  2. 通道可以异步读写。

  3. 通道总是基于缓冲区Buffer来读写。

实现

Channel 的实现(Channel Implementations)

下面列出Java NIO中最重要的集中Channel的实现:

  • FileChannel

FileChannel 用于文件的数据读写。

  • DatagramChannel

DatagramChannel 用于 UDP 的数据读写。

  • SocketChannel & ServerSocketChannel

ServerSocketChannel允许我们监听TCP链接请求,每个请求会创建会一个SocketChannel。

拓展阅读

nio buffer

FileChannel

Java NIO中的FileChannel是用于连接文件的通道。

通过文件通道可以读、写文件的数据。Java NIO的FileChannel是相对标准Java IO API的可选接口。

FileChannel不可以设置为非阻塞模式,他只能在阻塞模式下运行。

基本方法

打开文件通道

在使用FileChannel前必须打开通道,打开一个文件通道需要通过输入/输出流或者RandomAccessFile,

下面是通过RandomAccessFile打开文件通道的案例:

RandomAccessFile aFile = new RandomAccessFile("data/nio-data.txt", "rw");
FileChannel inChannel = aFile.getChannel();

从文件通道内读取数据

ByteBuffer buf = ByteBuffer.allocate(48);
int bytesRead = inChannel.read(buf);

首先开辟一个Buffer,从通道中读取的数据会写入Buffer内。

接着就可以调用read方法,read的返回值代表有多少字节被写入了Buffer,返回-1则表示已经读取到文件结尾了。

向文件通道写入数据

写数据用write方法,入参是Buffer:

String newData = "New String to write to file..." + System.currentTimeMillis();

ByteBuffer buf = ByteBuffer.allocate(48);
buf.clear();
buf.put(newData.getBytes());

buf.flip();

while(buf.hasRemaining()) {
    channel.write(buf);
}

关闭通道

操作完毕后,需要把通道关闭:

channel.close();    

Position

当操作FileChannel的时候读和写都是基于特定起始位置的(position),获取当前的位置可以用FileChannel的position()方法,设置当前位置可以用带参数的position(long pos)方法。

long pos channel.position();
channel.position(pos + 123);

假设我们把当前位置设置为文件结尾之后,那么当我们视图从通道中读取数据时就会发现返回值是-1,表示已经到达文件结尾了。

如果把当前位置设置为文件结尾之后,在想通道中写入数据,文件会自动扩展以便写入数据,但是这样会导致文件中出现类似空洞,即文件的一些位置是没有数据的。

Size

size() 方法可以返回FileChannel对应的文件的文件大小:

long fileSize = channel.size();    

Truncate

利用 truncate() 可以截取指定长度的文件:

channel.truncate(1024);

Force

force方法会把所有未写磁盘的数据都强制写入磁盘。

这是因为在操作系统中出于性能考虑回把数据放入缓冲区,所以不能保证数据在调用write写入文件通道后就及时写到磁盘上了,除非手动调用force方法。

force方法需要一个布尔参数,代表是否把meta data也一并强制写入。

channel.force(true);

实战代码

  • 传统方式复制
/**
 * io 方式复制文件
 *
 * @param source 源文件
 * @param target 目标文件
 */
public static void copyFileIO(File source, File target) {
    try (InputStream inputStream = new BufferedInputStream(new FileInputStream(source));
         OutputStream outputStream = new BufferedOutputStream(new FileOutputStream(target))) {
        byte[] bytes = new byte[1024];
        int i;
        //读取到输入流数据,然后写入到输出流中去,实现复制
        while ((i = inputStream.read(bytes)) != -1) {
            outputStream.write(bytes, 0, i);
        }
    } catch (Exception e) {
        e.printStackTrace();
    }
}
  • nio 方式复制
/**
 * nio 方式复制文件
 *
 * @param source 源文件
 * @param target 目标文件
 */
public static void copyFileNio(File source, File target) {
    try (FileInputStream inputStream = new FileInputStream(source);
         FileOutputStream outputStream = new FileOutputStream(target);
         FileChannel fileChannelInput = inputStream.getChannel();
         FileChannel fileChannelOutput = outputStream.getChannel();
    ) {
        //将fileChannelInput通道的数据,写入到fileChannelOutput通道
        fileChannelInput.transferTo(0, fileChannelInput.size(), fileChannelOutput);
    } catch (Exception e) {
        e.printStackTrace();
    }
}    

SocketChannel

在Java NIO体系中,SocketChannel是用于TCP网络连接的套接字接口,相当于Java网络编程中的Socket套接字接口。

创建SocketChannel主要有两种方式,如下:

  1. 打开一个SocketChannel并连接网络上的一台服务器。

  2. 当ServerSocketChannel接收到一个连接请求时,会创建一个SocketChannel。

创建

SocketChannel socketChannel = SocketChannel.open();
socketChannel.connect(new InetSocketAddress("localhost", 18888));  

关闭

关闭一个SocketChannel只需要调用他的close方法,如下:

socketChannel.close();

读数据

从一个SocketChannel连接中读取数据,可以通过read()方法,如下:

ByteBuffer buf = ByteBuffer.allocate(48);
int bytesRead = socketChannel.read(buf);

首先需要开辟一个Buffer。从SocketChannel中读取的数据将放到Buffer中。

接下来就是调用SocketChannel的read()方法.这个read()会把通道中的数据读到Buffer中。

read()方法的返回值是一个int数据,代表此次有多少字节的数据被写入了Buffer中。

如果返回的是-1,那么意味着通道内的数据已经读取完毕,到底了(链接关闭)。

写数据

向SocketChannel中写入数据是通过write()方法,write也需要一个Buffer作为参数。下面看一下具体的示例:

String newData = "New String to write to file...";

ByteBuffer buf = ByteBuffer.allocate(48);
buf.clear();
buf.put(newData.getBytes());
buf.flip();

while(buf.hasRemaining()) {
    channel.write(buf);
}

仔细观察代码,这里我们把write()的调用放在了while循环中。

这是因为我们无法保证在write的时候实际写入了多少字节的数据,因此我们通过一个循环操作,不断把Buffer中数据写入到SocketChannel中知道Buffer中的数据全部写入为止。

非阻塞模式

我们可以吧SocketChannel设置为non-blocking(非阻塞)模式。这样的话在调用connect(), read(), write()时都是异步的。

connect()

如果我们设置了一个SocketChannel是非阻塞的,那么调用connect()后,方法会在链接建立前就直接返回。

为了检查当前链接是否建立成功,我们可以调用 finishConnect(), 如下:

socketChannel.configureBlocking(false);
socketChannel.connect(new InetSocketAddress("localhost", 18888));

while(! socketChannel.finishConnect() ){
    //wait, or do something else...    
}

write()

在非阻塞模式下,调用write()方法不能确保方法返回后写入操作一定得到了执行。

因此我们需要把write()调用放到循环内。这和前面在讲write()时是一样的,此处就不在代码演示。

read()

在非阻塞模式下,调用read()方法也不能确保方法返回后,确实读到了数据。

因此我们需要自己检查的整型返回值,这个返回值会告诉我们实际读取了多少字节的数据。

ServerSocketChannel

在Java NIO中,ServerSocketChannel是用于监听TCP链接请求的通道,正如Java网络编程中的ServerSocket一样。

打开

打开一个ServerSocketChannel我们需要调用他的open()方法,例如:

ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();

关闭

关闭一个ServerSocketChannel我们需要调用他的close()方法,例如:

serverSocketChannel.close();

监听链接

通过调用accept()方法,我们就开始监听端口上的请求连接。

当accept()返回时,他会返回一个SocketChannel连接实例,实际上accept()是阻塞操作,他会阻塞带去线程知道返回一个连接;

很多时候我们是不满足于监听一个连接的,因此我们会把accept()的调用放到循环中,就像这样:

while(true){
    SocketChannel socketChannel = serverSocketChannel.accept();
    //do something with socketChannel...
}

非阻塞模式

实际上ServerSocketChannel是可以设置为非阻塞模式的。

在非阻塞模式下,调用accept()函数会立刻返回,如果当前没有请求的链接,那么返回值为空null。

因此我们需要手动检查返回的SocketChannel是否为空,例如:

ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
serverSocketChannel.socket().bind(new InetSocketAddress(9999));
serverSocketChannel.configureBlocking(false);

while(true){
    SocketChannel socketChannel = serverSocketChannel.accept();

    if(socketChannel != null){
        //do something with socketChannel...
    }
}

实战代码

  • NioServer
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.util.Iterator;

public class NioServer {

    /**
     * 通道管理器
     */
    private Selector selector;

    /**
     * initServer方法中:
     * 1.创建Selector
     * 2.Channelr注册到Selector
     * @param port
     * @throws Exception
     */
    public void initServer(int port) throws Exception {
        // 获得一个ServerSocket通道
        ServerSocketChannel serverChannel = ServerSocketChannel.open();
        // 设置通道为非阻塞
        serverChannel.configureBlocking(false);
        // 将该通道对于的serverSocket绑定到port端口
        serverChannel.socket().bind(new InetSocketAddress(port));
        // 获得一个通道管理器(选择器)
        this.selector = Selector.open();
        /*
         * 将通道管理器和该通道绑定,并为该通道注册selectionKey.OP_ACCEPT事件
         * 注册该事件后,当事件到达的时候,selector.select()会返回,
         * 如果事件没有到达selector.select()会一直阻塞
         */
        serverChannel.register(selector, SelectionKey.OP_ACCEPT);
    }

    /**
     * 采用轮训的方式监听selector上是否有需要处理的事件,如果有,进行处理
     */
    public void listen() throws Exception {
        System.out.println("start server");
        // 轮询访问selector
        while (true) {
            // 当注册事件到达时,方法返回,否则该方法会一直阻塞
            selector.select();
            // 获得selector中选中的相的迭代器,选中的相为注册的事件
            Iterator ite = this.selector.selectedKeys().iterator();
            while (ite.hasNext()) {
                SelectionKey key = (SelectionKey) ite.next();
                // 删除已选的key以防重负处理
                ite.remove();
                // 客户端请求连接事件
                if (key.isAcceptable()) {
                    ServerSocketChannel server = (ServerSocketChannel) key.channel();
                    // 获得和客户端连接的通道
                    SocketChannel channel = server.accept();
                    // 设置成非阻塞
                    channel.configureBlocking(false);
                    // 在这里可以发送消息给客户端
                    channel.write(ByteBuffer.wrap("hello client".getBytes()));
                    // 在客户端连接成功之后,为了可以接收到客户端的信息,需要给通道设置读的权限
                    channel.register(this.selector, SelectionKey.OP_READ);
                    // 获得了可读的事件
                } else if (key.isReadable()) {
                    read(key);
                }
            }
        }
    }

    /**
     * 处理读取客户端发来的信息事件
     */
    private void read(SelectionKey key) throws Exception {
        // 服务器可读消息,得到事件发生的socket通道
        SocketChannel channel = (SocketChannel) key.channel();
        // 读取的缓冲区
        ByteBuffer buffer = ByteBuffer.allocate(16);
        channel.read(buffer);
        byte[] data = buffer.array();
        String msg = new String(data).trim();
        buffer.flip();
        System.out.println("Server receive from client: " + msg);
        ByteBuffer outBuffer = ByteBuffer.wrap(msg.getBytes());
        channel.write(outBuffer);
    }

    public static void main(String[] args) throws Throwable {
        NioServer server = new NioServer();
        server.initServer(8989);
        server.listen();
    }
}
  • NioClient
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.SocketChannel;
import java.util.Iterator;

public class NioClient {

    // 通道管理器
    private Selector selector;

    /**
     * 获得一个Socket通道,并对该通道做一些初始化的工作
     * @param address 连接服务器ip
     * @param port 连接服务器端口
     * @throws IOException
     */
    public void initClient(String address, int port) throws IOException {
        // 获得一个Socket通道
        SocketChannel channel = SocketChannel.open();
        // 设置通道为非阻塞
        channel.configureBlocking(false);
        // 获得一个通道管理器
        this.selector = Selector.open();
        // 用channel.finishConnect();才能完成连接
        // 客户端连接服务器,其实方法执行并没有实现连接,需要在listen()方法中调
        channel.connect(new InetSocketAddress(address, port));
        // 将通道管理器和该通道绑定,并为该通道注册SelectionKey.OP_CONNECT事件
        channel.register(selector, SelectionKey.OP_CONNECT);
    }

    /**
     * 采用轮询的方式监听selector上是否有需要处理的事件,如果有,则进行处理
     * @throws Exception
     */
    @SuppressWarnings("unchecked")
    public void listen() throws Exception {
        // 轮询访问selector
        while (true) {
            /*
             * 选择一组可以进行I/O操作的事件,放在selector中,客户端的该方法不会阻塞,
             * selector的wakeup方法被调用,方法返回,而对于客户端来说,通道一直是被选中的
             * 这里和服务端的方法不一样,查看api注释可以知道,当至少一个通道被选中时。
             */
            selector.select();
            // 获得selector中选中的项的迭代器
            Iterator ite = this.selector.selectedKeys().iterator();
            while (ite.hasNext()) {
                SelectionKey key = (SelectionKey) ite.next();
                // 删除已选的key,以防重复处理
                ite.remove();
                // 连接事件发生
                if (key.isConnectable()) {
                    // 如果正在连接,则完成连接
                    SocketChannel channel = (SocketChannel) key.channel();
                    if (channel.isConnectionPending()) {
                        channel.finishConnect();
                    }
                    // 设置成非阻塞
                    channel.configureBlocking(false);
                    // 在这里可以给服务端发送信息哦
                    channel.write(ByteBuffer.wrap("hello server!".getBytes()));
                    // 在和服务端连接成功之后,为了可以接收到服务端的信息,需要给通道设置读的权限。
                    // 获得了可读的事件
                    channel.register(this.selector, SelectionKey.OP_READ);
                } else if (key.isReadable()) {
                    read(key);
                }
            }
        }
    }

    private void read(SelectionKey key) throws Exception {
        SocketChannel channel = (SocketChannel) key.channel();
        // 分配缓冲区
        ByteBuffer buffer = ByteBuffer.allocate(16);
        channel.read(buffer);
        byte[] data = buffer.array();
        String msg = new String(data).trim();
        buffer.flip();
        System.out.println("Client receive msg from server:" + msg);
        ByteBuffer outBuffer = ByteBuffer.wrap(msg.getBytes());
        channel.write(outBuffer);
    }

    public static void main(String[] args) throws Exception {
        NioClient client = new NioClient();
        client.initClient("localhost", 8989);
        client.listen();
    }
}

DatagramChannel

一个 Java NIO DatagramChannel 是一个可以发送、接收UDP数据包的通道。

由于UDP是面向无连接的网络协议,我们不可用像使用其他通道一样直接进行读写数据。正确的做法是发送、接收数据包。

打开

DatagramChannel channel = DatagramChannel.open();
channel.socket().bind(new InetSocketAddress(9999));

接收

接收数据,直接调用DatagramChannel的receive()方法:

ByteBuffer buf = ByteBuffer.allocate(48);
buf.clear();

channel.receive(buf);

receive()方法会把接收到的数据包中的数据拷贝至给定的Buffer中。

如果数据包的内容超过了Buffer的大小,剩余的数据会被直接丢弃

发送

发送数据是通过DatagramChannel的send()方法:

String newData = "New String to wrte to file...";
ByteBuffer buf = ByteBuffer.allocate(48);
buf.clear();
buf.put(newData.getBytes());
buf.flip();

int byteSent = channel.send(buf, new InetSocketAddress("localhsot", 1888));

上述示例会吧一个字符串发送到“localhsot”服务器的UDP端口1888。

目前这个端口没有被任何程序监听,所以什么都不会发生。

当发送了数据后,我们不会收到数据包是否被接收的的通知,这是由于UDP本身不保证任何数据的发送问题。

链接特定机器地址

DatagramChannel实际上是可以指定到网络中的特定地址的。

由于UDP是面向无连接的,这种链接方式并不会创建实际的连接,这和TCP通道类似。

确切的说,他会锁定DatagramChannel,这样我们就只能通过特定的地址来收发数据包。

channel.connect(new InetSocketAddress("localhsot", 1888));

当连接上后,可以向使用传统的通道那样调用read()和Writer()方法。区别是数据的读写情况得不到保证。

下面示例:

int bytesRead = channel.read(buf);    
int bytesWritten = channel.write(buf);

代码实战

  • UDPServer
import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.DatagramChannel;

public class UDPServer {

    public static void main(String[] args) throws IOException {
        // 获取通道
        DatagramChannel datagramChannel = DatagramChannel.open();
        // 绑定端口
        datagramChannel.bind(new InetSocketAddress(8989));
        // 分配Buffer
        ByteBuffer buffer = ByteBuffer.allocate(1024);
        byte b[];
        while(true) {
            // 清空Buffer
            buffer.clear();
            // 接受客户端发送数据
            SocketAddress socketAddress = datagramChannel.receive(buffer);
            if (socketAddress != null) {
                int position = buffer.position();
                b = new byte[position];
                buffer.flip();
                for(int i=0; i<position; ++i) {
                    b[i] = buffer.get();
                }
                System.out.println("receive remote " +  socketAddress.toString() + ":"  + new String(b, "UTF-8"));
                //接收到消息后给发送方回应
                sendReback(socketAddress,datagramChannel);
            }
        }
    }

    public static void sendReback(SocketAddress socketAddress, DatagramChannel datagramChannel) throws IOException {
        String message = "I has receive your message";
        ByteBuffer buffer = ByteBuffer.allocate(1024);
        buffer.put(message.getBytes("UTF-8"));
        buffer.flip();
        datagramChannel.send(buffer, socketAddress);
    }
}
  • UDPClient
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.DatagramChannel;
import java.util.Scanner;

public class UDPClient {

    public static void main(String[] args) throws IOException {

        final DatagramChannel channel = DatagramChannel.open();
        //接收消息线程
        new Thread(new Runnable() {
            @Override
            public void run() {
                ByteBuffer buffer = ByteBuffer.allocate(1024);
                byte b[];
                while(true) {
                    buffer.clear();
                    SocketAddress socketAddress = null;
                    try {
                        socketAddress = channel.receive(buffer);
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                    if (socketAddress != null) {
                        int position = buffer.position();
                        b = new byte[position];
                        buffer.flip();
                        for(int i=0; i<position; ++i) {
                            b[i] = buffer.get();
                        }
                        try {
                            System.out.println("receive remote " +  socketAddress.toString() + ":"  + new String(b, "UTF-8"));
                        } catch (UnsupportedEncodingException e) {
                            e.printStackTrace();
                        }
                    }
                }
            }
        }).start();;

        //发送控制台输入消息
        while (true) {
            Scanner sc = new Scanner(System.in);
            String next = sc.next();
            try {
                sendMessage(channel, next);
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    public static void sendMessage(DatagramChannel channel, String mes) throws IOException {
        if (mes == null || mes.isEmpty()) {
            return;
        }
        ByteBuffer buffer = ByteBuffer.allocate(1024);
        buffer.clear();
        buffer.put(mes.getBytes("UTF-8"));
        buffer.flip();
        System.out.println("send msg:" + mes);
        int send = channel.send(buffer, new InetSocketAddress("localhost",8989));
    }
}

AsynchronousFileChannel

AsynchronousFileChannel使得数据可以进行异步读写。

创建

Path path = Paths.get("data/test.xml");
AsynchronousFileChannel fileChannel = AsynchronousFileChannel.open(path, StandardOpenOption.READ);

open()的第一个参数是一个Path实体,指向我们需要操作的文件。

第二个参数是操作类型。上述示例中我们用的是StandardOpenOption.READ,表示以读的形式操作文件。

读取数据

读取AsynchronousFileChannel的数据有两种方式。

每种方法都会调用AsynchronousFileChannel的一个read()接口。

通过Future读取数据

Future<Integer> operation = fileChannel.read(buffer, 0);

这种方式中,read()接受一个ByteBuffer座位第一个参数,数据会被读取到ByteBuffer中。第二个参数是开始读取数据的位置。

read()方法会立刻返回,即使读操作没有完成。我们可以通过isDone()方法检查操作是否完成。

下面是一个略长的示例:

AsynchronousFileChannel fileChannel = AsynchronousFileChannel.open(path, StandardOpenOption.READ);
ByteBuffer buffer = ByteBuffer.allocate(1024);
long position = 0;

Future<Integer> operation = fileChannel.read(buffer, position);

while(!operation.isDone()) {
    //wait
}

buffer.flip();
byte[] data = new byte[buffer.limit()];
buffer.get(data);
System.out.println(new String(data));
buffer.clear();

在这个例子中我们创建了一个AsynchronousFileChannel,然后创建一个ByteBuffer作为参数传给read。

接着我们创建了一个循环来检查是否读取完毕isDone()。

这里的循环操作比较低效,它的意思是我们需要等待读取动作完成。

一旦读取完成后,我们就可以把数据写入ByteBuffer,然后输出。

通过CompletionHandler读取数据

这里,一旦读取完成,将会触发CompletionHandler的completed()方法,并传入一个Integer和ByteBuffer。

前面的整形表示的是读取到的字节数大小。

第二个ByteBuffer也可以换成其他合适的对象方便数据写入。 如果读取操作失败了,那么会触发failed()方法。

fileChannel.read(buffer, position, buffer, new CompletionHandler<Integer, ByteBuffer>() {
    @Override
    public void completed(Integer result, ByteBuffer attachment) {
        System.out.println("result = " + result);

        attachment.flip();
        byte[] data = new byte[attachment.limit()];
        attachment.get(data);
        System.out.println(new String(data));
        attachment.clear();
    }

    @Override
    public void failed(Throwable exc, ByteBuffer attachment) {

    }
});

写数据

和读数据类似某些数据也有两种方式,调动不同的的write()方法。

通过Future写数据

Path path = Paths.get("data/test-write.txt");
AsynchronousFileChannel fileChannel = AsynchronousFileChannel.open(path, StandardOpenOption.WRITE);

ByteBuffer buffer = ByteBuffer.allocate(1024);
long position = 0;

buffer.put("test data".getBytes());
buffer.flip();

Future<Integer> operation = fileChannel.write(buffer, position);
buffer.clear();

while(!operation.isDone()) {
    // wait
}
System.out.println("Write done");

首先把文件已写方式打开,接着创建一个ByteBuffer座位写入数据的目的地。再把数据进入ByteBuffer。

最后检查一下是否写入完成。

需要注意的是,这里的文件必须是已经存在的,否者在尝试write数据是会抛出一个java.nio.file.NoSuchFileException.

通过CompletionHandler写数据

同样当数据吸入完成后completed()会被调用,如果失败了那么failed()会被调用。

Path path = Paths.get("data/test-write.txt");
if(!Files.exists(path)){
    Files.createFile(path);
}
AsynchronousFileChannel fileChannel = AsynchronousFileChannel.open(path, StandardOpenOption.WRITE);

ByteBuffer buffer = ByteBuffer.allocate(1024);
long position = 0;

buffer.put("test data".getBytes());
buffer.flip();

fileChannel.write(buffer, position, buffer, new CompletionHandler<Integer, ByteBuffer>() {

    @Override
    public void completed(Integer result, ByteBuffer attachment) {
        System.out.println("bytes written: " + result);
    }

    @Override
    public void failed(Throwable exc, ByteBuffer attachment) {
        System.out.println("Write failed");
        exc.printStackTrace();
    }
});

通道传输接口

在Java NIO中如果一个channel是FileChannel类型的,那么他可以直接把数据传输到另一个channel。

这个特性得益于 FileChannel 包含的 transferTo()transferFrom() 两个方法。

transferFrom

FileChannel.transferFrom方法把数据从通道源传输到FileChannel:

RandomAccessFile fromFile = new RandomAccessFile("fromFile.txt", "rw");
FileChannel      fromChannel = fromFile.getChannel();

RandomAccessFile toFile = new RandomAccessFile("toFile.txt", "rw");
FileChannel      toChannel = toFile.getChannel();

long position = 0;
long count    = fromChannel.size();

toChannel.transferFrom(fromChannel, position, count);

transferFrom的参数position和count表示目标文件的写入位置和最多写入的数据量。如

果通道源的数据小于count那么就传实际有的数据量。

另外,有些SocketChannel的实现在传输时只会传输哪些处于就绪状态的数据,即使SocketChannel后续会有更多可用数据。

因此,这个传输过程可能不会传输整个的数据。

transferTo

transferTo方法把FileChannel数据传输到另一个channel,下面是案例:

RandomAccessFile fromFile = new RandomAccessFile("fromFile.txt", "rw");
FileChannel      fromChannel = fromFile.getChannel();

RandomAccessFile toFile = new RandomAccessFile("toFile.txt", "rw");
FileChannel      toChannel = toFile.getChannel();

long position = 0;
long count    = fromChannel.size();

fromChannel.transferTo(position, count, toChannel);

这段代码和之前介绍transfer时的代码非常相似,区别只在于调用方法的是哪个FileChannel.

SocketChannel的问题也存在与transferTo.SocketChannel的实现可能只在发送的buffer填充满后才发送,并结束。

参考资料

http://wiki.jikexueyuan.com/project/java-nio-zh/java-nio-channel.html

http://wiki.jikexueyuan.com/project/java-nio-zh/java-nio-filechannel.html

  • FileChannel

oralce jdk7 FileChannel

https://javapapers.com/java/java-nio-file-read-write-with-channels/

https://blog.csdn.net/qq_16628781/article/details/70532307

  • SocketChannel

https://blog.csdn.net/yhl_jxy/article/details/79335552

  • DatagramChannel

http://shift-alt-ctrl.iteye.com/blog/1841460

https://blog.csdn.net/chenxuegui1234/article/details/17981203

https://blog.csdn.net/yhl_jxy/article/details/79336635