Java NIO 总结

本文深入讲解Java NIO的基础概念及应用,包括通道、缓冲区、选择器等核心组件的使用方法,以及NIO如何提高文件读写效率。还介绍了非阻塞网络通信的实现原理,并演示了基于NIO的服务器端与客户端的具体实现。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Java NIO简介

Java NIO(New IO)是从Java 1.4版本开始引入的一个新的IO API,可以替代标准的Java IO API。NIO与原来的IO有同样的作用和目的,但是使用的方式完全不同,NIO支持面向缓冲区的、基于通道的IO操作。NIO将以更加高效的方式进行文件的读写操作。

Java NIO 与IO的主要区别:
IO :
- 面向流
- 阻塞IO

NIO:
- 面向缓冲区
- 非阻塞IO
- 选择器

通道(Channel)与缓冲区(Buffer)

Channel 负责传输, Buffer 负责存储。

通道

  • 表示打开到 IO 设备(例如:文件、套接字)的连接。若需要使用 NIO 系统,需要获取用于连接 IO 设备的通道以及用于容纳数据的缓冲区。然后操作缓冲区,对数据进行处理。
  • 由 java.nio.channels 包定义的。Channel 表示 IO 源与目标打开的连接。Channel 类似于传统的“流”。只不过 Channel 本身不能直接访问数据,Channel 只能与Buffer 进行交互。

Java 为 Channel 接口提供的最主要实现类如下:
- FileChannel:用于读取、写入、映射和操作文件的通道。
- DatagramChannel:通过 UDP 读写网络中的数据通道。
- SocketChannel:通过 TCP 读写网络中的数据。
- ServerSocketChannel:可以监听新进来的 TCP 连接,对每一个新进来的连接都会创建一个 SocketChannel。

获取通道的一种方式是对支持通道的对象调用getChannel() 方法。支持通道的类如下:
- FileInputStream
- FileOutputStream
- RandomAccessFile
- DatagramSocket
- Socket
- ServerSocket

第二种获取通道的方式是使用 Files 类的静态方法 newByteChannel() 获取字节通道。
第三种获取通道的方式通过通道的静态方法 open() 打开并返回指定通道。

  • 将 Buffer 中数据写入 Channel write(buf);
  • 从 Channel 读取数据到 Buffer read(buf);
分散(Scatter)和聚集(Gather)
  • 分散读取(Scattering Reads)是指从 Channel 中读取的数据“分散”到多个 Buffer 中。
  • 聚集写入(Gathering Writes)是指将多个 Buffer 中的数据“聚集”到 Channel。

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

transferFrom

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

transferTo

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

