网络体系:
OSI IEEE
IO体系
本地(local)IO
字节流:InputStream、OutputStream
字符流:Reader、Writer
网络(远程)IO
NIO与IO区别:
NIO通道和缓冲区:
通道(Channel):表示打开IO设备(例如:文件、套接字Socket)的连接。若需要NIO系统,需要获取用于连接IO设备的通道以及用于容纳数据的缓冲区(Buffer)。然后操作缓冲区,对数据进行处理。
缓冲区(Buffer):一个用于特定基本数据类型的容器。由java.nio包定义的,所有缓冲区都是Buffer抽象类的子类。Java NIO中的Buffer主要用于与NIO通道进行交互,数据是从通道读入缓冲区,从缓冲区写入通道中。
简单说:Channel负责传输,Buffer负责存储
缓冲区:
-
在java NIO中负责数据的存储。缓冲区就是数组。用于存储不同数据类型的数据。
根据数据类型不同,提供相应的数据类型的缓冲区:
ByteBuffer、CharBuffer、ShortBuffer、IntBuffer、LongBuffer、FloatBuffer、DoubleBuffer、
上述缓冲区管理方式几乎一致,通过allocate() 获取缓冲区 -
缓冲区存储数据的两个核心方法:
put() :存入数据到缓冲区中
get() :获取缓冲区中的数据 -
缓冲区的四个核心属性
capacity:容量,表示缓冲区中最大存储数据的容量。一旦声明不能改变。
limit:界限,表示缓冲区中可以操作数据的大小。(limit后数据不能进行读写)
position:位置,表示缓冲区中正在操作数据的位置。
mark : 标记,表示记录当前position的位置。可以通过reset() 恢复到mark的位置。
0 <= mark <= position <= limit <= capacity
API:
allocate : 分配一个指定大小的缓冲区
put : 存入数据到缓冲区
get : 读取缓冲区中数据
flip : 切换读取数据模式
rewind : 可重复读数据
clear : 清空缓冲区,回到最初状态,但是实际上,数据并没有被清空,缓冲区数据依然存在,处于被遗忘状态,四个核心属性重置了。
reset :恢复到mark的位置
mark :标记,表示记录当前position的位置。可以通过reset() 恢复到mark的位置。
hasRemaining :判断缓冲区还有没有可以操作的数据
remaining : 获取缓冲区中可操作的数量
-
直接缓冲区和非直接缓冲区
非直接缓冲区:通过allocate() 方法分配缓冲区,将缓冲区建立在JVM的内存中
直接缓冲区:通过allocateDirect()方法分配直接缓冲区,将缓冲区建立在物理内存中。
- 4.1 字节缓冲区要么是直接的,要么是非直接的。如果为直接字节缓冲区,则java虚拟机会尽最大努力直接在此缓冲区上执行本级I/O操作,在每次调用基础操作系统的一个本季I/O操作之前(或之后),虚拟机都会尽量避免将缓冲区的内容复制到中间缓冲区(或从中间缓冲区中复制内容)。
- 4.2 直接字节缓冲区可以通过调用此类allocateDirect()工厂方法来创建。此方法返回的缓冲区进行分配和取消分配所需成本通常高于非直接缓冲区。直接缓冲区的内容可以驻留在常规的垃圾回收堆之外,因此,它们对应用程序的内存需求量造成的影响可能并不明显。所以,建议直接缓冲区主要分配给那些易受基础系统的本季I/O操作影响的大型、持久的缓冲区。一般情况下最好仅在直接缓冲区能在程序性能方面带来明显好处时分配它们。
- 4.3 直接字节缓冲区还可以通过FileChannel的map()方法将文件区域直接映射到内存中来创建。该方法返回MappedByteBuffer。java平台的实现有助于通过JNI从本机代码创建直接缓冲区。如果以上这些缓冲区中的某个缓冲区实例指的是不可以访问的内存区域,则试图访问该区域不会更改该缓冲区的内容,并且将会在访问期间或稍后的某个时间导致抛出不确定的异常。
- 4.4 字节缓冲区是直接缓冲区还是非直接缓冲区可以通过调用其isDirect()方法来确定。提供此方法是为了能够在性能关键型代码中执行显示缓冲区管理。
非直接缓冲区
直接缓冲区
通道(Channel)
改进
加入DMA,直接存储器
DMA总线
再次改进:
将DMA改进成通道
通道
-
通道(Channel):用于源节点和目标节点的连接。在java NIO中负责缓冲区数据的传输。Channel本身不存储数据,因此需要配合缓冲区进行传输。
-
通道的主要实现类:
java.nio.channels.channel接口
| — FileChannel 用于本地
| — SocketChannel 用于网络
| — ServerSocketChannel 用于网络
| — DatagramChannel 用于网络 -
获取通道三种方式
3.1 Java针对支持通道的类提供了getChannel()方法
本地IO:
FileInputStream/FileOutputStream
RandomAccessFile
网络IO:
Socket
ServerSocket
DatagramSocket
3.2 在JDK1.7 中的NIO 2 ,针对各个通道提供了静态方法open()
3.3 在JDK1.7 中的NIO 2 的Files工具类的newByteChannel() -
通道之间传输
transferFrom()
transferTo() -
分散(Scatter)与聚集(Gather)
分散读取(Scattering Reads):将通道(Channel)中的数据分散到多个缓冲区中
注意:按照缓冲区的顺序,从Channel中读取的数据依次将Buffer填满
聚集写入(Gathering Writes):将多个缓冲区中的数据聚集到一个通道中
注意:按照缓冲区的顺序,写入position和limit之间的数据到Channel
6. 字符集:Charset
编码:字符串 -> 字节数组
解码:字节数组 -> 字符串
NIO 的非阻塞式网络通信
阻塞与非阻塞:
- 传统的IO流都是阻塞式的。也就是说,当一个线程调用read()或write()时,该线程被阻塞,直到有一些数据被读取或写入该线程在此期间不能执行其它任务。因此,在完成网络通信进行IO时,由于线程会阻塞,所以服务器端必须为每个客户都提供一个独立的线程进行处理,当服务器端需要大量客户端时,性能急剧下降。
- Java NIO是非阻塞模式的。当线程从某通道进行读写数据时,若没有数据可用时,该线程可以执行其它任务。线程通常将非阻塞式IO的空闲时间用于其它通道上执行IO操作。因从,NIO可以让服务端使用一个或几个线程来同时处理连接到服务器端的所有客户端。
选择器(Selector): - 选择器(Selector)是SelectableChannel对象的多路复用器,selector可以同时监控多个SelectableChannel的IO状况,也就是说,利用Selector可以使用一个单独的线程管理多个Channel。Selector是非阻塞IO的核心。
- SelectableChannel的结构:
SocketChannel:
- SocketChannel是一种面向流连接sockets套接字的可选择通道。
- SocketChannel是用来连接Socket套接字
- SocketChannel主要用途用来处理网络I/O的通道
- SocketChannel是基于TCP连接传输
- SocketChannel实现了可选择通道,可以被多路复用的
SocketChannel具有以下的特征:
- 对于已经存在的socket不能创建SocketChannel
- SocketChannel中提供的open接口创建的Channel并没有进行网络级联,需要使用connect接口连接到指定地址
- 未进行连接的SocketChannle执行I/O操作时,会抛出NotYetConnectedException
- SocketChannel支持两种I/O模式:阻塞式和非阻塞式
- SocketChannel支持异步关闭。如果SocketChannel在一个线程上read阻塞,另一个线程对该SocketChannel调用shutdownInput,则读阻塞的线程将返回-1表示没有读取任何数据;如果SocketChannel在一个线程上write阻塞,另一个线程对该SocketChannel调用shutdownWrite,则写阻塞的线程将抛出AsynchronousCloseException
- SocketChannel支持设定参数
DatagramChannel:
- java NIO中DatagramChannel是一个能收发UDP包的通道。
DatagramChannel是无连接的。每个数据报(datagram)都是一个自包含的实体,拥有它自己的目的地址及不依赖其他数据报的数据净荷。与面向流的的socket不同,DatagramChannel可以发送单独的数据报给不同的目的地址。同样,DatagramChannel对象也可以接收来自任意地址的数据包。每个到达的数据报都含有关于它来自何处的信息(源地址)。
一个未绑定的DatagramChannel仍能接收数据包。当一个底层socket被创建时,一个动态生成的端口号就会分配给它。绑定行为要求通道关联的端口被设置为一个特定的值(此过程可能涉及安全检查或其他验证)。不论通道是否绑定,所有发送的包都含有DatagramChannel的源地址(带端口号)。未绑定的DatagramChannel可以接收发送给它的端口的包,通常是来回应该通道之前发出的一个包。已绑定的通道接收发送给它们所绑定的熟知端口(wellknown port)的包。数据的实际发送或接收是通过send( )和receive( )方法来实现的
SelectionKey:
-
SelectionKey:表示SelectableChannel和Selector之间的注册关系。每次向选择器注册通道时就会选择一个事件(选择键)。选择键包含两个表示为整数值的操作集。操作集的每一位都表示该键的通道所支持的一类可选择操作。
-
当调用register(Selector set,int ops)将通道注册选择器时,选择器对通道的监听事件,需要通过第二个参数ops指定。
-
可以监听的事件类型(可使用SelectionKey的四个常量表示):
- 读:SelectionKey.OP_READ (1)
- 写:SelectionKey.OP_WRITE (4)
- 连接:SelectionKey.OP_CONNECT (8)
- 接收: SelectionKey.OP_ACCEPT (16)
-
若注册时不止监听一个事件,则可以使用“位或”操作符连接起来。
例:// 注册“监听事件”
int interestSet = SelectionKey.OP_READ | SelectionKey.OP_WRITE
管道(Pipe):
- Java NIO管道是2个线程之间的单向数据连接。
Pipe有一个source通道和一个sink通道。数据会被写到sink通道,从source通道读取。
开通微信公众号:
我的微信公众号同步文章(含代码)-NIO(1)
我的微信公众号同步文章(含代码)-NIO(2)