Java NIO的学习笔记

本笔记记录了视频Java NIO 视频教程全集的学习笔记。

1. Java NIO 简介

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

2. Java NIO 与 IO 的主要区别

mark

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

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

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

缓冲区(Buffer)

  • 缓冲区(Buffer):一个用于特定基本数据类型的容器。由 java.nio 包定义的,所有缓冲区都是 Buffer 抽象类的子类。

  • Java NIO 中的 Buffer 主要用于与 NIO 通道进行交互,数据是从通道读入缓冲区,从缓冲区写入通道中的。

  • Buffer 就像一个数组,可以保存多个相同类型的数据。根据数据类型不同( boolean 除外),有以下 Buffer 常用子类:

    • ByteBuffer
    • CharBuffer
    • ShortBuffer
    • IntBuffer
    • LongBuffer
    • FloatBuffer
    • DoubleBuffer

    上述 Buffer 类 他们都采用相似的方法进行管理数据,只是鸽子管理的数据类型不同而已。都是通过 static XxxBuffer allocate(int capacity)获取一个 Buffer 对象

  • Buffer 中的重要概念

    • 容量 (capacity):表示 Buffer 最大数据容量,缓冲区容量不能为负,并且创建后不能更改。
    • 限制(limit):第一个不应该读取或写入的数据的索引,即位于 limit 后的数据不可读写。缓冲区的限制不能为负,并且不能大于其容量。
    • 位置(position):下一个要读取或写入的数据的索引。缓冲区的位置不能为负,并且不能大于其限制。
    • 标记(mark)与重置(reset):标记是一个索引,通过 Buffer 中的 mark() 方法指定 Buffer 中一个特定的 position,之后可以通过调用 rest()方法恢复到这个 position。
    • 0 <= mark <= position <= limit <= capacity

缓冲区的基本属性

mark

缓冲区的操作

直接上代码:

package top.db.nio.p2;


import java.nio.ByteBuffer;

/**
 */
public class ByteTest {


//  缓冲区 Buffer, Java NIO中负责数据的存取。用于存取不同数据类型
//        ByteBuffer
//        CharBuffer
//        ShortBuffer
//        IntBuffer
//        LongBuffer
//        FloatBuffer
//        DoubleBuffer
//    上述缓存区的管理方式几乎一致,通过allocate()获取缓冲区

//    二、缓冲区存取数据的两个核心方法
//    put():存入缓冲区数据
//    get():获取缓冲区中的数据
//
//    三、缓冲区的4个核心指标属性
//    capacity:容量,表示缓冲区中最大存储数据的容量,一旦声明不能改变
//     limit:界限,表示缓冲区中路操作数据的大小,(limit后数据不能读写)
//    position:位置,表示缓存区正在操作数据的位置
//
//
//    position <=limit <=capacity
//
//

    /**
     * 我们通过一个比较常用的ByteBuffer来熟悉一下
     * allocate(int capacity)
     * put(byte[] bytes)
     * flip()
     * get(byte[] bytes, int offset, int length)
     * 我们通过以下代码来观察以上四种方法调用的时候,
     * position
     * limit
     * capacity
     * 的变化
     *
     * @param args
     */
    public static void main(String[] args) {
        ByteBuffer buf = ByteBuffer.allocate(1024);
        System.out.println("-------------allocate-----------------");
        System.out.println("capacity:" + buf.capacity());
        System.out.println("limit:" + buf.limit());
        System.out.println("position:" + buf.position());

        String s = "hello";
        buf.put(s.getBytes());
        System.out.println("-------------put-----------------");
        System.out.println("capacity:" + buf.capacity());
        System.out.println("limit:" + buf.limit());
        System.out.println("position:" + buf.position());

        buf.flip();
        System.out.println("-------------flip-----------------");
        System.out.println("capacity:" + buf.capacity());
        System.out.println("limit:" + buf.limit());
        System.out.println("position:" + buf.position());



        byte[] bytes = new byte[1024];
        buf.get(bytes, 0, buf.limit());
        System.out.println("-------------get-----------------");
        System.out.println("capacity:" + buf.capacity());
        System.out.println("limit:" + buf.limit());
        System.out.println("position:" + buf.position());
        System.out.println(new String(bytes, 0, buf.limit()));
    }

}

