JAVA NIO(New IO)

目录

一.了解NIO

1.什么是NIO

2.NIO的新特性

3.IO和NIO的区别

二.核心组件

1.Channel

2.Buffer

3.Selector

三.代码演示

1.与本地磁盘IO (FileChannel)

2.网络IO (ServerSocketChannel、SocketChannel)

3.Selector

4.零拷贝

一.了解NIO

1.什么是NIO

首先我们要明白传统IO比如InputStream,OutputStream,在执行read()和write()时是会阻塞的,直到数据准备好才会被唤醒。

NIO,是一种非阻塞IO,NIO 从JDK1.4提出的,本意是New l0,它的出现为了弥补传统I0的不足,提供了更高效的方式

NIO在处理网络数据时底层用到(select/poll)模型 多路复用  (可以提升服务端处理连接的数量)jdk1.5使用了epoll代替了select/poll

2.NIO的新特性

基于通道(Channel)和缓冲区(Buffer)操作:

  • 通道(Channel) : 1个新的原始I/O抽象
  • 缓冲支持(Buffer) :为所有的原始类型提供缓冲支持
  • 具体操作:1.数据从 通道 读取到缓冲区、2..数据从 缓冲区 写入通道

非阻塞:

提供多路非阻塞式的I/O操作

选择器(Selectors):

  • 用于监听多个通道的事件,如:连接打开、数据到达
  • 单个线程可监听多个数据通道

其他:

  • 提供字符集编码、解码的解决方案:Java.nio.charset
  • 支持锁&内存映射文件的文件访问接口

3.IO和NIO的区别

二.核心组件

通道(`Channel `) : Java NIO数据来源,可以是网络,也可以是本地磁盘

缓冲区(`Buffer `):数据读写的中转区,

选择器(`Selectors '):异步I0的核心类,可以实现异步非阻塞I0,一个selectors可以管理多个通道Channel

1.Channel

Channel的实现

FileChangel:从文件中读写数据

DatagramChannel:通过UDP协议读写网络中的数据

SocketChannel:通过TCP协议读写网络中的数据

ServerSocketChannel:监听一个TCP连接,对于每一个新的客户端连接都会创建一个SocketChannel.

2.Buffer

什么是Buffer

buffer是一个对象,它包含了需要写入或者刚读出的数据,最常用的缓冲区类型是 ByteBuffer,数据IO必须经过这个buffer

Buffer的本质

缓冲区本质上是一块可以写入数据,以及从中读取数据的内存,实际上也是一个byte[]数据,只是在NIO中被封装成了NIO Buffer对象,并提供了一组方法来访问这个内存块,要理解buffer的工作原理,需要知道几个属性

  • capacity  初始化时的容量大小
  • position  表示我们要操作元素的位置 他是要不断变化的
  • limit  表示可以操作元素的最大的下标(读模式中他应该和我们的容量相等,写模式中他应该代表我们缓存区最后一个元素的位置)

图解

第一次读取数据

第二次读取数据

 转换写的时候一定要调用flip操作,他会改变position 和 limit的位置

 数据写出 position会不断移动

3.Selector

select/poll与epoll

什么是Selector

Selector(选择器,多路复用器)是Java NIO中能够检测一到多个NIO通道,是否为诸如读写事件做好准备的组件。这样,一个单独的线程可以管理多个channel,从而管理多个网络连接。

Selector工作流程

三.代码演示

1.与本地磁盘IO (FileChannel)

实现一个文件copy功能

public class CopyDemo {
    public static void main(String[] args) {
        //实现文件的复制
        try {
//            FileInputStream fi = new FileInputStream(new File("d:/111/123.txt"));
//            FileOutputStream fo = new FileOutputStream(new File("d:/111/123_cp.txt"));
//            //NIO  通道Channel  缓存buffer
//            //创建通道
//            FileChannel fic = fi.getChannel();
//            FileChannel foc = fo.getChannel();
            FileChannel fic = FileChannel.open(Paths.get("d:/111/123.txt"), 
                    StandardOpenOption.READ);
            FileChannel foc = FileChannel.open(Paths.get("d:/111/123_cp.txt"),
                    StandardOpenOption.CREATE, StandardOpenOption.Write);

            //初始缓冲区
            ByteBuffer buffer = ByteBuffer.allocate(1024);
//            byte[] buffer = new byte[1024]; //两种方式效果相同
//            ByteBuffer.wrap(buffer);

            int i = 0;
            while (true) {
                //读操作
                if ((i = fic.read(buffer)) == -1) {
                    break;
                }

                buffer.flip();//把读模式转换成写模式
                //写操作
                foc.write(buffer);
                buffer.clear();//清空缓存区
            }

        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

2.网络IO (ServerSocketChannel、SocketChannel)

关键代码 

server:

创建ServerSocketChannel管道与 ServerSocket相似                               

ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();

设置连接的方式 默认为阻塞连接 默认参数为ture ture代表阻塞连接 false非阻塞

serverSocketChannel.configureBlocking(false);

将管道绑定一个套接字地址  客户端可以根据这个端口进行连接  也就是服务端的监听端口     

serverSocketChannel = serverSocketChannel.bind(new InetSocketAddress(7486));

接受与此通道的套接字建立的连接 与ServerSocket.accept相似                                     

SocketChannel socketChannel = serverSocketChannel.accept();

client:

创建SocketChannel管道                                                                                           

SocketChannel socketChannel = SocketChannel.open();

创建一个连接                                                                                       

socketChannel.connect(new InetSocketAddress("localhost",7486));

客户端设置为非阻塞IO                                                     

socketChannel.configureBlocking(false);

改为非阻塞IO后 在IO操作的代码前要判断是否连接上 连接上了才可以做后续的操作

//判断是否已经连接上了
if (socketChannel.isConnectionPending()) {
    //如果建立连接失败 finishConnect() 会抛出异常
    socketChannel.finishConnect();
}

在读服务端数据的IO操作代码前最好睡一秒  因为是非阻塞IO 客户端可能没收到服务端发送的消息就执行结束了 会导致服务端报错,客户端接收到的数据不准确     

Thread.sleep(1000);

 server端

public class Server {
    public static void main(String[] args) {
        try {
            //ServerSocketChannel 支持两种模式:阻塞 和 非阻塞
            ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
            //默认为ture  ture代表阻塞连接  false非阻塞
            serverSocketChannel.configureBlocking(false);
            //相当与 ServerSocket 创建一个监听
            //new InetSocketAddress 创建一个套接字地址
            serverSocketChannel = serverSocketChannel.bind(new InetSocketAddress(7486));
            while (true) {
                SocketChannel socketChannel = serverSocketChannel.accept();
                //非阻塞就需要加上判断 判断是有连接 若有才能进行网络IO
                if (socketChannel != null) {
                    //创建buffer
                    ByteBuffer buffer = ByteBuffer.allocate(1024);
                    socketChannel.read(buffer); //读
                    System.out.println(new String(buffer.array()));
                    //sleep 5秒
//                Thread.sleep(5000);
                    String msg = "Hello,I`m Server";
                    buffer.clear();
                    buffer.put(msg.getBytes());
                    buffer.flip();//读切换写模式
                    socketChannel.write(buffer); //写
                    buffer.clear();
                } else {
                    Thread.sleep(1000);
                    System.out.println("等待客户端连接");
                }

            }

        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

