NIO

简介: 所谓NIO,就是New IO的缩写。是从JDK 1.4开始引入的全新的IO API。NIO将以更高效的方式进行文件的读写操作,可完全代替传统的IO API使用。而且JDK 1.7对NIO又进行了更新,可以称作NIO 2.0。

一、NIO与IO的区别


区别主要如下:


IO NIO
面向流 面向缓冲区
阻塞IO 非阻塞IO
无选择器 有选择器


  • 传统的IO流,可以理解为水流,需要在文件系统与程序之间建立水管,然后数据就在这水管中流通,而且这流动是单向的。


  • NIO传输数据也需要在文件系统和程序之间建立通道,但是这个通道和水管不同。可以把这个通道理解为铁路,人不会直接在铁路上跑,数据也不会直接在这通道上传输。而是通过缓冲区将数据装起来,然后用缓冲区在这通道中传输数据。这个缓冲区就可以理解为火车,火车装了人,在铁路上跑,缓冲区装了数据,在通道上跑,而且这个过程是双向的。


二、通道和缓冲区


上面说了NIO是利用通道和缓冲区进行数据传输的,那下面就来了解一下。


image.png


1、缓冲区(Buffer):


一个用于特定基本数据类型的容器。由 java.nio 包定义的,所有缓冲区都是 Buffer 抽象类的子类。简单的说,缓冲区就是用于存储数据的。八种基本类型中,除了boolean没有对应的缓冲区,其他的都有,分别是:ByteBuffer、ShortBuffer、IntBuffer、LongBuffer、FloatBuffer、DoubleBuffer 、CharBuffer。


(1). 缓冲区的四个核心属性:


  • capacity:容量
  • limit:界限
  • position:当前位置
  • mark:标记


image.png


(2). 缓冲区的核心方法:


  • put():存入数据到缓冲区中
  • get():获取缓冲区中的数据
  • allocate:分配指定大小的缓冲区


看例子:

@Test
public void test1(){
        String str = "abcde";
        //1.分配一个指定大小的缓冲区
        ByteBuffer buffer = ByteBuffer.allocate(1024);
        System.out.println("==================allocate================");
        System.out.println(buffer.position());//0
        System.out.println(buffer.limit());//1024
        System.out.println(buffer.capacity());//1024
        //2.利用put()把数据存入缓冲区
        buffer.put(str.getBytes());
        System.out.println("===================put====================");
        System.out.println(buffer.position());//5
        System.out.println(buffer.limit());//1024
        System.out.println(buffer.capacity());//1024
        //3.切换到读数据模式
        buffer.flip();
        System.out.println("===================flip====================");
        System.out.println(buffer.position());//0
        System.out.println(buffer.limit());//5
        System.out.println(buffer.capacity());//1024
        //4.读取缓冲区的数据
        byte[] dst = new byte[buffer.limit()];
        buffer.get(dst);
        System.out.println(new String(dst,0,dst.length));//abcde
        System.out.println("===================get====================");
        System.out.println(buffer.position());//5
        System.out.println(buffer.limit());//5
        System.out.println(buffer.capacity());//1024
        //6.清空缓冲区(但是缓冲区中的数据依然存在,只不过里面的数据处于被遗忘状态)
        buffer.clear();
        System.out.println("===================clear====================");
        System.out.println(buffer.position());//0
        System.out.println(buffer.limit());//1024
        System.out.println(buffer.capacity());//1024
        System.out.println((char)buffer.get());//a
}


(3). 直接缓冲区和非直接缓冲区:


  • 非直接缓冲区:通过allocate()方法分配缓冲区,将缓冲区建立在JVM内存中。
  • 直接缓冲区:通过allocateDirect()方法分配直接缓冲区,将缓冲区建立在物理内存中。

@Test
public void test2(){
        //分配直接缓冲区
        ByteBuffer buffer = ByteBuffer.allocateDirect(1024);
        System.out.println(buffer.isDirect());//判断是否是直接缓冲区
}


2、通道(Channel):


Channel 表示 IO 源与目标打开的连接。 上面说了,通道就是铁路,缓冲区就是火车,所以这两个要搭配使用。


