Java NIO详细总结(源码解析)

目录NIO概述I/O与NIO区别FileChannel和ByteBuffer的使用ByteBuffer创建ByteBuffer对象ByteBuffer如何写入/读取数据视图缓冲区ByteOrderFileChannel创建FileCHannel对象FileChannel如何写入数据FileChannel文件加锁文件映射参考文献NIO概述...
摘要由CSDN通过智能技术生成

目录

NIO概述

I/O与NIO区别

FileChannel和ByteBuffer的使用

ByteBuffer

创建ByteBuffer对象

ByteBuffer如何写入/读取数据

视图缓冲区

ByteOrder

FileChannel

创建FileCHannel对象

FileChannel如何写入数据

FileChannel文件加锁

文件映射

参考文献


NIO概述

  Java NIO(New I/O,也有称为Non-Blocking IO)在jdk1.4提出,目的在于提高IO的执行效率。NIO基于Channel通道以及Buffer缓冲区来实现I/O操作,其 I/O速度的提高在于把数据的填充和提取放回了操作系统中去执行。例如在Java IO详细总结(一篇涵盖所有)一文中提到为了提供速度用Buffer来装饰原有的I/O,但是不管是在读取数据源数据并将数据放到缓冲区,还是把缓冲区中的数据写入到数据源。这两种操作都是在程序中执行的,而NIO则把上述的操作放回了操作系统因此速度有所提高。此外,本文只讨论文件I/O。

  下面是Java NIO类的思维导图:

I/O与NIO区别

  1. I/O面向流操作每次执行单个字节,NIO面向缓冲Buffer每次操作的的数据由Buffer大小来决定;
  2. I/O中缓冲区数据的读取或者写入在程序中实现,NIO则是允许在操作系统中完成;
  3. NIO非阻塞,IO是阻塞的。

