NIO AIO及BIO、NIO、AIO的比较

NIO

同步非阻塞

NIO是采用内存映射文件的方式处理输出输出,可以将文件或者文件的一段区域映射到内存中,然后就 可以像访问内存一样来访问文件,所以进行输入输出的速度比BIO快的多

  • BIO是同步阻塞式;NIO是同步非阻塞式
  • BIO是面向流的,NIO是面向缓冲区的
  • NIO是一个线程从某个通道发送读取请求时,仅仅获取当前可以用的数据不会保持阻塞等待

NIO基础

NIO中由3个核心对象Channel通道、Buffer缓冲和Selector多路复用器

  • Channel是对传统输入输出系统的模拟,可以通过map方法将一块数据映射到内存中
  • Buffer本质上就是一个数组,发送到Channel中的所有数据都先放入到buffer中,针对基本数据类 型提供7种不同的buffer,没有boolean类型没有
  • selector多路复用器,针对事件进行选择,实现一对多的连接效果

Buffer抽象类

Buffer主要作用就是用于转入数据,然后输出数据。最基本的实现是ByteBuffer,可以在字节数组上进 行 get/set操作;另外针对基本数据类型,系统提供了7种具体的实现,例如CharBuffer、ShortBuffer、 DoubleBuffer

  • 静态方法allocate(int capacity)创建一个容积为capacity的xxxBuffer对象

buffer有3个核心概念:容积capacity、界限limit和位置position

  • capacity表示该buffer的最大数据容量,一旦创建对象则不能修改
  • limit表示位于limit之后的数据既不可读,也可写
  • position表示下一次可读写的索引位置
  • mark标记特定位置,需要注意只能在0到position之间

常用方法

  • capacity():int 返回buffer的容积值
  • hasRemaining():boolean 判断是否还有数据可以进行处理
  • remaing():int 获取position当前位置和limit界限之间的元素个数
  • position():int 返回当前的操作位置
  • mark():Buffer 设置标记位置,已被后面重返这个位置 reset():Buffer 将位置position重新返回到mark所在为位置
  • rewind():Buffer 将position重新设置到0,并取消mark标记
  • put(obj) :xxxBuffer 用于向buffer中添加数据
  • get():xxx 用于从buffer中获取数据
// 创建对应的buffer对象,其中最大容积值为10
CharBuffer buffer = CharBuffer.allocate(10);
System.out.println(buffer.capacity()); // 获取buffer的容积值
capacity=10,position=0,limit=10
System.out.println(buffer.position());
System.out.println(buffer.limit());
buffer.put('中');
System.out.println(buffer.capacity()); // 10
System.out.println(buffer.position()); // 1
System.out.println(buffer.limit()); // 10
String ss="国梦的梦之蓝";
for(int i=0;i<ss.length();i++)
buffer.put(ss.charAt(i));
buffer.flip(); // 将limit设置到position,并且把position设置为0。相当于是将buffer中没有数据的存储位置封印起来,从而避免读取时读到不合法的数据
System.out.println(buffer.capacity()); // 10
System.out.println(buffer.position()); // 0
System.out.println(buffer.limit()); //7
char cc=buffer.get();//获取position对应的字符
System.out.println(cc); //中
System.out.println(buffer.capacity()); // 10
System.out.println(buffer.position()); // 1
System.out.println(buffer.limit()); //7
buffer.clear(); //清空buffer中的数据,并重置position和limit
System.out.println(buffer.capacity()); // 10
System.out.println(buffer.position()); // 0
System.out.println(buffer.limit()); //10
ss="中国梦的梦之蓝";
for(int i=0;i<ss.length();i++)
buffer.put(ss.charAt(i));
cc=buffer.get(2); //按照下标位置获取对应的数据,并不会操作position
System.out.println(cc);
System.out.println(buffer.position()); //7

Channel通道

Channel可以直接将文件的部分或者全部映射到 buffer中。注意不能直接访问Channel中的数据,包括 读写都不行,Channel只能与buffer进行交互

  • 所有的Channel不应该使用构造器来直接创建,而是通过传统的InputStream\OutputStream的 getChannel方法获取。一般使用FileInputStream和FileOutputStream中的 getChannel获取