直接与非直接缓冲区

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

mark

mark

通道

通道(Channel):由 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() 打开并返回指定通道。

通道的代码如下:

package top.db.nio.p3;

import lombok.extern.slf4j.Slf4j;
import org.springframework.util.ResourceUtils;

import java.io.*;
import java.nio.ByteBuffer;
import java.nio.CharBuffer;
import java.nio.MappedByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.charset.CharacterCodingException;
import java.nio.charset.Charset;
import java.nio.charset.CharsetDecoder;
import java.nio.charset.CharsetEncoder;
import java.nio.file.Paths;
import java.nio.file.StandardOpenOption;

/**
 * 测试
 *
 */
@Slf4j
public class ChannelTest {

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

 二、通道的主要实现类
    java.nio.channels.Channel 接口     since 1.4
    java.nio.channels.ByteChannel

    java.nio.channels.FileChannel
                             |--SocketChannel
                             |--ServerSocketChannel
                             |--DatagramChannel

 三、获取通道
    1.Java针对通道的类提供了getChannel()方法
    本地IO:
    FileInputStream、FileOutStream
    RandomAccessFile

    网络IO:
    Socket
    ServerSocket
    DatagramSocket

    2.在JDK1.7==NIO2 中 静态方法: open()
    3.在JDK1.7==NIO2 中 Files工具类中newByteChannel()

四、通道之间传输数据
    transferTo()
    transferFrom()

五、分散(Scatter)与聚集(Gather)
    分散读取: 将通道中大数据分散到多个缓冲区中
    聚集写入:将多个缓冲区中的数据聚集到通道中
    注意:按照缓冲区的顺序,从channel中读取依次将buffer填满

六、字符集:CharSet
    编码:字符串-->字节数组
    解码:字节数组-->字符串

*/
    public static void main(String[] args) throws IOException {

//        new ChannelTest().test1();
//        new ChannelTest().test2();
//        new ChannelTest().test3();
//        new ChannelTest().test4();
//        new ChannelTest().test5();

    }

    /**
     * 字符集
     *
     * @throws CharacterCodingException
     */
    private void test5() throws CharacterCodingException {
//        下面三行代码用来查看NIO包下的Charset都有什么字符集
//        SortedMap<String, Charset> map = Charset.availableCharsets();
//        for (Map.Entry<String, Charset> entry : map.entrySet()) {
//            System.out.println(entry.getKey() + "--" + entry.getValue());
//        }

        Charset gbkCharset = Charset.forName("GBK");

        //获取编码器
        CharsetEncoder charsetEncoder = gbkCharset.newEncoder();

        //获取解码器
        CharsetDecoder charsetDecoder = gbkCharset.newDecoder();

        // 分配缓冲空间
        CharBuffer cBuffer = CharBuffer.allocate(1024);

        String msg = "测试测试1234";
        // 将数据写入缓冲区
        cBuffer.put(msg);
        // 转换成写模式
        cBuffer.flip();

        // 编码,使用GBK编码器对流进行编码
        ByteBuffer bBuffer = charsetEncoder.encode(cBuffer);

        // 可以获得编码后的结果
        for (int i = 0; i < msg.length(); i++) {
            // 这里的原理是,每get一次,position都会移动
            System.out.println(bBuffer.get());
        }


        // 解码,转换成写模式
        bBuffer.flip();
        // 使用GBK解码
        CharBuffer decodeCharBuffer = charsetDecoder.decode(bBuffer);
        System.out.println(decodeCharBuffer.toString());
        System.out.println("__________________________________");

        // 使用UTF-8进行解码
        Charset charset2 = Charset.forName("UTF-8");
        bBuffer.flip();
        CharBuffer decode2 = charset2.decode(bBuffer);
        // 编码解码使用的字符集不同,必然是乱码的结果
        System.out.println(decode2.toString());

    }