FileChannel 的常用方法

        /**
         * 通道(channel):用于源节点与目标节点的连接,在Java NIO 中负责缓冲区中数据的传输,Channel本身不存储数据,因此需要配合缓冲区进行传输。
         */
        public class TestChannel {

            //1.用通道完成文件的复制(非直接缓冲区)
            @Test
            public void test1(){
                FileInputStream fis = null;
                FileOutputStream fos = null;
                //2.获取通道
                FileChannel inChannel = null;
                FileChannel outChannel = null;
                try {
                    fis = new FileInputStream("1.jpg");
                    fos = new FileOutputStream("2.jpg");

                    inChannel = fis.getChannel();
                    outChannel = fos.getChannel();

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

                    //将通道中数据存入缓冲区中
                    while (inChannel.read(buf) != -1) {
                        //切换读取数据的模式
                        buf.flip();
                        //将缓冲区中数据写入通道中
                        outChannel.write(buf);
                        //将缓冲区中数据写入通道中
                        buf.clear();
                    }
                } catch (FileNotFoundException e) {
                    e.printStackTrace();
                } 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();
                        }
                    }
                }
            }

            //2.用通道完成文件的复制(直接缓冲区)(内存映射文件)
            @Test
            public void test2(){
                FileChannel inChannel = null;
                FileChannel outChannel = null;
                try {
                    inChannel = FileChannel.open(Paths.get("e:/", "1.jpg"), StandardOpenOption.READ);
                    outChannel = FileChannel.open(Paths.get("2.jpg"), StandardOpenOption.READ,
                            StandardOpenOption.WRITE,StandardOpenOption.CREATE);

                    //内存映射文件
                    MappedByteBuffer inMappedBuffer = inChannel.map(MapMode.READ_ONLY, 0, inChannel.size());
                    MappedByteBuffer outMappedBuffer = outChannel.map(MapMode.READ_WRITE, 0, inChannel.size());

                    //直接对缓冲区进行数据的读写操作
                    byte[] dst = new byte[inMappedBuffer.limit()];
                    inMappedBuffer.get(dst);
                    outMappedBuffer.put(dst);

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

            //3.通道之间的数据传输(直接缓冲区)
            @Test
            public void test3(){
                FileChannel inChannel = null;
                FileChannel outChannel = null;
                try {
                    inChannel = FileChannel.open(Paths.get("e:/1.mp4"), StandardOpenOption.READ);
                    outChannel = FileChannel.open(Paths.get("2.mp4"),StandardOpenOption.READ,StandardOpenOption.WRITE,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();
                        }
                    }
                }
            }

            //分散和聚集
            @SuppressWarnings("resource")
            @Test
            public void test4() throws Exception{
                RandomAccessFile file = new RandomAccessFile("1.txt", "rw");

                //1.获取通道
                FileChannel channel = file.getChannel();

                //2.分配指定大小的缓冲区
                ByteBuffer buf1 = ByteBuffer.allocate(100);
                ByteBuffer buf2 = ByteBuffer.allocate(1024);

                //3.分散读取
                ByteBuffer[] bufs = {buf1,buf2};
                channel.read(bufs);

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

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

                //4.聚集写入
                RandomAccessFile accessFile = new RandomAccessFile("2.txt", "rw");
                FileChannel fileChannel = accessFile.getChannel();
                fileChannel.write(bufs);
            }

            //字符集
            @Test
            public void test5(){
                SortedMap<String,Charset> charsetMap = Charset.availableCharsets();

                for (Entry<String, Charset> entry : charsetMap.entrySet()) {
                    System.out.println(entry.getKey()+"=="+entry.getValue());
                }
            }

            //字符集
            @Test
            public void test6() throws Exception{
                Charset gbk = Charset.forName("GBK");
                //获取编码器
                CharsetEncoder newEncoder = gbk.newEncoder();
                //获取解码器
                CharsetDecoder newDecoder = gbk.newDecoder();

                CharBuffer buffer = CharBuffer.allocate(1024);
                buffer.put("字符集测试!");
                buffer.flip();

                //编码
                ByteBuffer encoder = newEncoder.encode(buffer);

                for (int i = 0; i < 12; i++) {
                    System.out.println(encoder.get());
                }

                //解码
                encoder.flip();
                CharBuffer decode = newDecoder.decode(encoder);
                System.out.println(decode.toString());

                System.out.println("------------");

                Charset cs = Charset.forName("UTF-8");
                encoder.flip();
                CharBuffer cBuf = cs.decode(encoder);
                System.out.println(cBuf.toString());
            }
        }

缓冲区

  • 一个用于特定基本数据类型的容器。由 java.nio 包定义的,所有缓冲区都是 Buffer 抽象类的子类。
  • Java NIO 中的 Buffer 主要用于与 NIO 通道进行交互,数据是从通道读入缓冲区,从缓冲区写入通道中的。
  • Buffer 就像一个数组,可以保存多个相同类型的数据。根据数据类型不同(boolean 除外)
    通过如下方法获取一个 Buffer 对象:
    static XxxBuffer allocate(int capacity) : 创建一个容量为 capacity 的 XxxBuffer 对象