(1). Channel接口主要实现类:


  • FileChannel:用于读取、写入、映射和操作文件的通道。
  • DatagramChannel:通过 UDP 读写网络中的数据通道。
  • SocketChannel:通过 TCP 读写网络中的数据。
  • ServerSocketChannel:可以监听新进来的 TCP 连接,对每一个新进来的连接都会创建一个 SocketChannel。


(2). 获取通道:


  • 1.java对支持通道的类 (FileInputStream 、FileOutputStream 、RandomAccessFile 、DatagramSocket 、Socket 、ServerSocket) 提供了getChannel()方法获取。
  • 2.在jdk1.7中可以使用静态方法open()获取。
  • 3.使用Files工具类的newByteChannel()方法获取。


(3). 通道之间的数据传输:


  • transferTo()、transferForm():将数据从源通道传输到其他 Channel 中。


(4). 通道与缓冲区之间的数据传输:


  • inChannel.write(buffer):将缓冲区数据写入通道。
  • inChannel.read(buffer):将通道中的数据读到缓冲区。


看例子:利用通道完成文件的复制。


方式一:非直接缓冲区

@Test
public void test() throws IOException {
        FileInputStream fis = new FileInputStream("1.jpg");//读取项目中的1.jpg图片
        FileOutputStream fos = new FileOutputStream("2.jpg");//复制1.jpg,重命名为2.jpg
        //1.获取通道
        FileChannel inChannel = fis.getChannel();
        FileChannel outChannel = fos.getChannel();
        //2.分配缓冲区
        ByteBuffer buffer = ByteBuffer.allocate(1024);
        //3.将通道中的数据存入缓冲区
        while (inChannel.read(buffer) != -1){
            buffer.flip();//切换成读数据模式
            //4.将缓冲区中的数据写入通道中
            outChannel.write(buffer);
            buffer.clear();
        }
        outChannel.close();
        inChannel.close();
        fos.close();;
        fis.close();
}


上面代码使用通道和缓冲区进行了图片的复制,将1.jpg复制并重命名为2.jpg。


方式二:直接缓冲区

@Test
public void test3() throws IOException {
        FileChannel inChannel = FileChannel.open(Paths.get("1.jpg"),
                StandardOpenOption.READ);
        FileChannel outChannel = FileChannel.open(Paths.get("3.jpg"),
                StandardOpenOption.WRITE,StandardOpenOption.READ,StandardOpenOption.CREATE_NEW);
        //inChannel.transferTo(0,inChannel.size(),outChannel);
        outChannel.transferFrom(inChannel,0,inChannel.size());
        inChannel.close();
        outChannel.close();
}


3、分散读取与聚集写入:


  • 分散读取(Scattering Reads):将通道中的数据分散到多个缓冲区中去。
  • 聚集写入(Gathering Writes):将多个缓冲区中的数据聚集到通道中。


看例子:

//分散读取与聚集写入
    @Test
    public void test4() throws IOException {
        RandomAccessFile raf = new RandomAccessFile("1.jpg","rw");
        //1.获取通道
        FileChannel channel = raf.getChannel();
        //2.分配缓冲区
        ByteBuffer buffer1 = ByteBuffer.allocate(128);//缓冲区1
        ByteBuffer buffer2 = ByteBuffer.allocate(1024);//缓冲区2
        //3.分散读取
        ByteBuffer[] bufs = {buffer1,buffer2};//将两个缓冲区加到数组中
        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 raf1 = new RandomAccessFile("5.jpg","rw");
        FileChannel channel1 = raf1.getChannel();
        channel1.write(bufs);
    }


4、编码与解码:


  • 编码:将字符串转成字节数组。
  • 解码:将字节数组转成字符串。


看例子:

//字符集(GBK编GBK解,不会乱码,GBK编UTF8解就会乱码)
    @Test
    public void test5() throws IOException {
        Charset cs1 = Charset.forName("GBK");//指定编码
        CharsetEncoder ce = cs1.newEncoder();//获取编码器
        CharsetDecoder cd = cs1.newDecoder();//获取解码器
        CharBuffer cBuf = CharBuffer.allocate(1024);
        cBuf.put("花自飘零水自流");
        cBuf.flip();
        //编码
        ByteBuffer bBuf = ce.encode(cBuf);
        for (int i=0; i<14;i++){
            System.out.println(bBuf.get());
        }
        System.out.println("===========================");
        //解码
        bBuf.flip();
        CharBuffer cBuf2 = cd.decode(bBuf);
        System.out.println(cBuf2.toString());
    }


