Java 常用类之 IO

IO

Java.io 包几乎包含了所有操作输入、输出需要的类。所有这些流类代表了输入源和输出目标。

Java.io 包中的流支持很多种格式,比如:基本类型、对象、本地化字符集等等。

一个流可以理解为一个数据的序列。输入流表示从一个源读取数据,输出流表示向一个目标写数据。

Java 为 I/O 提供了强大的而灵活的支持,使其更广泛地应用到文件传输和网络编程中。

但本节讲述最基本的和流与 I/O 相关的功能。我们将通过一个个例子来学习这些功能。

FileInputStream

对象创建

1
2
FileInputStream stream = new FileInputStream("C:\\JavaIO\\a.txt");
FileInputStream stream1 = new FileInputStream(new File("C:\\JavaIO\\a.txt"));

一次读取一个字节

1
2
3
4
FileInputStream stream = new FileInputStream(new File("C:\\JavaIO\\a.txt"));
int read = stream.read();
stream.close();
System.out.println(read);

当读到 - 1 时则表示读取结束了

1
2
3
4
5
6
FileInputStream stream = new FileInputStream(new File("C:\\JavaIO\\a.txt"));
int b;
while ((b=stream.read()) != -1){
System.out.println(b);
}
stream.close();

循环读取全部内容

一次读一个字节数组

1
2
3
4
FileInputStream stream = new FileInputStream(new File("C:\\JavaIO\\a.txt"));
byte[] bytes = new byte[5];
stream.read(bytes);
System.out.println(Arrays.toString(bytes));

一次读完文件

1
2
3
4
5
6
7
8
FileInputStream stream = new FileInputStream(new File("C:\\JavaIO\\a.txt"));
byte[] bytes = new byte[1024 * 2];
int len;
while ((len=stream.read(bytes))!=-1){
System.out.println(len);
System.out.println(new String(bytes,0,len));
}
stream.close();

资源释放

解决读取期间出现异常如何正确释放资源

