java.io位于java.base包下面。需要可以查阅jdk文档。
之前有浅总结过一趴这里
里面大概讲了一下初步认识java.io的感受,目前的总结会覆盖上次的部分。
然后可能文章偏向整体认识,再具体到java。
1.io概念/认识
io:input & output。输入设备输入,输出设备输出。涉及到计算机核心与其他设备之间的数据迁移过程都是io。
包括:
- 磁盘IO:从磁盘读取数据,写数据到磁盘。
- 其他外设IO:将数据写到网卡(发送给远程主机/服务器)、显示器、硬盘……
io实现流程:【因为需要用到外部设备,防止用户进程的不安全,需要操作系统使用系统调用协助实现】
应用程序向操作系统发起IO调用。
操作系统等待io设备准备好,将外部设备的数据加载到内核缓冲区。【或者将用户进程的数据拷贝到内核】
将数据从内核缓冲区拷贝到用户进程缓冲区。【将内核的数据拷贝到外部设备】
接着需要明确下java.io下面有几个问题需要讨论:也是我学了一点点之后觉得这些是一个话题下的内容,如果有任何其他补充或见解,欢迎交流呀
- java.io类的解析
- 序列化/反序列化
- io模型
2.java.io类解析
前面提到java.io包在java.base【javaSE的功能API模块】下的java.io。java.base (Java SE 18 & JDK 18)
分类
java.io按照读取单位的大小可以分为:字节流【读取单位:字节】,字符流【读取单位:字符{中文:2个字节,英文:1个字节}】
按照方向可以分为:输入流、输出流。
因为读取数据类型不同或者除了完成必要的读写功能之外还想要添加一些功能,或者提高效率或者加解密等,又可以细分出很多经过包装的io类,被称为装饰流。而Filter_X就是很多装饰类的父类,但是字符流、字节流的装饰类的继承不一样,字符流直接继承read/write,字节流继承Filter_X,但其实Filter_X也是继承input/outputStream。
略区分下不同装饰流是干啥的:
3.序列化 & 反序列化
是针对对象的一个概念。
1)概念
序列化:将java对象变成一个二进制内容【byte数组】
反序列化:将一个二进制内容变成java对象。
2)出现原因
- 需要将对象保存到本地。因为java对象在程序结束之后生命周期就结束了,其会被销毁处理,但是我们想要将其持久化到磁盘或者数据库。
- 想要将对象在网络上传输,例如用到RPC框架,用户需要向服务器获得javabean对象,然后调用其方法/获取其属性。需要将对象转化成二进制在网络上传输。
3)使用方法
一个对象想要被序列化:可以将这个对象的所有属性(private属性、protected、public以及其引用的对象)都序列化保存/传输。
实现序列化有2个接口可以使用:
- Serializble
- Externalizable
Serializble
实现的方法:
- wirteObject()
- readObject()
ps:
1.如果有某个属性不想要实现序列化:用transient修饰该属性。【具体用法待查阅】
2.静态成员变量也不可以被序列化。
3.需要显示声明serialVersionUID【控制序列化版本】。如果没有显示化生成,系统则会根据生成的输入:类名、类及其属性修饰符、接口及接口顺序、属性、静态初始化、构造器,自动生成serialVersionUID。
可以控制不同版本的类具有相同的serialVersionUID,实现不同版本的序列化兼容。
可以控制不同版本的类具有不同的serialVersionUID,反序列化时,对新增字段填入默认值的null,对于减少的字段直接忽略。
Externalizable
serializble的子类。
实现的方法:
- wirteExternal()
- readExternal()
ps:
1.可以指定序列化哪些属性。
2.反序列化,会先调用类的无参构造方法。【因此如果删除了无参构造方法/或者将无参构造方法设置为private、默认或者protected级别,就会产生异常:java.io.InvalidException: no valid constructor异常】
4)序列化需要注意的问题
a.序列化会破坏单例模式
单例模式:在全剧中,对象只有一个实例,没有副本。
序列化如何破坏单例模式:
- 序列化对象,再反序列化得到这个对象就是一个新的对象。
- readObject(),运用了反射技术,调用无参构造函数创建新的对象。
- 解决措施:在需要单例的对象类中添加代码:
b.序列化不安全
当再网络上传输对象序列化后的二进制数据时,所有字段(包括private)的数据都是以明文二进制的形式出现在网络传输中,容易被截获和发现数据内容。
解决方案
- 序列化/Hook化(移位/复位)。
- 序列数据加密/签名。
- 利用transient。【transient修饰的变量反序列化之后显示的是null【readObjecct】】
- 打包解包代理。
5)实现序列和反序列化的底层原理
【需要继续看看源码】
4.IO模型
1)摸几个概念
同步:发起调用之后,调用如果不处理就一直卡着不返回。【一直不能执行其他任务,一直要等待拿到结果】
异步:发起调用之后,不需要获得返回结果,可以等后面事件、回调等机制通知事件已完成。可以再发送下一个请求。【可以干点别的,等待通知工作解决好了】
阻塞:调用结果返回之前,这个线程被挂起,得到结果之后才继续执行。【不做事】【eg:BIO, socket】
非阻塞:不能立刻得到结果之前,该调用不会阻塞当前线程。【还可以继续做事】
2)IO模型的发展
BIO:同步阻塞
发起io请求 —— 等待数据准备好【线程挂起】 —— 返回结果继续执行操作。
可以使用多线程来并行提高处理效率。
——>但是当高并发访问发生时,无法无限创建线程,线程过多消耗资源,直至耗尽,造成服务器宕机。
————>使用线程池可以限制线程的数量,避免服务器宕机,但是仍旧无法处理高并发问题,当有多个请求的时候,如果访问者和都持有线程并且没有释放,就会造成后续的访问无限等待。
NIO:同步非阻塞【java1.4】
发起IO请求 —— 不阻塞,不断轮询是否已经准备好数据 —— 处理完成,返回成功。
3大核心组件:
1.buffer:读写数据直接操作缓冲区。而不是从流中一个一个读取数据。
2.channel:双向通道,与buffer进行交互。
3.selector:单线程选择channel,避免线程切换的代价。监控文件描述符的准备状态,当有状态变为可读时,发起系统调用。
1)监听io个数有限制max:1024【linux】;
2)监听到io就绪之后,遍历找到就绪的文件描述符。
——> poll:解决了连接数目限制的问题。仍然遍历找文件描述符。
AIO:异步非阻塞【java7】
发起IO —— 注册事件,监听 —— IO准备好 ——通知进程进行后续操作。
epoll多路复用机制:
1. epoll_ctl注册文件描述符,加入被监听。
2. 文件描述符就绪,内核回调,激活文件描述符。
3. 进程调用epoll_wait得到通知。