Java NIO学习笔记

Java NIO

1、简介

Java NIO(New IO),是从java1.4开始引入的一个新的IO API,可以代替标准的Java IO API

NIO与原来的IO有同样的作用和目的,但是使用方式完全不同

NIO支持面向缓冲区的、基于通道的IO操作

NIO可以以更高效的方式进行文件的读写操作

Java API中提供了两套NIO

  • 一套是针对标准输入输出NIO
  • 另一套是网络编程NIO

IO操作的模式:

  • PIO(Programing IO):所有IO操作由CPU处理,CPU占用率较高
  • DMA(Direct Memory Access):CPU将IO操作控制权交给DMA控制器,只能以固定方式读写,CPU空闲做其他工作
  • 通道方式(Channel):能执行有限通道指令的IO控制器,代理CPU管理控制外设。通道有自己的指令系统,是一个协同处理器,具有更强的独立处理数据输入输出能力

2、NIO和IO主要区别

IO模型IONIO
通信面向流(Stream Oriented)面向缓冲区(Buffer Oriented)
处理阻塞IO(Blocking IO)非阻塞IO(Non Blocking IO)
触发选择器(Selectors)

2.1 面向流与面向缓冲区

Java IO和NIO之间第一个最大的区别是,IO是面向流的,NIO是面向缓冲区的。

Java IO面向流意味着每次从流中读一个或多个字节,直至读取所有字节,它们没有被缓存在任何地方。此外,它不能前后移动流中的数据。如果需要前后移动从流中读取的数据,需要先将它缓存到一个缓冲区。 Java NIO的缓冲导向方法略有不同。数据读取到一个它稍后处理的缓冲区,需要时可在缓冲区中前后移动。这就增加了处理过程中的灵活性。但是,还需要检查是否该缓冲区中包含所有您需要处理的数据。而且,需确保当更多的数据读入缓冲区时,不要覆盖缓冲区里尚未处理的数据。

2.2 阻塞与非阻塞IO

Java IO的各种流是阻塞的。这意味着,当一个线程调用read() 或 write()时,该线程被阻塞,直到有一些数据被读取,或数据完全写入。该线程在此期间不能做任何事情。Java NIO的非阻塞模式,使一个线程从某通道发送请求读取数据,但是它仅能得到目前可用的数据,如果目前没有数据可用时,就什么都不会获取,而不是保持线程阻塞,所以直至数据变的可以读取之前,该线程可以继续做其他的事情。 非阻塞写也是如此。一个线程请求写入一些数据到某通道,但不需要等待它完全写入,这个线程同时可以去做别的事情。 线程通常将非阻塞IO的空闲时间用于在其它通道上执行IO操作,所以一个单独的线程现在可以管理多个输入和输出通道(channel)。

2.3选择器(Selectors)

Java NIO的选择器允许一个单独的线程来监视多个输入通道,你可以注册多个通道使用一个选择器,然后使用一个单独的线程来“选择”通道:这些通道里已经有可以处理的输入,或者选择已准备写入的通道。这种选择机制,使得一个单独的线程很容易来管理多个通道。

3、缓冲区(Buffer)和通道(Channel)

通道表示打开到IO设备(例如文件、套接字(socket))的连接。若需要使用NIO系统,需要获取用于连接IO设备的通道以及用于容纳数据的缓冲区。然后操作缓冲区,对数据进行处理

简而言之,Channel负责传输,Buffer负责存储

3.1 缓冲区

缓冲区是一个用于特定基本数据类的容器,就像一个数组,可以保存多个相同类型的数据。

在NIO库中,所有数据都是用缓冲区处理。Java NIO中的Buffer主要用于与NIO通道进行交互。在读取数据时,直接读到缓冲区中; 在写入数据时,它也是写入到缓冲区中;任何时候访问 NIO 中的数据,都是将它放到缓冲区中

而在面向流I/O系统中,所有数据都是直接写入或者直接将数据读取到Stream对象中。在NIO中,所有的缓冲区类型都继承于抽象类Buffer

