【挑战学习一百天冲刺实习面试】第二十一天:全面理解BIO、NIO、AIO

偶尔搞搞实习吧,然后学年鉴定等等杂事(还有刷抖音、打王者…)

一些基本概念

阻塞与非阻塞

指的是等待调用结果返回之前,调用方的状态

阻塞:发出请求等待请求结果时不能进行其他操作

非阻塞:发出请求等待请求结果时可以进行其他操作

同步与异步

指的是通信机制的区别,等待调用的结果的

同步:调用后必须等待结果才可以返回

异步:调用后可以不先知道结果直接返回,直到后面收到结果

四种排列组合

基于阻塞与非阻塞、同步与异步,IO操作可以分为四类:

  • 同步阻塞(告白时一直等着回复,同时不能做其他事)
  • 同步非阻塞(告白时一直等着回复,但是等的时候可以做别的事)
  • 异步阻塞(告白后回家等消息,但是一直想着结果)
  • 异步非阻塞(告白时回家等消息,同时可以做很多事)

网络编程演进史

始于BIO,陷于NIO,终于AIO

同步阻塞式IO(BIO Block IO)->同步阻塞与非阻塞模式(NIO No/New Block IO jdk1.4)->异步非阻塞I/O模型(AIO Asynchronous IO jdk1.7)

在这里插入图片描述

网络层的解析与协议

URL的解析

在这里插入图片描述

域名的解析

域名的解析是从右向左解析,下面以www.google.com.root为例看一下域名的层级,根域名都是.root默认会被省略

在这里插入图片描述

域名DNS查询的两种方式

DNS有一个分布式映射数据库,查询DNS分为递归查询和迭代查询,查询时经过的域名服务器,只要IP地址被查询出来了就会进行缓存,方便下次查询。

递归查询:DNS客户端查询根域名,如果根域名知道IP地址就直接返回,如果不知道就查询顶级域名,如果…

在这里插入图片描述

迭代查询:如果根域名知道I地址就直接返回,如果不知道就把可以查询的顶级域名返回,DNS客户端去顶级域名查询,如果知道了就直接返回,如果不知道就将二级域名返回…

在这里插入图片描述

网络协议快速扫盲

我们其实是把网络页面信息转化为电信号中的0和1,把它通过光缆传给另一方,另一方接收到之后将电信号层层解析并加载到浏览器中。

将网络传输分层之后,每一层只需要依赖(适配于)下一层就可以,需要担心的事情就减少了很多,当做了改动后不用担心其他层的适配,只需要担心一层即可

分层介绍常见协议向上传输的依赖
应用层用户能接触的应用HTTP FTP SMTP应用程序
传输层端口与端口之间的连接TCP UDP端口
网络层主机与主机之间的联系IPIP地址
链路层网卡和网卡的传输Ethernetmac地址
实体层将电信号通过物理连接进行传输电信号电信号

网络各层数据包格式

分层数据包格式
应用层将TCP/UDP数据进行划分:应用层数据等内容
传输层将IP数据进行划分:TCP/UDP标头+TCP/UDP数据
网络层将Ethernet数据进行划分:IP标头+IP数据
链路层帧:1500(Ethernet数据)+18(Ethernet标头保存mac地址等)个字节

在这里插入图片描述

网络编程基础知识

网络编程的本质

网络编程的本质是进程间的通信,其实就是数据的输入输出。因此我们要了解输入输出模型,数据从数据源输入到应用进程就是输入流,反之是输出流。数据源可以是文件、字符串、对象、Socket等等

在这里插入图片描述

java.io的流

字节流操作单位是字节,即8bit,人类常常一个个字符读,为了减少额外转化的努力,Java帮我们做了这类工作

在这里插入图片描述

字符流介绍

先看一下常见的字符流吧:

在这里插入图片描述

提供了额外功能的Reader和Writer,需要建立在基本的Reader和Writer的基础上,InputStreamReader是一个连接的桥梁,将字节流转化为字符流

在这里插入图片描述

字节流介绍

康康最基本的字节流对象:

在这里插入图片描述

在基础的字节流的基础上添加一些功能的字节流对象,DataInputStream或帮助我们直接转化为Java数据类型:

在这里插入图片描述

流中存在的装饰器模式

不管是在字符流还是字节流,我们都有一些高级的对象,但是这些对象是基于一些基本的对象实现的,我们有基本对象的功能,同时还提供了更好的功能。例如BufferedInputStream提供了缓存区,这就是装饰

