Java NIO的一些知识

Java NIO对于数据的处理是使用管道(Channel)和缓冲区(Buffer)来进行处理的,可以从管道读取数据到缓冲区,也可以从缓冲区读取数据写入到管道。

1、分散(scatter)读取

从一个管道读取数据写入到多个缓冲区

从管道读取数据写入数据到一个缓冲区,一个缓冲区满了之后再去写入到另一个缓冲区

ByteBuffer header = ByteBuffer.allocate(128);
ByteBuffer body   = ByteBuffer.allocate(1024);

ByteBuffer[] bufferArray = { header, body };

channel.read(bufferArray);

2、聚合(gather)写入

从多个缓冲区读取数据写入到一个管道

按顺序读取buffer数组的数据写入到管道,写入的数据在buffer中的位置不能超过position和limit之间这个限制

ByteBuffer header = ByteBuffer.allocate(128);
ByteBuffer body   = ByteBuffer.allocate(1024);

//write data into channel

ByteBuffer[] bufferArray = { header, body };

channel.write(bufferArray);

3、从一个管道传输到另一个管道

(1) 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);

position指的是从目标文件的哪个位置写入数据,count指的是可传输的最大字节数

SockectChannel会传输此时已经准备好的数据到另一个Channel,并不一定会传输请求的所有数据(count个字节的数据)

(2) TransferTo()可以从一个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();

fromChannel.transferTo(position, count, toChannel);

SocketChannel的问题和上面的TransferFrom()方法提到的问题一样

4、选择器(Selector)

使用选择器可以做到一个线程管理多个管道

        //创建选择器
        Selector selector = Selector.open();
        //注册Channel到Selector
        SocketChannel channel = SocketChannel.open();
        //channel注册到Selector里面需要是非阻塞的
        channel.configureBlocking(false);
        //注册到Selector中,设置的监听的触发事件为可以读取数据
        SelectionKey key = channel.register(selector, SelectionKey.OP_READ);
        //兴趣集
        int interestSet = key.interestOps();
        boolean isInterestedInAccept = SelectionKey.OP_ACCEPT == (interestSet & SelectionKey.OP_ACCEPT);
        boolean isInterestedInRead = SelectionKey.OP_READ == (interestSet & SelectionKey.OP_READ);
        //获取就绪集
        int readySet = key.readyOps();
        //判断是否就绪的方法
        //管道是否已经准备就绪接收传入的连接
        key.isAcceptable();
        //管道是否已经与一台服务器成功建立连接
        key.isConnectable();
        //管道是否已经有数据已经准备就绪可以读取
        key.isReadable();
        //管道是否已经准备就绪可以写入数据
        key.isWritable();
        //获取管道
        Channel channel1 = key.channel();
        //获取选择器
        Selector selector1 = key.selector();
        //附加对象或者一些信息到SelectionKey
        key.attach("");
        Object attachedObj = key.attachment();
        //在注册的时候附加对象
        SelectionKey key1 = channel.register(selector, SelectionKey.OP_READ, "");
        //通过获取到的已选择的键集来访问已经就绪的管道
        Set<SelectionKey> selectedKeys = selector.selectedKeys();
        Iterator<SelectionKey> keyIterator = selectedKeys.iterator();
        while (keyIterator.hasNext()) {
            SelectionKey key2 = keyIterator.next();
            if (key2.isAcceptable()) {
                //一个连接已经被一个ServerSocketChannel接收了
            } else if (key2.isConnectable()) {
                //已经和一个远程服务器建立了连接
            } else if (key2.isReadable()) {
                //一个管道的数据已经准备就绪可以进行读取
            } else if (key2.isWritable()) {
                //一个管道已经准备就绪可以进行写入数据
            }
            keyIterator.remove();
        }
        //如果线程1调用了select()方法进入阻塞的状态,即使是没有管道是准备就绪的,线程2可以通过调用wakeup()方法来使得线程1可以离开select()方法的阻塞状态
        selector.wakeup();
        //调用该方法可以关闭选择器和使SelectionKey实例无效,但是管道自身不会被关闭
        selector.close();

5、FileChannel

被用于连接到一个文件,可以从文件读取数据,也可以写入数据到文件里。这个管道运行的时候是阻塞模式的。

6、SocketChannel

        //创建一个SocketChannel
        SocketChannel socketChannel = SocketChannel.open();
        socketChannel.connect(new InetSocketAddress("http://jenkov.com", 80));

        //读取管道数据到缓冲区
        ByteBuffer buf = ByteBuffer.allocate(48);
        int bytesRead = socketChannel.read(buf);

        //从缓冲区读取数据写入到管道
        String newData = "New String to write to file..." + System.currentTimeMillis();
        buf.clear();
        buf.put(newData.getBytes());
        buf.flip();
        while(buf.hasRemaining()) {
            socketChannel.write(buf);
        }

        //设置为非阻塞模式
        socketChannel.configureBlocking(false);
        socketChannel.connect(new InetSocketAddress("http://jenkov.com", 80));
        //判断是否和服务器已经建立连接
        while(! socketChannel.finishConnect() ){
            //wait, or do something else...
        }

        socketChannel.close();

7、ServerSocketChannel

//打开一个管道,能够监听接入的TCP连接
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();

serverSocketChannel.socket().bind(new InetSocketAddress(9999));

while(true){
    //监听接入的连接,返回一个带有连接的Channel,是阻塞模式,需要等待有连接到达才返回
    SocketChannel socketChannel =
            serverSocketChannel.accept();

    //do something with socketChannel...
}
//设置为非阻塞模式,立即返回,不管是否有连接接入,使用前需要校验返回的Channel是否为null
serverSocketChannel.configureBlocking(false);
//关闭管道
serverSocketChannel.close();

8、DatagramChannel

DatagramChannel能够发送和接收UDP的数据包。

        //打开一个DatagramChannel
        DatagramChannel channel = DatagramChannel.open();
        channel.socket().bind(new InetSocketAddress(9999));

        //从收到的数据包中复制数据到给定的缓冲区,如果数据包的内容量大于缓冲区的容量,超出的部分将会被忽略掉
        ByteBuffer buf = ByteBuffer.allocate(48);
        buf.clear();
        channel.receive(buf);

        //发送数据到指定的网址
        String newData = "New String to write to file..."+System.currentTimeMillis();
        ByteBuffer buf1 = ByteBuffer.allocate(48);
        buf1.clear();
        buf1.put(newData.getBytes());
        buf.flip();
        int bytesSent = channel.send(buf1,new InetSocketAddress("localhost",80));

        //连接到特定的地址
        channel.connect(new InetSocketAddress("localhost",80));
        int bytesRead1 = channel.read(buf);
        int bytesWritten1 = channel.write(buf);

9、Java NIO Pipe

一个Java NIO Pipe在两个线程之间传输数据的数据连接方式,一个Pie包含一个源管道和一个接收管道,线程A可以写数据到接收管道,这些数据能够从源管道读取出来

10、Java NIO适用的情况

可以通过Java NIO来实现一个线程或者比较少的线程来管理多个Channel。如果需要管理成千上万的连接,而每一个连接只是发送一小部分数据,例如一个聊天服务器,这种比较适合用NIO。如果是需要保持和一些计算机的许多开放连接,例如P2P网络,这种情况下使用一个线程来管理多个出站连接是比较适合的,下面是一个线程管理多个连接的图:

 如果是只有少量的连接,但是一次传输的数据量却很大,传统的IO实现会比较适合,下面传统IO的设计图:

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值