client端

public class Client {
    public static void main(String[] args) {
        try {
            SocketChannel socketChannel = SocketChannel.open();
            socketChannel.configureBlocking(false);//设置非阻塞IO
            socketChannel.connect(new InetSocketAddress("localhost",7486));
            ByteBuffer buffer = ByteBuffer.allocate(1024);
            //判断是否已经连接上了
            if (socketChannel.isConnectionPending()) {
                //如果建立连接失败 finishConnect() 会抛出异常
                socketChannel.finishConnect();
            }
            String msg = "hello,I`m client01!";
            buffer.put(msg.getBytes());
            buffer.flip();
            socketChannel.write(buffer);
            buffer.clear();
            //睡一秒等待服务端发送消息 因为是非阻塞IO 客户端可能没收到服务端发送的消息就执行结束了
            Thread.sleep(1000);
            socketChannel.read(buffer);
            System.out.println(new String(buffer.array()));

        } catch (Exception e) {
            e.printStackTrace();
        }
    }

}

3.Selector

server

public class SelectorServer {

    private static Selector selector;

    public static void main(String[] args) {
        try {
            selector = Selector.open(); //创建一个多路复用器
            ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
            serverSocketChannel.configureBlocking(false); //必须设为非阻塞连接
            //绑定连接端口
            serverSocketChannel.bind(new InetSocketAddress(7486));
            //把监听注册到selector  每次注册会的得到一个相对的钥匙SelectionKey
            serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);

            while (true) {
                selector.select(); //阻塞 直到查询要有连接准备好了数据
                //获取已经准备好的selectedKeys
                Set<SelectionKey> selectionKeys = selector.selectedKeys();
                Iterator<SelectionKey> iterator = selectionKeys.iterator();
                //用迭代器的方式遍历
                if (iterator.hasNext()) {
                    SelectionKey selectionKey = iterator.next();
                    iterator.remove();//拿到后一定要删除 避免重复处理
                    //如果是个连接时间
                    if (selectionKey.isAcceptable()) {
                        handleAccept(selectionKey);
                    } else if (selectionKey.isReadable()) {
                    //读事件
                        handleRead(selectionKey);
                    }
                }
            }

        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            //TODO 关闭
        }
    }

    private static void handleAccept(SelectionKey selectionKey) throws IOException {
        //获取ServerSocketChannel管道
        //每个SelectionKey相当于一个要是 他有对应的通道
        ServerSocketChannel serverSocketChannel = (ServerSocketChannel) selectionKey.channel();
        SocketChannel socketChannel = serverSocketChannel.accept();
        socketChannel.configureBlocking(false); //改为非阻塞IO
        ByteBuffer buffer = ByteBuffer.wrap("Hello,I`m Server".getBytes());

        socketChannel.write(buffer);
        //把socketChannel的读事件注册
        socketChannel.register(selector,SelectionKey.OP_READ);
    }
    private static void handleRead(SelectionKey selectionKey) throws IOException {
        SocketChannel socketChannel = (SocketChannel) selectionKey.channel();
        ByteBuffer buffer = ByteBuffer.allocate(1024);
        socketChannel.read(buffer);
        System.out.println(new String(buffer.array()));
    }
}

