→ IO
字符流、字节流、输入流、输出流
输入流:数据从文件流向内存。 输出流:数据从内存流向文件 。输入流和输入流是相对程序而言的 。
输入流 | 输出流 | 说明 | |
---|---|---|---|
字节流stream | InputStream | OutputStream | 处理二进制数据 |
字符流reader/writer | Reader | Writer | unicode码,处理文本数据 |
字节输入流:
- InputStream 字节输入流的抽象基类 read(byte[] b) read(byte[] b, int off, int len) close()
- FileInputStream 主要用来操作文件输入流 read()
- BufferedInputStream 【BufferedInputStream不是InputStream的直接实现子类,是FilterInputStream的子类】 可以提高效率 read()
字节输出流:
- OutputStream 字节输出流的基类 write(byte[] b) write(byte[] b,int off,int len) flush() close()
- FileOutputStream 写文件的输出流 write(int b)
- BufferedOutputStream 【BufferedOutputStream不是OutputStream的直接实现子类,是FilterOutputStream的子类】 可以提高效率 write(int b)
字符输入流:
- Reader 字符输入流的抽象基类 read() read(char[] cbuf) close()
- InputStreamReader 把InputStream中的 字节流→字符流 read(char[] cbuf, int offset, int length)
- FileReader 与InputStreamReader 功能类似 read(char[] cbuf, int offset, int length)
- BufferedReader 可以把字符输入流进行封装,将数据进行缓冲,提高读取效率。 readLine()
字符输出流:
- Writer 字符输出流的抽象基类 write(int c) write(String str) write(char[] cbuf) write(String str, int off, int len) close() flush()
- OutputStreamWriter 把字符流→字节流
- FileWriter 与OutputStreamWriter功能类似
- BufferedWriter 利用了缓冲区来提高写的效率 newLine()
【参考链接】https://www.cnblogs.com/progor/p/9357676.html
同步、异步、阻塞、非阻塞、Linux 5 种 IO 模型
- 阻塞I/O(blocking I/O)
- 非阻塞I/O (nonblocking I/O) 区别:应用程序的调用是否立即返回
- I/O复用(select 和poll) (I/O multiplexing)
- 信号驱动I/O (signal driven I/O (SIGIO))
- 异步I/O (asynchronous I/O (the POSIX aio_functions)) 区别: 数据拷贝的时候进程是否阻塞
阻塞IO: recv
函数默认是阻塞的,等待数据准备好(没有得到结果之前,不会返回,当前线程被阻塞),数据没有准备好时进入等待,准备好了就进行数据拷贝 。
当用户线程发出 IO 请求之后,内核会去查看数据是否就绪,如果没有就绪就会等待数据就绪,而用户线程就会处于阻塞状态,用户线程交出 CPU。当数据就绪之后,内核会将数据拷贝到用户线程,并返回结果给用户 线程,用 户线程才解除 block 状态。典型的阻塞IO 模型的例子为: data = socket.read();如果数据没有就绪,就会一直阻塞在 read 方法
非阻塞IO: recv
函数为非阻塞, 即使没有数据到来,recv
函数也不会阻塞 ,并且立即返回-1。数据到来之前会反复调用函数并判断返回值,这种循环测试称为忙等待。但是在数据拷贝时线程是阻塞的 。【用户线程】
多路IO复用 :IO复用多了一个select函数,select函数有一个参数是文件描述符集合,select可以循环监听多个文件描述符是否有数据,从而提高性能。IO复用也是属于阻塞IO。【内核】
通过一个线程就可以管理多个 socket,只有当 socket 真正有读写事件发生才会占用资源来进行实际的读写操作。
多路复用 IO 为何比非阻塞 IO 模型的效率高?
在非阻塞 IO 中,不断地询问 socket 状态时通过用户线程去进行的,而在多路复用IO 中,轮询每个 socket 状态是内核在进行的,这个效率要比用户线程要高的多。
信号驱动I/O(拉数据): 在用户进程安装SIGIO信号处理函数,即recv
函数。用户进程可以执行其他操作不会被阻塞。当数据准备好时,用户进程会收到SIGIO信号,跳转到信号处理函数中调用recv
函数,接收数据。当数据从内核空间拷贝到用户态空间后,recv
函数返回。这种方式使异步处理成为可能,信号是异步处理的基础。
当用户线程发起一个 IO 请求操作,会给对应的 socket 注册一个信号函数,然后用户线程会继续执行,当内核数据 就绪时会发送一个信号给用户线程,用户线程接收到信号之后,便在信号函数中调用 IO 读写操作来进行实际的 IO 请求操作。
异步IO(推数据):通过aio_read
函数实现,aio_read
提交请求,并递交一个用户态空间下的缓冲区。即使内核中没有数据到来,aio_read
函数也立刻返回,应用程序就可以处理其他的事情。等待有结果时通过状态、通知和回调来通知调用者。 属于非阻塞状态
阻塞程度:阻塞IO>非阻塞IO>IO复用>信号驱动IO>异步IO,效率是由低到高的。
异步IO
和信号驱动IO
的不同?
在于信号通知用户态程序时数据所处的位置。异步
IO
已经把数据从内核空间拷贝到用户空间了;而信号驱动IO
的数据还在内核空间,等着recv
函数把数据拷贝到用户态空间。 异步IO
是一种推数据的机制,相比于信号处理IO
拉数据的机制效率更高。 推数据是直接完成的,而拉数据是需要调用recv
函数,调用函数会产生额外的开销,故效率低。
【参考链接】https://www.jianshu.com/p/e4768446f7eb
BIO、NIO 和 AIO 的区别、三种 IO 的用法与原理、netty
Netty是一个提供异步事件驱动的网络应用框架,用以快速开发高性能、高可靠性的网络服务器和客户端程序。换句话说,Netty是一个NIO框架,使用它可以简单快速地开发网络应用程序,比如客户端和服务端的协议。这是官方文档中的描述 。
区别:
BIO:同步并阻塞, 服务器实现模式为一个连接一个线程,即客户端有连接请求时服务器端就需要启动一个线程进行处理(一客户端一线程)。 该模型缺乏弹性伸缩能力,当客户端并发访问量增加后,服务端的线程数与客户端并发访问数呈1:1的关系,随着并发访问量的继续增加,系统会发生线程堆栈溢出、创建新线程失败等问题,并最终导致宕机或僵死。
NIO:同步非阻塞 。 服务器实现模式为一个请求一个线程,客户端发送的连接请求都会注册到多路复用器上,多路复用器轮询到连接有I/O请求时才启动一个线程进行处理。
对于NIO,有两点需要强调的: (1)关于概念有两种理解,New I/O(相对于之前的I/O库是新增的)和Non-block I/O(非阻塞的)。由于NIO的目标就是让java支持非阻塞I/O,所以更多人喜欢用Non-block I/O。 (2)很多人喜欢将NIO称为异步非阻塞I/O,但是,如果按照严格的NUIX网络编程模型和JDK的实现进行区分,实际上它只是非阻塞I/O,不能称之为异步非阻塞I/O。但由于NIO库支持非阻塞读和写,相对于之前的同步阻塞读和写,它是异步的,因此很多人习惯称NIO为异步非阻塞I/O。
AIO(NIO2.0):异步非阻塞。 JDK1.7升级了NIO库,升级后的NIO库被称为NIO2.0,此即AIO。其服务器实现模式为一个有效请求一个线程,客户端的I/O请求都是由OS先完成了再通知服务器应用去启动线程进行处理。
用法:
BIO适用于连接数目比较小且固定的架构,这种方式对服务器资源要求比较高,并发局限于应用中,JDK1.4以前的唯一选择,但程序直观简单易理解。
NIO适用于连接数目多且连接比较短(轻操作)的架构,比如聊天服务器,并发局限于应用中,编程比较复杂,JDK1.4开始支持。
AIO适用于连接数目多且连接比较长(重操作)的架构,比如相册服务器,充分调用OS参与并发操作,编程比较复杂,JDK7开始支持。
BIO是一个连接一个线程。
NIO是一个请求一个线程。
AIO是一个有效请求一个线程。
原理:
Java NIO(New IO)是从JDK1.4新增的IO操作API,是面向通道和缓冲区进行操作。在NIO库中,数据从通道读取到缓冲区中,或者从缓冲区写入到通道中。通过设置通道为非阻塞机制,可以同时进行读写双向操作而不发生阻塞。
NIO中另外一个重要概念是多路复用器(Selector)。Selector通过轮询检查每个注册在其上的NIO通道,如果某个通道上面发生读(SelectionKey.OP_READ)或者写(SelectionKey.OP_WRITE)操作,则这些通道会被Selector检测出来,并且存放在一个Set集合中,进行后续操作。因为JDK底层采用epoll()代替传统select实现,当一个连接创建后,这个连接会被注册到多路复用器上面,所有的连接对应一个线程就可以搞定。因此只需要一个线程负责Selector的轮询,可以管理多个channel,从而管理多个网络连接。 优点是在高并发场景下,可以有效减少服务端起的线程数量和CPU时间片的浪费,降低了内存的消耗。
AIO异步非阻塞是从JDK7新增的IO操作API,使用回调的方式,真正实现了高效异步IO。当进行读写操作时,只须直接调用API的read或write方法即可,不需要再使用Selector。由于这两种方法均为异步的,所以均由底层操作系统进行处理,完成后主动通知应用程序。
【参考链接】https://blog.csdn.net/qq_36907589/article/details/80689091
→ 反射
使用new创建对象之外,还可以用反射创建对象
通过new创建对象的效率比较高。通过反射时,先找查找类资源,使用类加载器创建,过程比较繁琐,所以效率较低
反射与工厂模式、反射的作用
反射机制: 反射机制是在运行(不是编译期)状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性;这种动态获取的信息以及动态调用对象的方法的功能称为java语言的反射机制。
jdk提供了三种方式获取一个对象的Class,就User user来说:
1.user.getClass(),这个是Object类里面的方法
2.User.Class属性,任何的数据类型,基本数据类型或者抽象数据类型,都可以通过这种方式获取类
3.Class.forName(“”),Class类提供了这样一个静态方法,让我们通过类名来获取到对象类。(最安全/性能最好)
Class.forName(‘com.mysql.jdbc.Driver.class’);//加载MySQL的驱动类
反射机制的优缺点:优点是可以实现动态创建对象和编译,体现出很大的灵活性。缺点是对性能有影响。
工厂模式分为三种:
- 简单工厂模式(Simple Factory)
- 工厂方法模式(Factory Method)
- 抽象工厂模式(Abstract Factory)
其中简单工厂模式是工厂方法模式的一种特例。
Class.forName(fullName).newInstance()
使用反射机制实现的工厂模式可以通过反射取得接口的实例,但是需要传入完整的包和类名。而且用户也无法知道一个接口有多少个可以使用的子类,所以我们可以使用属性文件配置所需要的子类。
反射的作用:
反编译:.class -> java
- 在运行时判断任意一个对象所属的类;
- 在运行时构造任意一个类的对象;
- 在运行时判断任意一个类所具有的成员变量和方法;
- 在运行时调用任意一个对象的方法;
-
Class 类、java.lang.reflect.*
在java.lang.reflect包中有三个类Field, Method, Contructor分别用于描述类的域, 方法和构造器
Field类中有一个getType方法,用来返回描述域所属类型的Class对象。
Method 和 Contructor 类有能够报告参数类型的方法,Method类还有一个可以报告返回类型的方法。
三个类都有一个叫做getModifiers的方法, 它将返回一个整形数值, 用不同的位开关描述public和static 这样的描述符的使用情况。
java.lang.reflect包中的Modifier类的静态方法分析getModifiers()返回的整形数值。 例如,可以使用Modifier类中的isPublic、isPrivate、isFinal判断权限修饰符。
Class类中的getFields,getMethods和getContructors 方法将分别返回类提供的public域,方法,和构造器数组,其中包括超类的公有成员。
Class类的getDeclareFields、getDeclareMethods和getDeclareContructors 方法将分别返回类中声明的全部域、方法和构造器,其中包括私有的和受保护的成员,但不包括超类的成员。
【参考链接】https://blog.csdn.net/q5706503/article/details/84892959→ 动态代理
静态代理、 动态代理
代理设计模式:提供了对目标对象另外的访问方式,即通过代理对象访问目标对象。这样做的好处是:可以在目标对象实现的基础上,增强额外的功能操作,即扩展目标对象的功能。这里使用到编程中的一个思想:不要随意去修改别人已经写好的代码或者方法,如果需改修改,可以通过代理的方式来扩展该方法。
静态代理:由程序员创建或工具生成代理类的源码,再编译代理类。所谓静态也就是在程序运行前就已经存在代理类的字节码文件,代理类和委托类的关系在运行前就确定了。 代理使客户端不需要知道实现类是什么,怎么做的,而客户端只需知道代理即可
- 代理对象的一个接口只服务于一种类型的对象,如果要代理的方法很多,势必要为每一种方法都进行代理,静态代理在程序规模稍大时就无法胜任了。
- 代理类和委托类实现了相同的接口,代理类通过委托类实现了相同的方法。这样就出现了大量的代码重复。如果接口增加一个方法,除了所有实现类需要实现这个方法外,所有代理类也需要实现此方法。增加了代码维护的复杂度。
动态代理:其类的源码是在程序运行期间由JVM根据反射等机制动态的生成,所以不存在代理类的字节码文件。代理类和委托类的关系是在程序运行时确定。 动态代理也叫JDK代理或接口代理。
动态代理是实现JDK里的 InvocationHandler 接口的 invoke 方法,但注意的是代理的是接口,也就是你的业务类必须要实现接口,通过 Proxy 里的 newProxyInstance 得到代理对象。
具体步骤是:
- 实现InvocationHandler接口创建自己的调用处理器
- 给Proxy类提供ClassLoader和代理接口类型数组创建动态代理类
- 以调用处理器类型为参数,利用反射机制得到动态代理类的构造函数
- 以调用处理器对象为参数,利用动态代理类的构造函数创建动态代理类对象
优点: 动态代理与静态代理相比较,最大的好处是接口中声明的所有方法都被转移到调用处理器一个集中的方法中处理(InvocationHandler.invoke)。这样,在接口方法数量比较多的时候,我们可以进行灵活处理,而不需要像静态代理那样每一个方法进行中转。
缺点: Proxy 已经设计得非常优美,但是还是有一点点小小的遗憾之处,那就是它始终无法摆脱仅支持 interface 代理的桎梏,因为它的设计注定了这个遗憾。回想一下那些动态生成的代理类的继承关系图,它们已经注定有一个共同的父类叫 Proxy。Java 的继承机制注定了这些动态代理类们无法实现对 class 的动态代理,原因是多继承在 Java 中本质上就行不通。
如果我们把对外的接口都通过动态代理来实现,那么所有的函数调用最终都会经过invoke函数的转发,因此我们就可以在这里做一些自己想做的操作,比如日志系统、事务、拦截器、权限控制等。这也就是AOP(面向切面编程 AspectOrientedProgramming )的基本原理。
AOP 编程就是基于动态代理实现的,比如著名的 Spring 框架、 Hibernate 框架等等都是动态代理的使用例子。
AOP(AspectOrientedProgramming):将日志记录,性能统计,安全控制,事务处理,异常处理等代码从业务逻辑代码中划分出来,通过对这些行为的分离,我们希望可以将它们独立到非指导业务逻辑的方法中,进而改变这些行为的时候不影响业务逻辑的代码—解耦。
动态代理和反射的关系
反射的作用:
1、动态地创建类的实例,将类绑定到现有的对象中,或从现有的对象中获取类型。
2、应用程序需要在运行时从某个特定的程序集中载入一个特定的类
学习Spring的时候,我们知道Spring主要有两大思想,一个是IOC,另一个就是AOP,对于IOC,它利用的是反射机制,依赖注入就不用多说了,而对于Spring的核心AOP来说,使用了动态代理,其实底层也是反射。我们不但要知道怎么通过AOP来满足的我们的功能,我们更需要学习的是其底层是怎么样的一个原理,而AOP的原理就是java的动态代理机制。
在java的动态代理机制中,有两个重要的类或接口,一个是 InvocationHandler(Interface)、另一个则是 Proxy(Class),这一个类和接口是实现我们动态代理所必须用到的。 每一个动态代理类都必须要实现InvocationHandler这个接口,并且每个代理类的实例都关联到了一个handler,当我们通过代理对象调用一个方法的时候,这个方法的调用就会被转发为由InvocationHandler这个接口的 invoke 方法来进行调用。
public class DynamicProxy implements InvocationHandler{
// 将要代理的对象传递到代理中
private UserDao userDao;
// 构造方法,给我们要代理的真实对象赋初值
public DynamicProxy(UserDao userDao){
this.userDao = userDao;
}
/**
* 生成代理的方法
*/
public UserDao createProxy(){
UserDao userDaoProxy = (UserDao)Proxy.newProxyInstance(
userDao.getClass().getClassLoader,userDao.getClass().getInterfaces(),this);
return userDaoProxy;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 在代理真实对象前我们可以添加一些自己的操作
System.out.println("before invoke");
System.out.println("Method:" + method);
// 当代理对象调用真实对象的方法时,会自动跳转到代理对象关联的handler对象的invoke方法
method.invoke(userDao, args);
// 在代理真实对象后我们也可以添加一些自己的操作
System.out.println("after invoke");
return method.invoke(userDao, args);
}
}
// 调用 new DynamicProxy(userDao).createProxy()
通过 Proxy.newProxyInstance() 创建的代理对象是在jvm运行时动态生成的一个对象,它并不是我们的InvocationHandler类型,也不是我们定义的那组接口的类型,而是在运行是动态生成的一个对象, 并且命名方式都是这样的形式,以$开头,proxy为中,最后一个数字表示对象的标号。
当我通过代理对象来调用方法的时候,其实际就是委托由其关联到的 handler 对象的invoke方法中来调用,并不是自己来真实调用,而是通过代理的方式来调用的。这就是我们的java动态代理机制。
【参考链接】深入理解Java反射和动态代理https://www.cnblogs.com/aspirant/p/9036805.html
动态代理的几种实现方式
Java领域中,常用的动态代理实现方式有两种,一种是利用jdk反射机制生成代理,另外一种是使用cglib代理。
AOP的源码中用到了两种动态代理来实现拦截切入功能:jdk动态代理和cglib动态代理。两种方法同时存在,各有优劣。 jdk动态代理是由java内部的反射机制来实现的,cglib动态代理底层则是借助asm来实现的。
反射机制在生成类的过程中比较高效,而asm在生成类之后的相关执行过程中比较高效(可以通过将asm生成的类进行缓存,这样解决asm生成类过程低效问题)。还有一点必须注意:jdk动态代理必须是目标类提供统一的接口。由此可以看出,jdk动态代理有一定的局限性,cglib这种第三方类库实现的动态代理应用更加广泛,且在效率上更有优势。
jdk动态代理是jdk原生就支持的一种代理方式,它的实现原理,就是通过让target类和代理类实现同一接口,代理类持有target对象,来达到方法拦截的作用,这样通过接口的方式有两个弊端,一个是必须保证target类有接口,第二个是如果想要对target类的方法进行代理拦截,那么就要保证这些方法都要在接口中声明,实现上略微有点限制。
Cglib是一个优秀的动态代理框架,它的底层使用ASM在内存中动态的生成被代理类的子类,使用CGLIB即使代理类没有实现任何接口也可以实现动态代理功能。CGLIB具有简单易用,它的运行速度要远远快于JDK的Proxy动态代理。
【参考链接】https://blog.csdn.net/riemann_/article/details/86849078
AOP
AOP的源码中用到了两种动态代理来实现拦截切入功能:jdk动态代理和cglib动态代理。两种方法同时存在,各有优劣。 JDK代理:只能对实现了接口的类进行代理。 Cglib:对没有实现接口的类产生代理对象
//观光代码=>cglib代理
public class CglibProxy implements MethodInterceptor {
private UserDao userDao;
// 构造方法,给我们要代理的真实对象赋初值
public DynamicProxy(UserDao userDao){
this.userDao = userDao;
}
public UserDao createProxy(){
// 帮我们生成代理对象
Enhancer en = new Enhancer();
// 设置对谁进行代理
en.setSuperclass(UserDao.class);
// 设置回调
en.setCallback(this);
UserDao proxy = (UserDao) en.create();
return proxy;
}
@Override
public Object intercept(Object prxoy, Method method, Object[] arg, MethodProxy methodProxy) throws Throwable {
// 打开事务
System.out.println("打开事务!");
// 增强
Object returnValue = methodProxy.invokeSuper(proxy, arg);
// 提交事务
System.out.println("提交事务!");
return returnValue;
}
}
→ 序列化
什么是序列化与反序列化、为什么序列化、序列化底层原理、序列化与单例模式、protobuf、为什么说序列化并不安全
序列化:把对象转换为字节序列的过程称为对象的序列化。
反序列化:把字节序列恢复为对象的过程称为对象的反序列化。
当两个进程进行远程通信时,可以相互发送各种类型的数据,包括文本、图片、音频、视频等, 而这些数据都会以二进制序列的形式在网络上传送。 发送方需要把这个Java对象转换为字节序列,然后在网络上传送;另一方面,接收方需要从字节序列中恢复出Java对象。
序列化优点:
(1)永久性保存对象,保存对象的字节序列到本地文件或者数据库中;
(2)通过序列化以字节流的形式使对象在网络中进行传递和接收;
(3)通过序列化在进程间传递对象;
底层原理:
1、JDK类库中序列化和反序列化API
(1)java.io.ObjectOutputStream:表示对象输出流。它的writeObject(Object obj)方法可以对参数指定的obj对 象进行序列化,把得到的字节序列写到一个目标输出流中;
(2)java.io.ObjectInputStream:表示对象输入流:它的readObject()方法源输入流中读取字节序列,再把它们 反序列化成为一个对象,并将其返回;
2、实现序列化的要求
只有实现了Serializable或Externalizable接口的类的对象才能被序列化,否则抛出异常!
序列化与单例模式:
序列化会破坏单例模式, 因为序列化会通过反射调用无参数的构造方法创建一个新的对象。
ProtoBuf 是结构数据序列化 方法,可简单类比于 XML,其具有以下特点:
- 语言无关、平台无关。即 ProtoBuf 支持 Java、C++、Python 等多种语言,支持多个平台
- 高效。即比 XML 更小(3 ~ 10倍)、更快(20 ~ 100倍)、更为简单
- 扩展性、兼容性好。你可以更新数据结构,而不影响和破坏原有的旧程序
protobuf 序列化后 生成 std::string 然后将std::string 转化为 QString 再反转回 std::string 进行反序列化 发现生成的值不对。
最常见的反序列化安全问题是通过修改序列化之后的数据字段, 抽象来看,只要是从Application之外读取或接收数据,并将其反序列化成Application或API中的对象,都可能存在反序列化安全问题。
【相关链接】https://blog.csdn.net/Roger_CoderLife/article/details/86704380
https://www.ibm.com/developerworks/cn/linux/l-cn-gpb/index.html