JAVA IO流
基础
IO
数据的传输,可以看做是一种数据的流动,按照流动的方向,以内存为基准,分为输入Input 和输出Output ,即流向内存是输入流,流出内存的输出流,统称为 IO流。 Java中I/O操作主要是指使用java.io包下的内容,进行输入、输出操作。输入也叫做读取数据,输出也叫做作写出数据。
流
流代表任何有能力产出数据的数据源对象或者是有能力接受数据的接收端对象;流的本质是数据传输,我们根据数据传输特性将流抽象为各种类,方便更直观的进行数据操作。流的具体作用是数据源和目的地建立一个输送通道。
流的分类
流向
根据数据流向不同分为:
输入流:读取外部数据(磁盘、网卡等设备的数据)到程序(内存)中。如InputStream,Reader
输出流:把程序(内存)中的内容输出到磁盘、网卡设备中。如OutputStream、Writer
类型
根据处理数据类型的不同分为:
- 字节流:可以用于读写包括二进制文件在内的任何类型文件。
- 字符流:可以用于读写文本文件
功能
根据功能的不同分为:
- 节点流:可以从或向一个特定的地方(节点)读写数据。如FileInputStream,FileReader。
- 处理流:是对一个已存在的流的连接和封装,通过所封装的流的功能调用实现数据读写。如BufferedReader。处理流的构造方法总是要带一个其他的流对象做参数。一个流对象经过其他流的多次包装,称为流的链接。
装饰器模式
Java I/O 使用了装饰者模式来实现。以 InputStream 为例:
- InputStream 是抽象组件;
- FileInputStream 是 InputStream 的子类,属于具体组件,提供了字节流的输入操作;
- FilterInputStream 属于抽象装饰者,装饰者用于装饰组件,为组件提供额外的功能。例如 BufferedInputStream 为 FileInputStream 提供缓存的功能
实例化一个具有缓存功能的字节流对象时,只需要在 FileInputStream 对象上再套一层 BufferedInputStream 对象即可。
1 | FileInputStream fileInputStream = new FileInputStream(filePath); |
编码与解码
编码就是把字符转换为字节,而解码是把字节重新组合成字符。如果编码和解码过程使用不同的编码方式,就出现了乱码。
编码与解码的过程需要遵循某种规则,这种规则就是不同的字符编码。我们在刚刚学习编程的时候最早接触就是ASCII码,它主要是用来显示英文和一些符号,到后面还有接触到别的编码规则常用的有:gb2312,gbk,utf-8等。它们分别属于不同的编码集。
我们需要明确的是字符编码和字符集是两个不同层面的概念。
- encoding是charset encoding的简写,即字符集编码,简称编码。
- charset是character set的简写,即字符集。
编码是依赖于字符集的,一个字符集可以有多个编码实现,就像代码中的接口实现依赖于接口一样。
String 可以看成一个字符序列,可以指定一个编码方式将它编码为字节序列,也可以指定一个编码方式将一个字节序列解码为 String。如果不指定编码解码方式,默认的编码解码方式与平台有关,一般为 UTF-8。
1 | String str1 = "中文"; |
JAVA IO流结构
Java IO流中所有的接口和类都放在java.io这个包下。其中最重要的就是5个类和一个接口:
- File(文件特征与管理):File类是对文件系统中文件以及文件夹进行封装的对象,可以通过对象的思想来操作文件和文件夹。 File类保存文件或目录的各种元数据信息,包括文件名、文件长度、最后修改时间、是否可读、获取当前文件的路径名,判断指定文件是否存在、获得当前目录中的文件列表,创建、删除文件和目录等方法。
- InputStream(二进制格式操作):抽象类,基于字节的输入操作,是所有输入流的父类。定义了所有输入流都具有的共同特征。
- OutputStream(二进制格式操作):抽象类。基于字节的输出操作。是所有输出流的父类。定义了所有输出流都具有的共同特征。
- Reader(文件格式操作):抽象类,基于字符的输入操作。
- Writer(文件格式操作):抽象类,基于字符的输出操作。
- RandomAccessFile(随机文件操作):一个独立的类,直接继承至Object.它的功能丰富,可以从文件的任意位置进行存取(输入输出)操作。
- Serializable(序列化操作):是一个空接口,为对象提供标准的序列化与反序列化操作。
Java IO流的整体架构图如下:
File类
File类是文件和目录路径名的抽象表示形式,主要用于文件和目录的创建、查找和删除等操作。即Java中把文件或者目录(文件夹)都封装成File对象。也就是说如果我们要去操作硬盘上的文件或者目录只要创建File这个类即可。不过要注意的是File类只是对文件的操作类,只能对文件本身进行操作,不能对文件内容进行操作。
File类常用方法
获取文件的相关信息
- String getAbsolutePath() :获取绝对路径名字符串。
- String getName():获取文件或目录的名称。
- String getPath():获取路径名字符串。
- String getParent() :获取路径名父目录的路径名字符串;如果此路径名没有指定父目录,则返回 null。
- Long lastModified():获取最后一次修改的时间(返回毫秒)。
- Long length():获取文件的长度,如果表示目录则返回值未指定。
判断功能
- Boolean isDirectory():判断此路径是否为一个目录
- Boolean isFile():判断是否为一个文件
- Boolean exists():判断文件或目录是否存在
- Boolean canExecute():判断文件是否可执行
- Boolean canRead():判断文件是否可读
- Boolean canWrite():判断文件是否可写
- Boolean isHidden():判断是否为隐藏文件
新建和删除
- Boolean createNewFile():创建文件,如果文件存在则不创建,返回false,反之返回true。
- Boolean mkdir():创建文件目录。如果此文件目录存在则不创建,如果此文件目录的上层目录不存在也不创建。
- Boolean mkdirs(): 创建文件目录。如果上层文件目录不存在也会创建。
- Boolean delete():删除的文件或目录。如果目录下有文件或目录则不会删除。
目录下文件的获取
- String[] list():返回一个字符串数组,获取指定目录下的所有文件或者目录名称的数组。
- File[] listFiles():返回一个抽象路径名数组,获取指定目录下的所有文件或者目录的File数组。
重命名文件
- Boolean renameTo(File dest):把文件重命名到指定路径。
目录递归
1 | //递归地列出一个目录下所有文件 |
字节操作
无论是文本、图片、音频还是视频,所有的文件都是以二进制(字节)形式存在的,IO流中针对字节的输入输出提供了一系列的流,统称为字节流。字节流是程序中最常用的流。
InputStream
InputStream是所有字节输入流的父类,定义了所有字节输入流都具有的共同特征。其内部提供的方法如下(重点关注**read()**方法):
变量和类型 | 方法 | 描述 |
---|---|---|
int |
available() |
返回可以从此输入流中无阻塞地读取(或跳过)的字节数的估计值,可以是0,或者在检测到流结束时为0。 |
void |
close() |
关闭此输入流并释放与该流关联的所有系统资源。 |
void |
mark(int readlimit) |
标记此输入流中的当前位置。 |
boolean |
markSupported() |
测试此输入流是否支持 mark 和 reset 方法。 |
static InputStream |
nullInputStream() |
返回一个不读取任何字节的新 InputStream 。 |
abstract int |
read() |
从输入流中读取下一个数据字节,如果没有字节可用,因为已经到达了流的末端,则返回值-1。这个方法会阻塞,直到输入数据可用,检测到流的结束,或者抛出一个异常。 |
int |
read(byte[] b) |
从输入流中读取一些字节数并将它们存储到缓冲区数组 b |
int |
read(byte[] b, int off, int len) |
从输入流 len 最多 len 字节的数据读入一个字节数组。 |
byte[] |
readAllBytes() |
从输入流中读取所有剩余字节。 |
int |
readNBytes(byte[] b, int off, int len) |
从输入流中读取请求的字节数到给定的字节数组中。 |
byte[] |
readNBytes(int len) |
从输入流中读取指定的字节数。 |
void |
reset() |
将此流重新定位到上次在此输入流上调用 mark 方法时的位置。 |
long |
skip(long n) |
跳过并丢弃此输入流中的 n 字节数据。 |
long |
transferTo(OutputStream out) |
从该输入流中读取所有字节,并按读取顺序将字节写入给定的输出流。 |
OutputStream
OutputStream是所有字节输出流的父类,定义了所有字节输出流都具有的共同特征。其内部提供的方法如下(重点关注write方法):
变量和类型 | 方法 | 描述 |
---|---|---|
void |
close() |
关闭此输出流并释放与此流关联的所有系统资源。 |
void |
flush() |
刷新此输出流并强制写出任何缓冲的输出字节。 |
static OutputStream |
nullOutputStream() |
返回一个新的 OutputStream ,它丢弃所有字节。 |
void |
write(byte[] b) |
将 b.length 字节从指定的字节数组写入此输出流。 |
void |
write(byte[] b, int off, int len) |
将从偏移量 off 开始的指定字节数组中的 len 字节写入此输出流。 |
abstract void |
write(int b) |
将指定的字节写入此输出流。 |
文件复制
1 | public static void copyFile(String src, String dist) throws IOException { |
字符操作
Reader
Reader是所有字符输入流的父类,定义了所有字符输入流都具有的共同特征。其内部提供的方法如下:
变量和类型 | 方法 | 描述 |
---|---|---|
abstract void |
close() |
关闭流并释放与其关联的所有系统资源。 |
void |
mark(int readAheadLimit) |
标记流中的当前位置。 |
boolean |
markSupported() |
判断此流是否支持mark()操作。 |
static Reader |
nullReader() |
返回不读取任何字符的新 Reader 。 |
int |
read() |
读一个字符。 |
int |
read(char[] cbuf) |
将字符读入数组。 |
abstract int |
read(char[] cbuf, int off, int len) |
将字符读入数组的一部分。 |
int |
read(CharBuffer target) |
尝试将字符读入指定的字符缓冲区。 |
boolean |
ready() |
判断此流是否可以读取。 |
void |
reset() |
重置流。 |
long |
skip(long n) |
跳过字符。 |
long |
transferTo(Writer out) |
读取此阅读器中的所有字符,并按照读取的顺序将字符写入给定的编写器。 |
Writer
Reader是所有字符输出流的父类,定义了所有字符输出流都具有的共同特征。其内部提供的方法如下:
变量和类型 | 方法 | 描述 |
---|---|---|
Writer |
append(char c) |
将指定的字符追加到此writer。 |
Writer |
append(CharSequence csq) |
将指定的字符序列追加到此writer。 |
Writer |
append(CharSequence csq, int start, int end) |
将指定字符序列的子序列追加到此writer。 |
abstract void |
close() |
关闭流,先冲洗它。 |
abstract void |
flush() |
刷新流。 |
static Writer |
nullWriter() |
返回一个新的 Writer ,它丢弃所有字符。 |
void |
write(char[] cbuf) |
写一个字符数组。 |
abstract void |
write(char[] cbuf, int off, int len) |
写一个字符数组的一部分。 |
void |
write(int c) |
写一个字符。 |
void |
write(String str) |
写一个字符串。 |
void |
write(String str, int off, int len) |
写一个字符串的一部分。 |
实现逐行输出文本文件的内容
1 | public static void readFileContent(String filePath) throws IOException { |
对象操作
序列化与反序列化
序列化就是将一个对象转换成字节序列,方便存储和传输。
- 序列化:ObjectOutputStream.writeObject()
- 反序列化:ObjectInputStream.readObject()
不会对静态变量进行序列化,因为序列化只是保存对象的状态,静态变量属于类的状态。
transient
transient 关键字可以使一些属性不会被序列化。
ArrayList 中存储数据的数组 elementData 是用 transient 修饰的,因为这个数组是动态扩展的,并不是所有的空间都被使用,因此就不需要所有的内容都被序列化。通过重写序列化和反序列化方法,使得可以只序列化数组中有内容的那部分数据。
1 | private transient Object[] elementData; |
Serializable示例
序列化的类需要实现 Serializable 接口,它只是一个标准,没有任何方法需要实现,但是如果不去实现它的话而进行序列化,会抛出异常。
1 | public static void main(String[] args) throws IOException, ClassNotFoundException { |
缓冲
缓冲流也叫高效流,是处理流的一种,即是作用在流上的流。其目的就是加快读取和写入数据的速度。
缓冲流本身并没有IO功能,只是在别的流上加上缓冲效果从而提高了效率。当对文件或其他目标频繁读写或操作效率低,效能差时。这时使用缓冲流能够更高效的读写信息。因为缓冲流先将数据缓存起来,然后一起写入或读取出来。所以说,缓冲流还是很重要的,在IO操作时加上缓冲流提升性能。
Java IO流中对应的缓冲流有以下四个:
字节缓冲流:BufferedInputStream、BufferedOutputStream
字符缓冲流:BufferedReader、BufferedWriter
实例化一个具有缓存功能的字节流对象:
1 | FileInputStream fileInputStream = new FileInputStream(filePath); |
转换
转换流也是一种处理流,它提供了字节流和字符流之间的转换。在Java IO流中提供了两个转换流:InputStreamReader 和 OutputStreamWriter,这两个类都属于字符流。其中InputStreamReader将字节输入流转为字符输入流,继承自Reader。OutputStreamWriter是将字符输出流转为字节输出流,继承自Writer。
转换流的原理是:字符流 = 字节流 + 编码表。在转换流中选择正确的编码非常的重要,因为指定了编码,它所对应的字符集自然就指定了,否则很容易出现乱码,所以编码才是我们最终要关心的。
InputStreamReader
InputStreamReader是字节流到字符流的桥梁:它读取字节,并使用指定的字符集将其解码为字符。它的字符集可以由名称指定,也可以接受平台的默认字符集。
构造方法:
- InputStreamReader(InputStream in):创建一个默认字符集字符输入流。
- InputStreamReader(InputStream in, String charsetName):创建一个指定字符集的字符流。
OutputStreamWriter
OutputStreamWriter是字符流通向字节流的桥梁:用指定的字符集将字符编码为字节。它的字符集可以由名称指定,也可以接受平台的默认字符集。
构造方法:
- OutputStreamWriter(OutputStream in): 创建一个使用默认字符集的字符流。
- OutputStreamWriter(OutputStream in, String charsetName): 创建一个指定字符集的字符流。
转换文件编码
1 | //将读入UTF-8文件转换为GBK |