文章目录

  • 1 IO,NIO,AIO
    • 1.1 各个基本概念
      • 1.1.1 同步阻塞IO
      • 1.1.2 同步非阻塞 IO模型
      • 1.1.3 IO复用模型 (NIO 方法)
      • 1.1.4 异步非阻塞 (AIO方法,JDK7 发布)
    • 1.2 NIO详解
      • 1.2.1 Buffer读写数据
      • 1.2.2 Buffer和clear方法
      • 1.2.3 Buffer参数
      • 1.2.4 散射&聚集
      • 1.2.5 NIO简单例子讲解
    • 1.3 Java AIO
    • 1.4 使用例子
      • 1.4.1 散射聚集
      • 1.4.2 I/O 的三种方式对比试验
      • 1.4.3 DirectBuffer VS ByteBuffer
      • 1.4.4 对DirectBuffer监控代码
      • 1.4.5 AIO使用例子

1 IO,NIO,AIO

1.1 各个基本概念

Java I/O 的相关方法如下所述

1.1.1 同步阻塞IO

同步并阻塞 (I/O 方法):
服务器实现模式为一个连接启动一个线程,每个线程亲自处理 I/O 并且一直等待 I/O 直到完成,即客户端有连接请求时服务器端就需要启动一个线程进行处理。但是如果这个连接不做任何事情就会造成不必要的线程开销,当然可以通过线程池机制改善这个缺点。
I/O 的局限是它是面向流的、阻塞式的、串行的一个过程。对每一个客户端的 Socket 连接 I/O 都需要一个线程来处理,而且在此期间,这个线程一直被占用,直到 Socket 关闭。在这期间,TCP的连接、数据的读取、数据的返回都是被阻塞的。也就是说这期间大量浪费了 CPU 的时间片和线程占用的内存资源。此外,每建立一个 Socket 连接时,同时创建一个新线程对该 Socket 进行单独通信 (采用阻塞的方式通信)。这种方式具有很快的响应速度,并且控制起来也很简单。在连接数较少的时候非常有效,但是如果对每一个连接都产生一个线程无疑是对系统资源的一种浪费,如果连接数较多将会出现资源不足的情况

优缺点:

  • 优点:模型简单,实现难度低,适用于并发量较小的应用开发。
  • 缺点:IO调用阶段和IO执行阶段都会阻塞
  • 生活场景:某天,你跟朋友去奶茶店买奶茶,点完奶茶后后,由于你们不知道奶茶什么时候才能做好,所以你们就只能一直等着,其他什么事情也不能干

1.1.2 同步非阻塞 IO模型

非阻塞IO模型中,应用进程需要不断询问内核数据是否就绪,在内核数据还未就绪时,应用进程还可以做其他事情。

优缺点:

  • 优点:模型简单,实现难度低;与阻塞IO模型对比,它在等待数据报的过程中,进程并没有阻塞,它可以做其他的事情。
  • 缺点:轮询发送 recvform,消耗CPU 资源。
  • 生活场景:你和朋友去奶茶店买奶茶,吸取了上一次的教训,点完奶茶后顺便去逛了逛商场。由于担心会错过取餐,所以你们就每隔一段时间就来问下服务员,你们的奶茶做好了没有,来来回回好多回,若干次后,终于问到奶茶已经准备好了,然后你们就开心的喝了起来。

1.1.3 IO复用模型 (NIO 方法)

多个进程的IO注册到一个复用器(select)上,select 会监听所有注册进来的IO。如果内核的数据包没有准备好,调用 select 的进程将会被阻塞,而当任一IO在内核缓冲区中有数据,select 调用就会返回可读条件,然后进程再进行recvform系统调用,内核将数据拷贝到用户空间,注意这个过程是阻塞的。

注意IO 复用模型在第一个阶段和第二个阶段其实都有阻塞,第一个阶段阻塞于 select 调用,第二个阶段阻塞于数据复制。

服务器实现模式为一个请求启动一个线程,每个线程亲自处理I/O,但是另外的线程轮询检查是否 I/O 准备完毕,不必等待 I/O 完成,即客户端发送的连接请求都会注册到多路复用器上,多路复用器轮询到连接有 I/O 请求时才启动一个线程进行处理。NIO 则是面向缓冲区,非阻塞式的,基于选择器的,用一个线程来轮询监控多个数据传输通道,哪个通道准备好了 (即有一组可以处理的数据) 就处理哪个通道。服务器端保存一个 Socket 连接列表,然后对这个列表进行轮询,如果发现某个 Socket 端口上有数据可读时,则调用该 Socket 连接的相应读操作;如果发现某个 Socket 端口上有数据可写时,则调用该 Socket 连接的相应写操作;如果某个端口的 Socket 连接已经中断,则调用相应的析构方法关闭该端口。这样能充分利用服务器资源,效率得到大幅度提高;

