【Java网络编程】基于BIO/NIO/AIO的多人聊天室(三):NIO概述与实践

课程《一站式学习Java网络编程 全面理解BIO/NIO/AIO》的学习笔记(三):
NIO概念 & 使用NIO进行文件拷贝

源码地址:https://github.com/NoxWang/web-program

【Java网络编程】基于BIO/NIO/AIO的多人聊天室(一):java IO与内核IO
【Java网络编程】基于BIO/NIO/AIO的多人聊天室(二):BIO聊天室
【Java网络编程】基于BIO/NIO/AIO的多人聊天室(四):NIO聊天室
【Java网络编程】基于BIO/NIO/AIO的多人聊天室(五):AIO聊天室
【Java网络编程】基于BIO/NIO/AIO的多人聊天室(六):思维导图

一、NIO概述

NIO(Non-blocking IO 或 New IO),非阻塞式IO。与BIO不同,NIO使用Channel代替Stream,特征如下:

  • Stream具有方向性,分为输入流和输出流,而Channel无方向,一个Channel既可以写入数据也可以读取数据。
  • Stream的读写均为阻塞式(例如InputStream.read(),OutputStream.write()),而Channnel的读写具有两种模式,既可以阻塞式读写,也提供了非阻塞式读写的方法
  • NIO使用Selector监控多条Channel
  • 可以在一个线程里处理多个Channel I/O

1.1 Buffer

NIO向Channel中读写数据都需要通过一个Buffer类来实现。

Buffer,顾名思义,是一个缓冲区,代表内存中我们可以进行读写的一个区域。Channel可以支持双向操作,因此Buffer也可进行读写双向操作。

1.1.1 向Buffer中写入数据

写模式主要用到两个指针:

  • position:当前写入的位置
  • capacity:最远可写入位置(缓冲区最大容量)

写模式

1.1.2 由写模式转换为读模式

调用flip()方法实现从写模式到读模式的转换,步骤如下:

  1. position指针移至缓冲区头部
  2. limit指针移至写入的最远位置,limit指针表示的是读模式下最远所能读取的位置

flip

1.1.3 读模式①

第一种读取模式是将写入的数据全部读取完,读取完后position指针将和limit指针指向相同的位置

调用clear()方法转换为写模式,步骤如下:

  1. 将position指针移回头部
  2. 将limit指针移回capacity处

从图中可以看出,clear()方法其实并没有清除已读取的数据,而是通过指针操作,使得再次写入的数据覆盖之前的数据。

读模式1

1.1.4 读模式②

第二种读取模式是只读取部分已写入的数据,读取完后position指针的位置在limit指针之上。

此时若想转换为写模式,需要调用compact()方法,步骤如下:

  1. 将已写入但未读取的数据复制到缓冲区头部
  2. 将position指针移至未读取数据的下面一个区域
  3. 将limit指针移回capacity处

读模式2

1.2 Channel

1.2.1 Channel的基本操作

  1. 通过Buffer写入/读取数据
  2. Channel之间可以直接进行数据传输:每个Channel可以向其他Channel数据传输数据,也可以接受从其他Channel传输过来的数据

1.2.2 几个重要的Channel

常见Channel

1.3 Selector

Channel可以进行非阻塞式的读写,而并不是每个时刻Channel上都有数据可供读写,所以我们需要不停地询问Channel是否处于可操作的状态。Jahannlelva提供了一个Selector类来实现这个功能,该类负责监听各个Channel的状态

Selector也称为 I/O多路复用器

1.3.1 Channel的状态

Channel的状态并不是固定不变的,Channel的状态会随着不同事件的发生而在如下状态间发生变化:

  • CONNECT:客户端与服务端建立连接后,客户端的SocketChannel会处于CONNECT状态
  • ACCEPT:服务端接受了客户端的连接建立请求后,服务端的ServerSocketChannel会处于ACCEPT状态
  • READ:Channel上有了可读取信息后
  • WRITE:可以向Channel写入数据的状态

1.3.2 Channel的注册

要想使用Selector监听Channel,我们首先需要将该Channel注册到Selector上。