常见方法有3个map/read/write

读取文件内容

public class Test1 {
	public static void main(String[] args) throws Exception {
		File file = new File("data/Test1.java");
// 获取channel对象
		FileChannel inChannel = new F		ileInputStream(file).getChannel();
		ByteBuffer buffer = ByteBuffer.allocate(256);// 构建一个容积为256的buffer
// 从channel中获取数据写入到buffer中。文件可能大于256,所以需要进行多次读取。可以通过-1判定读取结束
		while (inChannel.read(buffer) != -1) {
// 将buffer中没有数据的部分封印起来
			buffer.flip(); // 设置limit,limit之后的数据不允许操作
			System.out.println(buffer.limit()); //256
			Charset charset = Charset.forName("UTF-8"); // 参数为编码字符集名
称,创建对应编码字符集的解码器
			CharsetDecoder decoder = charset.newDecoder();// 通过Charset创建对应的解码器对象
			CharBuffer cb = decoder.decode(buffer);// 使用解码器可以将ByteBuffer转换为CharBuffer
			System.out.println(cb);
			buffer.clear();// 清空buffer中的数据,同时将position设置为0,为下一次读取操作做准备
		}
	}
}

异常:java.nio.charset.MalformedInputException一般是编码转换时由于编码字符集错误导致的,可 以修改Charset中编码字符集名称解决。例如GBK、UTF-8等

map()将channel中对应的部分或者全部数据映射到ByteBuffer中

public class Test2 {
	public static void main(String[] args) throws Exception {
		File f = new File("data/Test1.java");
		File outpath = new File("out/");
		if (!outpath.exists())
			outpath.mkdirs();
		if (f.exists()) {
			FileChannel in = new FileInputStream(f).getChannel();
			FileChannel out = new
FileOutputStream("out/aaa.txt").getChannel();
// 可以将FileChannel中全部数据映射为ByteBuffer。参数1为执行映射时所采用的模式,有只读、读写两种模式;参数2和3用于设置哪些数据需要执行映射
			MappedByteBuffer buffer = in.map(MapMode.READ_ONLY, 0,f.length());
			out.write(buffer); //将buffer中的数据写入到out通道中,也就是直接关联写入到文件中
			buffer.clear(); // 将position设置为0,并没有直接清除数据
			Charset charset = Charset.forName("UTF-8");
			CharsetDecoder decoder = charset.newDecoder();
			CharBuffer cb = decoder.decode(buffer);
			System.out.println(cb);
		} else {
			System.out.println("文件中没有所需要的数据");
		}
	}
}

写出文件数据

如果实际上是原地操作,可以使用RandomAccessFile

File file = new File("data/a1.txt");
int len=(int)file.length();
RandomAccessFile raf = new RandomAccessFile(file, "rw");
FileChannel channel = raf.getChannel();// 返回的channel是只读还是读写,取决于
RandomAccessFile文件对象的打开模式
ByteBuffer buffer = channel.map(MapMode.READ_ONLY, 0, len);//buffer支持可读,将
文件中的所有内容映射到buffer中
channel.position(len); //移动指针到内容末尾
channel.write(buffer); //重新写出buffer中的内容,实际上就是将文件内容拷贝

selector

是Java NIO核心组件中的一个,用于检查一个或多个NIO Channel通道的状态是否处于可读、可写。如 此可以实现单线程管理多个channels也就是可以管理多个网络链接。

使用Selector的好处在于: 使用更少的线程来就可以来处理通道了, 相比使用多个线程,避免了线程上 下文切换带来的开销

  • FileChannel不能切换为非阻塞模式,更准确的来说是因为FileChannel没有继承 SelectableChannel
  • 多用于网络应用编程中
基本用法
  1. Selector的创建。通过调用Selector.open()方法创建一个Selector对象
 Selector selector = Selector.open();
  1. 注册Channel到Selector
channel.configureBlocking(false);
SelectionKey key = channel.register(selector, Selectionkey.OP_READ);

Channel必须是非阻塞的。

  1. 轮询方式获取选择器上的状态值