    /**
     * 分散(Scatter)与聚集(Gather)
     * 老师在这里用到了一个类 RandomAccessFile, 这里用这个类我感觉不太方便我们理解
     * 我这边将 RandomAccessFile 改成我们学习阶段比较舒徐的流的方式,方便大家理解 Scatter 与 Gather
     * @throws IOException
     */
    private void test4() throws IOException {
        File file = ResourceUtils.getFile("222.jpg");

        try (   // 1.获取通道
                FileInputStream fileInputStream = new FileInputStream(file);
                FileOutputStream fileOutputStream = new FileOutputStream("333.jpg");
                FileChannel inChannel = fileInputStream.getChannel();
                FileChannel outChannel = fileOutputStream.getChannel()
        ) {
            // 2.分配指定大小的缓冲区
            ByteBuffer buffer1 = ByteBuffer.allocate(1024);
            ByteBuffer buffer2 = ByteBuffer.allocate(4028000);

            // 3.分散获取
            ByteBuffer[] buffers = {buffer1, buffer2};
            long read = inChannel.read(buffers);
            System.out.println(read);

            for (ByteBuffer buffer : buffers) {
                buffer.flip();
            }
            // 4.聚集写入
            System.out.println(outChannel.write(buffers));
            for (ByteBuffer buffer : buffers) {
                buffer.clear();
            }
        }
    }

    /**
     * transferTo
     * transferFrom
     */
    private void test3() {

        try (// 开启通道
                FileChannel inChannel = FileChannel.open(Paths.get("222.jpg"), StandardOpenOption.READ);
                FileChannel outChannel = FileChannel.open(Paths.get("333.jpg"), StandardOpenOption.READ, StandardOpenOption.WRITE, StandardOpenOption.CREATE);
                ) {
//            // 使用transferTo
//            inChannel.transferTo(0, inChannel.size(), outChannel);
            // 使用transferFrom
            outChannel.transferFrom(inChannel, 0, inChannel.size());
        } catch (IOException e) {
            e.printStackTrace();
        }

    }

    /**
     * 2.利用通道完成文件复制(直接缓冲区--内存映射)
     *   通过FileChannel类的open()方法直接获取文件通道
     *   声明内存映射(直接缓冲)
     *   文件传输
     *
     */
    private void test2() {

        try ( // 1.创建通道
             FileChannel inChannel = FileChannel.open(Paths.get("222.jpg"), StandardOpenOption.READ);
             FileChannel outChannel = FileChannel.open(Paths.get("333.jpg"), StandardOpenOption.READ, StandardOpenOption.WRITE, StandardOpenOption.CREATE)
        ) {
            // 2.声明内存映射 ByteBuffer buffer = ByteBuffer.allocateDirect(1024);
            MappedByteBuffer inMappedByteBuffer = inChannel.map(FileChannel.MapMode.READ_ONLY, 0, inChannel.size());
            MappedByteBuffer outMappedByteBuffer = outChannel.map(FileChannel.MapMode.READ_WRITE, 0, inChannel.size());

            // 3.文件传输 --  直接对缓冲区数据进行读写操作
            byte[] dest = new byte[inMappedByteBuffer.limit()];
            inMappedByteBuffer.get(dest);
            outMappedByteBuffer.put(dest);
        } catch (IOException e) {
            e.printStackTrace();
        }

    }