注册后,我们会得到一个SelectionKey对象,该对象可理解为每一个注册在Selector上的Channel的ID。通过SelectionKey,我们可以得到如下信息:

  • interestOps():得到一组Selector需要监听的该Channel的状态
  • readyOps():在需要监听的状态中,哪些是处于准备好、可操作的
  • channel():返回监听的Channel对象
  • selector():返回被注册的Selector
  • attachment():根据具体业务需求,可以是需要加载在channel上的任意对象

二、使用NIO进行文件拷贝

使用FileChannel实现本地文件拷贝。如上所述,有通过Buffer传输和Channel之间直接传输两种方式

2.1 通过Buffer进行数据传输

几个需要注意的点:

  • 通过文件输入输出流获得文件通道:
    FileChannel fin = new FileInputStream(source).getChannel();

  • 注意Channel和Buffer的方向:将数据从文件Channel中读取出来fin.read(buffer)),写入缓冲区(缓冲区为写模式);将缓冲区转换为读模式(buffer.flip());将数据从缓冲区中读出(缓冲区为读模式),写入文件Channelfout.write(buffer));将缓冲区转换为写模式(buffer.clear()

import java.io.*;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;

public class NioBufferCopy {

    private static void close(Closeable closeable) {
        if (closeable != null) {
            try {
                closeable.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    public static void copyFile(File source, File target) {
        // 声明文件通道
        FileChannel fin = null;
        FileChannel fout = null;

        try {
            // 通过文件输入输出流得到文件通道
            fin = new FileInputStream(source).getChannel();
            fout = new FileOutputStream(target).getChannel();

            // 创建ByteBuffer类型的缓冲区(按字节读取)
            ByteBuffer buffer = ByteBuffer.allocate(1024);
            // 将数据从文件通道中读取出来,写进Buffer
            while (fin.read(buffer) != -1) {
                // 将Buffer从写模式转换为读模式
                buffer.flip();
                while (buffer.hasRemaining()) { // 确保Buffer中的内容被读完
                    // 将Buffer中的数据写入文件通道
                    fout.write(buffer);
                }
                // 将Buffer从读模式转换为写模式
                buffer.clear();
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            close(fin);
            close(fout);
        }

    }

    public static void main(String[] args) {

        File file = new File("E:/JavaProject/web/nio-file-copy/tmp/smallFile.jpg");
        File fileCopy = new File("E:/JavaProject/web/nio-file-copy/tmp/smallFile-copy.jpg");

        System.out.println("--- Copying small file ---");
        copyFile(file, fileCopy);
    }
}

2.2 直接在Channel间进行数据传输

  • 调用通道的transferTo()方法实现Channel间的数据传输
  • write()方法一样,transferTo()不能保证拷贝通道中的所有数据,因此需要使用一个while循环,确保文件被完整拷贝
import java.io.*;
import java.nio.channels.FileChannel;

public class NioTransferCopy {

    private static void close(Closeable closeable) {
        if (closeable != null) {
            try {
                closeable.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    public static void copyFile(File source, File target) {
        // 声明文件通道
        FileChannel fin = null;
        FileChannel fout = null;

        try {
            // 通过输入输出流创建通道
            fin = new FileInputStream(source).getChannel();
            fout = new FileOutputStream(target).getChannel();

            long transferred = 0L;
            long size = fin.size();
            while (transferred != size) {
                // 从位置0开始,拷贝fin通道中size长度的数据,至fout通道,返回的是已拷贝长度
                // transferTo不能保证拷贝通道中的所有数据,因此使用while循环
                transferred += fin.transferTo(0, size, fout);
            }

        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            close(fin);
            close(fout);
        }
    }

    public static void main(String[] args) {

        File file = new File("E:/JavaProject/web/nio-file-copy/tmp/smallFile.jpg");
        File fileCopy = new File("E:/JavaProject/web/nio-file-copy/tmp/smallFile-copy.jpg");

        System.out.println("--- Copying small file ---");
        copyFile(file, fileCopy);
    }
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值