优缺点:

  • 优点:适用于高并发应用程序。
  • 缺点:模型复杂,实现、开发难度较大。
  • 生活场景:如果每个人都过一会就来问一下奶茶好了没有,奶茶店的压力也太大了。于是奶茶店想到了一个办法,找一个中间人(select)挡在奶茶店前面,顾客(应用进程)询问那个中间人奶茶好了没有(对应多个进程的IO注册到一个复用器(select)上),如果没有好就让顾客等待(应用进程阻塞于 select 调用)。中间人持续查看顾客的奶茶是否准备好,如果有一个人的奶茶准备好了就会去通知那个人可以取了(而当任一IO在内核缓冲区中有数据,select调用就会返回可读条件,然后进程再进行recvform系统调用)。

1.1.4 异步非阻塞 (AIO方法,JDK7 发布)

异步 IO 是基于事件回调机制实现的,也就是应用操作之后会直接返回,不会堵塞在那里,当后台处理完成,操作系统会通知相应的线程进行后续的操作
服务器实现模式为一个有效请求启动一个线程,客户端的 I/O 请求都是由操作系统先完成了再通知服务器应用去启动线程进行处理(回调),每个线程不必亲自处理 I/O,而是委派操作系统来处理,并且也不需要等待 I/O 完成,如果完成了操作系统会另行通知的。该模式采用了 Linuxepoll 模型。

点击了解 Linux之select、poll、epoll讲解

1.2 NIO详解

在连接数不多的情况下,传统 I/O 模式编写较为容易,使用上也较为简单。但是随着连接数的不断增多,传统 I/O 处理每个连接都需要消耗一个线程,而程序的效率,当线程数不多时是随着线程数的增加而增加,但是到一定的数量之后,是随着线程数的增加而减少的。所以 传统阻塞式 I/O 的瓶颈在于不能处理过多的连接
非阻塞式 I/O 出现的目的就是为了解决这个瓶颈。非阻塞IO处理连接的线程数和连接数没有联系,例如系统处理 10000 个连接,非阻塞 I/O 不需要启动 10000 个线程,你可以用 1000 个,也可以用 2000 个线程来处理。因为非阻塞 IO 处理连接是异步的,当某个连接发送请求到服务器,服务器把这个连接请求当作一个请求事件,并把这个事件分配给相应的函数处理。我们可以把这个处理函数放到线程中去执行,执行完就把线程归还,这样一个线程就可以异步的处理多个事件。而阻塞式 I/O 的线程的大部分时间都被浪费在等待请求上了

I/O NIO
面向流 面向缓冲
阻塞 IO 非阻塞 IO
选择器

NIO 是基于块 (Block) 的,它以块为基本单位处理数据。在 NIO 中,最为重要的两个组件是缓冲Buffer 和通道 Channel。缓冲是一块连续的内存块,是 NIO 读写数据的中转地。通道标识缓冲数据的源头或者目的地,它用于向缓冲读取或者写入数据,是访问缓冲的接口。Channel 是一个双向通道,即可读,也可写。Stream 是单向的。应用程序不能直接对 Channel 进行读写操作,而必须通过 Buffer 来进行,即 Channel 是通过 Buffer 来读写数据的。
使用 Buffer 读写数据一般遵循以下四个步骤:

  • 写入数据到 Buffer
  • 调用 flip() 方法;
  • Buffer 中读取数据;
  • 调用 clear() 方法或者 compact() 方法。

当向 Buffer 写入数据时,Buffer 会记录下写了多少数据。一旦要读取数据,需要通过 flip() 方法将 Buffer 从写模式切换到读模式。在读模式下,可以读取之前写入到 Buffer 的所有数据。
一旦读完了所有的数据,就需要清空缓冲区,让它可以再次被写入。有两种方式能清空缓冲区:调用 clear() 或 compact() 方法。clear() 方法会清空整个缓冲区。compact() 方法只会清除已经读过的数据。任何未读的数据都被移到缓冲区的起始处,新写入的数据将放到缓冲区未读数据的后面。
Buffer 有多种类型,不同的 Buffer 提供不同的方式操作 Buffer 中的数据

1.2.1 Buffer读写数据

Buffer 写数据有两种情况:

  • Channel 写到 Buffer,如例子中 Channel 从文件中读取数据,写到 Channel
  • 直接调用 put 方法,往里面写数据。

