一,概述
原本的java是基于同步阻塞式的i/o通信(bio) 性能低下,所以出现了nio这种非阻塞式的
二,Java 的I/O演进之路
2.1 i/o模型基本说明
i/o模型:就是用什么样的通道或者说通信模式和架构进行数据的传输和接收,很大程度上决定了程序通信的性能,java支持的3种网络编程的io模型:BIO,NIO,AIO
2.2 I/O模型
BIO
一个连接是一个线程
NIO
同步非阻塞的,客户端发送的连接请求都会注册到多路复用器上,多路复用器查询到连接有i/o请求就会进行处理
AIO
异步非阻塞
2.3 BIO,NIO,AIO 使用场景分析
- bio适合连接数目小的且固定的架构
- nio适合连接数目多且连接比较短的架构(聊天)
- AIO方式使用于连接数目多且连接比较长(重操作)的架构,比如相册服务器,充
四 ,NIO
4.1 NIO 基本介绍
- 面向缓冲区基于通道的
- 在java.nio包下以及子包下
- 有三大核心 **Channel(通道),Buffer(缓冲区),Selector(选择器)
- 如果通道里面没有数据就会去做其他事情不会去等待
4.2 NIO 和 BIO 比较
- BIO 以流的方式,NIO 以块的方式,效率更高
- NIO是非阻塞的
- BIO 是基于字节流和字符流进行操作,而NIO是基于Channel(通道)和Buffer(缓冲区)进行操作,数据总是由缓冲区写入通道或者从通道读入缓冲区,选择器用于监听
4.3 NIO 三大核心原理
三大核心分别为 Channel(通道) , Buffer(缓冲区),Selector(选择器)
Buffer缓冲区
Channel(通道)
通道可以非阻塞读取和写入通道,通道可以支持读取或写入缓冲区,也支持异步地读写,而流是单向的
Selector(选择器)
可以检测多个通道
程序切换到哪个channel是由事件决定的
4.4 缓冲区(Buffer)
一个容器,由java.nio包定义,所有缓冲区都是Buffer抽象类的子类,
Buffer类与其子类
- ByteBuffer
- CharBuffer
- ShortBuffer
- IntBuffer
- LongBuffer
- FloatBuffer
- DoubleBuffer
static xxxBuffer allocate(int capacity): 创建一个容量为capicity的 buffer对象
buffer.wrap()
数据已知
ByteBuffer.allocate()
构建ByteBuffer对象,参数实际上是指底层的字节数组的容量
缓冲区的基本属性
- 容量 创建后不能改变
- 限制 (limit) limit后面不能读写,不能为负,不能大于其容量,写入模式,限制等于buffer的容量,读取模式下,limit等与写入的数据量
- 位置(position): 下一个读取或写入的数据的索引,索引库的位置不能为负,并且不能大于其限制
- 标记(mark)与重置(reset):标记是一个索引,通过Buffer中的mark() 方法指定Buffer中一个特定的position,之后通过调用reset()方法恢复到这个position
标记、位置、限制、容量遵守以下不变式: 0<=mark <= position <= limit <= capacity
Buffer常见方法
Buffer clear() 洁空缓冲区并返回对缓冲区的引用(只是把position变为0位置) Buffer flip(为将缓冲区的界限设置为当前位置,并将当前位置设值为0 使用wrap() 类java.nio.IntBuffer中的方法,可以将int数组包装到缓冲区中。此方法需要一个参数,即将数组包装到缓冲区中,并返回创建的新缓冲区。如果返回的缓冲区被修改,则数组的内容也将被修改,反之亦然。 int capacity(返回 Buffer 的capacity大小 boolean hasRemaining(判断缓区中是否还有元素 int limit()返同Buffer的界限(limit)的位置 Buffer limit(int n)将设置缓冲区界限为n,并返回一个具有新limit 的缓冲区对象 Buffer mark()对缓冲区设置标记 int position()返回缓冲区的当前位置position Buffer position(int n)将设置缓冲区的当前位置为n ,并返回修改后的 Buffer对象 int remaining()返回position和 limit 之间的元素个数 Buffer reset()将位置position转到以前设置的 mark所在的位置 Buffer rewind()将位置设为为节、取消设置的mark
Buffer所有子类提供了两个用于数据操作的方法: get()put()方法取获取Buffer中的数据 get():读取单个字节 get(byte[] dst):批量读取多个字节到dst中 get(int index):读取指定索引位置的字节(不会移动position) 放到入数据到 Buffer 中十 put (byte b):将给定单个字节写入缓冲区的当前位置 put(byte[] src);将 src中的字节写入缓冲区的当前位置 put(int index,byte b):将指定字节写入缓冲区的索引位置(不会移动 position)
缓冲区的数据操作
public class BufferTest { @Test public void test01(){ //1.分配一个缓冲区,容量设置10 ByteBuffer buffer = ByteBuffer.allocate(10); System.out.println(buffer.position()); System.out.println(buffer.limit()); System.out.println(buffer.capacity()); System.out.println("---------------------"); //2.添加数据 String name = "itheima"; buffer.put(name.getBytes(StandardCharsets.UTF_8)); System.out.println(buffer.position()); System.out.println(buffer.limit()); System.out.println(buffer.capacity()); System.out.println("---------------------"); buffer.flip(); //为将缓冲区的界限设置为当前位置,并将当前位置设值为0 System.out.println(buffer.position()); System.out.println(buffer.limit()); System.out.println(buffer.capacity()); System.out.println("---------------------"); //4,. get数据的读取 char b = (char)buffer.get(); System.out.println(b); System.out.println(buffer.position()); System.out.println(buffer.limit()); System.out.println(buffer.capacity()); } }
0 10 10 --------------------- 7 10 10 --------------------- 0 7 10 --------------------- i 1 7 10
直接与非直接缓冲区
什么是直接内存与非直接内存根据官方文档的描述:
byte byffer可以是两种类型,一种是基于直接内存(也就是非堆内存)﹔另一种是非直接内存(也就是堆内存)。对于直接内存来说,JVM将会在IO操作上具有更高的性能,因为它直接作用于本地系统的IO操作。而非直接内存,也就是堆内存中的数据,如果要作IO操作,会先从本进程内存复制到直接内存,再利用本地IO处理。
从数据流的角度,非直接内存是下面这样的作用链:
本地IO-->直接内存-->非直接内存--→>直按内存-->本地IO
而直接内存是:
本地IO-->直接内存-->本地IO
很明显,在做IO处理时,比如网络发送大量数据时,直接内存会具有更高的效率。直接内存使用allocateDirect创建,但是它比申请普通的堆内存需要耗费更高的性能。不过,这部分的数据是在IVM之外的,因此它不会占用应用的内存。所以呢,当你有很大的数据要缓存,并且它的生命周期又很长,那么就比较适合使用直接内存。只是一般来说,如果不是能带来很明显的性能提升,还是推荐直接使用堆内存。字节缓冲区是直接缓冲区还是非直接缓冲区可通过调用其isDirect()方法来确定。