在这里插入图片描述

Socket概述

socket也是一种数据源,绑定了Socket就可以传输数据流,那socket到底是怎么发送数据呢?其实是需要网卡此类硬件传输数据,首先应用会创建一个socket,其次将这个ip地址、端口、socket绑定到网卡的驱动程序,然后发信息就先发给socket即可,socket发给网卡驱动程序,网卡从硬件层面将信息发送到

在这里插入图片描述

同理,接受数据的时候也是类似,网卡收集到远程网卡传来数据,就会发给socket,socket将信息发给应用进程

在这里插入图片描述

网络通信的线程池

如果为每个请求创建一个线程再回收的话,会有浪费,同时由于操作系统的限制,我们也不能无止境的创建线程。Java提供了很多实现线程池的工具,通过实现ExecutorService接口,提供了两种不同类型的任务,没有返回值就是Runnable,需要返回值就用Callable

最终,我们从线程池中得到的是Future对象,代表任务最终完成的状态,例如isDone()表示现在任务是否已完成,完成了可以使用get()方法得到结果

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-D8EhElGX-1631464435540)(C:\Users\forev\AppData\Roaming\Typora\typora-user-images\image-20210911222746341.png)]

Executors提供了许多静态方法帮我们创建线程池

在这里插入图片描述

BIO阻塞模型

socket与serversocket

socket是客户端的,serversocket是服务端的,其连接过程通常如下

在这里插入图片描述

BIO阻塞模型

有一个线程Acceptor接受其他线程的请求,创建一个新线程用来连接client,再来一个client同理

在这里插入图片描述

伪异步IO编程模型(线程池)

使用线程池进行优化,否则线程太多太浪费了,如果有可用的线程就使用,没有就等待,这样优化后,系统的可靠性和可伸缩性就有了很大的提升

在这里插入图片描述

NIO非阻塞模型

NIO概述

ServerSocket的appcet是阻塞的,InputStream的read和OutStream的write都是阻塞的,同时无法在同一个线程处理多个Stream IO,即一个用户阻塞整个线程,在BIO中我们可以使用多线程,然后使用了线程池进行优化。我们可以使用一种非阻塞的形式来处理数据的输入和输出,即NIO。

NIO可以理解为new或NoblockingIO,我们不再使用流这个东西,而是使用Channel(通道)替代Stream,流是有方向的单向的,但Channel是双向的;流的读写是阻塞的,Channel可以阻塞也可以非阻塞。

NIO还提供了Selector,一个Selector可以监控多条Channel,比如我们想从非阻塞的Channel读取数据,但是要用读的数据的时候我们不知道Channel是否读完了,可以使用Selector。

同时,NIO可以在一个线程中处理多个Channel I/O。多线程不一定会提高效率,当线程数太多会造成上下文交换压力,此外创建、销毁一个线程还会浪费系统资源。

在这里插入图片描述

Buffer简析

我们使用Channel读写数据,其实是用的Buffer,针对一个Channel操作,离不开他的Buffer操作。Channel支持双向操作,可读可写,所以Buffer也可以双向操作,同时buffer是有大小的

  • 使用flip()方法将buffer从写模式改为读模式
  • clear()方法将buffer从读模式改为写模式(实际上是将position、limit指针回归原位)
  • compact()方法会把buffer读后修改成写模式剩下的数据放到最上面,position紧接着在它下面,同时写是从position开始写,不会覆盖原来没读完的数据,下次flip()后一读就是最开始的

关于动态过程,大家可以下载这个原视频null

Channel简析

Channel通过Buffer进行操作,不同Channel也可以直接传输数据,简单介绍几个Channel类

在这里插入图片描述

多方法拷贝文件

  • 不使用任何缓冲区,一个个字节来写
  • 使用缓冲区,一次读一个缓冲区
  • Channel用它的buffer操作
  • 两个Channel直接传输数据

测试小文件(179 KB)耗时:1783、2、13、1

测试大文件(36.2 MB)耗时:

interface FileCopyRunner {
    void copyFile(File source,File target);
}
public class FileCopyDemo {

    private static final int ROUNDS=5;

