java基础——NIO

一、NIO简介

NIO(Non-blocking IO)即非阻塞IO,在JDK1.4中引入,提供面向块的非阻塞IO操作

1.NIO家族成员

a).Buffer

NIO是面向块的,不是一个一个的传递数据,自然要有一个东西来打包数据。Buffer缓冲区就是来打包数据用的,它是NIO数据处理的基础单元。
NIO具体有八种Buffer即ByteBuffer,CharBuffer,ShortBuffer,FloatBuffer…对应了java的基本数据类型(除了Boolean类型)
在这里插入图片描述
Buffer的几个重要属性
在这里插入图片描述

b).Channel

类似于流,但数据在Channel中的流动是双向的
Channel具体实现有FileChannel,SocketChannel,DatagramChannel等

c).Selectors

处理事情的分发器。Selector能够检测到多个注册的通道上是否由事件放生,如果有事件发生,便获取事情然后针对每个事情进行相应的处理。这样就可以只用一个单线程去管理多个通道
我们把Channel注册到Selector上,指定监听的事件。
Selctor使用SelectionKey这个类去一对一绑定Channel,每一个Selector内部有一个Set集合来维护所有的SelectionKey。
Selector提供select()以及其他重载方法,来轮询所有的SelectionKey,观察他们对应的Channel是否有事件发生,返回有事件发生的SelectionKey个数。
Selector提供selectedKeys()方法来返回当前有事件发生的SelectorKey的集合,通过每一个selectionKey获取它发生的事件,然后获取它对应绑定的channel进行相应的业务逻辑处理

Selector的4个基本事件
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

二、Demo

1.Buffer

(1).示例1

这个例子创建了一个容量为5的IntBuffer

public class IntBufferTest {

    public static void main(String[] args) {
        IntBuffer intBuffer =IntBuffer.allocate(5);
        for (int i = 0; i <intBuffer.capacity() ; i++) {
            intBuffer.put(i+5);
        }
        //flip方法的作用是使limit=Position,且使Position指针重回数组的首位
        intBuffer.flip();
        while (intBuffer.hasRemaining())
        {
            //get方法得到一个数据后,并不会在从数组中删除它,而是使Position指针后移
            System.out.println(intBuffer.get());
        }

    }
}

在这里插入图片描述

(2)实例2

Buffer的两个特性
Scattering: 将数据写入到buffer时,可以采用Buffer数组,依次写入
Gathering:从buffer读数据,可以采用Buffer数组,依次读

public static void main(String[] args) throws IOException {
        //开启一个serverSocketChannel
        ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
        //绑定端口
        serverSocketChannel.socket().bind(new InetSocketAddress(7000));
        //开辟一个ByteBuffer数组并赋值
        ByteBuffer[] byteBuffers = new ByteBuffer[2];
        byteBuffers[0] =ByteBuffer.allocate(5);
        byteBuffers[1] =ByteBuffer.allocate(3);
        SocketChannel accept = serverSocketChannel.accept();
        while (true)
        {

            accept.read(byteBuffers);
        }

    }

补充
Buffer的filp方法

public final Buffer flip() {
        limit = position;
        position = 0;
        mark = -1;
        return this;
    }

2.Channel

输出流程为
1.创建一个输出流
2.从输出流获得管道Channel
3.创建一个Buffer
4.将数据写入Buffer
5.将Buffer写入Channel

 public static void write(String filename) throws IOException {
        String str = "hello,fileChannel";
        //创建一个输出流
        FileOutputStream fileOutputStream = new FileOutputStream(filename);
        //通过输出流获取对应的channel
        FileChannel fileChannel = fileOutputStream.getChannel();
        //创建缓冲区
        ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
        //将数据放入到缓冲区
        byteBuffer.put(str.getBytes());
        //对buffer进行反转,position指针放置数组开头
        byteBuffer.flip();
        //将buffer写入channel
        fileChannel.write(byteBuffer);
        fileOutputStream.close();
    }

输入

 public static void read (String filename) throws IOException {
        FileInputStream fileInputStream = new FileInputStream(filename);
        FileChannel fileChannel = fileInputStream.getChannel();
        ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
        //从通道读取数据放入缓冲区
        fileChannel.read(byteBuffer);
        byteBuffer.flip();
        System.out.println(new String(byteBuffer.array()));
        fileInputStream.close();
    }