Buffer 中读取数据有两种方式:

  • Buffer 读取数据到 Channel
  • 使用 get() 方法从 Buffer 中读取数据。

Bufferrewin 方法将 position 设回 0,所以可以重读 Buffer 中的所有数据。limit 保持不变,仍然表示能从 Buffer 中读取多少个元素(byte、char 等)。

1.2.2 Buffer和clear方法

clear()compact() 方法
一旦读完 Buffer 中的数据,需要让 Buffer 准备好再次被写入。可以通过 clear() 或 compact() 方法来完成。
如果调用的是 clear() 方法,position 将被设回 0limit 被设置成 capacity 的值。换句话说,Buffer 被清空了。Buffer 中的数据并未清除,只是这些标记告诉我们可以从哪里开始往 Buffer 里写数据。
如果 Buffer 中有一些未读的数据,调用 clear() 方法,数据将被遗忘,意味着不再有任何标记会告诉你哪些数据被读过,哪些还没有。如果 Buffer 中仍有未读的数据,且后续还需要这些数据,但是此时想要先写些数据,那么使用 compact() 方法。compact() 方法将所有未读的数据拷贝到 Buffer 起始处。然后将 position 设到最后一个未读元素正后面。limit 属性依然像 clear() 方法一样,设置成 capacity。现在 Buffer 准备好写数据了,但是不会覆盖未读的数据。

1.2.3 Buffer参数

Buffer 有 3 个重要的参数:位置 (position)、容量 (capacity) 和上限 (limit)。
capacity 是指 Buffer 的大小,在 Buffer 建立的时候已经确定。
limitBuffer 处于写模式,指还可以写入多少数据;处于读模式,指还有多少数据可以读。
positionBuffer 处于写模式,指下一个写数据的位置;处于读模式,当前将要读取的数据的位置。每读写一个数据,position+1,也就是limitpositionBuffer 的读/写时的含义不一样。当调用 Bufferflip 方法,由写模式变为读模式时,limit(读)=position(写)position(读) =0

1.2.4 散射&聚集

NIO 提供了处理结构化数据的方法,称之为散射 (Scattering) 和聚集 (Gathering)。
散射是指将数据读入一组 Buffer 中,而不仅仅是一个。聚集与之相反,指将数据写入一组 Buffer 中。
散射和聚集的基本使用方法和对单个 Buffer 操作时的使用方法相当类似。在散射读取中,通道依次填充每个缓冲区。填满一个缓冲区后,它就开始填充下一个,在某种意义上,缓冲区数组就像一个大缓冲区。在已知文件具体结构的情况下,可以构造若干个符合文件结构的 Buffer,使得各个 Buffer 的大小恰好符合文件各段结构的大小。此时,通过散射读的方式可以一次将内容装配到各个对应的 Buffer 中,从而简化操作。如果需要创建指定格式的文件,只要先构造好大小合适的 Buffer 对象,使用聚集写的方式,便可以很快地创建出文件

1.2.5 NIO简单例子讲解