对于Java中的基本类型,基本都有一个具体Buffer类型与之相对应

在这里插入图片描述

3.1.1 四个基本属性

Capacity 容量

缓冲区能够容纳的数据元素的最大数量,容量在缓冲区创建时被设定,容量不能为负,并且永远不能改变

Position 位置

下一个要被读或写的元素的索引。位置会自动由相应的 get( )和 put( )函数更新。

写数据时,表示当前位置,初始值为0;读数据时从某个特定位置读;从写模式切换到读模式,会被重置为0

Limit 上界

在写模式下,相当于capacity。表示最多能往Buffer中写多少数据

Buffer切换到读模式下,表示最多能读多少数据。同时limit会被设置为写模式下的position值,能读到之前写入的所有数据

Mark 标记

临时存放的位置下标索引。通过mark() 方法记录当前position位置,之后可以通过reset() 方法恢复到mark标记位置

0<=mark<=postion<=limit<=capacitiy

3.1.2 Buffer基本用法
读写数据遵循步骤
  1. 创建缓冲区,写入数据到Buffer
  2. 调用flip()方法切换模式
  3. 冲Buffer中读取数据
  4. 调用clear()或compact()方法清空缓冲区

当向buffer写入数据时,buffer会记录下写了多少数据。一旦要读取数据,需要通过flip()方法将Buffer从写模式切换到读模式。在读模式下,可以读取之前写入到buffer的所有数据。

一旦读完了所有的数据,就需要清空缓冲区,以便它可以再次被写入。

两种方式清空缓冲区:

  • clear()

    clear()方法会清空整个缓冲区

  • compact()

    compact()方法只会清除已经读过的数据。任何未读的数据都被移到缓冲区的起始处,新写入的数据将放到缓冲区未读数据的后面。

Buffer常用方法
方法描述
flip()写模式转换为读模式
clear()清空缓冲区
compact()清除已读过的数据
rewind()position重置为0,一般用于重复读
mark()对当前position设置标记
reset()恢复到标记位置
hasRemaining()判断缓冲区中是否还有数据
数据操作
  • 获取缓冲区中数据

    • get() 读取单个字节
    • get(byte[] dst) 批量读取多个字节到dst中
    • get(int index) 读取指定索引位置字节(不会移动position)
  • 将数据存放到缓冲区中

    • put(byte b) 将单个字节写入到缓冲区中当前位置
    • put(byte[] src) 将src中字节写入到缓冲区中当前位置
    • put(int index, byte b) 将指定字节写入到缓冲区中索引位置
Buffer的分配

想要获得一个Buffer对象首先要进行分配。每一个Buffer类都有一个allocate方法。

ByteBuffer buf = ByteBuffer.allocate(1024);  //间接缓冲区,大小为1024
ByteBuffer buf2=ByteBuffer.allocateDirect(1024); //直接缓冲区

直接缓冲区与间接缓冲区

直接缓冲区:在物理内存中开辟空间,空间比较大,读写文件速度快。缺点:不受垃圾回收器控制,创建和销毁耗性能

间接缓冲区:在堆中开辟空间,易于管理,垃圾回收器可以回收,空间有限,读写文件速度较慢

3.2 通道

Channel是一个对象,表示IO源与目标打开的连接。Channel不能直接访问数据,但可以通过Buffer读取和写入数据

3.2.1 Channel主要实现类
  • FileChannel 用于读取、写入、映射和操作文件的通道
  • DatagramChannel 通过UDP读取网络中的数据通道
  • SocketChannel 通过TCP读取网络中的数据通道
  • ServerSocketChannel 可以监听新进来的TCP连接,对每一个新进来的连接都会创建一个SocketChannel
3.2.2 获取通道
  1. Java针对支持通道的类提供了 getChannel() 方法
    • FileInputStream/FileOutputStream
    • RandomAccessFile
    • DatagramSocket
    • Socket
    • ServerSocket
  2. 通过通道的静态方法 open() 打开并返回指定通道
  3. Files类提供的静态方法 newByteChannel() 获取字节通道