将一个文件拷贝到另一个文件,这里考虑到文件的大小大于Buffer需要多次读取的情况

  FileInputStream fileInputStream = new FileInputStream("file01.txt");
        FileChannel fileInputStreamChannel = fileInputStream.getChannel();
        FileOutputStream fileOutputStream = new FileOutputStream("file02.txt");
        FileChannel fileOutputStreamChannel = fileOutputStream.getChannel();
        ByteBuffer byteBuffer = ByteBuffer.allocate(4);
        while (true)
        {
            //重要!
            //循环读时,一次性读不完,再读要清空buffer
            byteBuffer.clear();
            int read = fileInputStreamChannel.read(byteBuffer);
            if(read ==-1) break;
            byteBuffer.flip();
            fileOutputStreamChannel.write(byteBuffer);

        }
        fileInputStream.close();
        fileOutputStream.close();

使用Channel的transferFrom直接将另一个管道的数据转移过来

public static void transform() throws IOException {
        FileInputStream fileInputStream = new FileInputStream("file01.txt");
        FileChannel fileInputStreamChannel = fileInputStream.getChannel();

        FileOutputStream fileOutputStream = new FileOutputStream("file02.txt");
        FileChannel fileOutputStreamChannel = fileOutputStream.getChannel();

        fileOutputStreamChannel.transferFrom(fileInputStreamChannel,0,fileInputStreamChannel.size());
    }

3.综合示例

在这里插入图片描述

public class NIOServer {
    public static void main(String[] args) throws IOException {
        ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
        //得到一个Selector
        Selector selector = Selector.open();
        //绑定一个端口
        serverSocketChannel.socket().bind(new InetSocketAddress(6666));
        //设置为非阻塞
        serverSocketChannel.configureBlocking(false);

        //把channel注册到selector,监听的事情为OP_ACCEPT
        serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
        while (true)
        {
            if(selector.select(1000)==0)
            {
                System.out.println("服务器等待了一秒,无连接");
                continue;
            }
            //如果selector.select返回大于0,就获取到相关的selectionkey集合
            //1.如果返回的>0 表示已经h获取到关注的事件
            //2.selector.selectedKeys()返回关注事件的集合
            //通过selectionkeys反向获取通道
            Set<SelectionKey> selectionKeys = selector.selectedKeys();
            Iterator<SelectionKey> keyIterator = selectionKeys.iterator();
            while (keyIterator.hasNext())
            {
                //获得SelectionKey
                SelectionKey selectionKey = keyIterator.next();
                //获取key对应通道发生的事件
                if(selectionKey.isAcceptable())
                {
                    //事件是连接事件,那我们调用accept方法的时候就会立刻获取一个连接
                    SocketChannel socketChannel = serverSocketChannel.accept();
                    socketChannel.configureBlocking(false);
                    //把这个channel注册到selector,关注事情为OP_READ,同时给这个channel关联一个BUFF
                    socketChannel.register(selector,SelectionKey.OP_READ, ByteBuffer.allocate(1024));
                }
                if(selectionKey.isReadable())
                {
                    //通过key反向获得Channel
                    SocketChannel socketChannel = (SocketChannel) selectionKey.channel();
                    //获得和这个Channel关联的buffer
                    ByteBuffer byteBuffer = (ByteBuffer)selectionKey.attachment();
                    socketChannel.read(byteBuffer);
                    System.out.println("客户端发来的"+new String(byteBuffer.array()));
                }
                //手动从集合中移动,避免重复处理
                keyIterator.remove();

            }
        }
    }
}

public class NIOClient {
    public static void main(String[] args) throws IOException {
        SocketChannel socketChannel = SocketChannel.open();
        socketChannel.configureBlocking(false);
        //提供服务器端IP和端口

        if(!socketChannel.connect(new InetSocketAddress("127.0.0.1",6666)))
        {
            while (!socketChannel.finishConnect())
            {
                System.out.println("没连接上,也不会阻塞");
            }
        }
        String str = "hello server";
        //获得和数据大小相同的buffer
        ByteBuffer byteBuffer = ByteBuffer.wrap(str.getBytes());
        socketChannel.write(byteBuffer);
        System.in.read();

    }
}

三、补充

1.NIO和零拷贝

主要应用就是FileChannel的transferTo方法
很多操作系统可以直接将文件缓存传输到channel,不经过CPU的拷贝

//Linux环境下可以把文件一次性的传输到Channel
//Windows环境下一次只可以传输8M
//var1表示文件开始的字节,var3表示要传输的字节数,var5表示目标channel
public long transferTo(long var1, long var3, WritableByteChannel var5) throws IOException
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值