缓冲区的基本属性
  • 容量 (capacity) :表示 Buffer 最大数据容量,缓冲区容量不能为负,并且创建后不能更改。
  • 限制 (limit):第一个不应该读取或写入的数据的索引,即位于 limit 后的数据不可读写。缓冲区的限制不能为负,并且不能大于其容量。
  • 位置 (position):下一个要读取或写入的数据的索引。缓冲区的位置不能为负,并且不能大于其限制
  • 标记 (mark)与重置 (reset):标记是一个索引,通过 Buffer 中的 mark() 方法指定 Buffer 中一个特定的 position,之后可以通过调用 reset() 方法恢复到这个 position

标记、位置、限制、容量遵守以下不变式: 0 <= mark <= position <= limit <= capacity

常用方法

缓冲区的数据操作

Buffer 所有子类提供了两个用于数据操作的方法:get() 与 put() 方法

获取 Buffer 中的数据
- get() :读取单个字节
- get(byte[] dst):批量读取多个字节到 dst 中
- get(int index):读取指定索引位置的字节(不会移动 position)

放入数据到 Buffer 中
- put(byte b):将给定单个字节写入缓冲区的当前位置
- put(byte[] src):将 src 中的字节写入缓冲区的当前位置
- put(int index, byte b):将指定字节写入缓冲区的索引位置(不会移动 position)

直接与非直接缓冲区
  • 字节缓冲区要么是直接的,要么是非直接的。如果为直接字节缓冲区,则 Java 虚拟机会尽最大努力直接在此缓冲区上执行本机 I/O 操作。也就是说,在每次调用基础操作系统的一个本机 I/O 操作之前(或之后),虚拟机都会尽量避免将缓冲区的内容复制到中间缓冲区中(或从中间缓冲区中复制内容)。
  • 直接字节缓冲区可以通过调用此类的 allocateDirect() 工厂方法来创建。此方法返回的缓冲区进行分配和取消分配所需成本通常高于非直接缓冲区。直接缓冲区的内容可以驻留在常规的垃圾回收堆之外,因此,它们对应用程序的内存需求量造成的影响可能并不明显。所以,建议将直接缓冲区主要分配给那些易受基础系统的本机 I/O 操作影响的大型、持久的缓冲区。一般情况下,最好仅在直接缓冲区能在程序性能方面带来明显好处时分配它们。
  • 直接字节缓冲区还可以通过 FileChannel 的 map() 方法 将文件区域直接映射到内存中来创建。该方法返回MappedByteBuffer 。Java 平台的实现有助于通过 JNI 从本机代码创建直接字节缓冲区。如果以上这些缓冲区中的某个缓冲区实例指的是不可访问的内存区域,则试图访问该区域不会更改该缓冲区的内容,并且将会在访问期间或稍后的某个时间导致抛出不确定的异常。
  • 字节缓冲区是直接缓冲区还是非直接缓冲区可通过调用其 isDirect() 方法来确定。提供此方法是为了能够在性能关键型代码中执行显式缓冲区管理。
        public class TestBuffer {

            @Test
            public void test1(){
                //1.分配一个指定大小的缓冲区
                ByteBuffer byteBuf = ByteBuffer.allocate(1024);

                System.out.println("---allocate()---");
                System.out.println(byteBuf.position());
                System.out.println(byteBuf.limit());
                System.out.println(byteBuf.capacity());

                String str = "abcd";
                //2.用put()把数据存入缓冲区
                byteBuf.put(str.getBytes());
                System.out.println("---put()---");
                System.out.println(byteBuf.position());
                System.out.println(byteBuf.limit());
                System.out.println(byteBuf.capacity());

                //3.切换读取数据模式
                byteBuf.flip();
                System.out.println("---flip()---");
                System.out.println(byteBuf.position());
                System.out.println(byteBuf.limit());
                System.out.println(byteBuf.capacity());

                //4.get()读取缓冲区数据
                byte[] dst = new byte[byteBuf.limit()];
                byteBuf.get(dst);
                System.out.println(new String(dst,0,dst.length));
                System.out.println("---get()---");
                System.out.println(byteBuf.position());
                System.out.println(byteBuf.limit());
                System.out.println(byteBuf.capacity());

                //5.rewind(),可重复读数据
                byteBuf.rewind();
                System.out.println("---rewind()---");
                System.out.println(byteBuf.position());
                System.out.println(byteBuf.limit());
                System.out.println(byteBuf.capacity());

                //6.clear()清空缓冲区但是缓冲区中数据依然存在,数据处于"被遗忘"状态
                byteBuf.clear();
                System.out.println("---clear()---");
                System.out.println(byteBuf.position());
                System.out.println(byteBuf.limit());
                System.out.println(byteBuf.capacity());

                System.out.println((char)byteBuf.get());
            }

            @Test
            public void test2(){
                String str = "abcde";
                ByteBuffer buf = ByteBuffer.allocate(1024);
                buf.put(str.getBytes());
                buf.flip();
                byte[] dst = new byte[buf.limit()];
                buf.get(dst,0,2);
                System.out.println(new String(dst,0,2));
                System.out.println(buf.position());

                //mark,标记
                buf.mark();

                buf.get(dst,2,2);
                System.out.println(new String(dst,2,2));
                System.out.println(buf.position());

                //reset():恢复到mark位置
                buf.reset();
                System.out.println(buf.position());

                //判断缓冲区中是否有剩余的数据
                if (buf.hasRemaining()) {
                    //获取缓冲区中剩余的数据量
                    System.out.println(buf.remaining());
                }
            }

            @Test
            public void test3(){
                //分配直接缓冲区
                ByteBuffer buf = ByteBuffer.allocateDirect(1024);
                System.out.println(buf.isDirect());
            }
        }