while(selector.select()>0){
	Iterator<SelectionKey> it=selector.selectedKeys().iterator();
	...
}

Charset字符集

所有的文件底层都是二进制数据存储的,字符文件是系统将底层的二进制序列转换为字符,这里一定会 涉及编码字符集和对应的编码器和解码器

  • 将明文的字符序列转换为计算机理解的二进制序列称为编码
  • 将二进制序列转换为明文字符串称为解码

Charset类

Charset.availableCharsets()获取当前JDK所支持的所有字符集

//获取当前JDK所支持的所有字符集
SortedMap<String, Charset> sm = Charset.availableCharsets();
for(String tmp:sm.keySet()) {
	System.out.println(tmp);
}

字符串别名:

  • GBK简体中文 GB2312
  • ISO-8859-1 拉丁文 欧洲的所有编码
  • UTF-8 变长编码,支持中文,实际上是Unicode编码的一种实现

构建对象

Charset c=Charset.forName("GBK");

另外还有 JDK1.7新增的工具类

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-PVZlVLfL-1645439115872)(C:\Users\Administrator\Desktop\博客\屏幕截图 2022-02-18 150904.png)]

newDecoder(): CharsetDecoder 获取编码字符集对应的解码器

  • decode(ByteBuffer): CharBuffer

newEncoder():CharsetEncoder获取编码字符集对应的编码器

  • encode(CharBuffer):ByteBuffer
Charset c1 = Charset.forName("GBK");
CharsetEncoder encoder = c1.newEncoder();
CharsetDecoder decoder = c1.newDecoder();
CharBuffer cb = CharBuffer.allocate(8);
cb.put('一');
cb.put('二');
cb.put('三');
cb.flip();
ByteBuffer bb=encoder.encode(cb);//将CharBuffer转换为ByteBuffer
for(int i=0;i<6;i++) {
System.out.println(bb.get(i)+" ");
}
System.out.println("====================");
System.out.println(decoder.decode(bb));//将byteBuffer转换为charBuffer

文件锁FileLock

文件锁用于阻止多个进程并发修改同一个文件

在NIO中提供了FileLock支持文件锁定功能

  • 在FileChannel中提供了lock/tryLock方法可以获取文件锁对象以实现锁定文件,避免同时修改

    • lock()用于锁定某个文件,如果无法获取文件锁,则程序一直阻塞等待
    • tryLock()是尝试锁定文件,如果可以锁定则直接返回该文件锁,如果不能锁定也是立即返回 null,不会出现阻塞等待
  • 锁可以是排它锁或者共享锁,shared=true表示共享,允许多个进程同时读取文件,但是会阻止其 它进程获取该文件的排他锁

文件锁是建议性质,不是强制性。

FileChannel channel=new FileOutputStream("aa.txt").getChannel();
FileLock lock=channel.tryLock(); //非阻塞性的申请文件加锁
lock.release(); //释放锁

AIO

异步非阻塞式

JDK7新增了一些和文件、网络IO相关的API,AIO最大的特性就是异步处理能力,一般用于网络编程和大 文件IO处理中。AIO其实就是一种在读写操作结束之前允许执行其它操作的IO处理,等读写执行完毕自 动通知调用后续处理

提供了3个异步处理通道

  • AsynchronousFileChannel用于文件的异步读写
  • AsynchronousSocketChannel用于socket客户端异步读写
  • AsynchronousServerSocketChannel用于serverSocket服务端异步读写

异步无非是通知系统做一件事情,然后自己做其它事情,异步调用如何进行后续处理,处理方式有2种:

  • 将来式,主线程发起异步请求,轮询并等待结果使用
  • 回调式,异步回调

AIO的具体实施需要充分调用操作系统OS参与,IO需要OS支持,并发也同样需要OS支持,所以OS不同 时执行执行性能方便差异会比较明显,因此在具体的应用种AIO使用并不是很广泛

将来式异步读取

使用Future用来保存异步操作的处理结果

