在接触 IO 流前,无论是 变量的声明、数组的创建,又或者是复杂的并发设计还是 Jvm 的性能调优,我们更多的还是和内存打交道。但我们知道计算机组成包括运算器,控制器,存储器,输入设备,输出设备。也就是不仅仅只有内存和 CPU,所以程序设计语言必须要提供程序与外部设备交互的方式,这就是 IO 框架的由来。我们需要和外部设备进行数据的交互。
~
本篇内容包括:关于 IO 流、关于 Java IO 流、Java IO 层次体系结构
在接触 IO 流前,无论是 变量的声明、数组的创建,又或者是复杂的并发设计还是 Jvm 的性能调优,我们更多的还是和内存打交道。但我们知道计算机组成包括运算器,控制器,存储器,输入设备,输出设备。也就是不仅仅只有内存和 CPU,所以程序设计语言必须要提供程序与外部设备交互的方式,这就是 IO 框架的由来。我们需要和外部设备进行数据的交互。
我们的系统和外部的交互主要依赖于 比特流(数据线里传播),比特就是 bit 的谐音,计算机中“位”的意思,代表 0 或 1。1 位或者 1bit,就是一个 0 或一个 1。但是,毕竟 0 或 1 不能表示什么,所以计算机更常见的基本单位是字节,也就是用 8 位 0 或 1 组成的一段数据。
既然计算机和外界进行信息的输入和输出交互,用的是比特流,那么很容易就能想到 IO 流名字的由来了。就是比喻输入输出的数据像流一样。我们可以这么认为,任何外部设备与内存之间输入输出的操作,都是需要输入输出流(IO流)来完成的,这里的 IO 流,指的就是比特流(或者称字节流。这些外部设备,包括,键盘(标准输入设备)、显示器(标准输出设备)、音响、网络上另一台主机,甚至你玩游戏用的游戏手柄,以及各种各样的信号传感器,都可以叫做外部设备,和这些设备之间进行数据交互,显然不可能靠之前学习的那些数组,集合,常用类,String 等等来完成。而是要靠和外界数据交换的类来完成。靠什么来进行数据交换,就是前面说的,比特流,或者说 IO 流类。
Java 对数据的操作是通过流的方式,IO 流用来处理设备之间的数据传输,上传文件和下载文件,Java 用于操作流的对象都在 IO 包中。
既然要学习 IO 流,就得针对某一个输入输出设备来学习。哪种输入输出设备最重要同时也最常见?当然是硬盘。硬盘在这里的含义也可以理解为文件系统。(Java 程序是运行在某操作系统平台上的应用软件 JVM 上的,实际上 Java 程序可见的并不是硬盘,而是操作系统提供的文件系统,因此此处可直接理解为文件系统)。因此,我们学习 IO 流的时候,基本上是学习的 Java 如何操作文件系统,除了文件系统,我们还能够了解 Java 操作标准输入输出设备,如 System.in 和 System.out。
API 提供了两个顶层抽象类,用来表示操作所有的输入输出:InputStream、OutputStream。并且,这两个类表示字节的输入输出,因为输入输出的本质是字节流。这里注意体会一句话“字节流是最最基本的流”,这句话的由来就是因为计算机底层传递的就是字节。
当我们要操作文件的时候,就需要具体的对文件系统操作的 IO 实现类,于是就有了 FileInputStream 和 FileOutputStream,它们是文件输入输出字节流。这里之所以 FileInputStream/OutputStream 作为子类出现,按照面向对象思想理解就是,将来还有别的字节流来操作别的设备(比如将来需要通过操作网络设备获取网络数据,再比如需要操作机器人,那么或许就会再来个 RobotInputStream 和 RobotOutputStream,这些新的需求也就都可以继承这个体系)
学了文件 IO 字节流之后,我们会发现原始的字节流对象用起来没那么高效,因为每个读或写请求都由底层操作系统处理,这些请求往往会触发磁盘访问、网络活动或其他一些相对昂贵的操作。不带缓冲区的流对象,只能一个字节一个字节的读,每次都调用底层的操作系统 API,非常低效,而带缓冲区的流对象,可以一次读一个缓冲区,缓冲区空了才去调用一次底层 API,这就能大大提高效率。所以又有了 BufferedInputStream 和 BufferedOutputSteam,他们的用法是把字节流对象传入后再使用,也相当于把它俩套在了字节流的外面,给字节流装了个“外挂”,让基本字节流如虎添翼。
人们需要有能够处理字符的类,或者说这个类提供一个功能:就是把输入的字节转成字符,把要输出的字符转成计算机可以识别的字节。所以,你需要两个转换流:InputStreamReader 和 OutputStreamWriter。这两个类的作用分别是把字节流转成字符流,把字符流转成字节流。但是这两个流需要套在现成的字节流上才能使用,当中用到的设计模式也就是常说的装饰模式。当字节流被转成字符流之后,恭喜你,你可以不必操作字节流了,而是可以用人类的方式 read 和 write 各种“文字”。
还是回到文件系统,我们最常见的是和文件系统打交道,那么针对如此常见的用途,读取文本文件能不能用一种方便的方式呢?当然,大牛们替你想到并提供了。FileReader和FileWriter这两个流对象可以直接把文件转成读取、写入流。让你省去了创建字节流,再套上转换流的步骤。看看这类名起的,实际上很形象,xxxReader 和 xxxWriter,明摆着告诉你“阅读和书写”都是“人可以做的”也就是他们表示的是字符流。同理上面的 InputStreamReader 和 OutputStreamWriter,表示的是把字节流转成人可读的,把字节流转成人可写的。因此他们的顶层抽象类:Reader 和 Writer,表示的是所有人类可读可写的字符流统称。
在整个 Java.io 包中最重要的就是 5 个类和一个接口。5个类指的是 File、OutputStream、InputStream、Writer、Reader;一个接口指的是 Serializable 掌握了这些 IO 的核心操作那么对于 Java 中的 IO 体系也就有了一个初步的认识了
Java I/O主要包括如下几个层次,包含三个部分:
主要的类如下:
File(文件特征与管理):用于文件或者目录的描述信息,例如生成新目录,修改文件名,删除文件,判断文件所在路径等。
InputStream(二进制格式操作):抽象类,基于字节的输入操作,是所有输入流的父类。定义了所有输入流都具有的共同特征。
OutputStream(二进制格式操作):抽象类。基于字节的输出操作。是所有输出流的父类。定义了所有输出流都具有的共同特征。
Java 中字符是采用 Unicode 标准,一个字符是 16 位,即一个字符使用两个字节来表示。为此,Java 中引入了处理字符的流。
Reader(文件格式操作):抽象类,基于字符的输入操作。
Writer(文件格式操作):抽象类,基于字符的输出操作。
RandomAccessFile(随机文件操作):它的功能丰富,可以从文件的任意位置进行存取(输入输出)操作。