工作时遇到一些开发任务,如文件上传下载,数据流的转换处理等,做起来并不是那么得心应手,于是花时间总结一些java IO的基础知识。
IO定义
IO是什么呢?顾名思义IO是input/output缩写简称,即输入输出。Java IO就是java程序的输入输出,即Java程序运行时从外部接收数据或往外部发送数据,外部是指数据源头,如文件,内存,网络连接,数据库,数据存储设备等。所以说,学习Java IO时候,要以java程序为中间来考虑输入输出,然后找出数据源头,也就是弄清楚Java程序和什么数据源头进行数据交互。
API中对于java.io包的说明:通过数据流、序列化和文件系统提供系统的输入和输出。我认为 提供系统的输入输出,就是提供当前java运行系统的输入输出。
IO中几个重要概念
- 字节和字符
位是计算机内部数据储存的最小单位,存储的是0或1,一个字节8位。字节是计算机中 数据处理的基本单位。字符是计算机中使用的字母、数字、字和符号。
通俗来说字节是计算机能识别的,而字符是人类可以看懂的。这两者是可以转换的。
编码解码肯定需要“密钥”,很明显,这里的密钥就是简单的字节和字符的对应关系,对应关系就是字符编码所用的字符集。有变长的字符编码集,也有定长的字符编码集。比如说变长编码UTF-8,一个英文字符对应一个字节,一个汉字对应3个字节。而GBK则是定长编码,无论中英文字符都是占用2个字节。在不同的应用场景都有优缺点。以上说明偏移了主题,还是拿ASCII码来说明字节和字符的对应关系吧,A对应的十进制是65,用字节表示就是01000001,a对应的十进制是97,用字节表示就是01100001,所以拿着ASCII的码表就可以做字节和字符的转换。 - 流、 刷新、关闭
流,是一个抽象的概念,表示数据从程序和数据源之间传递的过程,具有方向性,这样说好像完全无法理解有木有?按照我自己的理解用一个场景来说明流和刷新,关闭的概念。
如下图所示,一个搬家的场景,流就表示从旧所搬运行李到新家的过程。比如说,开启一个搬家流,唯一确定的就是旧所和新家的位置,流就是旧所到新家的通道,就是搬家规划的路线,方向是从旧所到新家。搬家流相对于旧所就是输出流,相对于新家就是输入流。
建立好搬家流之后就可以开始搬家了呀,需要做什么呢?劳务公司请N个工人,清点行李。如下图所示,如果有100件行李(程序员强迫症,从0开始编号),一个工人搬一件。
需要注意的是,管道流的数据是连续不断的,这里画成这样主要便于理解。所谓刷新,就是通知工人把编号为3的行李搬出旧所之前,先让正在路上的工人把行李都搬进新家。所谓关闭,就是通知工人开始搬编号为100的行李时,发现该编号不存在,说明东西已经全部搬完啦,但是工人还不会走,这个时候你要跟劳务公司说东西已经搬完了,让工人撤,否则就会浪费人工资源,这个过程就叫关闭。IO流中的刷新,就是把管道中的数据都输出到目的地。IO流的关闭就是通知操作系统关闭和IO操作相关的系统资源。 - 序列化和反序列化
序列化是将一个对象编码成一个字节流,反序列化时将字节流还原成对象。对象的序列化主要有两个作用,一是把对象的字节序列存放在一个文中,永久保存到硬盘上,也称为对象的持久化存储,二是把对象序列化成字节流方便对象在网络上传输,也称数据传输中的线路级对象表示。
序列化对象中有个serialVersionUID字段,该序列号在反序列化期间用于验证序列化对象的发送者和接收者是否已加载与该序列化兼容的对象的类。也就是说,这个序列化用于反序列化,还原对象时做一致性校验,如果一致,就可以进行反序列化,否则就会出现序列化版本不一致的异常。
IO中的三个重要接口
- Closeable
一个 Closeable是源或数据,可以关闭的目的地。调用关闭方法以释放对象所持有的资源(如打开文件)。 - Flushable
一个 Flushable是一个目的地的数据可以被刷新。调用刷新方法来将任何缓冲输出写入到基础流中。 - Seriaizable
序列化接口
java IO常用类继承体系结构图
流分类
- 输入流和输出流(按照数据流向)
- 节点流和处理流(按照功能)
节点流:直接从数据源或者目的地读写数据
处理流:基于对节点流的封装,为了简化操作和提升性能 - 字节流和字符流(按照数据表示)
一个面向字节,一个面向字符。字符流是为了方便纯文本文件的处理,底层还是基于字节流,只是屏蔽了字符集的差异,避免出现乱码情况。
常用节点流
流分类中介绍的节点流是指可以直接从数据源读写数据,而数据源有很多,可以是内存,也可以是磁盘,也可以是网络。
有两种常见的节点流:
- FileInputStream/FileOutputStream
- ByteArrayInputStream/ByteArrayOutputSteam
文件即磁盘上的文件,Java操作文件需要通过操作系统,所以文件流使用完毕之后需要通知操作系统释放文件管理相关资源。而字节数组则是存在于内存中的,可以理解为程序之外的一块内存,或远程服务器的内存,或网络内存,相对于Java程序还是外部资源,而内存的是通过JVM来管理的,字节数组所占内存的释放是通过GC垃圾回收机制,所以使用字节数组流时不需要关闭流,close是一个空方法。字节数组优点:所有的数据都可以转换成字节数组,可以提高空间利用率,方便网络传输。但是要考虑到内存大小,字节数组不能过大。
常用处理流
- 缓冲流:BufferedInputStrean/BufferedOutputStream/BufferedReader/BufferedWriter增加缓冲功能,避免频繁读写硬盘。缓冲流直接套在字节流上即可,内部开辟了一个缓冲空间,可提高数据传输速度,相当于搬家时候工人不再一件一件般,而是使用推车一次搬N件行李,N就是缓冲区的大小。
- 转换流:InputStreamReader/OutputStreamWriter实现字节流和字符流之间的转换。
InputStreamReader是从字节流到字符流的桥梁:它读取字节,并使用指定的charset将其解码为字符。
OutputStreamWriter是字符流到字节流的桥梁:向其写入的字符编码成使用指定的字节charset. - 数据流:DataInputStream DataOutputStream 等提供将基础数据类型写入到文件中,或者读取出来.
处理流设计思想之装饰者模式
装饰者模式设计原则:
- 封装变化
- 多用组合、少用继承
- 针对接口编程、不针对实现编程
- 实现交互对象之间的低耦合设计
- 对扩展开放,对修改关闭
使用实例(文件拷贝)
public class CopyTest {
public static void main(String[] args) {
/**
* 文件操作步骤
* 1.创建数据源
* 2.选择io流
* 3.执行I/O操作
* 4.关闭资源
*/
//创建数据源
File originalFile = new File("C:/test/io.txt");
File newFile = new File("c:/test/newio.txt");
//选择io流,由于对文件操作,所以选择文件输入输出流
InputStream inputStream = null;
OutputStream outputStream = null;
try {
inputStream = new FileInputStream(originalFile);
outputStream = new FileOutputStream(newFile);
//IO操作
byte[] temp = new byte[1024];
int len= -1;
//-1表示达到文件末尾,没有数据可读
while ((len=inputStream.read(temp))!=-1){
outputStream.write(temp,0,temp.length);
outputStream.flush();
}
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}finally {
//关闭io资源,后开启的先关闭
if(outputStream!=null){
try {
outputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (inputStream != null) {
try {
inputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
想法
多看看api文档,多写写代码,了解各个类的继承关系和用途,理解IO相关的概念。