Path path = Paths.get("data/a1.txt"); // 从语义的角度上说比File好一些,用于封装一个文件或者文件夹对象
AsynchronousFileChannel channel = AsynchronousFileChannel.open(path,
StandardOpenOption.READ);
ByteBuffer buffer = ByteBuffer.allocate(1024);
Future<Integer> future = channel.read(buffer, 0);

int readNum = future.get();
buffer.flip();
CharBuffer cb = CharBuffer.allocate(1024);
CharsetDecoder decoder = Charset.forName("UTF-8").newDecoder();
decoder.decode(buffer,cb,false);
cb.flip();

String ss=new String(cb.array(),0,cb.limit());
System.out.println(ss);

回调式异步读取

回调式是采用事件处理技术实现的

Path path = Paths.get("data/a1.txt"); // 从语义的角度上说比File好一些,用于封装一
个文件或者文件夹对象
AsynchronousFileChannel channel = AsynchronousFileChannel.open(path,
StandardOpenOption.READ);
ByteBuffer buffer = ByteBuffer.allocate(1024);
channel.read(buffer, 0, buffer, new CompletionHandler<Integer, ByteBuffer>()
{
// 当读取完成后自动执行的方法
	public void completed(Integer result, ByteBuffer attachment) {
		System.out.println(Thread.currentThread().getName() + "读取数据完
毕!");
}
// 当读取处理过程中出错时自动执行的方法
	public void failed(Throwable exc, ByteBuffer attachment) {
		System.out.println(Thread.currentThread().getName() + "读取数据出现异常:" + exc);
	}
});
System.out.println("立即看到结果!");
while (true) {
	System.out.println(Thread.currentThread().getName() + "sleep....");
	Thread.sleep(1000);
}

BIO vs NIO vs AIO

概念

  • BIO:同步阻塞,实现模式为一个连接一个线程,就是客户端连接请求时,服务器需要启动一个线程进 行处理
  • NIO:同步非阻塞,实现模式为一个连接一个线程,客户端发起请求时会注册到selector多路复用器上, 多路复用器会轮询所有有IO请求时才启动一个线程进行处理
  • AIO:异步非阻塞,一个有效请求一个线程,客户端IO请求都是由OS操作系统先完成了再通知服务器应 用启动线程进行处理

场景

  • BIO适用于连接数目比较小且固定的架构,这种方式对服务器资源要求较高,并发局限于应用中, JDK1.4以前的唯一选择,但是程序直观、简单且易于理解
  • NIO适用于连接数目比较多且连接时间较短的架构中,例如聊天服务器,并发局限于当前应用中, 编程相对比较复杂,从JDK1.4开始支持
  • AIO适用于连接数目多且连接时间长的架构中,例如相册服务器,可以充分调用OS参与同步IO处 理,而且proactor用于异步IO中

区别

  • 同步阻塞:用户进程发起一个IO操作请求后,必须等待IO操作完成后,用户进程才能继续运行后续 操作,否则用户进程阻塞等待
  • 同步非阻塞:用户进程发起一个IO操作请求后可以返回做其它事情,但是用户进程需要不时询问 IO操作是否已经就绪,这里会造成不必要的CPU资源浪费
    IO适用于连接数目比较小且固定的架构,这种方式对服务器资源要求较高,并发局限于应用中, JDK1.4以前的唯一选择,但是程序直观、简单且易于理解
  • NIO适用于连接数目比较多且连接时间较短的架构中,例如聊天服务器,并发局限于当前应用中, 编程相对比较复杂,从JDK1.4开始支持
  • AIO适用于连接数目多且连接时间长的架构中,例如相册服务器,可以充分调用OS参与同步IO处 理,而且proactor用于异步IO中

区别

  • 同步阻塞:用户进程发起一个IO操作请求后,必须等待IO操作完成后,用户进程才能继续运行后续 操作,否则用户进程阻塞等待
  • 同步非阻塞:用户进程发起一个IO操作请求后可以返回做其它事情,但是用户进程需要不时询问 IO操作是否已经就绪,这里会造成不必要的CPU资源浪费
  • 异步非阻塞:用户进程发起一个IO操作请求后可以立即返回做其它事情,等到IO操作真正完成后, 应用程序会收到IO操作完成的通知,此时用户进程只需要对数据进行处理即可
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值