    /**
     * 本机内文件移动
     * 1、利用通道完成文件复制(非直接缓冲区--JVM 堆内存)
     * 通过FileInputStream获取系统中的文件,将其转为流
     * 分配缓冲区,使用ByteBuffer.allocate(1024)
     * 通过FileInputStream获取通道,使用通道来读取文件
     * 边读边写,ByteBuffer既可以写入数据,也可以读取数据
     * 因此直接使用FileOutputStream获取输出通道
     * 通过输出通道写出数据即可
     *
     * @throws FileNotFoundException
     */
    public void test1() throws FileNotFoundException {

        File file = ResourceUtils.getFile("D:/壁纸/看电脑的女生.jpg");
        String target = "222.jpg";
        try ( // 获取流
             FileInputStream fileInputStream = new FileInputStream(file);
             FileOutputStream fileOutputStream = new FileOutputStream(target);
             // 获取通道
             FileChannel fileInputChannel = fileInputStream.getChannel();
             FileChannel fileOutputStreamChannel = fileOutputStream.getChannel()) {

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

            // 将通道中的数据存入缓冲区
            // 实际上就是,缓冲区每读满一次,写入一次,直到读完所有数据
            while (fileInputChannel.read(buffer) != -1) {
                // 通过虾米那四行代码可以清晰的看到拷贝的数据流动
                System.out.println("------------------------------");
                System.out.println("capacity:" + buffer.capacity());
                System.out.println("limit:" + buffer.limit());
                System.out.println("position:" + buffer.position());
                // 将缓冲区(buffer)切换成读数据模式
                buffer.flip();
                // 将缓冲区(buffer)的数据写入通道
                fileOutputStreamChannel.write(buffer);
                // 清空缓冲区
                buffer.clear();
            }
        } catch (IOException e) {
            log.error("IOException occupy", e);
        }
    }

}

4. NIO 的非阻塞式网络通信

mark

直接上代码:

阻塞网络通信

package top.db.nio.p9;

import lombok.extern.slf4j.Slf4j;
import org.junit.Test;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.nio.file.Paths;
import java.nio.file.StandardOpenOption;

@Slf4j
public class ServerSocketChannelTest {

//客户端数据发送一个读写请求到服务端
//    服务无端不能确定客户端判断数据真实有效状态时候,该线程会一直处于阻塞状态,服务端会等客户端发送
//
//    服务端会判断数据在内核地址空间中是否存在,此时服务端线程阻塞,原来是多线程来解决,为每一个请求分配一个线程来处理请求,充分利用cpu资源,
//    后续请求,线程没有100%的利用,
//
//    NIIO--非阻塞式: 通道加缓冲区,
//    把每一个用于传输数据的通道注册到选择器,选择器实时监控通道上的状况,
//    当某一个通道上的某一个请求的事件完全准备就绪时,选择器才会将这个任务分配到服务端的一个或多个线程上在去运行
//
//
//
//
//    读数据状态--完全准备就绪
//    选择器:Selector
//
//===============================
//    一、使用NIO完成网络通信的三个核心
//1、通道(Channel::负责连接
//    java.nio.channels.Channel
//                                  |--SelectableChannel
//                                  |--ServerSocketChannel
//                                  |--DatagramChannel
//
//                                  |--Pipe.SinkChannel
//                                  |--Pipe.SourceChannel
//
//    FileChannel不能切换成非阻塞模式
//
//
//2、缓冲区(Buffer):负责数据的存取
//
//3、选择器(Selector):是SelectableChannel的多路复涌去。用于监控SelectableChannel的IO情况
//
//

    public static void main(String[] args) throws InterruptedException {
        long s1 = System.currentTimeMillis();
        Runnable server = new Runnable() {
            @Override
            public void run() {
                new ServerSocketChannelTest().server();
            }
        };

        Runnable client = new Runnable() {
            @Override
            public void run() {
                new ServerSocketChannelTest().client();
            }
        };

        //todo 为何两个线程启动 不是串行的
        server.run();
        Thread.sleep(100L);
        client.run();

        System.out.println("END=" + (System.currentTimeMillis() - s1));
    }


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

