NIO 是New IO 的简称,从JDK1.4开始添加支持,用来解决传统IO的问题,比如并发,高效传输等。NIO作为传统IO的一个补充及优化,在解决一些传统IO无法应对的场景非常有效,而且对于NIO的掌握也对于开发人员是一种技能水平的提升,所以就同我一起来学习一下NIO的相关知识吧。
通过前文我们对于Java I/O的结构已经有了一个比较清晰的认知,Java引入了一个新包java.nio。以下是java.nio的包目录结构:
java.nio 包定义了缓冲区类Buffer及相关子类,这些类适用于所有 NIO API;
java.nio.charset 包中定义了字符集相关 API;
java.nio.channels 包中定义了信道和选择器相关 API;
java.nio.charsetspi 与java.nio.channels.spi 子包的内容可用于扩展平台的默认实现或构造替代实现,说白了就是只有那些需要定义新的选择器提供者的开发人员才应直接使用spi包;
包结构如下:
java.nio |--java.nio.channels |--java.nio.channels.spi |--java.nio.charset |--java.nio.charset.spi
在NIO中存在着一些与传统I/O迥异的设计思想及概念,NIO中存在着缓存区(Buffer),字符集(charset),通道(Channel),选择器(Selector)等概念,所以学习NIO前需要理解他们,并且清楚他们的作用。
NIO中的相关概念:
1)缓冲区:缓冲区可以理解为数据容器,用于承载数据;
2)字符集、解码器和编码器:利用它们在字节和 Unicode 字符之间进行转换;
3)通道:用于连接执行 IO 操作的实体;
4)选择器、选择键:它们与可选择通道(已注册通道)一起定义了多路的、无阻塞的I/O 模式。
本文从缓冲区开始逐一对NIO中的概念及实现进行学习。
1.缓冲区简介
缓冲区是一个固定大小且指定基本类型的数据容器,缓冲区是特定基本类型元素的线性有限序列。除内容外,缓冲区的基本属性还包括容量、限制和位置:
容量(capacity):缓冲区所包含的元素的数量,缓冲区的容量不能为负并且不能更改。
限制(limit):缓冲区中有效位置的数目,也就是限制缓冲区的可使用大小。缓冲区的限制不能为负,并且不能大于其容量。
位置(position):是下一个要读取或写入的元素的索引(index)。缓冲区的位置不能为负,并且不能大于其限制。 通俗理解就是从哪个位置开始写入或读取。
缓冲区的设计实现类为Buffer,Buffer类是一个抽象类,其中包含缓冲区一些的基本属性和方法,上述三种概念的对应实现为:
capacity(容量) public final int capacity() 返回:此缓冲区的容量 position(位置) public final int position() 返回:此缓冲区的位置 limit(限制) public final int limit() 返回:此缓冲区的限制
2.缓冲区种类
缓冲区的最上层设计实现为Buffer类,在Buffer类下层还包含了ByteBuffer, CharBuffer, DoubleBuffer, FloatBuffer, IntBuffer, LongBuffer, ShortBuffer七个子类:
这七个子类分别是除 boolean 基本类型外几种Java基本类型所对应的缓冲区实现:
Byte --> ByteBuffer
Char --> CharBuffer
Double --> DoubleBuffer
Float --> FloatBuffer
Int --> IntBuffer
Long --> LongBuffer
Short --> ShortBuffer
缓冲区按基本类型分成了七种,分别用于对应存储不同类型的数据,其中ByteBuffer 比较特殊,ByteBuffer 可以转换成其他6种缓冲区类型,而其他6种类型间无法转换。
3.数据操作
每个缓冲区子类都定义了两种(get)和放置(put)操作,被称为“相对”和“绝对”,其实也就是是否指定index索引位置,如:
get():相对获取,会读取此缓冲区当前位置(position)的数据,然后该位置(position)递增。
get(int index):从指定索引处获取数据,获取数据不会改变位置(position)值。
“相对”操作读取或写入一个或多个元素时,会从从当前位置(position)开始,然后将位置增加所传输的元素数。如果请求的传输超出限制,则相对获取(get)操作将抛出 BufferUnderflowException,相对放置(put)操作将抛出BufferOverflowException;这两种情况下,都讲没有数据被传输。
“绝对”操作采用指定元素索引方式调用,该操作不会影响位置(position)。如果索引参数超出限制,绝对获取(get)操作和放置(put)操作将抛出 IndexOutOfBoundsException。
4.标记和重置
缓冲区的标记(mark)是一个索引,在调用 reset 方法时会将缓冲区的位置重置为该索引。我们不需要初始化定义标记,但在一旦定义了标记时,不能将其定义为负数,并且不能让它大于位置。如果定义了标记,则在将位置或限制调整为小于该记的值时,该标记将被丢弃。如果未定义标记,那么调用 reset 方法将导致抛出 InvalidMarkException。
5.不变式
不变式可以理解为缓冲区各属性间永恒关系,标记、位置、限制和容量值遵守以下不变式:
新创建的缓冲区总有一个数值为0 的位置和一个未定义的标记。初始限制可以为 0,也可以为其他值,这取决于缓冲区类型及其构建方式。一般情况下,缓冲区的初始内容是未定义的。
6.只读缓冲区
每个缓冲区都是可读取的,但并非每个缓冲区都是可写入的。可以调用其 isReadOnly 方法确定缓冲区是否为只读。每个缓冲区类的转变方法都被指定为可选操作,当对只读缓冲区调用时,将抛出 ReadOnlyBufferException。只读缓冲区不允许更改其内容,但其标记、位置和限制值是可变的。
通过asReadOnlyBuffer()方法可以轻松的将缓冲区转换成只读缓冲区,对于保护数据很有用。在将缓冲区传递给某个对象的方法时,您无法知道这个方法是否会修改缓冲区中的数据。创建一个只读的缓冲区可以保证该缓冲区不会被修改。
注意:无法将只读的缓冲区再次转换为可写的缓冲区。
7.“直接”与“非直接”缓冲区
缓冲区要么是直接的,要么是非直接的。直接缓冲区是为加快 I/O 速度,而以一种特殊的方式分配其内存的缓冲区。创建ByteBuffer 缓冲区时可以通过方法allocateDirect(int capacity)来创建直接缓冲区,如果为直接字节缓冲区,则 Java 虚拟机会尽最大努力直接在此缓冲区上执行本机 I/O 操作。也就是说,在每次调用基础操作系统的一个本机 I/O 操作之前(或之后),虚拟机都会尽量避免将缓冲区的内容复制到中间缓冲区中(或从中间缓冲区中复制内容)。
直接字节缓冲区(ByteBuffer)通过调用ByteBuffer 的allocateDirect(int capacity) 工厂方法来创建。此方法返回的缓冲区进行分配和取消分配所需成本通常高于非直接缓冲区。直接缓冲区的内容可以驻留在常规的垃圾回收堆之外,因此,它们对应用程序的内存需求量造成的影响可能并不明显。所以,建议将直接缓冲区主要分配给那些易受基础系统的本机 I/O 操作影响的大型、持久的缓冲区。一般情况下,最好仅在直接缓冲区能在程序性能方面带来明显好处时分配它们。
直接字节缓冲区(ByteBuffer)还可以通过 mapping 将文件区域直接映射到内存中来创建。Java 平台的实现有助于通过 JNI 从本机代码创建直接字节缓冲区。如果以上这些缓冲区中的某个缓冲区实例指的是不可访问的内存区域,则试图访问该区域不会更改该缓冲区的内容,并且将会在访问期间或稍后的某个时间导致抛出不确定的异常。
除ByteBuffer 外其他缓冲区实现通过 wrap 方法创建的 long 缓冲区都是非直接的。当且仅当字节缓冲区本身为直接时,作为字节缓冲区的视图创建的其他缓冲区才是直接的。通过调用 isDirect 方法可以确定该缓冲区是否为直接的。 相关实例会在后面展现。
其他6种类型缓冲区并不含有allocateDirect(int capacity) 工厂方法,所以他们无法通过此方法直接来直接创建缓冲区,想要创建这几种类型的直接缓冲区就需要通过ByteBuffer 类的as****Buffer方法转换:
ByteBuffer byteBuffer = ByteBuffer.allocateDirect(1024); System.out.println(byteBuffer.isDirect()); CharBuffer charBuffer = byteBuffer.asCharBuffer(); System.out.println(charBuffer.isDirect()); //打印结果: true true
关于直接、非直接缓冲区后面的文章还会深入讲解。
8.清除、反转和重绕
除了访问位置、限制、容量值的方法以及做标记和重置的方法外,还定义了以下可对缓冲区进行的操作:
clear():使缓冲区为一系列新的通道读取或相对放置(put)操作做好准备:它将限制设置为容量大小,将位置设置为 0。
flip():使缓冲区为一系列新的通道写入或相对获取(get)操作做好准备:它将限制设置为当前位置,然后将位置设置为 0。
rewind():使缓冲区为重新读取已包含的数据做好准备:它使限制保持不变,将位置设置为 0。
9.线程安全
多个当前线程使用缓冲区是不安全的。如果一个缓冲区由不止一个线程使用,则应该通过适当的同步来控制对该缓冲区的访问。
10.调用链
指定此类中的方法返回调用它们的缓冲区(否则它们不会返回任何值)。此操作允许将方法调用组成一个链;例如,语句序列:
b.flip(); b.position(23); b.limit(42);
可以由以下更紧凑的一个语句代替:
b.flip().position(23).limit(42);
调用链这种形式在开发中较为常见,我这里就不多说了。
以上就是对于NIO中缓冲区的一些概念性了解,接下来的几篇文章会更深入的去学习更多的知识及原理。
相关推荐
NIO的核心在于通道(Channels)和缓冲区(Buffers)的概念,与传统的流(Streams)有所不同。 1. **通道(Channels)**: 通道是NIO中的核心概念之一,它提供了从一个数据源(如文件、套接字)到另一个数据源的...
Java NIO: Channels and Buffers(通道和缓冲区) 标准的IO基于字节流和字符流进行操作的,而NIO是基于通道(Channel)和缓冲区(Buffer)进行操作,数据总是从通道读取到缓冲区中,或者从缓冲区写入到通道中。 ...
2. **缓冲区(Buffer)**:在NIO中,数据被存储在缓冲区对象中。缓冲区提供了一种高效的方式管理内存,可以方便地进行读写操作。缓冲区有固定大小,一旦写满,需要清空或者翻转才能继续写入。同样,读取数据也需要将...
本示例主要关注如何使用NIO解决“沾包”问题以及处理因缓冲区满导致的写入失败问题。首先,我们需要理解这两个问题的背景。 "沾包"问题通常发生在网络通信中,特别是TCP协议中。由于TCP是流式传输,不保留消息边界...
Java NIO(Non-blocking I/O)是Java平台中的一种I/O处理方式,它提供了面向缓冲区的I/O处理机制,可以实现高性能、高效的I/O操作。 缓冲区(Buffer) 缓冲区是Java NIO中非常重要的一个概念,它是特定基本类型...
#### 一、Java NIO简介 Java NIO(New IO)是Java平台上的新输入/输出流API,它提供了与传统IO(即Java IO)不同的数据处理方式。NIO在Java 1.4版本引入,并在后续版本中得到了进一步增强和完善。相较于传统的Java ...
Java NIO提供了字节、字符、短整型、整型、长整型、浮点型和双精度浮点型等类型的缓冲区。缓冲区具有特定的方法,如put和get,用于数据的存取。 3. **选择器(Selector)**:选择器是Java NIO的核心,它可以监视多...
01-Java NIO-课程简介....17-Java NIO-Buffer-缓冲区分片.mp4 18-Java NIO-Buffer-只读缓冲区.mp4 19-Java NIO-Buffer-直接缓冲区.mp4 21-Java NIO-Selector-概述.mp4 23-Java NIO-Selector-示例代码(客户端).mp4 24
1. **缓冲区管理**:合理使用缓冲区可以提高效率,但过度使用可能导致内存占用过高。 2. **选择器使用**:选择器的选择和配置对性能有很大影响,需要根据具体需求进行调整。 3. **异常处理**:NIO的错误处理相对复杂...
NIO的主要特点是面向缓冲区,非阻塞I/O,以及选择器,这些特性使得NIO在处理大量并发连接时表现出更高的效率。在本篇文章中,我们将深入探讨Java NIO如何读取文件。 一、NIO的基本概念 1. 缓冲区(Buffer):NIO的...
3. **Buffer(缓冲区)**:在NIO中,数据读写都是通过缓冲区进行的。缓冲区是一个可以容纳特定类型数据(如字节、字符、整数等)的容器,它提供了对数据的高效访问和管理。 4. **FileChannel**:用于文件的读写,...
Java NIO提供了ByteBuf、CharBuf、ShortBuf、IntBuf、LongBuf、FloatBuf和DoubleBuf等不同类型的缓冲区,它们都有统一的API,如put()用于写入数据,get()用于读取数据,clear()用于清空缓冲区,flip()用于切换读写...
缓冲区类型包括ByteBuffer、CharBuffer、IntBuffer、DoubleBuffer等,它们都继承自`java.nio.Buffer`。 3. **选择器(Selector)**:用于监听多个通道的事件(如连接就绪、数据到达等),当某个通道准备好进行读写...
传统的Java I/O基于流(Stream)和缓冲区(Buffer)的模型,是阻塞式的,即在进行读写操作时会一直等待数据准备好或全部写入完成。而NIO的核心理念在于非阻塞,它允许程序在没有数据可读或可写时继续执行其他任务,提高...