文件 File 类

创建 File 实例

首先在进行输入输出处理之前,我们来看看数据的载体——文件。

因为我们在内存中存储的数据都是暂时的,一旦断电,都会消失,所以通常我们会把数据以文件的形式存储到永久存储的硬盘中,可以说,数据的操作单位就是文件。而 Java 中的 File 类就是专门对文件进行概括的一个类,它能很好的控制一个类。

1
File file=new File("Xorex.txt");

上面我们通过给构造方法传入一个文件的路径,成功实例化了 File 类。这个实例就是 Xorex.txt 这个文件,上面我们传入了 Xorex.txt 这个文件的相对路径,可以唯一确定这个文件,当然绝对路径也是可以的:E:\\Java\\Xorex.txt

需要注意的是,在不同的操作系统中,路径的表示是不同的,Windows 里面使用 \ 表示路径分隔符,而 Linux 里面使用 / 表示路径分隔符。又因为在字符串中 \ 表示转义,所以在写绝对路径或者相对路径的时候,需要用 \\ 来分隔路径。

得到文件路径

得到文件路径中,File 类里面有三种不同的方法,分别是:

1
2
3
getPath(); // 返回构造方法传入的的路径
getAbsolutePath(); // 若构造方法为相对路径,则和当前路径拼接成绝对路径之后返回。若为绝对路径,则直接返回构造方法传入的绝对路径。
getCanonicalPath(); // 返回标准的绝对路径,会将 .// ..// 这些转化。

获取 File 实例的信息

对于一个通过路径得到的 File 实例,如果路径结尾是一个文件名,那么 File 实例就是这个文件,如果是一个文件夹的名字,那么 File 实例就是这个文件夹。两者都可以被 File 类表示。

对于确定了一个文件/文件夹的 File 实例,我们可以通过方法获取一些这个文件/文件夹的信息。

1
2
3
4
5
6
boolean isFile() 是否为文件
boolean isDirectory() 是否为文件夹
boolean canRead() 是否可读
boolean canWrite() 是否可写
boolean canExecute() 是否可执行/可列出来(可执行指 JVM 是否有权限读取、修改它)
long length() 文件大小

如果确定了这个实例是一个文件夹,那么还可以使用下面的方法来获取文件夹里面的东西。

1
2
3
4
5
String[] list() // 以字符串列出所有内容 
String[] list(FileNameFilter) // 传入 FilenameFilter 接口的实例过滤列出内容
File[] listFiles() // 以 File 文件列出所有内容
File[] listFiles(FileFilter) // 传入 FileFilter 接口的实例过滤列出内容
File[] listFiles(FileNameFilter) // 传入 FilenameFilter 接口的实例过滤列出内容

操作文件

如果我们给的路径源文件不存在,可以直接创建,如果存在了,可以直接删除:

1
2
3
boolean createNewFile() // 文件路径的所有文件夹必须全部都存在才能创建成功
boolean delete() // 删除文件
boolean deleteOnExit() // JVM 结束运行时删除此文件

我们还可以用 File 的静态方法创建一个临时文件,这个临时文件默认保存在 C 盘用户文件夹下面等等等的一个叫作 Temp 的文件夹。我们可以自定义这个临时文件的前缀和后缀,甚至是保存地点,这个临时文件的名字会在前缀和后缀之间加上一串随机数,且不会自动删除,所以最好获得临时文件的实例之后直接调用 deleteOnExit()

1
File static createTempFile(prefix,suffix,[File]) 

可选参数 File ,为是否改变默认的保存地址为 File 的地址(需要传入的 File 实例是一个文件夹,然后临时文件创建到这个文件夹中)。

文件夹操作

1
2
3
boolean mkdir() //创建文件夹,必须前面路径的文件夹存在才能创建成功。
boolean mkdirs() //创建全路径文件夹,若前面路径不存在则自动创建。
boolean delete() // 文件夹目录为空才能删除。

字节 IO 流

try(resource)-catch 语句

InputStrean 和 OutputStream 这种流 IO 在运行的时候会占用各种资源,所以在结束流之后关闭流来释放资源是很有必要的。但是对于这种操作,往往可能关闭的不太顺利,可能出现流正在被使用无法关闭的情景,所以 try-catch 语句也就常常伴随着 close() 的调用而出现,来解决流关闭失败后的处理。