三、NIO的网络通信


1、阻塞和非阻塞:


  • 传统的 IO 流都是阻塞式的。也就是说,当一个线程调用 read() 或 write() 时,该线程被阻塞,直到有一些数据被读取或写入,该线程在此期间不 能执行其他任务。因此,性能很低。
  • Java NIO 是非阻塞模式的。当线程从某通道进行读写数据时,若没有数据可用时,该线程可以进行其他任务。因此,性能比传统IO更好。


2、NIO进行网络通信的三个核心:


  1. 通道(Channel):负责连接。
  2. 缓冲区(Buffer):负责数据的存取。
  3. 选择器(Selector):是 SelectableChannel 的多路复用器。用于监控 SelectableChannel 的 IO 状况。


3、使用NIO进行网络通信案例(阻塞式):

//客户端
    @Test
    public void client() throws IOException {
        //1.获取通道
        SocketChannel sChannel = SocketChannel.open(new InetSocketAddress("127.0.0.1", 9898));
        FileChannel inChannel = FileChannel.open(Paths.get("1.jpg"), StandardOpenOption.READ);
        //2.分配缓冲区
        ByteBuffer buf = ByteBuffer.allocate(1024);
        //3.读取本地文件,并发送到服务端
        while(inChannel.read(buf) != -1){
            buf.flip();
            sChannel.write(buf);
            buf.clear();
        }
        sChannel.shutdownOutput();
        //4.接收服务端的反馈
        int len = 0;
        while((len = sChannel.read(buf)) != -1){
            buf.flip();
            System.out.println(new String(buf.array(), 0, len));
            buf.clear();
        }
        //5.关闭通道
        inChannel.close();
        sChannel.close();
    }
    //服务端
    @Test
    public void server() throws IOException{
        //1.获取通道
        ServerSocketChannel ssChannel = ServerSocketChannel.open();
        FileChannel outChannel = FileChannel.open(Paths.get("6.jpg"), 
                StandardOpenOption.WRITE, 
                StandardOpenOption.CREATE);
        //2.绑定连接
        ssChannel.bind(new InetSocketAddress(9898));
        //3.获取客户端连接通道
        SocketChannel sChannel = ssChannel.accept();
        //4.分配指定大小的缓冲区
        ByteBuffer buf = ByteBuffer.allocate(1024);
        //5.接收客户端数据并保存到本地
        while(sChannel.read(buf) != -1){
            buf.flip();
            outChannel.write(buf);
            buf.clear();
        }
        //6.发送反馈给客户端
        buf.put("服务端接收数据成功".getBytes());
        buf.flip();
        sChannel.write(buf);
        //7.关闭通道
        sChannel.close();
        outChannel.close();
        ssChannel.close();
    }


4、使用NIO进行网络通信案例(非阻塞式):

//客户端
    @Test
    public void client() throws IOException {
        //1. 获取通道
        SocketChannel sChannel = SocketChannel.open(new InetSocketAddress("127.0.0.1", 9998));
        //2. 切换非阻塞模式
        sChannel.configureBlocking(false);
        //3. 分配指定大小的缓冲区
        ByteBuffer buf = ByteBuffer.allocate(1024);
        //4. 发送数据给服务端
        Scanner scan = new Scanner(System.in);
        while (scan.hasNext()) {
            String str = scan.next();
            buf.put((new Date().toString() + "\n" + str).getBytes());
            buf.flip();
            sChannel.write(buf);
            buf.clear();
        }
        //5. 关闭通道
        sChannel.close();
    }
    //服务端
    @Test
    public void server() throws IOException {
        //1. 获取通道
        ServerSocketChannel ssChannel = ServerSocketChannel.open();
        //2. 切换非阻塞模式
        ssChannel.configureBlocking(false);
        //3. 绑定连接
        ssChannel.bind(new InetSocketAddress(9998));
        //4. 获取选择器
        Selector selector = Selector.open();
        //5. 将通道注册到选择器上, 并且指定“监听接收事件”
        ssChannel.register(selector, SelectionKey.OP_ACCEPT);
        //6. 轮询式的获取选择器上已经“准备就绪”的事件
        while (selector.select() > 0) {
            //7. 获取当前选择器中所有注册的“选择键(已就绪的监听事件)”
            Iterator<SelectionKey> it = selector.selectedKeys().iterator();
            while (it.hasNext()) {
                //8. 获取准备“就绪”的是事件
                SelectionKey sk = it.next();
                //9. 判断具体是什么事件准备就绪
                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 buf = ByteBuffer.allocate(1024);
                    int len = 0;
                    while ((len = sChannel.read(buf)) > 0) {
                        buf.flip();
                        System.out.println(new String(buf.array(), 0, len));
                        buf.clear();
                    }
                }
                //15. 取消选择键 SelectionKey
                it.remove();
            }
        }
    }


