[笔记]BIO、NIO、AIO

简述

BIO为同步阻塞BlockingIO,BIO适用于连接数小,结构固定的场景。
NIO为同步非阻塞NoBlockingIO,NIO适用于连接数大,但是任务小的场景。
AIO(NIO.2)为异步非阻塞,AIO适用于连接数大,且时间长的场景。
实际上,主要的分类还是BIO与NIO。

BIO及其经典写法

BIO就是传统IO,用流的方式处理IO。
所有的IO都被视为单个字节的移动,stream对象一次移动一个字节,流IO先把流转换为字节,再转换为对象。
BIO的经典写法如下:
读取输入流的写法

URL url=new URL("http://...");
InputStream is=url.openStream();//获取字节流
InputStreamReader ir=new InputStreamReader(is,"UTF-8");//转为字符流
BufferReader br=new BufferReader(ir);
String data=br.readLine();
while(data!=null){
  data=br.readLine();
}
br.close();
ir.close();
is.close();

Socket的客户端写法

Socket socket=new Socket(ip,port);//TCP握手建立连接
OutpurStream os=socket.getOutputStream();//向服务端的输出流
PrintWriter pw=new PrintWriter(os);//包装输出流
pw.write("message");
pw.flush();
socket.shutdownOutput();
//输入流
InputStream is=socket.getInputStream();
BufferReader br=new BufferReader(new InputStreamReader(is));
String data=br.readLine();
while(data!=null){
   data=br.readLine();
}
br.close();
is.close();
pw.close();
os.close();
socket.close();

Socket的服务端写法

ServerSocket serverSocket=new ServerSocket(port);
Socket socket=serverSocket.accept();//开始监听,阻塞等待客户端的连接,TCP三次握手后,才能完成
InputStream is=socket.getInputStream();
InputStreamReader isr=new InputStreamReader(is);
BufferReader br=new BufferReader(isr);
String data=br.readLine();
while(data!=null){
    data=br.readLine();
}
socket.shutdownInput();
//用输出流向客户端返回信息
OutputStream os=socket.getOutputStream();
PrintWriter pw=new PrintWriter(os);
pw.write("response");
pw.flush();
pw.close();
os.close();
br.close();
isr.close();
is.close();
socket.close();

NIO及其经典写法

NIO不再是流的概念,而是以块的方式处理IO。
不需要一点点地移动字节,这样每个线程只需要处理IO拿到的数据块,不需要等待IO,所以可以复用线程,更可以避免线程切换带来的上下文切换,提升效率。
NIO需要有一个专门的线程处理所有的IO事件,是事件驱动的。

以SocketIO为例,NIO的客户端写法如下:
创建通道和管理器

SocketChannel channel=SocketChannel.open();//通道
channel.configBlocking(false);//非阻塞
this.selector=Selector.open();//通道管理器
channel.connect(new InetSocketAddress(ip,port));//需要通过channel.finishConnect才能完成连接
channel.register(selector,SelectionKey.OP_CONNECT);//通道管理器监听

轮询事件处理

public void listen() throws IOException{
    while(true){
        Iterator itr=this.selector.selectKeys().iterator();
        while(itr.hasNext()){
           SelectionKey key=(SelectionKey)itr.next();
           itr.remove();
           if(key.isConnectable()){
              SocketChannel channel=(SelectionKey)key.channel();
              if(channel.isConnectionPending){
                 channel.finishConnect();
              }
              channel.configBlocking(false);
              channel.write(ByteBuffer.wrap(new String("Message content'").getBytes()));
              channel.register(this.selector,SelectionKey.OP_READ);
           }else if(key.isReadable()){
             ...
           }
        }
    }
}

NIO的服务端写法如下:
创建通道和管理器

ServerSocketChannel channel=ServerSocketChannel.open();
channel.configBlocking(false);
this.selector=Selector.open();
channel.socket().bind(new InetSocketAddress(port));//server端用bind,client端用connect
channel.register(selector,SelectionKey.OP_ACCEPT);//注册感兴趣的事件

处理事件

public void listen() throws IOException{
  while(true){
    selector.select();//如果没有感兴趣事件,会一直阻塞
    Iterator itr=this.selector.selectKeys().iterator();
    while(itr.hasNext()){
SelectionKey key=(SelectionKey)itr.next();
           itr.remove();
           if(key.isConnectable()){
              SocketChannel channel=(SelectionKey)key.channel();
              if(channel.isConnectionPending){
                 channel.finishConnect();
              }
              channel.configBlocking(false);
              channel.write(ByteBuffer.wrap(new String("Message content'").getBytes()));
              channel.register(this.selector,SelectionKey.OP_READ);
           }else if(key.isReadable()){
             ...
           }
    }
  }
}

NIO详解

NIO有三个核心对象Buffer、Channel、Selector,其中Channel代替了流,Buffer是流的容器对象,写入Channel需要先经过Buffer,读取Channel也需要先读进Buffer。

Channel是直接读写数据的对象,但它不是流,Channel是双向的(操作系统底层通常都是双向的,所以Channel比流更贴近真实情况),可以异步读写,Channel不允许应用层直接操作。
NIO中的Channel包括:
1.SocketChannel TCP网络IO
2.ServerSocketChannel 监听TCP连接
3.DatagramChannel UDP网络IO
4.FileChannel 文件IO

Buffer是应用层的操作对象,应用层不能直接操作Channel层,只能操作Buffer层,Buffer是个中转池,实质是个数组,能对IO数据结构化访问,而且可以跟踪系统的读写进程。
Buffer的读写功能包括:
1.写入到Buffer
2.从Buffer读出
3.用filp切换读写模式
4.用clear清空buffer
5.用compact压紧,就是清除buffer中已经读过的数据,未读过的数据挪到开头。
Buffer的flip,clear,compact等操作,实质都是设置数组的position、limit和capacity,position代表从哪里开始处理,limit代表处理到哪里,capacity代表容量长度。