但是用一次 close() 就写一个 try-catch 有点麻烦了,Java 就给了一个小语法糖:try(resource)-catch 语句。这个语句和 try-catch 的区别就是对于在(resource) 这里开启的资源,结束运行之后,会自动调用资源的 close() 方法。因此能使用 try(resource)-catch 的语句必须拥有 close() 方法,也就是实现 closeable 接口。

正巧 IOStream 都实现了这个接口,就可以使用上面的语句是申请 IO 流资源而不用写关闭代码,因为编译器会自动帮忙写,所以也是一个语法糖。

1
2
3
try(new InputStream(File)) {
} catch(Exception e) {
} // 结束运行之后会自动调用 close() 来关闭流

看源代码发现,这个 InputStream 和 OutputStream 抽象类实现了 Closeable 接口。

1
public abstract class InputStream implements Closeable

而这个 Colseable 接口只有一个方法,就是 close() 方法, 也是我们对 IOStream 类操作的时候,最后需要释放资源时使用的 close() 方法。只要这个类实现了 Closeable 接口,那么就可以应用在 try(resource)-catch 语句里面,运行结束之后自动关闭流。

InputStream:FileInputStream

对于从文件中读取,使用 InputStream 的子类 FileInputStream,在构造方法传入 File 实例或者一个文件路径的字符串。

1
2
public FileInputStream(String name) //自己根据路径找文件
public FileInputStream(File file) //借助File实例找文件

确认文件之后就可以使用内置的 read() 方法读取文件内容了。

1
2
3
public int read() //一次读取一个字节
public int read(byte b[]) //一次读取b数组大小的字节
public int read(byte b[], int off, int len) //off是指读取的内容写入b数组中的起始位置,len表示写入的字节长度

OutputStream:FileOutputStram

对于向文件写入,使用 OutputStream 的子类 FileOutputStream,在构造方法传入 File 实例或者一个文件路径的字符串。

1
2
3
4
public FileOutputStream(String name) //传入文件路径覆盖写入
public FileOutputStream(String name, boolean append) //append为true时往文件末尾写入
public FileOutputStream(File file) //传入File实例获取路径
public FileOutputStream(File file, boolean append) //append为true时往文件末尾写入

确认文件之后就可以使用内置的 write() 方法向文件中写入内容了。

1
2
3
public void write(int b) //读取一个字节
public void write(byte b[]) //读取一个字节数组
public void write(byte b[], int off, int len) //设置写入文件的数据中,在b数组的起始位置off以及总长度len

在 Java 中为了效率,有了输入输出的缓冲区,只有缓冲区内数据到达一定程度,才会真正的写入到文件中,但是有些情况我们需要即使写入,那么就可以调用 flush() 来将缓冲区的数据直接写入到文件中。

Filter 设计模式

因为对于 IO 的输入输出来说,有的时候我们想要在输入输出的同时添加一些额外的工作,比如加密,解码等等。在通常情况下,解决方法就是单独写一个类继承于 IOStream 然后添加新功能,但当各种小功能需要排列组合使用的时候,就会引起功能类数量爆炸的情况,所以这里引入了 Filter 的设计模式,来对功能类进行排列组合。

首先要实现的功能需要继承于 FilterInputStream/FilterOutputStream ,然后功能类的构造方法参数为 IOStream 并用 super() 传给 Filter 类的构造方法,最后就可以重写 read() 和 write() 方法,使用原输入输出就用 in.read() 或者 out.write() ,只需要前后添加新功能即可,类似于普通代理。

有了功能类之后,就可以利用在构造方法中不停套娃其他的功能类,实现功能的排列组合,一个简单的对输出数据进心 base64 加密的功能类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
package Java;

import java.io.File;
import java.io.FileOutputStream;
import java.io.FilterOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.util.Base64;

public class test {
public static void main(String[] args) {
File file=new File("Xorex.Tempest");
try(Base64OutputString BaseOut=new Base64OutputString(new FileOutputStream(file))) {
BaseOut.write("I like coding!".getBytes());
} catch (IOException e) {
//TODO: handle exception
}
}
}

class Base64OutputString extends FilterOutputStream {
Base64OutputString(OutputStream out) {
super(out);
}

@Override
public void write(byte[] b) throws IOException {
out.write(Base64.getEncoder().encode(b));
}
}

Filter 父类里面的构造方法其实就是将传入的输入流实例赋值给 in 或者 out,然后这个可以被子类重写方法的时候调用,从而实现功能的组合。

剩下的东西不太想写了,鸽了。以后直接上传思维导图好了。

主要 IO 框架的思维导图:

IO 相关类的继承关系的思维导图: