Java NIO

本文详细介绍了Java NIO(New IO)的基础知识,包括NIO与传统IO的区别、缓冲区(Buffer)和通道(Channel)的概念及其交互方式。通过实例展示了Buffer的常用方法,如allocate、flip、rewind和clear等,以及直接缓冲区和非直接缓冲区的差异。此外,还讲解了文件通道(FileChannel)的使用,包括数据传输和文件复制。最后,讨论了分散读取和聚集写入的概念,并给出了代码示例。文章还提到了NIO的非阻塞特性及其在实际应用中的价值。

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

写在前面:

在学习完IO后,又深入学习了NIO,这篇笔记花了笔者挺长时间,文章较长,可根据目录各区所需,如果觉得写的不错,点个赞吧,如果有什么问题,请不吝赐教,多多交流学习

Java NIO

1.Java NIO简介

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

2.JavaNIO与IO的主要区别

IONIO
面向流(Stream Oriented)面向缓冲区(Buffer Oriented)
阻塞IO(Blocking IO)非阻塞IO(Non Blocking IO)
选择器(Selector)

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

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

那什么是缓冲区呢?
缓冲区底层就是数组,用于存储不同类型的数据,根据数据类型,根据数据类型的不同(除Boolean外),都提供了对应的缓冲区类型:ByteBuffer,CharBuffer,shortBuffer,FloatBuffer等等,但管理方式都相同,通过allocate()进行操作,会在下文中详解其各种方法

缓冲区和通道是什么关系呢?

Java NIO中的Buffer主要用于与NIO通道进行交互,数据是从通道读入缓冲区,从缓冲区写入到通道中的,,通道负责存储数据,缓冲区负责数据传输
在这里插入图片描述
如图所示,通道就像轨道,缓冲区就像列车,往返在程序和文件之间,“承载”数据在两者之间传输,在普通的IO中,流分为输入流输出流是单向的,但是NIO是双向的不区分方向

4.详解缓冲区的创建及常见方法

4.1详解Buffer类

抽象Buffer类是所有数据类型Buffer的父类,源码中有四个属性

  • capacity:表示创建缓冲区的容量
  • limit:当前写到的位置
  • position:当前读到的位置
  • mark:用来标记position的位置,使用reset()方法会是指position=mark,复位position

接下来我们通过源码来看看常见的方法(如果是第一次接触这些方法可能无法理解具体细节,可以结合接下来的实例代码进一步理解各个方法):

  • allocate(int capacity): 创建一个对应数据类型的缓冲类,不同数据类型对应的Buffer的创建方式相同,使用allocate()方法,同时创建时指名容量
  • capacity():返回当前类型Buffer的容量
  • position:返回当前读到的位置
  • limit():返回当前写到的位置
  • mark():标记当前的position,使mark = position
  • reset():复位position,使position = mark,通过源码得知,mark初始值为-1,所以我们使用reset()方法前必须先使用mark()方法,重置mark,否则会抛出异常
  • flip():转换模式,由读模式转换到写模式,此时执行limit = position,position = 0,mark = -1;
  • rewind():重读操作,position = 0,mark = -1,重新读取数据
  • clear():清空操作,此时会将四种属性变为初始值,此时缓冲区的数据处在“迷失”状态
  • put():各种写操作
  • get():各种读操作
    还有许多方法,在此就不一一写出,如果感兴趣,可以点进Buffer类中查看源码

4.2接下来一个实例来解释执行各种方法时四种变量的变化

在这里插入图片描述
我们编写一个实例来看一下各个变量的具体变化:

public class TestBufffer {
    public static void main(String[] args) {
        ByteBuffer allocate = ByteBuffer.allocate(1024);
        System.out.println("-------------初始状态----------------");
        System.out.println("position:"+allocate.position());
        System.out.println("limit:"+allocate.limit());
        System.out.println("capacity:"+allocate.capacity());
        String str = "hello";
        allocate.put(str.getBytes());
        System.out.println("-------------put()----------------");
        System.out.println("position:"+allocate.position());
        System.out.println("limit:"+allocate.limit());
        System.out.println("capacity:"+allocate.capacity());
        allocate.flip();
        System.out.println("-------------flip()----------------");
        System.out.println("position:"+allocate.position());
        System.out.println("limit:"+allocate.limit());
        System.out.println("capacity:"+allocate.capacity());
        byte[] dst = new byte[allocate.limit()];
        allocate.get(dst);
        System.out.println(new String(dst,0,dst.length));
        System.out.println("-------------get()----------------");
        System.out.println("position:"+allocate.position());
        System.out.println("limit:"+allocate.limit());
        System.out.println("capacity:"+allocate.capacity());
        allocate.rewind();
        System.out.println("-------------rewind()----------------");
        System.out.println("position:"+allocate.position());
        System.out.println("limit:"+allocate.limit());
        System.out.println("capacity:"+allocate.capacity());
        allocate.clear();//此时缓冲区中的数据依然存在,处于被遗忘状态,仍然可以读取到
        System.out.println("-------------clear()----------------");
        System.out.println("position:"+allocate.position());
        System.out.println("limit:"+allocate.limit());
        System.out.println("capacity:"+allocate.capacity());
    }
}