client

public class SelectorClient {

    private static Selector selector;

    public static void main(String[] args) {
        try {
            selector = Selector.open();
            SocketChannel socketChannel = SocketChannel.open();
            socketChannel.configureBlocking(false); //改为非阻塞IO
            socketChannel.connect(new InetSocketAddress("localhost",7486));
            socketChannel.register(selector, SelectionKey.OP_CONNECT);

            while (true) {
                selector.select();
                Set<SelectionKey> selectionKeys = selector.selectedKeys();
                Iterator<SelectionKey> iterator = selectionKeys.iterator();
                if (iterator.hasNext()) {
                    SelectionKey selectionKey = iterator.next();
                    iterator.remove();//删除
                    if (selectionKey.isConnectable()) {
                        //连接事件
                        handleConnect(selectionKey);
                    } else if (selectionKey.isReadable()) {
                        //读事件
                        handleRead(selectionKey);
                    }
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            //TODO 关闭
        }
    }

    private static void handleConnect(SelectionKey selectionKey) throws IOException {
        SocketChannel socketChannel = (SocketChannel) selectionKey.channel();
        socketChannel.configureBlocking(false); //改为非阻塞IO
        if (socketChannel.isConnectionPending()) {
            socketChannel.finishConnect();
        }
        socketChannel.write(ByteBuffer.wrap("Hello,I`m Client".getBytes()));
        socketChannel.register(selector,SelectionKey.OP_READ);
    }

    private static void handleRead(SelectionKey selectionKey) throws IOException {
        SocketChannel socketChannel = (SocketChannel) selectionKey.channel();
        ByteBuffer buffer = ByteBuffer.allocate(1024);
        socketChannel.read(buffer);
        System.out.println(new String(buffer.array()));
    }
    
}

4.零拷贝

transferTo方法可以实现零拷贝,我们要传输的文件么不用先放在buffer中,在进行传输,减少了从内核空间在复制到用户空间的过程。

client

public class ZeroCopyClient {
    public static void main(String[] args) throws IOException {
        SocketChannel socketChannel = SocketChannel.open();
        socketChannel.connect(new InetSocketAddress("localhost",7486));
        FileChannel fileChannel = new FileInputStream("d:/111/test.zip").getChannel();
        long position = 0;
        long size = fileChannel.size();
        //f 代表一次传输的大小
        long f = 0;
        while (size > 0) {
            //因为transferto一次最多只能copy 8M大小的数据 所以我们需要多次传输
            f = fileChannel.transferTo(position,size,socketChannel);
            if (f > 0) {
                position += f;
                size -= f;
            }
        }
        System.out.println("传输结束传输了总字节数" + fileChannel.size());
        //TODO 关闭流
        socketChannel.close();
        fileChannel.close();
    }
}

server

public class ZeroCopyServer {
    public static void main(String[] args) {
        try {
            ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
            serverSocketChannel.bind(new InetSocketAddress(7486));
            SocketChannel socketChannel = serverSocketChannel.accept();
            ByteBuffer buffer = ByteBuffer.allocate(1024);
            FileChannel fileChannel = new FileOutputStream("d:/111/test_cp.zip").getChannel();
            int i = 0;
            while (i != -1) {
                i = socketChannel.read(buffer);
                buffer.flip();
                fileChannel.write(buffer);
                buffer.clear();
            }
            System.out.println("copy完成");
            //TODO 关闭流
            fileChannel.close();
            socketChannel.close();
            serverSocketChannel.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

w7486

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值