NIO 的非阻塞式网络通信

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

选择器(Selector)

  • 选择器(Selector) 是 SelectableChannle 对象的多路复用器,Selector 可以同时监控多个 SelectableChannel 的 IO 状况,也就是说,利用 Selector 可使一个单独的线程管理多个 Channel。Selector 是非阻塞 IO 的核心。
  • SelectableChannle 的结构如下图:
  • 创建 Selector :通过调用 Selector.open() 方法创建一个Selector。
  • 向选择器注册通道:SelectableChannel.register(Selector sel, int ops)

选择器(Selector)的应用:
- 当调用 register(Selector sel, int ops) 将通道注册选择器时,选择器对通道的监听事件,需要通过第二个参数 ops 指定。
- 可以监听的事件类型(可使用 SelectionKey 的四个常量表示):
- 读 : SelectionKey.OP_READ (1)
- 写 : SelectionKey.OP_WRITE (4)
- 连接 : SelectionKey.OP_CONNECT (8)
- 接收 : SelectionKey.OP_ACCEPT (16)
- 若注册时不止监听一个事件,则可以使用“位或”操作符连接。

SelectionKey
- SelectionKey:表示 SelectableChannel 和 Selector 之间的注册关系。每次向选择器注册通道时就会选择一个事件(选择键)。选择键包含两个表示为整数值的操作集。操作集的每一位都表示该键的通道所支持的一类可选择操作。
- 当调用 register(Selector sel, int ops) 将通道注册选择器时,选择器对通道的监听事件,需要通过第二个参数 ops 指定。
- 可以监听的事件类型(可使用 SelectionKey 的四个常量表示):
- 读 : SelectionKey.OP_READ (1)
- 写 : SelectionKey.OP_WRITE (4)
- 连接 : SelectionKey.OP_CONNECT (8)
- 接收 : SelectionKey.OP_ACCEPT (16)
- 若注册时不止监听一个事件,则可以使用“位或”操作符连接。

Selector 的常用方法:

SocketChannel

  • Java NIO中的SocketChannel是一个连接到TCP网络套接字的通道。
  • Java NIO中的 ServerSocketChannel 是一个可以监听新进来的TCP连接的通道,就像标准IO中的ServerSocket一样。
  • 操作步骤:
    • 打开 SocketChannel
    • 读写数据
    • 关闭 SocketChannel