            //测试中是发送一个图片给server,这里用FileChannel去获取
            inChannel = FileChannel.open(Paths.get("T:/data/nio/1.jpg"), StandardOpenOption.READ);

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

            //3、发送数据

            //long transferTo = inChannel.transferTo(0, inChannel.size(), socketChannel);

            while (inChannel.read(buffer) != -1) {
                buffer.flip();
                socketChannel.write(buffer);
                buffer.clear();
            }
        } catch (IOException e) {
            e.printStackTrace();
            log.error("结果={}", e);
        } finally {
            if (inChannel != null) {
                try {
                    inChannel.close();
                } catch (IOException e) {
                    e.printStackTrace();
                    log.error("结果={}", e);
                }
            }

            if (socketChannel != null) {
                try {
                    socketChannel.close();
                } catch (IOException e) {
                    e.printStackTrace();
                    log.error("结果={}", e);
                }
            }
        }
    }

    //服务器--阻塞式
    @Test
    public void server() {
        ServerSocketChannel ssChannel = null;
        SocketChannel acceptSocketChannel = null;
        FileChannel outChannel = null;
        try {
            //1、获取通道
            ssChannel = ServerSocketChannel.open();

            outChannel = FileChannel.open(Paths.get("T:/data/nio/_21.jpg"), StandardOpenOption.READ, StandardOpenOption.WRITE, StandardOpenOption.CREATE);


            //2、绑定连接
            ssChannel.bind(new InetSocketAddress(9898));

            //3、获取客户端连接的通道
            acceptSocketChannel = ssChannel.accept();


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


            //5、接收客户端的数据,并保存到本地
            while (acceptSocketChannel.read(buffer) != -1) {
                buffer.flip();
                outChannel.write(buffer);
                buffer.clear();
            }

        } catch (IOException e) {
            e.printStackTrace();
            log.error("结果={}", e);
        } finally {

            if (ssChannel != null) {
                try {
                    ssChannel.close();
                } catch (IOException e) {
                    e.printStackTrace();
                    log.error("结果={}", e);
                }
            }
            //acceptSocketChannel
            if (acceptSocketChannel != null) {
                try {
                    acceptSocketChannel.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            //outChannel
            if (outChannel != null) {
                try {
                    outChannel.close();
                } catch (IOException e) {
                    e.printStackTrace();
                    log.error("结果={}", e);
                }
            }
        }
    }


}

非阻塞通信1

package top.db.nio.p10;

import lombok.extern.slf4j.Slf4j;
import org.junit.Test;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.*;
import java.time.LocalDateTime;
import java.util.Iterator;
import java.util.Scanner;


@Slf4j
public class NoBlockingNioTest {
//    读数据状态--完全准备就绪
//    选择器:Selector
//
//===============================
//    一、使用NIO完成网络通信的三个核心
//1、通道(Channel::负责连接
//    java.nio.channels.Channel
//                                  |--SelectableChannel
//                                  |--ServerSocketChannel
//                                  |--DatagramChannel
//
//                                  |--Pipe.SinkChannel
//                                  |--Pipe.SourceChannel
//
//    FileChannel不能切换成非阻塞模式
//
//
//2、缓冲区(Buffer):负责数据的存取
//
//3、选择器(Selector):是SelectableChannel的多路复涌去。用于监控SelectableChannel的IO情况
//
//
//


    public static void main(String[] args) {
        Scanner input = new Scanner(System.in);
        System.out.println("请输入一个字符串(中间能加空格或符号)");
        String line = input.nextLine();
        String s = LocalDateTime.now().toString() + "\n" + line;
        System.out.println(s);
    }


    //客户端
    @Test
    public void client() {
        SocketChannel socketChannel = null;
        FileChannel inChannel = null;
        try {

            //1、获取通道
            socketChannel = SocketChannel.open(new InetSocketAddress("127.0.0.1", 9898));

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

            //3、分配缓冲区
            ByteBuffer buffer = ByteBuffer.allocate(1024);

            //测试中是发送一个 时间字符串给server
            //
            String msg = getMsg();
            buffer.put(msg.getBytes());
            buffer.flip();

            //4、发送数据
            socketChannel.write(buffer);
            buffer.clear();


        } catch (IOException e) {
            e.printStackTrace();
            log.error("结果={}", e);
        } finally {
            if (inChannel != null) {
                try {
                    inChannel.close();
                } catch (IOException e) {
                    e.printStackTrace();
                    log.error("结果={}", e);
                }
            }

            if (socketChannel != null) {
                try {
                    socketChannel.close();
                } catch (IOException e) {
                    e.printStackTrace();
                    log.error("结果={}", e);
                }
            }
        }
    }

    private String getMsg() {
//        Scanner scanner = new Scanner(System.in);
//        System.out.println("请输入:");
//        while (scanner.hasNext()) {
//            String msg = scanner.next();
//        }
//        return "null";

//        Scanner input = new Scanner(System.in);
//        System.out.println("请输入一个字符串(中间能加空格或符号)");
//        String line = input.nextLine();
//        String s = LocalDateTime.now().toString() + "\n" + line;
//        System.out.println(s);
        // scanner 在junit中不能正常工作
        return "1111111";

    }

//        System.out.println("请输入一个字符串(中间不能加空格或符号)");
//        String b = input.next();
//        System.out.println("请输入一个整数");
//        int c;
//        c = input.nextInt();
//        System.out.println("请输入一个double类型的小数");
//        double d = input.nextDouble();
//        System.out.println("请输入一个float类型的小数");
//        float f = input.nextFloat();
//        System.out.println("按顺序输出abcdf的值:");
//        System.out.println(a);
//        System.out.println(b);
//        System.out.println(c);
//        System.out.println(d);
//        System.out.println(f);


    //服务器--阻塞式
    @Test
    public void server() {
        ServerSocketChannel ssChannel = null;
        SocketChannel acceptSocketChannel = null;
        FileChannel outChannel = null;
        try {
            //1、获取通道
            ssChannel = ServerSocketChannel.open();
            //2、设置非阻塞
            ssChannel.configureBlocking(false);
            //3、绑定连接
            ssChannel.bind(new InetSocketAddress(9898));
            //4、获取选择器
            Selector selector = Selector.open();

            //5、将通道注册到选择器,并且指定“监听事件”  ,对比之前的 accept方法=阻塞
            ssChannel.register(selector, SelectionKey.OP_ACCEPT);

            //6、轮询式的获取选择器上“已经准备就绪”的事件
            while (selector.select() > 0) {  //至少有一个准备就绪的事件

                //7、包括所有注册的“选择键(已就绪的监听事件)”
                Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();

                //8、获取准备就绪的事件
                while (iterator.hasNext()) {
                    //9、判断具体是什么准备就绪事件
                    SelectionKey sk = iterator.next();
                    if (sk.isAcceptable()) {
                        //10、若"接受就绪",获取客户端连接
                        SocketChannel sChannel = ssChannel.accept();
                        //11、设置非阻塞模式
                        sChannel.configureBlocking(false);
                        //12、将该通道注册到选择器上
                        sChannel.register(selector, SelectionKey.OP_READ);
                    } else if (sk.isReadable()) {
                        //13、获取当前选择器上“读就绪”状态的通道
                        SocketChannel sChannel = (SocketChannel) sk.channel();
                        //14、读取数据
                        ByteBuffer buffer = ByteBuffer.allocate(1024);
                        int len = 0;
                        while ((len = sChannel.read(buffer)) > 0) {
                            buffer.flip();
                            System.out.println(new String(buffer.array(), 0, len));
                            buffer.clear();
                        }
                    }
                    //15、取消选择键
                    iterator.remove();
                }

            }

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


            //5、接收客户端的数据,并保存到本地
            while (acceptSocketChannel.read(buffer) != -1) {
                buffer.flip();
                outChannel.write(buffer);
                buffer.clear();
            }

        } catch (IOException e) {
            e.printStackTrace();
            log.error("结果={}", e);
        } finally {

            if (ssChannel != null) {
                try {
                    ssChannel.close();
                } catch (IOException e) {
                    e.printStackTrace();
                    log.error("结果={}", e);
                }
            }
            //acceptSocketChannel
            if (acceptSocketChannel != null) {
                try {
                    acceptSocketChannel.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            //outChannel
            if (outChannel != null) {
                try {
                    outChannel.close();
                } catch (IOException e) {
                    e.printStackTrace();
                    log.error("结果={}", e);
                }
            }
        }
    }
}

非阻塞通信2

package top.db.nio.p11;

import lombok.extern.slf4j.Slf4j;
import org.junit.Test;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.DatagramChannel;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.util.Iterator;


@Slf4j
public class NoBlockingNio2Test {


    //发送端
    @Test
    public void send() {
        DatagramChannel datagramChannel = null;
        try {
             datagramChannel = DatagramChannel.open();
            datagramChannel.configureBlocking(false);
            ByteBuffer buffer = ByteBuffer.allocate(1024);
            buffer.put("测试1234".getBytes());
            buffer.flip();
            datagramChannel.send(buffer, new InetSocketAddress("127.0.0.1", 8080));
            buffer.clear();

        } catch (IOException e) {
            e.printStackTrace();
        }finally {
            if (datagramChannel != null) {
                try {
                    datagramChannel.close();
                    System.out.println("发送完成关闭 DatagramChannel!");
                } catch (IOException e) {
                    e.printStackTrace();
                    log.error("e={}", e);
                }

            }
        }
    }


    //接受端
    @Test
    public void serverDatagramChannel() {
        DatagramChannel serverDatagramChannel = null;
        try {
            serverDatagramChannel = DatagramChannel.open();
            serverDatagramChannel.configureBlocking(false);
            serverDatagramChannel.bind(new InetSocketAddress(8080));

            Selector selector = Selector.open();
            serverDatagramChannel.register(selector, SelectionKey.OP_READ);

            while (selector.select() > 0) {
                Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();
                while (iterator.hasNext()) {
                    SelectionKey sk = iterator.next();
                    if (sk.isReadable()) {
                        ByteBuffer buffer = ByteBuffer.allocate(1024);
                        serverDatagramChannel.receive(buffer);
                        buffer.flip();
                        System.out.println(new String(buffer.array(), 0, buffer.limit()));
                        buffer.clear();
                    }
                }
                iterator.remove();
            }
        } catch (IOException e) {
            e.printStackTrace();
        }finally {
            if (serverDatagramChannel != null) {
                try {
                    serverDatagramChannel.close();
                } catch (IOException e) {
                    e.printStackTrace();
                    log.error("e={}", e);
                }

            }
        }


    }

}

5. 管道(Pipe)

管道

package top.db.nio.p12;

import org.junit.Test;

import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.Pipe;


public class PipeTest {
/*

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

*/

    @Test
    public void test1() throws IOException {

        //1、获取管道
        Pipe pipe = Pipe.open();

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

        Pipe.SinkChannel sinkChannel = pipe.sink();

        buffer.put("    数据会被写到sink通道".getBytes());
        buffer.flip();
        sinkChannel.write(buffer);

        //3、数据读取到缓冲区
        Pipe.SourceChannel sourceChannel = pipe.source();
        buffer.flip();
        int len = sourceChannel.read(buffer);
        System.out.println(new String (buffer.array(),0,len));

        sinkChannel.close();
        sourceChannel.close();
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值