运行结果:
在这里插入图片描述

5.直接缓冲区和非直接缓冲区

直接缓冲区:通过allocateDirect()方法分配直接缓冲区,将缓冲区建立在物理内存中,可以提高效率
非直接缓冲区:通过allocate()方法分配缓冲区,将缓冲区建立在JVM的内存中
在这里插入图片描述
在这里插入图片描述
可以看到直接缓冲区跳过了copy操作,直接建立在物理内存中,加快了运行效率,但同时也带来了很多问题,首先程序会直接占用物理内存,并且数据不易控制,抛出不确定的异常

6.文件通道(FileChannel)

Java为Channel接口提供的最主要实现类包括本地IO和网络IO

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

6.1获取通道

获取通道的一种方式就是对支持通道的对象调用getChannel()方法。支持通道的类如下:

  • FileInputStream
  • FIleOutStream
  • RandomAccessFile
  • DatagramSocket
  • Socket
  • ServerSocket
    只有byte型缓冲区才有通道,根据流对象调用getChannel()方法可以获取到对应的channel对象

6.2通道的数据传输

  • 将Buffer中数据写入Channel
    例如:将buffer中数据写入到Channel中,write方法的返回值和基本IO的返回值相同
int bytesWritten = inChannel,write(buf);
  • 从Channel读取数据到Buffer
    例如:从Channle读取数据到Buffer,read方法的返回值和基本IO的返回值相同,当你像缓冲区写数据时,记得将缓冲区切换为写模式
int bytesRead = inChannel,read(buf);

6.3利用通道复制文件的实例

public class TestChannel {
    public static void main(String[] args) throws IOException {
        FileInputStream fis = new FileInputStream("src\\pic1.png");
        FileOutputStream fos = new FileOutputStream("src\\pic4.png");

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

        //获取缓冲区
        ByteBuffer buf = ByteBuffer.allocate(1024);

        while((inChannel.read(buf))!=-1){//将通道中的数据写入到buf中,此时buf为读模式
            buf.flip();//将buf切换到写模式
            outChannel.write(buf);//将缓冲区数据写入到通道中
            buf.clear();//清空缓冲区,进行下一次读取
        }
        inChannel.close();
        outChannel.close();
        fis.close();
        fos.close();

    }
}

7.分散(Scatter)和聚集(Gather)

7.1什么是分散读取和聚集写入

  • 分散读取(Scattering Reads):将通道中的数据分到到多个缓冲区中
    在这里插入图片描述

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

  • 聚集写入(Gather Writes):将多个缓冲区中的数据聚集到通道中
    在这里插入图片描述
    按照缓冲区的顺序,写入position 和limit 之间的数据到Channel

7.2具体的代码示例

下面的代码中我创建了两个缓冲区,有不同的大小,将演示从文本中将数据读入到两个缓冲区中

public class TestScatter {
    public static void main(String[] args) throws IOException {
        FileInputStream fis = new FileInputStream("src\\hello.txt");

        // 创建通道
        FileChannel inChannel = fis.getChannel();

        // 创建多个缓冲区
        ByteBuffer buf1 = ByteBuffer.allocate(10);
        ByteBuffer buf2 = ByteBuffer.allocate(1024);

        // 创建缓冲区集合,作为参数
        ByteBuffer[] buffers = {buf1,buf2};
        inChannel.read(buffers);
        // 此时缓冲区处于写状态,切换成读状态,读取缓冲区中的数据
        for(ByteBuffer byteBuffer:buffers){
            byteBuffer.flip();
        }
        // 输出两个缓冲区的数据
        System.out.println("------------------------buffer1------------------------");
        System.out.println(new String(buffers[0].array(),0,buffers[0].limit()));
        System.out.println("------------------------buffer2------------------------");
        System.out.println(new String(buffers[1].array(),0,buffers[1].limit()));

    }
}

在这里插入图片描述
关于聚集写入道理相同,在此就不再重复展示

8.NIO_阻塞与非阻塞

关于NIO非阻塞性质以及选择器的使用详见我的另一篇博客:https://blog.csdn.net/CPrimer0/article/details/113919771

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值