阻塞式:

        /**
         * 使用NIO完成网络通信的三个核心:
         * 1.通道(Channel):负责连接
         * 2.缓冲区(Buffer):负责数据存取
         * 3.选择器(Selector):是SelectableChannel的多路复用器。用于监控SelectableChannel的IO状况。
         */
        public class TestBlockingNIO {

            //客户端
            @Test
            public void client() {
                //1.获取通道
                SocketChannel socketChannel = null;
                FileChannel inChannel = null;
                try {
                    socketChannel = SocketChannel.open(new InetSocketAddress("127.0.0.1", 9000));

                    inChannel = FileChannel.open(Paths.get("1.jpg"), 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 {
                    //4.关闭资源
                    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() {
                //1.读取通道
                ServerSocketChannel channel = null;
                FileChannel outChannel = null;
                //3.获取客户端连接通道
                SocketChannel sChannel = null;
                try {
                    channel = ServerSocketChannel.open();
                    outChannel = FileChannel.open(Paths.get("2.jpg"), StandardOpenOption.WRITE,StandardOpenOption.CREATE);

                    //2.绑定连接
                    channel.bind(new InetSocketAddress(9000));

                    sChannel = channel.accept();

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

                    //5.接收客服端数据,并保持到本地
                    while (sChannel.read(buffer) != -1) {
                        buffer.flip();
                        outChannel.write(buffer);
                        buffer.clear();
                    }
                } catch (Exception e) {
                    e.printStackTrace();
                } finally {
                    //6.关闭通道
                    if (sChannel != null) {
                        try {
                            sChannel.close();
                        } catch (IOException e) {
                            e.printStackTrace();
                        }
                    }
                    if (outChannel != null) {
                        try {
                            outChannel.close();
                        } catch (IOException e) {
                            e.printStackTrace();
                        }
                    }
                    if (channel != null) {
                        try {
                            channel.close();
                        } catch (IOException e) {
                            e.printStackTrace();
                        }
                    }
                }
            }
        }

        /**
         * 测试阻塞式网络通信(shutdownOutput可关闭通道)
         */
        public class TestBlockingNIO2 {

            //客户端
            @Test
            public void client(){
                SocketChannel socketChannel = null;
                FileChannel inChannel = null;
                try {
                    //1.获取通道
                    socketChannel = SocketChannel.open(new InetSocketAddress("127.0.0.1", 9000));
                    inChannel = FileChannel.open(Paths.get("1.jpg"), StandardOpenOption.READ);
                    //2.分配指定大小的缓冲区
                    ByteBuffer buffer = ByteBuffer.allocate(1024);
                    //3.读取本地文件,并发送到服务器
                    while (inChannel.read(buffer)!=-1) {
                        buffer.flip();
                        socketChannel.write(buffer);
                        buffer.clear();
                    }

                    //关闭
                    socketChannel.shutdownOutput();

                    //接收服务端反馈
                    int len = 0;
                    while ((len = socketChannel.read(buffer)) != -1) {
                        buffer.flip();
                        System.out.println(new String(buffer.array(),0,len));
                        buffer.clear();
                    }
                } catch (Exception e) {
                    e.printStackTrace();
                } finally {
                    //4.关闭资源
                    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("2.jpg"), StandardOpenOption.WRITE,StandardOpenOption.CREATE);
                    //2.绑定连接
                    serverSocketChannel.bind(new InetSocketAddress(9000));
                    //3.获取客户端连接通道
                    socketChannel = serverSocketChannel.accept();
                    //4.分配指定大小的缓冲区
                    ByteBuffer buffer = ByteBuffer.allocate(1024);
                    //5.接收客服端数据,并保持到本地
                    while (socketChannel.read(buffer) != -1) {
                        buffer.flip();
                        outChannel.write(buffer);
                        buffer.clear();
                    }

                    //发送反馈客户端
                    buffer.put("服务端发送数据成功".getBytes());
                    buffer.flip();
                    socketChannel.write(buffer);
                } catch (Exception e) {
                    e.printStackTrace();
                } finally {
                    //6.关闭通道
                    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();
                        }
                    }
                }
            }
        }

非阻塞式:

        /**
         * 测试非阻塞式网络通信
         */
        public class TestNoBlockingNIO {

            //客户端
            @Test
            public void client() {
                //1.获取通道
                SocketChannel socketChannel = null;
                try {
                    socketChannel = SocketChannel.open(new InetSocketAddress("127.0.0.1", 9000));

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

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

                    //4.发送数据给服务端
                    Scanner scanner = new Scanner(System.in);
                    while (scanner.hasNext()) {
                        String str = scanner.next();
                        buffer.put((new Date().toString() + "\n" + str).getBytes());
                        buffer.flip();
                        socketChannel.write(buffer);
                        buffer.clear();
                    }

                } catch (Exception e) {
                    e.printStackTrace();
                } finally {
                    //5.关闭通道
                    if (socketChannel != null) {
                        try {
                            socketChannel.close();
                        } catch (IOException e) {
                            e.printStackTrace();
                        }
                    }
                }
            }

            //服务端
            @Test
            public void server() throws IOException{
                //1.获取通道
                ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();

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

                //3.绑定连接
                serverSocketChannel.bind(new InetSocketAddress(9000));

                //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 socketChannel = serverSocketChannel.accept();

                            //11.切换非阻塞模式
                            socketChannel.configureBlocking(false);
                            //12.将通道注册到选择器上
                            socketChannel.register(selector, SelectionKey.OP_READ);
                        } else if (selectionKey.isReadable()) {
                            //13.获取当前选择器上"读就绪"状态通道
                            SocketChannel socketChannel = (SocketChannel) selectionKey.channel();

                            //14.读取数据
                            ByteBuffer buffer = ByteBuffer.allocate(1024);

                            int len = 0;
                            while ((len = socketChannel.read(buffer)) > 0) {
                                buffer.flip();
                                System.out.println(new String(buffer.array(),0,len));
                                buffer.clear();
                            }
                        }

                        //15.取消选择键SelectionKey
                        iterator.remove();
                    }
                }
            }
        }