java例子demo如下

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.nio.charset.Charset;
import java.util.Iterator;
import java.util.Set;public class NIOServerDemo {public static void main(String[] args) throws IOException {Selector serverSelector = Selector.open();Selector clientSelector = Selector.open();new Thread(() -> {try {// 对应IO编程中的服务端启动ServerSocketChannel listenerChannel = ServerSocketChannel.open();listenerChannel.socket().bind(new InetSocketAddress(8000));listenerChannel.configureBlocking(false);listenerChannel.register(serverSelector, SelectionKey.OP_ACCEPT);while (true) {// 监测是否有新连接,这里的1指阻塞的时间为 1msif (serverSelector.select(1) > 0) {Set<SelectionKey> set = serverSelector.selectedKeys();Iterator<SelectionKey> keyIterator = set.iterator();while (keyIterator.hasNext()) {SelectionKey key = keyIterator.next();if (key.isAcceptable()) {try {// (1)每来一个新连接,不需要创建一个线程,而是直接注册到clientSelectorSocketChannel clientChannel = ((ServerSocketChannel) key.channel()).accept();clientChannel.configureBlocking(false);clientChannel.register(clientSelector, SelectionKey.OP_READ);} finally {keyIterator.remove();}}}}}} catch (IOException ignored) {}}).start();new Thread(() -> {try {while (true) {// (2)批量轮询哪些连接有数据可读,这里的1指阻塞的时间为 1msif (clientSelector.select(1) > 0) {Set<SelectionKey> set = clientSelector.selectedKeys();Iterator<SelectionKey> keyIterator = set.iterator();while (keyIterator.hasNext()) {SelectionKey key = keyIterator.next();if (key.isReadable()) {try {SocketChannel clientChannel = (SocketChannel) key.channel();ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
// (3)面向BufferclientChannel.read(byteBuffer);byteBuffer.flip();System.out.println(Charset.defaultCharset().newDecoder(). decode(byteBuffer).toString());} finally {keyIterator.remove();key.interestOps(SelectionKey.OP_READ);}}}}}} catch (IOException ignored) {}}).start();}
}

先对照NIO来解释一下核心思路。
NIO模型中通常会有两个线程,每个线程都绑定一个轮询器Selector。在这个例子中,serverSelector负责轮询是否有新连接,clientSelector负责轮询连接是否有数据可读。
服务端监测到新连接之后,不再创建一个新线程,而是直接将新连接绑定到clientSelector上,这样就不用IO模型中的1万个while循环死等
clientSelector被一个while死循环包裹着,如果在某一时刻有多个连接有数据可读,那么通过clientSelector.select(1)方法可以轮询出来,进而批量处理。
数据的读写面向Buffer

1.3 Java AIO

AIO 相关的类和接口:

  • java.nio.channels.AsynchronousChannel:标记一个 Channel 支持异步 IO 操作;
  • java.nio.channels.AsynchronousServerSocketChannelServerSocketAIO 版本,创建 TCP 服务端,绑定地址,监听端口等;
  • java.nio.channels.AsynchronousSocketChannel:面向流的异步 Socket Channel,表示一个连接;
  • java.nio.channels.AsynchronousChannelGroup:异步 Channel 的分组管理,目的是为了资源共享。一个 AsynchronousChannelGroup 绑定一个线程池,这个线程池执行两个任务:处理 IO 事件和派发 CompletionHandlerAsynchronousServerSocketChannel 创建的时候可以传入一个 AsynchronousChannelGroup,那么通过 AsynchronousServerSocketChannel 创建的 AsynchronousSocketChannel 将同属于一个组,共享资源;
  • java.nio.channels.CompletionHandler:异步 IO 操作结果的回调接口,用于定义在 IO 操作完成后所作的回调工作。AIOAPI 允许两种方式来处理异步操作的结果:返回的 Future 模式或者注册 CompletionHandler,推荐用 CompletionHandler 的方式,这些handler的调用是由 AsynchronousChannelGroup 的线程池派发的。这里线程池的大小是性能的关键因素。

1.4 使用例子

1.4.1 散射聚集

使用散射和聚集读写结构化文件

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;public class NIOScatteringandGathering {public void createFiles(String TPATH){try {ByteBuffer bookBuf = ByteBuffer.wrap("java 性能优化技巧".getBytes("utf-8"));
ByteBuffer autBuf = ByteBuffer.wrap("test".getBytes("utf-8"));
int booklen = bookBuf.limit();
int autlen = autBuf.limit();
ByteBuffer[] bufs = new ByteBuffer[]{bookBuf,autBuf};
File file = new File(TPATH);
if(!file.exists()){try {file.createNewFile();
} catch (IOException e) {// TODO Auto-generated catch block
e.printStackTrace();
}
}
try {FileOutputStream fos = new FileOutputStream(file);
FileChannel fc = fos.getChannel();
fc.write(bufs);
fos.close();
} catch (FileNotFoundException e) {// TODO Auto-generated catch block
e.printStackTrace();
} catch (IOException e) {// TODO Auto-generated catch block
e.printStackTrace();
}ByteBuffer b1 = ByteBuffer.allocate(booklen);
ByteBuffer b2 = ByteBuffer.allocate(autlen);
ByteBuffer[] bufs1 = new ByteBuffer[]{b1,b2};
File file1 = new File(TPATH);
try {FileInputStream fis = new FileInputStream(file);
FileChannel fc = fis.getChannel();
fc.read(bufs1);
String bookname = new String(bufs1[0].array(),"utf-8");
String autname = new String(bufs1[1].array(),"utf-8");
System.out.println(bookname+" "+autname);
} catch (FileNotFoundException e) {// TODO Auto-generated catch block
e.printStackTrace();
} catch (IOException e) {// TODO Auto-generated catch block
e.printStackTrace();
}} catch (UnsupportedEncodingException e) {// TODO Auto-generated catch block
e.printStackTrace();
}}public static void main(String[] args){NIOScatteringandGathering nio = new NIOScatteringandGathering();nio.createFiles("C:\\1.TXT");}
}

1.4.2 I/O 的三种方式对比试验

以下所示代码对传统 I/O、基于 ByteNIO、基于内存映射的 NIO 三种方式进行了性能上的对比,使用一个有 400 万数据的文件的读、写操作耗时作为评测依据。

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.nio.ByteBuffer;
import java.nio.IntBuffer;
import java.nio.MappedByteBuffer;
import java.nio.channels.FileChannel;public class NIOComparator {public void IOMethod(String TPATH){long start = System.currentTimeMillis();try {DataOutputStream dos = new DataOutputStream(new BufferedOutputStream(new FileOutputStream(new File(TPATH))));
for(int i=0;i<4000000;i++){dos.writeInt(i);//写入 4000000 个整数
}
if(dos!=null){dos.close();
}} catch (FileNotFoundException e) {// TODO Auto-generated catch block
e.printStackTrace();} catch (IOException e) {// TODO Auto-generated catch block
e.printStackTrace();}long end = System.currentTimeMillis();System.out.println(end - start);start = System.currentTimeMillis();try {DataInputStream dis = new DataInputStream(new BufferedInputStream(new FileInputStream(new File(TPATH))));
for(int i=0;i<4000000;i++){dis.readInt();
}
if(dis!=null){dis.close();
}
} catch (FileNotFoundException e) {// TODO Auto-generated catch block
e.printStackTrace();
} catch (IOException e) {// TODO Auto-generated catch block
e.printStackTrace();
}end = System.currentTimeMillis();System.out.println(end - start);}public void ByteMethod(String TPATH){long start = System.currentTimeMillis();try {FileOutputStream fout = new FileOutputStream(new File(TPATH));
FileChannel fc = fout.getChannel();//得到文件通道
ByteBuffer byteBuffer = ByteBuffer.allocate(4000000*4);//分配 Buffer
for(int i=0;i<4000000;i++){byteBuffer.put(int2byte(i));//将整数转为数组
}
byteBuffer.flip();//准备写
fc.write(byteBuffer);
} catch (FileNotFoundException e) {// TODO Auto-generated catch block
e.printStackTrace();
} catch (IOException e) {// TODO Auto-generated catch block
e.printStackTrace();
}long end = System.currentTimeMillis();System.out.println(end - start);start = System.currentTimeMillis();FileInputStream fin;
try {fin = new FileInputStream(new File(TPATH));
FileChannel fc = fin.getChannel();//取得文件通道
ByteBuffer byteBuffer = ByteBuffer.allocate(4000000*4);//分配 Buffer
fc.read(byteBuffer);//读取文件数据
fc.close();
byteBuffer.flip();//准备读取数据
while(byteBuffer.hasRemaining()){byte2int(byteBuffer.get(),byteBuffer.get(),byteBuffer.get(),byteBuffer.get());//将 byte 转为整数
}
} catch (FileNotFoundException e) {// TODO Auto-generated catch block
e.printStackTrace();
} catch (IOException e) {// TODO Auto-generated catch block
e.printStackTrace();
}end = System.currentTimeMillis();System.out.println(end - start);}public void mapMethod(String TPATH){long start = System.currentTimeMillis();//将文件直接映射到内存的方法try {FileChannel fc = new RandomAccessFile(TPATH,"rw").getChannel();
IntBuffer ib = fc.map(FileChannel.MapMode.READ_WRITE, 0, 4000000*4).asIntBuffer();
for(int i=0;i<4000000;i++){ib.put(i);
}
if(fc!=null){fc.close();
}
} catch (FileNotFoundException e) {// TODO Auto-generated catch block
e.printStackTrace();
} catch (IOException e) {// TODO Auto-generated catch block
e.printStackTrace();
}long end = System.currentTimeMillis();System.out.println(end - start);start = System.currentTimeMillis();try {FileChannel fc = new FileInputStream(TPATH).getChannel();
MappedByteBuffer lib = fc.map(FileChannel.MapMode.READ_ONLY, 0, fc.size());
lib.asIntBuffer();
while(lib.hasRemaining()){lib.get();
}
if(fc!=null){fc.close();
}
} catch (FileNotFoundException e) {// TODO Auto-generated catch block
e.printStackTrace();
} catch (IOException e) {// TODO Auto-generated catch block
e.printStackTrace();
}end = System.currentTimeMillis();System.out.println(end - start);}public static byte[] int2byte(int res){byte[] targets = new byte[4];targets[3] = (byte)(res & 0xff);//最低位targets[2] = (byte)((res>>8)&0xff);//次低位targets[1] = (byte)((res>>16)&0xff);//次高位targets[0] = (byte)((res>>>24));//最高位,无符号右移return targets;}public static int byte2int(byte b1,byte b2,byte b3,byte b4){return ((b1 & 0xff)<<24)|((b2 & 0xff)<<16)|((b3 & 0xff)<<8)|(b4 & 0xff);}public static void main(String[] args){NIOComparator nio = new NIOComparator();nio.IOMethod("c:\\1.txt");nio.ByteMethod("c:\\2.txt");nio.ByteMethod("c:\\3.txt");}
}

1.4.3 DirectBuffer VS ByteBuffer

NIOBuffer 还提供了一个可以直接访问系统物理内存的类 DirectBufferDirectBuffer 继承自 ByteBuffer,但和普通的 ByteBuffer 不同。
普通的 ByteBuffer 仍然在 JVM 堆上分配空间,其最大内存受到最大堆的限制,而 DirectBuffer 直接分配在物理内存上,并不占用堆空间。在对普通的 ByteBuffer 访问时,系统总是会使用一个内核缓冲区进行间接的操作。而 DirectrBuffer 所处的位置,相当于这个内核缓冲区
因此,使用 DirectBuffer 是一种更加接近系统底层的方法,所以,它的速度比普通的 ByteBuffer 更快。DirectBuffer 相对于ByteBuffer而言,读写访问速度快很多,但是创建和销毁 DirectrBuffer 的花费却比 ByteBuffer 高。DirectBuffer 与 ByteBuffer 相比较的代码如下所示

import java.nio.ByteBuffer;public class DirectBuffervsByteBuffer {public void DirectBufferPerform(){long start = System.currentTimeMillis();ByteBuffer bb = ByteBuffer.allocateDirect(500);//分配 DirectBufferfor(int i=0;i<100000;i++){for(int j=0;j<99;j++){bb.putInt(j);}bb.flip();for(int j=0;j<99;j++){bb.getInt(j);}}bb.clear();long end = System.currentTimeMillis();System.out.println(end-start);start = System.currentTimeMillis();for(int i=0;i<20000;i++){ByteBuffer b = ByteBuffer.allocateDirect(10000);//创建 DirectBuffer}end = System.currentTimeMillis();System.out.println(end-start);}public void ByteBufferPerform(){long start = System.currentTimeMillis();ByteBuffer bb = ByteBuffer.allocate(500);//分配 DirectBufferfor(int i=0;i<100000;i++){for(int j=0;j<99;j++){bb.putInt(j);}bb.flip();for(int j=0;j<99;j++){bb.getInt(j);}}bb.clear();long end = System.currentTimeMillis();System.out.println(end-start);start = System.currentTimeMillis();for(int i=0;i<20000;i++){ByteBuffer b = ByteBuffer.allocate(10000);//创建 ByteBuffer}end = System.currentTimeMillis();System.out.println(end-start);}public static void main(String[] args){DirectBuffervsByteBuffer db = new DirectBuffervsByteBuffer();db.ByteBufferPerform();db.DirectBufferPerform();}
}
运行结果
920
110
531
390

可知,频繁创建和销毁 DirectBuffer 的代价远远大于在堆上分配内存空间。
使用参数-XX:MaxDirectMemorySize=200M –Xmx200MVM Arguments 里面配置最大 DirectBuffer最大堆空间,代码中分别请求了 200M 的空间,如果设置的堆空间过小,例如设置 1M,会抛出错误如下所示

Error occurred during initialization of VM
Too small initial heap for new size specified

1.4.4 对DirectBuffer监控代码

DirectBuffer 的信息不会打印在 GC 里面,因为 GC 只记录了堆空间的内存回收。可以看到,由于 ByteBuffer 在堆上分配空间,因此其 GC 数组相对非常频繁,在需要频繁创建 Buffer 的场合,由于创建和销毁 DirectBuffer 的代码比较高昂,不宜使用 DirectBuffer。但是如果能将DirectBuffer进行复用,可以大幅改善系统性能

import java.lang.reflect.Field;public class monDirectBuffer {public static void main(String[] args){try {Class c = Class.forName("java.nio.Bits");//通过反射取得私有数据
Field maxMemory = c.getDeclaredField("maxMemory");
maxMemory.setAccessible(true);
Field reservedMemory = c.getDeclaredField("reservedMemory");
reservedMemory.setAccessible(true);
synchronized(c){Long maxMemoryValue = (Long)maxMemory.get(null);
Long reservedMemoryValue = (Long)reservedMemory.get(null);
System.out.println("maxMemoryValue="+maxMemoryValue);
System.out.println("reservedMemoryValue="+reservedMemoryValue);
}
} catch (ClassNotFoundException e) {// TODO Auto-generated catch block
e.printStackTrace();
} catch (SecurityException e) {// TODO Auto-generated catch block
e.printStackTrace();
} catch (NoSuchFieldException e) {// TODO Auto-generated catch block
e.printStackTrace();
} catch (IllegalArgumentException e) {// TODO Auto-generated catch block
e.printStackTrace();
} catch (IllegalAccessException e) {// TODO Auto-generated catch block
e.printStackTrace();
}}
}
运行输出
maxMemoryValue=67108864
reservedMemoryValue=0

由于 NIO 使用起来较为困难,所以许多公司推出了自己封装 JDK NIO 的框架,例如 Apache 的 MinaJBoss 的 NettySun 的 Grizzly 等等,这些框架都直接封装了传输层的 TCPUDP 协议,其中 Netty 只是一个 NIO 框架,它不需要 Web 容器的额外支持,也就是说不限定 Web 容器

1.4.5 AIO使用例子

服务端

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.AsynchronousServerSocketChannel;
import java.nio.channels.AsynchronousSocketChannel;
import java.nio.channels.CompletionHandler;
import java.util.concurrent.ExecutionException;public class SimpleServer {public SimpleServer(int port) throws IOException {
final AsynchronousServerSocketChannel listener = AsynchronousServerSocketChannel.open().bind(new InetSocketAddress(port));
//监听消息,收到后启动 Handle 处理模块
listener.accept(null, new CompletionHandler<AsynchronousSocketChannel, Void>() {public void completed(AsynchronousSocketChannel ch, Void att) {
listener.accept(null, this);// 接受下一个连接
handle(ch);// 处理当前连接
}@Override
public void failed(Throwable exc, Void attachment) {// TODO Auto-generated method stub} });
}public void handle(AsynchronousSocketChannel ch) {
ByteBuffer byteBuffer = ByteBuffer.allocate(32);//开一个 Buffer
try { ch.read(byteBuffer).get();//读取输入
} catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace();
} catch (ExecutionException e) { // TODO Auto-generated catch block e.printStackTrace();
}
byteBuffer.flip();
System.out.println(byteBuffer.get());
// Do something
} }

客户端程序

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.AsynchronousSocketChannel;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;public class SimpleClientClass {private AsynchronousSocketChannel client;
public SimpleClientClass(String host, int port) throws IOException, InterruptedException, ExecutionException { this.client = AsynchronousSocketChannel.open(); Future<?> future = client.connect(new InetSocketAddress(host, port)); future.get();
} public void write(byte b) { ByteBuffer byteBuffer = ByteBuffer.allocate(32);System.out.println("byteBuffer="+byteBuffer);byteBuffer.put(b);//向 buffer 写入读取到的字符 byteBuffer.flip();System.out.println("byteBuffer="+byteBuffer);client.write(byteBuffer);
} }

Main 函数

import java.io.IOException;
import java.util.concurrent.ExecutionException;import org.junit.Test;public class AIODemoTest {@Test
public void testServer() throws IOException, InterruptedException { SimpleServer server = new SimpleServer(9021); Thread.sleep(10000);//由于是异步操作,所以睡眠一定时间,以免程序很快结束
} @Test
public void testClient() throws IOException, InterruptedException, ExecutionException {
SimpleClientClass client = new SimpleClientClass("localhost", 9021); client.write((byte) 11);
}public static void main(String[] args){AIODemoTest demoTest = new AIODemoTest();
try {demoTest.testServer();
} catch (IOException e) {// TODO Auto-generated catch block
e.printStackTrace();
} catch (InterruptedException e) {// TODO Auto-generated catch block
e.printStackTrace();
}
try {demoTest.testClient();
} catch (IOException e) {// TODO Auto-generated catch block
e.printStackTrace();
} catch (InterruptedException e) {// TODO Auto-generated catch block
e.printStackTrace();
} catch (ExecutionException e) {// TODO Auto-generated catch block
e.printStackTrace();
}
}}

IO流之IO,NIO和AIO讲解相关推荐

  1. 关于java流的几个概念:IO、BIO、NIO、AIO,有几个人全知道?

    转载自 关于java流的几个概念:IO.BIO.NIO.AIO,有几个人全知道? 关于同步.阻塞的知识我之前的文章有介绍,所以关于流用到这些概念与之前多线程用的概念一样. 下面具体来看看java中的几 ...

  2. Java IO模型:BIO、NIO、AIO讲解

    文章目录 IO 首先:什么是IO? 为什么要改进IO? BIO.NIO.AIO BIO NIO NIO实现原理 Channel(通道) : Buffer(缓冲区): Selector(选择器) : A ...

  3. 详解磁盘IO、网络IO、零拷贝IO、BIO、NIO、AIO、IO多路复用(select、poll、epoll)

    文章很长,但是很用心! 文章目录 1. 什么是I/O 2. 磁盘IO 3. 网络IO 4. IO中断与DMA 5. 零拷贝IO 6. BIO 7. NIO 8. IO多路复用 8.1 select 8 ...

  4. java io流操作_十个Demo进行讲解Java中IO流的常用操作~

    好久不见的IO流 对IO流的学习,我记得还是初学Java基础的时候,后来找工作过程中经常看到有些招聘信息中写到熟悉IO流,现在想想IO流,真的是一脸懵逼,不说这么多废话了,IO流这次好好整理一下. 说 ...

  5. 菜鸟学习笔记:Java提升篇5(IO流1——IO流的概念、字节流、字符流、缓冲流、转换流)

    菜鸟学习笔记:Java IO流1--IO流的概念.字节流.字符流.缓冲流.转换流 IO流的原理及概念 节点流 字节流 文件读取 文件写出 文件拷贝 文件夹拷贝 字符流 文件读取 文件写出 处理流 缓冲 ...

  6. java 修改最大nio连接数_关于java流的几个概念:IO、BIO、NIO、AIO,有几个人全知道?...

    关于同步.阻塞的知识我之前的文章有介绍,所以关于流用到这些概念与之前多线程用的概念一样. 下面具体来看看java中的几种流 IO/BIO BIO就是指IO,即传统的Blocking IO,即同步并阻塞 ...

  7. Java的IO流 ,BIO NIO AIO 的区别?

    目录 1.在了解不同的IO之前先了解:同步与异步,阻塞与非阻塞的区别: 2.BIO NIO AIO 分别代表什么?(面试简答): 3.BIO和NIO.AIO的区别: 4.java中io流的分类: •  ...

  8. 【谈谈IO】BIO、NIO和AIO

    BIO: BIO是阻塞IO,体现在一个线程调用IO的时候,会挂起等待,然后Thread会进入blocked状态:这样线程资源就会被闲置,造成资源浪费,通常一个系统线程数是有限的,而且,Thread进入 ...

  9. 【转】深入分析JAVA IO(BIO、NIO、AIO)

    IO的基本常识 1.同步 用户进程触发IO操作并等待或者轮询的去查看IO操作是否完成 2.异步 用户触发IO操作以后,可以干别的事,IO操作完成以后再通知当前线程继续处理 3.阻塞 当一个线程调用 r ...

最新文章

  1. Boston房价PaddlePaddle测试程序
  2. LiveVideoStackCon 2018公布优秀出品人与讲师
  3. c++ 多个字符串排序_Python小白干货宝典:sorted()函数:列表元素排序
  4. python向dict里添加_Python有条件地向Dict添加键
  5. javascript设计模式-学习笔记
  6. 从字符串数组中寻找数字的元素
  7. 怎样才能提升代码质量?
  8. iOS使用自定义字体,比如楷体
  9. 【音频隐写提取】MP3Stego下载、命令、使用方法
  10. USACO 2019 February Contest Platinum T3: Mowing Mischief
  11. linux ntp时间立即同步命令_linux时间同步,ntpd、ntpdate 【转】
  12. 聊聊Uber公司迁移数据库这件事
  13. Android高性能音频之opensl es播放流程(七)
  14. 双路服务器56核系统推荐,华硕发布双路志强主板:最高支持56核,112线程
  15. 紫书已经基本学完现在开启紫书题目补完计划!!!
  16. 护眼灯真能护眼吗?学习专用的护眼灯推荐
  17. 从抓包砍到接口测试,五分钟看完全过程解析,还说你不会测试?
  18. 出队列c语言程序,队列的c语言实现
  19. 淘宝联盟API使用教程
  20. 报错stack smashing detected ***:terminated

热门文章

  1. 关闭电脑网页百度右侧百度热搜
  2. 计算机公共课2-Windows 7操作系统
  3. 显示器驱动程序 NVIDIA Windows Kernel Mode Driver Version 已停止响应 并且己成功恢复 解决方法...
  4. Grafana 使用下拉框参数控制sql结果
  5. 2021中国农业生产数字化研究报告 附下载
  6. executeUpdate()方法报错或者不执行
  7. 球自由落地教程—ps
  8. 【攻防世界】十二、功夫再高也怕菜刀
  9. java字符串排列组合算法
  10. 简支梁内力的计算机分析程序,简支梁的有限元分析过程.doc