jdk7 之前

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
//创建对象并且初始化
FileInputStream stream = null;
try {
//更新stream内容
stream = new FileInputStream(new File("C:\\JavaIO\\a.txt"));
byte[] bytes = new byte[1024 * 2];
int len;
while ((len=stream.read(bytes))!=-1){
System.out.println(new String(bytes,0,len));
}
}catch (IOException ioException){
ioException.printStackTrace();
}finally {
//释放资源,在释放资源时仍有可能会有IO异常,继续tyr catch
//当stream为空时关闭资源会导致空指针异常,所以在关闭前需要确实要释放的对象是否为空
if (!Objects.isNull(stream)){
try {
stream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}

jdk7 版本

1
2
3
4
5
6
7
8
9
10
11
12
//在try的括号中进行初始化,如果在发生异常时会默认执行close
try(FileInputStream stream = new FileInputStream(new File("C:\\JavaIO\\a.txt"))) {
byte[] bytes = new byte[1024 * 2];
int len;
while ((len=stream.read(bytes))!=-1){
System.out.println(new String(bytes,0,len));
}
//无需释放资源,写不写close都会释放资源
stream.close();
}catch (IOException e){
e.printStackTrace();
}

jdk7 版本是如何做到自动释放资源的呢?下面通过代码来讲解一下。

在代码 try(FileInputStream stream = new FileInputStream(new File("C:\\JavaIO\\a.txt"))) 中,创建的对象 FileInputStream 是继承了 InputStream 的

B7417AD408CC6B084686DEDCD2A094CA

而在 InputStream 中实现了了 Closeable 接口

03BB929FFF67FB9B91E4F4A84723C3E9

在 Closeable 中定义了一个 close 方法,该方法继承于 AutoCloseable

365F812A7EB00CE607D5F5CC81BFB44D

因为 FileInputStream 继承了 InputStream,而 InputStream 实现了 Closeable 接口,所以在 FileInputStream 产生异常时就会执行它的 close 方法。

通过自己写代码来重现一下这个过程

1
2
3
4
5
6
7
public class Res implements Closeable {

@Override
public void close() throws IOException {
System.out.println("资源已经释放");
}
}
1
2
3
4
5
6
7
public static void main(String[] args) {
try(Res res = new Res()) {

}catch (IOException e){
e.printStackTrace();
}
}

jdk9 版本

1
2
3
4
5
6
7
8
public static void main(String[] args) {
Res res = new Res();
try(res) {

}catch (Exception e){
e.printStackTrace();
}
}

在 jdk9 中在 jdk7 的基础上做了优化,定义不一定要放到 try 中,只需要讲对象放在 try () 中就可以实现自动执行 close 方法的效果

FileOutputStream

对象创建

1
2
FileOutputStream fileOutputStream = new FileOutputStream("C:\\JavaIO\\b.txt");
FileOutputStream fileOutputStream1 = new FileOutputStream(new File("C:\\JavaIO\\b.txt"));

一次写一个字节

1
2
3
4
5
6
7
FileOutputStream fos = new FileOutputStream("C:\\JavaIO\\b.txt");
try(fos) {
// 传字符也可以是因为自动转型
fos.write('a');
}catch (IOException e){
e.printStackTrace();
}

一次写一个字节数组

1
2
3
4
5
6
7
8
9
    FileOutputStream fos = new FileOutputStream("C:\\JavaIO\\b.txt");
byte[] bytes;
try(fos) {
bytes = "abc".getBytes();
fos.write(bytes);
}catch (IOException e){
e.printStackTrace();
}
}

文件续写

1
2
3
4
5
6
7
8
//在FileOutputStream中加true说明是续写
FileOutputStream fos = new FileOutputStream("C:\\JavaIO\\b.txt",true);
try(fos) {
byte[] bytes = "defgh".getBytes();
fos.write(bytes);
}catch (IOException e){
e.printStackTrace();
}

文件的复制

文件的复制就是循环的循环读写

1
2
3
4
5
6
7
8
9
10
11
12
13
private static void copyFile(File from, File to) throws IOException{
FileInputStream inputStream = new FileInputStream(from);
FileOutputStream outputStream = new FileOutputStream(to);
try(inputStream;outputStream) {
byte[] bytes = new byte[1024 * 2];
int len;
while ((len=inputStream.read(bytes))!=-1){
outputStream.write(bytes,0,len);
}
}catch (IOException e){
e.printStackTrace();
}
}

定义一个方法,循环读取旧文件的内容并且写到新文件中

1
2
3
4
5
public static void main(String[] args) throws IOException {
File oldFile = new File("C:\\JavaIO\\file.mp3");
File newFile = new File("C:\\JavaIO\\newFile.mp3");
copyFile(oldFile,newFile);
}

C7EF56CC8329B8524973B05C9D0567F0

文件夹的复制

从简单到困难,先写一个不考虑子文件夹递归的复制文件操作

不考虑递归版本

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
public static void main(String[] args) throws IOException {
File fromDir = new File("C:\\本地素材库\\2022-07-19");
File toDir = new File("C:\\本地素材库\\2022年07月19日");
copyFolderFile(fromDir,toDir);
}
//不考虑子文件夹版本
private static void copyFolderFile(File from,File to) throws IOException {
File[] files = from.listFiles();
byte[] bytes = new byte[1024 * 2];
for (File file : files) {
if (file.isDirectory()){
continue;
}
File oldFile = new File(from, file.getName());
File newFile = new File(to, file.getName());
FileInputStream fis = new FileInputStream(oldFile);
FileOutputStream fos = new FileOutputStream(newFile);
try(fis;fos) {
int len;
while ((len=fis.read(bytes))!=-1){
fos.write(bytes,0,len);
}
}catch (IOException e){
e.printStackTrace();
}
}
}

D53A23F4F4C21ADE91A3D7A1443BFEF5

源文件夹共有 148 个文件,对该文件夹进行复制后,所以文件都复制到了目标文件夹

B8CC3BA2184A2A9C9BF5523C409F70B4

递归版本

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
31
32
33
34
35
36
37
38
39
40
public static void main(String[] args) throws IOException {
File fromDir = new File("C:\\本地素材库\\2022-07-19");
File toDir = new File("C:\\本地素材库\\2022年07月19日");
copyAllFile(fromDir,toDir);
}

//考虑递归所以文件包括子文件夹的版本
private static void copyAllFile(File from,File to) throws IOException {
//获取目录下的所以文件和目录
File[] files = from.listFiles();
byte[] bytes = new byte[1024 * 2];
//遍历文件和目录
for (File file : files) {
//判断是文件还是文件夹
if (file.isFile()){
//创建输入流和输出流
FileInputStream inputStream = new FileInputStream(file);
FileOutputStream outputStream = new FileOutputStream(new File(to, file.getName()));
//进行自动关闭资源
try(inputStream;outputStream) {
int len;
//写文件操作
while ((len=inputStream.read(bytes))!=-1){
outputStream.write(bytes,0,len);
}
}catch (IOException e){
e.printStackTrace();
}
}else {
//递归复制子目录,创建目标目录的子文件夹
File newDir = new File(to, file.getName());
//判断子文件夹是否存在,不存在则创建新的
if (!newDir.exists()){
newDir.mkdir();
}
//递归复制文件,将当前目录和创建好的目标目录传递进去进行递归
copyAllFile(file,newDir);
}
}
}

FileReader

对象创建

文件的目标不可以是文件夹,否则会产生异常

1
2
FileReader fileReader = new FileReader("C:\\JavaIO\\file.mp3");
FileReader reader = new FileReader(new File("C:\\JavaIO\\file.mp3"));

一次读取一个字节

1
2
3
4
5
6
FileReader fileReader = new FileReader("C:\\JavaIO\\a.txt");
try(fileReader) {
System.out.println((char)fileReader.read());
}catch (IOException e){
e.printStackTrace();
}
1
2
3
4
5
6
7
8
9
FileReader fileReader = new FileReader("C:\\JavaIO\\a.txt");
try(fileReader) {
int ch;
while ((ch=fileReader.read())!=-1){
System.out.println((char) ch);
}
}catch (IOException e){
e.printStackTrace();
}

一次读一个数组

1
2
3
4
5
6
7
8
9
try(FileReader fileReader = new FileReader("C:\\JavaIO\\a.txt")) {
char[] chars = new char[1024 * 2];
int len;
while ((len=fileReader.read(chars))!=-1){
System.out.println(new String(chars,0,len));
}
} catch (IOException e) {
e.printStackTrace();
}

需要注意的是需要使用 char [] 接收内容

FileWriter

对象创建

1
FileWriter fw = new FileWriter("C:\\JavaIO\\a.txt");

缓冲区

1
2
3
4
5
6
7
8
9
FileWriter fw = new FileWriter("C:\\JavaIO\\a.txt");
try(fw) {
//写入内存缓冲区
fw.write("12345678910");
//从内容缓冲区写到文件中
fw.flush();
}catch (IOException e){
e.printStackTrace();
}

在写操作中需要从缓冲区写到文件的操作,也就是需要通过 flush 写入文件。

复制文本文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public static void main(String[] args) throws IOException {
copyText(new File("C:\\JavaIO\\斗破苍穹.txt"),new File("C:\\JavaIO\\Break through the sky.txt"));
}
private static void copyText(File from, File to) throws IOException {
FileReader reader = new FileReader(from);
FileWriter writer = new FileWriter(to);
char[] chars = new char[1024 * 2];
try(reader;writer) {
int len;
while ((len=reader.read(chars))!=-1){
writer.write(chars,0,len);
writer.flush();
}
}catch (IOException e){
e.printStackTrace();
}
}

其实跟字节流的读写相比,其实差得并不算多。

设置字符流编码表

1
FileReader reader = new FileReader(from, StandardCharsets.UTF_8);

高速缓冲流

对象创建

1
2
3
4
BufferedInputStream bufferedInputStream = new BufferedInputStream(new FileInputStream("C:\\JavaIO\\a.txt"));
BufferedOutputStream bufferedOutputStream = new BufferedOutputStream(new FileOutputStream("C:\\JavaIO\\a.txt"));
BufferedReader bufferedReader = new BufferedReader(new FileReader("C:\\JavaIO\\Break through the sky.txt"));
BufferedWriter bufferedWriter = new BufferedWriter(new FileWriter("C:\\JavaIO\\Break through the sky.txt"));

高速缓冲读写对比

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
31
32
public static void main(String[] args) throws IOException {
FileReader fr = new FileReader("C:\\JavaIO\\Break through the sky.txt");
FileWriter fw = new FileWriter("C:\\JavaIO\\斗破苍穹.txt");
BufferedReader br = new BufferedReader(new FileReader("C:\\JavaIO\\Break through the sky.txt"));
BufferedWriter bw = new BufferedWriter(new FileWriter("C:\\JavaIO\\斗破苍穹.txt"));
copyFile(fr,fw);
bufferCopyFile(br,bw);
}
private static void copyFile(FileReader fr,FileWriter fw) throws IOException {
long start = System.nanoTime();
char[] chars = new char[1024 * 2];
int len;
while ((len=fr.read(chars))!=-1){
fw.write(chars,0,len);
}
fr.close();
fw.close();
long end = System.nanoTime();
System.out.println("普通复制运行时间:"+(end-start)+"ms");
}
private static void bufferCopyFile(BufferedReader br,BufferedWriter bw) throws IOException {
long start = System.nanoTime();
String line;
while ((line=br.readLine())!=null){
bw.write(line);
bw.newLine();
}
br.close();
bw.close();
long end = System.nanoTime();
System.out.println("高速缓存运行时间:"+(end-start)+"ms");
}

C5FE2EFEE783C456EF4F03D9F0AB4DB2