DatagramChannel

  • Java NIO中的DatagramChannel是一个能收发UDP包的通道。
  • 操作步骤:
    • 打开 DatagramChannel
    • 接收/发送数据
        /**
         * 测试非阻塞式网络通信
         */
        public class TestNoBlockingNIO2 {

            @Test
            public void send() {
                DatagramChannel channel = null;
                try {
                    channel = DatagramChannel.open();
                    //设置为非阻塞
                    channel.configureBlocking(false);
                    //设置缓冲区
                    ByteBuffer buffer = ByteBuffer.allocate(1024);

                    Scanner scanner = new Scanner(System.in);
                    while (scanner.hasNext()) {
                        String str = scanner.next();
                        buffer.put((new Date().toString() + "\n" +str).getBytes());
                        buffer.flip();
                        channel.send(buffer, new InetSocketAddress("127.0.0.1", 9000));
                        buffer.clear();
                    }
                } catch (Exception e) {
                    e.printStackTrace();
                } finally {
                    //关闭连接
                    if (channel != null) {
                        try {
                            channel.close();
                        } catch (IOException e) {
                            e.printStackTrace();
                        }
                    }
                }
            }

            @Test
            public void receive() {
                DatagramChannel channel = null;
                try {
                    channel = DatagramChannel.open();
                    //设置非阻塞
                    channel.configureBlocking(false);
                    //绑定地址
                    channel.bind(new InetSocketAddress(9000));
                    //选择器
                    Selector selector = Selector.open();
                    //注册选择器
                    channel.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 buffer = ByteBuffer.allocate(1024);
                                channel.receive(buffer);
                                buffer.flip();
                                System.out.println(new String(buffer.array(),0,buffer.limit()));
                                buffer.clear();
                            }
                        }
                        //取消选择键SelectionKey
                        iterator.remove();
                    }
                } catch (Exception e) {
                    e.printStackTrace();
                } finally {
                    //关闭连接
                    if (channel != null) {
                        try {
                            channel.close();
                        } catch (IOException e) {
                            e.printStackTrace();
                        }
                    }
                }
            }
        }