    private static void bencgmark(FileCopyRunner fileCopyRunner,File source,File target) {
        long elapsed=0L;
        for (int i=0;i<ROUNDS;i++) {
            long startTime = System.currentTimeMillis();
            fileCopyRunner.copyFile(source, target);
            elapsed += System.currentTimeMillis()-startTime;
            target.delete();
        }
        System.out.println(fileCopyRunner+":"+elapsed/ROUNDS);
    }

    private static void close(Closeable closeable) {
        if (closeable != null) {
            try {
                closeable.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    public static void main(String[] args) {
        //不使用任何缓冲区,一个个字节来写
        FileCopyRunner noBufferStreamCopy = new FileCopyRunner() {
            @Override
            public void copyFile(File source, File target) {
                InputStream fin = null;
                OutputStream fout = null;
                try {
                    fin = new FileInputStream(source);
                    fout = new FileOutputStream(target);
                    int read;
                    while ((read = fin.read())!=-1) {
                        fout.write(read);
                    }
                } catch (Exception e) {
                    e.printStackTrace();
                } finally {
                    close(fin);
                    close(fout);
                }
            }
        };
        //使用缓冲区,一次读一个缓冲区
        FileCopyRunner bufferedStreamCopy = new FileCopyRunner() {
            @Override
            public void copyFile(File source, File target) {
                BufferedInputStream fin = null;
                BufferedOutputStream fout = null;
                try {
                    fin = new BufferedInputStream(
                            new FileInputStream(source)
                    );
                    fout = new BufferedOutputStream(
                            new FileOutputStream(target)
                    );
                    byte[] buffer = new byte[1024];
                    int result;
                    while ((result = fin.read(buffer))!=-1) {
                        fout.write(buffer,0,result);
                        fout.flush();
                    }
                } catch (Exception e) {
                    e.printStackTrace();
                } finally {
                    close(fin);
                    close(fout);
                }
            }
        };
        //Channel用它的buffer操作
        FileCopyRunner nioBufferCopy = new FileCopyRunner() {
            @Override
            public void copyFile(File source, File target) {
                FileChannel fin = null;
                FileChannel fout = null;
                try {
                    fin = new FileInputStream(source).getChannel();
                    fout = new FileOutputStream(target).getChannel();
                    ByteBuffer buffer = ByteBuffer.allocate(1024);
                    while ((fin.read(buffer)!=-1)) {
                        buffer.flip();
                        //全部读完
                        while (buffer.hasRemaining()) {
                            fout.write(buffer);
                        }
                        buffer.clear();
                    }
                } catch (Exception e) {
                    e.printStackTrace();
                } finally {
                    close(fin);
                    close(fout);
                }
            }
        };
        //两个Channel直接传输数据
        FileCopyRunner nioTransferCopy = new FileCopyRunner() {
            @Override
            public void copyFile(File source, File target) {
                FileChannel fin = null;
                FileChannel fout = null;
                try {
                    fin = new FileInputStream(source).getChannel();
                    fout = new FileOutputStream(target).getChannel();
                    long transferred=0L;//记录一共拷贝了多少字节
                    long size = fin.size();
                    while (transferred != size) {
                        //从哪开始、传输多少、传到哪里
                        transferred += fin.transferTo(0, size, fout);
                    }
                } catch (Exception e) {
                    e.printStackTrace();
                } finally {
                    close(fin);
                    close(fout);
                }
            }
        };
        File smallFile = new File("E:\\IdeaProjects\\EasyIO\\resources\\smallFile.jpg");
        File smallFileCopy = new File("E:\\IdeaProjects\\EasyIO\\resources\\smallFileCopy.jpg");
        bencgmark(noBufferStreamCopy,smallFile,smallFileCopy);
        bencgmark(bufferedStreamCopy,smallFile,smallFileCopy);
        bencgmark(nioBufferCopy,smallFile,smallFileCopy);
        bencgmark(nioTransferCopy,smallFile,smallFileCopy);
    }
}

Selector简析

监控多个通道的状态,需要我们将Channel注册到Selector中,对于不同的类型,我么可以有一套逻辑

在这里插入图片描述

在Selector的常用方法如下:

  • innterestOps():注册的状态们
  • readyOps():有哪些状态是Channel准备好的,可操作的状态
  • channel():返回Channel对象
  • selector():返回Seletor对象
  • attachment:每一个Channel对象添加一个Object对象,可以是任意辅助对象

在这里插入图片描述

NIO优化聊天室

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

笼中小夜莺

嘿嘿嘿,请用金钱尽情地蹂躏我吧

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值