非阻塞模式的服务端写法更麻烦一点,都有详细注释。


总结:


总的来说,NIO性能比传统IO要好很多,主要就是理解通道和缓冲区这两个概念。而阻塞和非阻塞主要是用于网络编程。




相关文章
|
5月前
|
网络协议 编译器 C语言
C语言深度解析:内存对齐与结构体填充的底层逻辑
C语言中,内存对齐是CPU硬件强制要求的底层规则,直接影响结构体大小、访问性能与硬件兼容性。合理排列成员可减少填充、节省内存;滥用`#pragma pack`则易致崩溃或性能暴跌。嵌入式、网络协议与跨平台开发必备核心知识。(239字)
572 14
|
7月前
|
API 数据安全/隐私保护 计算机视觉
用Python批量处理图片,5分钟搞定一天的工作
用Python批量处理图片,5分钟搞定一天的工作
571 128
|
5月前
|
传感器 机器人 Linux
嵌入式开发新手入门教程(第一卷)
教程来源 https://app-a6nw7st4g741.appmiaoda.com/ 嵌入式系统是嵌入设备内部的专用计算机系统,广泛应用于智能手表、汽车电控、医疗设备乃至火星车等。其由硬件(MCU、存储器、外设)和软件(裸机/RTOS/嵌入式Linux)构成。新手可按电路与C语言→单片机开发→RTOS与ARM深入三阶段学习,推荐STM32平台入门。
|
10月前
|
数据可视化 前端开发 JavaScript
解锁Github star 1600+ 的神秘工具,为什么Marsview 能让“后台系统开发”快到令人咋舌?
Marsview 是一款专注中后台系统的低代码平台,支持可视化拖拽、事件流配置、接口管理与数据联动,助力快速搭建 CRM、ERP 等管理系统,显著提升开发效率,降低重复开发成本。
293 5
|
存储 安全 算法
Snowflake在云数据平台中的创新实践
Snowflake在云数据平台中的创新实践
671 59
|
Java 对象存储 开发者
如何找出Java进程占用CPU高的元凶
本文记录了一次Java进程CPU占用率过高的问题和排查思路。
|
前端开发 JavaScript Java
屎上最全vue-pdf+Springboot与aspose-words整合,开箱即用
本文详细介绍了如何通过Spring Boot与Aspose Words整合实现Word模板的填充及转换为PDF,并在前端使用Vue和javadog-vue-pdf实现PDF预览与下载。主要内容包括:实现Spring Boot与Aspose Words的整合,完成Word模板填充并转换为PDF;前端Vue集成javadog-vue-pdf进行PDF预览及下载。文章提供了详细的步骤说明,包括下载依赖、配置代理、代码示例等,并展示了最终成果。
1753 7
屎上最全vue-pdf+Springboot与aspose-words整合,开箱即用
|
JavaScript Java 测试技术
基于SpringBoot+Vue的 在线文档管理系统附带文章和源代码
基于SpringBoot+Vue的 在线文档管理系统附带文章和源代码
495 1
|
存储 NoSQL 关系型数据库
客户说|长桥科技引入阿里云ClickHouse重构行情分析业务,性能提升10倍
客户说|长桥科技引入阿里云ClickHouse重构行情分析业务,性能提升10倍
935 0
|
SpringCloudAlibaba 监控 Java
SpringCloud Alibaba微服务-- Sentinel的使用(保姆级)
SpringCloud Alibaba微服务-- Sentinel的使用(保姆级)

热门文章

最新文章