管道 (Pipe)

  • Java NIO 管道是2个线程之间的单向数据连接。Pipe有一个source通道和一个sink通道。数据会被写到sink通道,从source通道读取。
        /**
         * 测试管道
         */
        public class TestPipe {

            @Test
            public void test1() {
                SinkChannel sinkChannel = null;
                SourceChannel sourceChannel = null;
                try {
                    //1.获取管道
                    Pipe pipe = Pipe.open();

                    //2.将缓冲区中数据写入管道
                    ByteBuffer buffer = ByteBuffer.allocate(1024);

                    sinkChannel = pipe.sink();
                    buffer.put("通过单向管道发送数据".getBytes());
                    buffer.flip();
                    sinkChannel.write(buffer);

                    //3.读取缓冲区中的数据
                    sourceChannel = pipe.source();
                    buffer.flip();
                    int len = sourceChannel.read(buffer);
                    System.out.println(new String(buffer.array(),0,len));
                } catch (IOException e) {
                    e.printStackTrace();
                } finally {
                    //4.关闭连接
                    if (sinkChannel != null) {
                        try {
                            sinkChannel.close();
                        } catch (IOException e) {
                            e.printStackTrace();
                        }
                    }
                    if (sourceChannel != null) {
                        try {
                            sourceChannel.close();
                        } catch (IOException e) {
                            e.printStackTrace();
                        }
                    }
                }
            }
        }

NIO.2

随着 JDK 7 的发布,Java对NIO进行了极大的扩展,增强了对文件处理和文件系统特性的支持,以至于我们称他们为 NIO.2。因为 NIO 提供的一些功能,NIO已经成为文件处理中越来越重要的部分。

Path

  • java.nio.file.Path 接口代表一个平台无关的平台路径,描述了目录结构中文件的位置。
  • Path 常用方法:
    • boolean endsWith(String path) : 判断是否以 path 路径结束
    • boolean startsWith(String path) : 判断是否以 path 路径开始
    • boolean isAbsolute() : 判断是否是绝对路径
    • Path getFileName() : 返回与调用 Path 对象关联的文件名
    • Path getName(int idx) : 返回的指定索引位置 idx 的路径名称
    • int getNameCount() : 返回Path 根目录后面元素的数量
    • Path getParent() :返回Path对象包含整个路径,不包含 Path 对象指定的文件路径
    • Path getRoot() :返回调用 Path 对象的根路径
    • Path resolve(Path p) :将相对路径解析为绝对路径
    • Path toAbsolutePath() : 作为绝对路径返回调用 Path 对象
    • String toString() : 返回调用 Path 对象的字符串表示形式
  • Paths 提供的 get() 方法用来获取 Path 对象:
    • Path get(String first, String … more) : 用于将多个字符串串连成路径。

Files 类

  • java.nio.file.Files 用于操作文件或目录的工具类。
  • Files常用方法:
    • Path copy(Path src, Path dest, CopyOption … how) : 文件的复制
    • Path createDirectory(Path path, FileAttribute

自动资源管理

Java 7 增加了一个新特性,该特性提供了另外一种管理资源的方式,这种方式能自动关闭文件。这个特性有时被称为自动资源管理(Automatic Resource Management, ARM), 该特性以 try 语句的扩展版为基础。自动资源管理主要用于,当不再需要文件(或其他资源)时,可以防止无意中忘记释放它们。

当 try 代码块结束时,自动释放资源。因此不需要显示的调用 close() 方法。该形式也称为“带资源的 try 语句”。
1. try 语句中声明的资源被隐式声明为 final ,资源的作用局限于带资源的 try 语句
2. 可以在一条 try 语句中管理多个资源,每个资源以“;” 隔开即可。
3. 需要关闭的资源,必须实现了 AutoCloseable 接口或其自接口 Closeable

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值