3.2.3 通道之间的数据传输
transferFrom() / transferTo()
//获取通道
inChannel = FileChannel.open(Paths.get("1.png"), StandardOpenOption.READ);
outChannel = FileChannel.open(Paths.get("5.png"), StandardOpenOption.WRITE,StandardOpenOption.READ,StandardOpenOption.CREATE);

//定义传输位置
long position = 0L;
//最多传输字节数
long count = inChannel.size();

inChannel.transferTo(position, count, outChannel);
//outChannel.transferFrom(inChannel,0,inChannel.size());
3.2.4 分散(Scatter)和聚集(Gather)
  • 分散读取(Scattering Reads)

    指从Channel中读取的数据"分散"到多个Buffer中

    注意:按照缓冲区的顺序,从Channel中读取的数据依次将Buffer填满

    在这里插入图片描述

  • 聚集写入(Gathering Writes)

    将多个Buffer中数据"聚集"到Channel中

    注意:按照缓冲区的顺序,写入position和limit之间的数据到Channel

    在这里插入图片描述

@Test
public void testChannel4() throws IOException {
    //分散与聚集

    RandomAccessFile raf1 = new RandomAccessFile("1.txt", "rw");
    RandomAccessFile raf2 = new RandomAccessFile("2.txt", "rw");

    //1.获取通道
    FileChannel fileChannel = raf1.getChannel();
    FileChannel fileChannel2 = raf2.getChannel();

    //2.分配指定大小缓冲区
    ByteBuffer buffer1 = ByteBuffer.allocate(10);
    ByteBuffer buffer2 = ByteBuffer.allocate(1024);

    //3.分散读取
    ByteBuffer[] byteBuffers = {buffer1, buffer2};
    fileChannel.read(byteBuffers);

    for (ByteBuffer byteBuffer : byteBuffers) {
        byteBuffer.flip();
    }

    System.out.println(new String(byteBuffers[0].array(),0,byteBuffers[0].limit()));
    System.out.println("---------------");
    System.out.println(new String(byteBuffers[1].array(),0,byteBuffers[1].limit()));

    //聚集写入
    fileChannel2.write(byteBuffers);

}
3.2.5 文件通道(FileChannel)
FileChannle常用方法
方法描述
int read(ByteBuffer dst)从Channel中读取数据到ByteBuffer
long read(ByteBuffer[] dsts)将Channel中的数据"分散"到ByteBuffer[]
int write(ByteBuffer src)将ByteBuffer中数据写入到Channel中
long write(ByteBuffer[] srcs)将ByteBuffer[]中数据"聚集"到Channel中
long size()返回通道中文件当前大小
3.3.6 案例
@Test
public void testChannel(){
    //使用非直接缓冲区利用通道完成文件复制

    FileInputStream fis = null;
    FileOutputStream fos = null;

    FileChannel inChannel = null;
    FileChannel outChannel = null;

    try {
        fis = new FileInputStream("1.png");
        fos = new FileOutputStream("2.png");

        //①获取通道
        inChannel = fis.getChannel();
        outChannel = fos.getChannel();

        //②分配指定大小缓冲区
        ByteBuffer buffer = ByteBuffer.allocate(1024);

        //③将通道中的数据存入缓冲区
        while(inChannel.read(buffer) != -1){
            buffer.flip();  //切换读模式
            //④将缓冲区的数据写入通道
            outChannel.write(buffer);
            buffer.clear();  //清空缓冲区
        }
    } catch (IOException e) {
        e.printStackTrace();
    } finally {
        if(outChannel != null){
            try {
                outChannel.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        if(inChannel != null){
            try {
                inChannel.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        if(fos != null){
            try {
                fos.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        if(fis != null){
            try {
                fis.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}
@Test
public void testChannel2(){//37--27
    //1.使用直接缓冲区完成文件复制(内存映射文件)

    LocalTime start = LocalTime.now();

    FileChannel inChannel = null;
    FileChannel outChannel = null;

    try {
        inChannel = FileChannel.open(Paths.get("1.png"), StandardOpenOption.READ);
        outChannel = FileChannel.open(Paths.get("3.png"), StandardOpenOption.WRITE,StandardOpenOption.READ,StandardOpenOption.CREATE);

        //内存映射文件的创建
        MappedByteBuffer inMappedBuf = inChannel.map(FileChannel.MapMode.READ_ONLY, 0, inChannel.size());
        MappedByteBuffer outMappedBuf = outChannel.map(FileChannel.MapMode.READ_WRITE, 0, inChannel.size());

        //直接对缓冲区进行数据读写操作
        byte[] bytes = new byte[inMappedBuf.limit()];
        inMappedBuf.get(bytes);
        outMappedBuf.put(bytes);

    } catch (IOException e) {
        e.printStackTrace();
    } finally {
        if(inChannel != null){
            try {
                inChannel.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        if(outChannel != null){
            try {
                outChannel.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    LocalTime end = LocalTime.now();
    Duration between = Duration.between(start, end);
    System.out.println("复制文件耗费时间==>"+between.toMillis());
}
@Test
public void testChannel3(){
    //通道之间的数据传输(直接缓冲区)
    //transferTo
    //transferFrom

    FileChannel inChannel = null;
    FileChannel outChannel = null;

    try {
        inChannel = FileChannel.open(Paths.get("1.png"), StandardOpenOption.READ);
        outChannel = FileChannel.open(Paths.get("4.png"), StandardOpenOption.WRITE,StandardOpenOption.READ,StandardOpenOption.CREATE);

        //inChannel.transferTo(0,inChannel.size(),outChannel);
        outChannel.transferFrom(inChannel,0,inChannel.size());

    } catch (IOException e) {
        e.printStackTrace();
    } finally {
        if(inChannel != null){
            try {
                inChannel.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        if(outChannel != null){
            try {
                outChannel.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

4、NIO的非阻塞式网络通信息

4.1 阻塞与非阻塞概述

  • 传统的IO 流都是阻塞式的。也就是说,当一个线程调用read() 或write() 时,该线程被阻塞,直到有一些数据被读取或写入,该线程在此期间不能执行其他任务。因此,在完成网络通信进行IO 操作时,由于线程会阻塞,所以服务器端必须为每个客户端都提供一个独立的线程进行处理,当服务器端需要处理大量客户端时,性能会急剧下降。
  • Java NIO 是非阻塞式的。当线程从某通道进行读写数据时,若没有数据可用时,该线程可以进行其他任务。线程通常将非阻塞IO 的空闲时间用于在其他通道上执行IO 操作,所以单独的线程可以管理多个输入和输出通道。因此,NIO 可以让服务器端使用一个或有限几个线程来同时处理连接到服务器端的所有客户端。

4.2 选择器(Selector)

选择器(Selector)是非阻塞IO的核心。选择器是SelectableChannel对象的多路复用器,Selector可以同时监控多个SelectableChannel的IO状况,也就是说,利用Selector可使一个单独的线程管理多个Channel

SelectableChannel结构图:

在这里插入图片描述

SocketChannel、ServerSocketChannel、DatagramChannel

  • SocketChannel

    SocketChannel是一个连接到TCP网络套接字的通道

  • ServerSocketChannel

    ServerSocketChannel 是一个可以监听新进来的TCP连接的通道,就像标准IO中的ServerSocket一样。

  • DatagramChannel

    DatagramChannel是一个能收发UDP包的通道。

**选择器(Selector):**Selector选择器类管理被一个被注册式通道集合的信息和它们的就绪状态。通道和选择器一起被注册,并且使用选择器来更新通道的就绪状态。

**可选择通道(SelectableChannel):**SelectableChannel这个抽象类提供了实现通道的可选择性所需要的公共方法。它是所有支持就绪检查的通道类的父类。FileChannel类没有继承SelectableChannel,因此不是可选通道;而所有socket通道都是可选择的,SocketChannel和SeverSocketChannel是SelectableChannel的子类。

**选择键(SelectionKey):**表示SelectableChannel 和Selector 之间的注册关系,封装了特定的通道和特定的选择器的注册关系。每次向选择器注册通道时就会选择一个事件(选择键),选择键包含两个表示为整数值的操作集,操作集的每一位都表示该键的通道所支持的一类可选择操作。当调用register(Selector sel, int ops) 将通道注册选择器时,选择器对通道的监听事件,需要通过第二个参数ops 指定。

Java定义了四个常量类型来表示监听事件类型:

  • 读:SelectionKey.OP_READ
  • 写:SelectionKey.OP_WRITE
  • 连接:SelectionKey.OP_CONNECT
  • 接收:SelectionKey.OP_ACCEPT
Selector常用方法
方法描述
Set keys()所有的SelectionKey 集合
selectedKeys()被选择的SelectionKey 集合
int select()监控所有注册的Channel,当它们中间有需要处理的IO 操作时,该方法返回,并将对应得的SelectionKey 加入被选择的SelectionKey 集合中,该方法返回这些Channel 的数量。
int select(long timeout)可以设置超时时长的select() 操作
int selectNow()执行一个立即返回的select() 操作,该方法不会阻塞线程
Selector wakeup()使一个还未返回的select() 方法立即返回
void close()关闭该选择器
SelectionKey常用方法
方法描述
Selector selector()返回选择器
SelectableChannel channel()获取注册通道
int interestOps()获取感兴趣事件集合
int readyOps获取通道已经准备就绪的操作的集合
boolean isReadable()检查Channel中"读事件"是否就绪
boolean isWritable()检查Channel中"写事件"是否就绪
boolean isConnectable()检查Channel中"连接"是否就绪
boolean acceptable()检查Channel中"接收"是否就绪

选择器的应用

通过Selector.open()创建一个selector

Selector selector = Selector.open();

向选择器注册通道:SelectableChannel.register(Selector sel, int ops)

//1.获取通道
serverSocketChannel = ServerSocketChannel.open();
//2.切换为非阻塞模式
serverSocketChannel.configureBlocking(false);
//3.绑定连接
serverSocketChannel.bind(new InetSocketAddress(8989));
//4.获取选择器
Selector selector = Selector.open();
//5.将通道注册到选择器上,并指定"监听接收 事件"
serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
案例
//1.测试阻塞模式
@SpringBootTest
public class TestBlockingNIO {

    //客户端
    @Test
    public void client(){

        SocketChannel socketChannel = null;
        FileChannel inChannel  = null;

        try {
            //1.获取通道
            socketChannel = SocketChannel.open(new InetSocketAddress("127.0.0.1", 9898));
            inChannel = FileChannel.open(Paths.get("1.txt"), StandardOpenOption.READ);

            //2.分配指定大小缓冲区
            ByteBuffer byteBuffer = ByteBuffer.allocate(1024);

            //3.读取本地文件并发送到服务端
            while(inChannel.read(byteBuffer) != -1){
                byteBuffer.flip();
                socketChannel.write(byteBuffer);
                byteBuffer.clear();
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if(inChannel != null){
                try {
                    inChannel.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if(socketChannel != null){
                try {
                    socketChannel.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }


    }

    //服务端
    @Test
    public void server(){

        ServerSocketChannel serverSocketChannel = null;
        FileChannel outChannel = null;
        SocketChannel socketChannel = null;

        try {
            //1。获取通道
            serverSocketChannel = ServerSocketChannel.open();
            outChannel = FileChannel.open(Paths.get("3.txt"),StandardOpenOption.WRITE,StandardOpenOption.CREATE);

            //2.绑定连接
            serverSocketChannel.bind(new InetSocketAddress("127.0.0.1",9898));

            //3.获取客户端连接通道
            socketChannel = serverSocketChannel.accept();

            //4.分配指定大小缓冲区
            ByteBuffer byteBuffer = ByteBuffer.allocate(1024);

            //5.接收客户端数据保存到本地
            while(socketChannel.read(byteBuffer) != -1){
                byteBuffer.flip();
                outChannel.write(byteBuffer);
                byteBuffer.clear();
            }

        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if(socketChannel != null){
                try {
                    socketChannel.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if(outChannel != null){
                try {
                    outChannel.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if(serverSocketChannel != null){
                try {
                    serverSocketChannel.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

//测试费阻塞模式
@Test
public void client(){
    SocketChannel  socketChannel = null;

    try {
        //1.获取通道
        SocketAddress address = new InetSocketAddress("127.0.0.1", 8989);
        socketChannel = socketChannel.open(address);

        //2.切换非阻塞模式
        socketChannel.configureBlocking(false);

        //3.分配指定大小缓冲区
        ByteBuffer byteBuffer = ByteBuffer.allocate(1024);

        //4.发送数据到服务端
        byteBuffer.put(LocalDateTime.now().toString().getBytes());
        byteBuffer.flip();
        socketChannel.write(byteBuffer);
        byteBuffer.clear();

    } catch (IOException e) {
        e.printStackTrace();
    } finally {
        if(socketChannel != null){
            try {
                socketChannel.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}
@Test
public void server(){
    ServerSocketChannel serverSocketChannel = null;
    SocketChannel socketChannel = null;

    try {
        //1.获取通道
        serverSocketChannel = ServerSocketChannel.open();
        //2.切换为非阻塞模式
        serverSocketChannel.configureBlocking(false);
        //3.绑定连接
        serverSocketChannel.bind(new InetSocketAddress(8989));
        //4.获取选择器
        Selector selector = Selector.open();
        //5.将通道注册到选择器上,并指定"监听接收 事件"
        serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
        //6.轮询式的获取选择器上已经"准备就绪"的事件
        while(selector.select()>0){
            //7.获取当前选择器中所有注册的"选择键(已就绪的监听事件)"
            Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();
            while(iterator.hasNext()){
                //8.获取"准备就绪"的事件
                SelectionKey selectionKey = iterator.next();
                //9.判断具体是什么事件准备就绪
                if(selectionKey.isAcceptable()){
                    //10.若"接收"就绪,则获取客户端连接
                    socketChannel = serverSocketChannel.accept();
                    //11.切换为非阻塞模式
                    socketChannel.configureBlocking(false);
                    //12.将通道注册到选择器上
                    socketChannel.register(selector,SelectionKey.OP_READ);
                }else if(selectionKey.isReadable()){
                    //获取当前选择器上"读就绪"状态的通道
                    socketChannel = (SocketChannel) selectionKey.channel();
                    //分配指定大小缓冲区
                    ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
                    //读取数据
                    int len = 0;
                    while((len = socketChannel.read(byteBuffer)) > 0){
                        byteBuffer.flip();
                        System.out.println(new String(byteBuffer.array(),0,len));
                        byteBuffer.clear();
                    }
                }
                //取消选择键SelectionKey
                iterator.remove();
            }
        }
    } catch (Exception e) {
        e.printStackTrace();
    } finally {
        if(socketChannel != null){
            try {
                socketChannel.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        if(serverSocketChannel != null){
            try {
                serverSocketChannel.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}
//测试非阻塞模式 DatagramChannel
@Test
public void receive(){
    DatagramChannel datagramChannel = null;

    try {
        //获取udp通道
        datagramChannel = DatagramChannel.open();
        //设置为非阻塞模式
        datagramChannel.configureBlocking(false);
        //绑定
        datagramChannel.bind(new InetSocketAddress("127.0.0.1",9898));
        //获取选择器
        Selector selector = Selector.open();
        //将通道注册选择器,并指定模式
        datagramChannel.register(selector, SelectionKey.OP_READ);
        //轮询获取选择器上准备就绪事件
        while(selector.select()>0){
            Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();

            while(iterator.hasNext()){
                //获取准备就绪事件
                SelectionKey selectionKey = iterator.next();

                if(selectionKey.isReadable()){//读就绪
                    //读取数据
                    ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
                    datagramChannel.receive(byteBuffer);
                    byteBuffer.flip();
                    System.out.println(new String(byteBuffer.array(),0,byteBuffer.limit()));
                    byteBuffer.clear();
                }
            }
            iterator.remove();//取消SelectionKey
        }
    } catch (IOException e) {
        e.printStackTrace();
    } finally {
        if(datagramChannel != null){
            try {
                datagramChannel.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

public static void main(String[] args) {
    DatagramChannel datagramChannel = null;

    try {
        //获取udp通道
        datagramChannel = DatagramChannel.open();
        //设置为非阻塞模式
        datagramChannel.configureBlocking(false);
        //分配指定大小缓冲区
        ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
        //发送数据
        Scanner sc = new Scanner(System.in);
        System.out.println("请输入信息:");
        while(sc.hasNextLine()){
            String str = sc.nextLine();
            byteBuffer.put((new Date().toString()+":\n"+str).getBytes());
            byteBuffer.flip();
            datagramChannel.send(byteBuffer,new InetSocketAddress("127.0.0.1",9898));
            byteBuffer.clear();
        }
    } catch (IOException e) {
        e.printStackTrace();
    } finally {
        if(datagramChannel != null){
            try {
                datagramChannel.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

6、管道(Pipe)

Java NIO管道是2个线程之间的单向数据连接。Pipe有一个source通道和一个sink通道。数据会被写到sink通道,从source通道读取

在这里插入图片描述

@Test
public void test01() throws IOException {

    //获取管道
    Pipe pipe = Pipe.open();
    //分配指定大小缓冲区
    ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
    //将缓冲区中数据写入管道
    Pipe.SinkChannel sinkChannel = pipe.sink();
    byteBuffer.put("通过单向管道发送数据".getBytes());
    byteBuffer.flip();
    sinkChannel.write(byteBuffer);
    //读取缓冲区中数据
    Pipe.SourceChannel sourceChannel = pipe.source();
    byteBuffer.flip();
    int len = sourceChannel.read(byteBuffer);
    System.out.println(new String(byteBuffer.array(),0,len));
    //关闭资源
    sourceChannel.close();
    sinkChannel.close();
}

7、Java NIO2(Path、Paths、与Files)

Path与Paths

Paths提供了get()方法用来后去Path对象

Path get(String first, String …more) 用于将多个字符串连成路径

Path常用方法

方法描述
boolean endsWith(String path)判断是否以path路径结束
boolean startsWith(String path)判断是否已path路径开始
boolean isAbsolute()判断是否为绝对路径
Path getFileName()返回与调用Path对象关联的文件名
Path getName(int index)返回指定索引位置的路径名称
int getNameCount()返回Path根目录后元素的数量
Path getParent()返回Path对象包含整个路径,不包含Path对象指定的文件路径
Path getRoot()返回调用Path对象的根路径
Path resove(Path p)将相对路径解析为绝对路径
Path toAbsolutePath()作为绝对路径返回调用Path对象
String toString()返回调用Path对象的字符串表示形式

8、NIO与IO使用场景

BIO方式适用于连接数目比较小并且一次发送大量数据的场景,这种方式对服务器资源要求比较高,并发局限于应用中,JDK1.4以前的唯一选择,但程序直观简单易理解。
NIO方式适用于连接数目多,每次只是发送少量的数据,服务器需要支持超大量的长时间连接。比如10000个连接以上,并且每个客户端并不会频繁地发送太多数据。例如总公司的一个中心服务器需要收集全国便利店各个收银机的交易信息,只需要少量线程按需处理维护的大量长期连接,或者聊天服务器.Jetty、Mina、Netty、ZooKeeper等都是基于NIO方式实现。

对于BIO、NIO、AIO的区别和应用场景,知乎上有位同学是这样回答的:
BIO:Apache,Tomcat。主要是并发量要求不高的场景.如果你有少量的连接使用非常高的带宽,一次发送大量的数据,也许典型的IO服务器实现可能非常契合。
NIO:Nginx,Netty。主要是高并发量要求的场景,如果需要管理同时打开的成千上万个连接,这些连接,例如聊天服务器,实现NIO的服务器可能是一个优势。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值