综述
流代表任何有能力产出数据的数据源对象,或者是有能力接受数据的接收端对象。
流的本质是数据传输,根据数据传输类型,将流分为各种类,分别操作。
1、装饰者模式
装饰,类似于装饰房间,每添加一件装饰物品,房间气氛都会改变,装饰者模式也起到这样的作用,动态的为一个对象添加其他功能,装饰者提供了一种灵活的方式来替换继承。
装饰者模式的框架如下:
- Component,也就是我们需要装饰的对象,他是抽象的,定义了一些方法,实际上要装饰的就是它的方法。
- ConcreteComponent,上述对象子类化后的对象,实现了抽象方法。
- Decorator,装饰者,也继承了Component类,实现了抽象方法。
- ConcreteDecoratorA & B,具体的装饰类,覆盖了Component方法,增加了装饰逻辑。
简单例子加深理解
①、被装饰的类
public abstract class Human {
public abstract void wear();
public abstract void walk();
}
②、装饰者
public abstract class Decorator extends Human{
private Human human;
public Decorator(Human human) {
this.human = human;
}
@Override
public void wear() {
human.wear();
}
@Override
public void walk() {
human.walk();
}
}
③、定义三种装饰
public class Decorator_zero extends Decorator {
public Decorator_zero(Human human) {
super(human);
}
@Override
public void wear() {
super.wear();
goHome();
}
@Override
public void walk() {
super.walk();
outHome();
}
public void goHome(){
System.out.println("回家找衣服");
}
public void outHome(){
System.out.println("出门散步");
}
}
public class Decorator_first extends Decorator {
public Decorator_first(Human human) {
super(human);
}
@Override
public void wear() {
super.wear();
goRoom();
}
@Override
public void walk() {
super.walk();
goGarden();
}
public void goRoom(){
System.out.println("去房间找衣服");
}
public void goGarden(){
System.out.println("去花园");
}
}
public class Decorator_sec extends Decorator{
public Decorator_sec(Human human) {
super(human);
}
@Override
public void wear() {
super.wear();
findClothes();
}
@Override
public void walk() {
super.walk();
walking();
}
public void findClothes(){
System.out.println("找到衣服并穿上");
}
public void walking(){
System.out.println("已经到花园开始散步");
}
}
④、定义被装饰者,被装饰者初始状态有些自己的装饰
public class Person extends Human {
@Override
public void wear() {
System.out.println("穿衣服");
}
@Override
public void walk() {
System.out.println("出去散步");
}
}
⑤、测试
public class DecoratorMain {
public static void main(String[] args) {
Decorator decorator = new Decorator_sec(new Decorator_first(new Decorator_zero(new Person())));
decorator.wear();
decorator.walk();
/**
* 穿衣服
* 回家找衣服
* 去房间找衣服
* 找到衣服并穿上
* 出去散步
* 出门散步
* 去花园
* 已经到花园开始散步
*/
}
}
2、IO流总览
Java的IO流采用了装饰者模式对不同功能的流进行划分,我们可以动态的装饰这些Stream以获得想要的结果。
输入与输出
这里的输入与输出是基于程序而言的,从外部读取到程序中的叫输入,从程序写出到其他地方的叫输出。
4个基本的抽象流类型,所有的流都继承这四个
输入流 | 输出流 | |
---|---|---|
字节流 | InputStream | OutputStream |
字符流 | Reader | Writer |
IO流的特性
- 先进先出,最先写入输入流的数据最先被输入流读取到;
- 顺序存取,可以一个接一个的向流中写入一个字节,读取时也将按照写入的顺序读取一串字节,不能随机访问。
- 只读或只写,每个流只能是输入或输出的一种。
3、IO读写原理理解
用户程序进行IO的读写,基本上都会用到read&write两大系统调用。read系统调用并不是直接把数据从物理设备读到内存,而是把数据从内核缓冲区复制到用户缓冲区(也可以叫做进程缓冲区);write系统调用也不是直接把数据从内存写入物理设备,而是把数据从进程缓冲区复制到内核缓冲区。这两个操作都不负责数据在内核缓冲区与物理设备之间的交换,底层的数据交换是由操作系统完成的。
DMA,全称叫直接内存存取(Direct Memory Access),是一种允许外围设备(硬件子系统)直接访问系统主内存的机制。基于 DMA 访问方式,系统主内存与硬件设备的数据传输可以省去CPU 的全程调度
内核缓冲区与进程缓冲区
缓冲区的作用是为了减少频繁的系统IO调用,系统调用需要保存之前的进程数据和状态等信息,而结束调用之后还需要恢复之前的信息,为了减少这种损耗时间也损耗性能的系统调用于是出现了缓冲区。
有了缓冲区,系统调用read或write等待缓冲区到达一定数量的时候在进行系统调用,提高性能。至于什么时候读取和存储则由内核来决定不需要用户关心。
用户程序的IO操作大多数情况下并没有进行真正的IO操作,而是在读写自己的进程缓冲区。
4、四种IO模型
-
①、同步阻塞IO(Blocking IO)
分析:当用户进程进行系统调用时,内核就开始了第一个阶段:准备数据到缓冲区,当数据都准备完成后将数据从内核缓冲区复制到用户缓冲区,这时返回成功指示,解除阻塞。
例子:去餐厅吃饭->点餐->取餐->吃饭,这整个过程中什么都不干一直等待饭做好。 -
②、同步非阻塞IO(Non-Blocking IO)
分析:在函数调用之后,如果没有数据函数立马返回,需要加入轮询逻辑,轮询如果有数据就返回数据。
这种IO第一阶段(数据准备)并没有阻塞,只是第二阶段阻塞,比如,在等待饭做好的期间可以出去买其他的东西,而不必再一直等待。
非阻塞IO的特点有如下几种:- 复制数据的整个过程,进程仍然是阻塞的;
- 需要不断地轮询数据是否准备好了;
- 能够在等待任务完成的期间执行其他任务;
- 由于需要轮询,延迟会增加。
-
③、IO多路复用模型(I/O multiplexing)
IO多路复用模型解决了NIO中需要不断轮询的问题。IO多路复用技术的原理是select/epoll系统调用,单个线程不断地轮询select/epoll负责的成百上千的socket连接,当某个或某些socket有数据到达了,就返回这些可以读写的连接,它的好处在于通过一次select/epoll系统调用就查询到一个或多个可读写的网络连接。
分析:在这种模式中,首先将连接注册到select/epoll的可查询socket列表中,然后进行select/epoll系统调用,之后才是read操作。
流程大致如下:- 首先查询select/epoll系统调用,查询可读的连接,内核会查询所有select的可查询socket列表,当其中任何一个socket的数据准备好了select就会返回。
- 用户线程获取到连接之后发起read调用,用户线程阻塞内核开始复制数据,从内核缓冲区复制到进程缓冲区,然后内核返回结果。
- 用户线程接触阻塞状态,用户线程也才真正获取到数据。
IO多路复用模型其实也需要轮询,也即是不断地轮询select/epoll,获取到可以及进行IO操作的连接,IO多路复用技术可以处理成千上万个连接,而不必每个连接都创建一个线程,大大减小了系统的开销。IO多路复用是阻塞在select,epoll这样的系统调用之上,而没有阻塞在真正的I/O系统调用如recvfrom之上。
例子:我们点餐之后不用过一段时间就去询问店家是否做好,而是拿到一个牌子,当店家做好之后牌子回响铃提示,我们再去取就行了。
本质上select/epoll系统调用是同步IO,也是阻塞的,需要在读写事件就绪后,自己负责读写,这个读写过程是阻塞的。
- ④、异步IO模型(Asynchronous IO)
分析:当用户线程调用了read之后就可以立即去做其他事情了,而不必等待,用户线程步不阻塞,直到IO执行的两个阶段都完成之后,内核会给用户进程发送通知,告诉用户进程操作已经完成了。
例子:如果饿了可以直接点外卖,不用再去店里等了,点完之后等待送达即可。
参考文章:
Linux 五种IO模型
10分钟看懂, Java NIO 底层原理
【Linux基础】Linux的5种IO模型详解