Selector是NIO的核心实现,是管理channel通道的对象,每个线程通过1个Selector管理多个Channel对象,Selector注册并监听多个Channel,根据监听事件决定Channel的读写。
使用Selector,首先要打开Selector
Selector selector=Selector.open();
然后要注册到Channel
channel.configueBlocking(false);//设置为异步模式
SelectionKey key=channel.register(selector, SelectionKey.OP_READ | SelectionKey.OP_WRITE);
SelectionKey有OP_CONNECT、OP_ACCEPT、OP_READ、OP_WRITE四种。分别是成功连接,可以接收连接,可以读,可以写。
SelectionKey可以提供Channel和Selector,可以继续附加对象,在selector中监控的所有Channel,都可以通过SelectionKey来间接得到,通过Set<SelectionKey> keys=selector.selectedKeys();可以得到数据集合,通过遍历这个集合,就能检查selector的所有channel。
处理完channel后,我们需要自己手动把selectionKey从数据集中remove挪出来。

epoll

epoll是底层操作系统对NIO的支持机制,epoll用红黑树管理被监控的socket句柄文件,如果socket的读写中断到了,就放进一个准备就绪list链表,仅当链表有数据时,才返回,没有数据就继续等待。

NIO比BIO的优势

BIO是给每个连接分配一个线程,所以在需要大量连接时,就需要大量的线程,线程管理会带来额外开销。
NIO是采用了通知机制等待连接返回数据,只有活动的IO才会占用线程,线程不会被IO阻塞,所以不需要大量的线程,相应的线程创建、销毁、切换等开销也可以省略,资源可以更加集中在业务处理上。NIO适合高并发场景。
BIO的流以字节为单位,一次输入流产生一个字节,一次输出流消费1个自己,缺点是处理速度慢,优点是在字节上为流做过滤器简单方便。
NIO的块以块为单位,每步操作产生或消费一个块,优点是处理速度快,缺点是在块上不能像流一样做简单方便的处理。
BIO和NIO的Socket客户端与服务端写法都不同,BIO客户端是直接Socket socket=new Socket(ip,port),服务端是先建立ServerSocket serverSocket=new ServerSocket(port),然后Socket socket=serverSocket.accept();NIO客户端是channel.connect(new InetSocketAddress(ip,port)),服务端是channel.socket.bind(new InetSocketAddress(port));

BIO和NIO在文件处理上的写法

BIO和NIO不仅有网络IO操作,也有文件IO操作,不过文件IO操作不支持异步,无法设置connect.configBlocking(false)。

    //BIO文件拷贝
    public static void fileCopy(String source, String target) throws IOException {
        try (InputStream in = new FileInputStream(source)) {
            try (OutputStream out = new FileOutputStream(target)) {
                byte[] buffer = new byte[4096];
                int bytesToRead;
                while((bytesToRead = in.read(buffer)) != -1) {
                    out.write(buffer, 0, bytesToRead);
                }
            }
        }
    }

    //NIO文件拷贝
    public static void fileCopyNIO(String source, String target) throws IOException {
            //声明源文件和目标文件
            FileInputStream fi=new FileInputStream(new File(src));
            FileOutputStream fo=new FileOutputStream(new File(dst));
            //获得传输通道channel
            FileChannel inChannel=fi.getChannel();
            FileChannel outChannel=fo.getChannel();
            //获得容器buffer
            ByteBuffer buffer=ByteBuffer.allocate(1024);
            while(true){
                //判断是否读完文件
                int eof =inChannel.read(buffer);
                if(eof==-1){
                    break;  
                }
                //重设一下buffer的position=0,limit=position
                buffer.flip();
                //开始写
                outChannel.write(buffer);
                //写完要重置buffer,重设position=0,limit=capacity
                buffer.clear();
            }
            inChannel.close();
            outChannel.close();
            fi.close();
            fo.close();
}

引用

深入浅出NIO Socket实现机制
Java NIO 详解(一)
Java NIO 详解(二)
java IO 流Stream 序列化Serializable 文件File

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 219,753评论 6 508
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,668评论 3 396
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 166,090评论 0 356
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 59,010评论 1 295
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 68,054评论 6 395
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,806评论 1 308
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,484评论 3 420
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,380评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,873评论 1 319
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 38,021评论 3 338
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,158评论 1 352
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,838评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,499评论 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 32,044评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,159评论 1 272
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,449评论 3 374
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 45,136评论 2 356

推荐阅读更多精彩内容

  • Java NIO(New IO)是从Java 1.4版本开始引入的一个新的IO API,可以替代标准的Java I...
    JackChen1024阅读 7,555评论 1 143
  • 简介 Java NIO 是由 Java 1.4 引进的异步 IO.Java NIO 由以下几个核心部分组成: Ch...
    永顺阅读 1,793评论 0 15
  • 前言: 之前的文章《Java文件IO常用归纳》主要写了Java 标准IO要注意的细节和技巧,由于网上各种学习途径,...
    androidjp阅读 2,907评论 0 22
  • 这两天了解了一下关于NIO方面的知识,网上关于这一块的介绍只是介绍了一下基本用法,没有系统的解释NIO与阻塞、非阻...
    Ruheng阅读 7,128评论 5 48
  • 1.map out详细安排 map out one's plans for the holiday 2.inter...
    Maei阅读 157评论 0 0