FileChannel和ByteBuffer的使用

  首先通过一个简单的例子:NIOTest,来了解FileChannel以及ByteBuffer是如何使用的:

        // NIOTest.java
        try (FileChannel out = new FileOutputStream(new File("D://test.txt")).getChannel();
             FileChannel in = new FileInputStream(new File("D://test.txt")).getChannel()) {
            //ByteBuffer bb = ByteBuffer.allocateDirect(3);
            ByteBuffer bb = ByteBuffer.allocate(10);
            // out.write(ByteBuffer.wrap("abc测试".getBytes()));
            bb.put("abc测试".getBytes());
            bb.flip();
            out.write(bb);

            bb = ByteBuffer.allocate(10);
            // 到达文件末尾返回-1
            for (;in.read(bb) != - 1;) {
                bb.flip();
                // 指定字符集对缓冲区解码
                System.out.println(Charset.forName("UTF-8").decode(bb));
                bb.clear();
            }
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
输出:abc测试

  上例主要由下面几步来完成文件的输入输出:

  1. 打开文件对应的读写通道FileChannel;
  2. 创建缓冲区ByteBuffer;
  3. 程序填充数据到ByteBuffer,FileChannel写入;
  4. FileChannel一直读取数据到ByteBuffer中,直到文件末尾;
  5. 由于用字节写入,打印到控制台的时候通过Charset指定字符集使其能正常显示。

  后文将根据源码来了解ByteBuffer以及FileChannel是如果完成NIOTest中的读写过程的。

ByteBuffer

创建ByteBuffer对象

  ByteBuffer是一个抽象类不能实例化,因此不能通过显示的new来创建对象。创建ByteBuffer有两种方式:一种是直接缓冲区,通过调用allocateDirect来实现;一种是非直接缓冲区通过allocate来实现。直接缓冲区的优势在于针对其上的I/O操作都在操作系统上执行,相对的非直接缓冲区需要把缓冲区的数据复制到一块中间缓冲区再把中间缓冲区的数据写入内存。很明显前者的速度会快于后者。直接缓冲区开辟的内存空间称为直接内存不在JVM管理中,其不受GC的影响所以文档中建议将直接缓冲区主要分配给那些易受基础系统的本机 I/O 操作影响的大型、持久的缓冲区。一般情况下,最好仅在直接缓冲区能在程序性能方面带来明显好处时分配它们。

非直接缓冲区:通过allocate、wrap方法;

    public static ByteBuffer allocate(int capacity) {
        if (capacity < 0)
            throw new IllegalArgumentException();
        return new HeapByteBuffer(capacity, capacity);
    }

    HeapByteBuffer(int cap, int lim) {            // package-private
        super(-1, 0, lim, cap, new byte[cap], 0);
        /*
        hb = new byte[cap];
        offset = 0;
        */
    }

    public static ByteBuffer wrap(byte[] array) {
        return wrap(array, 0, array.length);
    }

    public static ByteBuffer wrap(byte[] array, int offset, int length)
    {
        try {
            return new HeapByteBuffer(array, offset, length);
        } catch (IllegalArgumentException x) {
            throw new IndexOutOfBoundsException();
        }
    }

    ByteBuffer(int mark, int pos, int lim, int cap,   // package-private
                 byte[] hb, int offset)
    {
        super(mark, pos, lim, cap);
        this.hb = hb;
        this.offset = offset;
    }

  从源码可以看到当ByteBuffer调用allocate方法时创建了一个新的对象HeapByteBuffer,该对象继承了ByteBuffer,通过该对象调用父类构造器进行初始化返回子类实例并上转成父类。值得一提的是wrap方法一样可以返回HeapByteBuffer。两者的区别在于是否wrap直接把要写入的数据直接赋值给ByteBuffer底层数据,在本例中相较allocate省去了为底层数组赋值这一步骤。

 直接缓冲区:allocateDirect方法

    public static ByteBuffer allocateDirect(int capacity) {
        return new DirectByteBuffer(capacity);
    }

    DirectByteBuffer(int cap) {                   // package-private
        super(-1, 0, cap, cap);
        boolean pa = VM.isDirectMemoryPageAligned();
        int ps = Bits.pageSize();
        long size = Math.max(1L, (long)cap + (pa ? ps : 0));
        Bits.reserveMemory(size, cap);

        long base = 0;
        try {
            base = unsafe.allocateMemory(size);
        } catch (OutOfMemoryError x) {
            Bits.unreserveMemory(size, cap);
            throw x;
        }
        unsafe.setMemory(base, size, (byte) 0);
        if (pa && (base % ps != 0)) {
            // Round up to page boundary
            address = base + ps - (base & (ps - 1));
        } else {
            address = base;
        }
        cleaner = Cleaner.create(this, new Deallocator(base, size, cap));
        att = null;
    }

  可以看到allocateDirect跟allocate方式完全不同,前者通过unsafe类直接向操作系统申请了一块内存(在虚拟机上这块内存称为直接内存)并且后续对缓冲区的所有操作都是在内存上直接进行。此外,如果申请的内存过大会抛出内存溢出。

  文章后续在介绍ByteBuffer特性的时候会针对HeapByteBuffer和DirectByteBuffer两种情况进行说明。

  • 1
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Java NIO(New IO)是Java 1.4版本提供的一种新的IO API,它提供了与传统IO API不同的IO处理方式,包括了通道(channel)和缓冲区(buffer)的概念。Java NIO的目标是提供比传统IO更快、更高效的IO操作方式。 Java NIO源码解析需要深入了解Java NIO的核心概念,主要包括以下几个部分: 1. 缓冲区(Buffer):Java NIO中的缓冲区是一个对象,它包含了一定数量的数据元素,并且提供了对这些数据元素的基本操作方法。Java NIO中的所有数据都是通过缓冲区来传输的。 2. 通道(Channel):Java NIO中的通道是一种对象,它可以用来读取和写入数据。通道类似于流,但是它们可以被双向读写,并且可以同时处理多个线程。 3. 选择器(Selector):Java NIO中的选择器是一种对象,它可以用来监视多个通道的事件(如读写就绪),从而实现单线程处理多个通道的能力。 4. 文件处理:Java NIO中提供了一组文件处理的API,包括了文件读写、文件锁、文件映射等功能。 Java NIO源码解析需要深入研究Java NIO的实现细节,包括了缓冲区的实现、通道的实现、选择器的实现等。其中,缓冲区的实现是Java NIO的核心,也是最复杂的部分。Java NIO中的缓冲区是通过JNI(Java Native Interface)和Java堆内存来实现的,它提供了高效的数据传输方式,但是也带来了一些复杂性。通道的实现是基于底层的操作系统文件描述符来实现的,它提供了高效的IO操作方式,但是也需要考虑系统平台的差异性。选择器的实现是基于操作系统提供的事件驱动机制来实现的,它可以实现单线程同时处理多个通道的能力,但是也需要考虑系统平台的差异性。 总之,Java NIO源码解析需要深入理解Java NIO的核心概念和实现细节,这样才能更好地理解Java NIO的工作机制,并且能够在实际开发中灵活运用Java NIO的各种功能。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值