Java之资源关闭

查看之前写了一个FileService.java的服务,在创建了流之后,没有关闭流,在测试的时候发现没有问题,是因为资源没有被另外引用。

当一直打开文件,并且不关闭的时候,会出现这样的异常:Too many open files in system,是指的系统级的文件句柄。可以理解为我们建立的OutputStream是先与系统的文件句柄连接,通过他才能操作文件。而系统的文件句柄资源是有限的。当我们不关闭流,那这个文件句柄就一直占用着,当“别人”再想用时就没有资源了,从而报出这样的异常。

image-20201030111637487

传统方式的关闭文件流

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
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
/**
* 传统方式,通过url获取本地文件内容,调用函数式接口处理
*
* @param url 文件内容
* @param fileConsumer 文件处理函数式接口
*/
public void oldFileHandle(String url, FileConsumer fileConsumer) {
// 声明
FileInputStream fileInputStream = null;
InputStreamReader inputStreamReader = null;
BufferedReader bufferedReader = null;

try {
// 创建文件读取流
fileInputStream = new FileInputStream(url);
inputStreamReader = new InputStreamReader(fileInputStream);
bufferedReader = new BufferedReader(inputStreamReader);

// 定义行变量和内容sb
String line;
StringBuilder stringBuilder = new StringBuilder();

// 循环读取文件内容
while ((line = bufferedReader.readLine()) != null) {
stringBuilder.append(line).append("\n");
}

// 调用函数式接口方法,将文件内容传递给lambda表达式,实现业务逻辑
fileConsumer.fileHandler(stringBuilder.toString());
} catch (IOException e) {
e.printStackTrace();
} finally {
// 关闭流
if (bufferedReader != null) {
try {
bufferedReader.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (inputStreamReader != null) {
try {
inputStreamReader.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (fileInputStream != null) {
try {
fileInputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}

新方式的关闭文件流

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
/**
* 新的方式,通过url获取本地文件内容,调用函数式接口处理
*
* @param url 文件内容
* @param fileConsumer 文件处理函数式接口
*/
public void newFileHandle(String url, FileConsumer fileConsumer) {
try (
// 声明、创建文件的读取流
FileInputStream fileInputStream = new FileInputStream(url);
InputStreamReader inputStreamReader = new InputStreamReader(fileInputStream);
BufferedReader bufferedReader = new BufferedReader(inputStreamReader);


) {
// 定义行变量和内容sb
String line;
StringBuilder stringBuilder = new StringBuilder();

// 循环读取文件内容
while ((line = bufferedReader.readLine()) != null) {
stringBuilder.append(line).append("\n");
}

// 调用函数式接口方法,将文件内容传递给lambda表达式,实现业务逻辑
fileConsumer.fileHandler(stringBuilder.toString());
} catch (IOException e) {
e.printStackTrace();
}
}

为什么要手动关闭文件流?

垃圾回收(GC)的特点

  • 垃圾回收机制只负责回收堆内存资源,不会回收任务物理资源
  • 程序无法精确控制垃圾回收动作的具体发生时间
  • 在垃圾回收之前,总会先调用它的finalize方法

常见需手动释放的物理资源

  • 文件/流资源
  • 套接字资源
  • 数据库连接资源

物理资源可以不手动释放吗?

  • 资源被长时间无效占用
  • 超过最大限制后,将无资源可用
  • 导致系统无法正常运行

实战案例:文件拷贝(传统方式关闭流资源)

  • 目的:Java7前,实现利用基础IO流完成文件拷贝功能。

  • 实现步骤:

    • 输入输出流的创建
    • 文件的复制
    • 流资源的关闭
  • 代码实现:

    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
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    package com.java.example.resource;

    import org.testng.annotations.Test;

    import java.io.FileInputStream;
    import java.io.FileOutputStream;
    import java.io.IOException;

    /**
    * JDK7之前的文件拷贝功能
    *
    * @author jingLv
    * @date 2020/10/30
    */
    public class FileCopyTest {

    @Test
    public void copyFile() {
    //1 .创建输入输出流
    // 定义输入路径和输出路径
    String originalUrl = "源文件路径";
    String targetUrl = "拷贝文件路径";
    // 声明文件输入流,文件输出流
    FileInputStream originalFileInputStream = null;
    FileOutputStream targetFileOutputStream = null;

    try {
    // 实例化文件流对象
    originalFileInputStream = new FileInputStream(originalUrl);
    targetFileOutputStream = new FileOutputStream(targetUrl);

    //2. 执行文件拷贝,读取文件内容,写入到另一个文件中
    // 读取的字节信息
    int content;

    // 迭代,读取/写入字节
    while ((content = originalFileInputStream.read()) != -1) {
    targetFileOutputStream.write(content);
    }
    } catch (IOException e) {
    e.printStackTrace();
    } finally {
    //3. 关闭文件流资源
    if (targetFileOutputStream != null) {
    try {
    targetFileOutputStream.close();
    } catch (IOException e) {
    e.printStackTrace();
    }
    }
    if (originalFileInputStream != null) {
    try {
    originalFileInputStream.close();
    } catch (IOException e) {
    e.printStackTrace();
    }
    }
    }
    }
    }

实战案例:文件拷贝(TWR方式关闭流资源)

  • 目的:Java7之后,实现利用基础IO流完成文件拷贝功能

  • 实现步骤:

    • 输入输出流的创建
    • 文件的复制

    注:这种方式不用显示的在关闭流了

  • 代码实现:

    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
    41
    42
    43
    package com.java.example.resource;

    import org.testng.annotations.Test;

    import java.io.FileInputStream;
    import java.io.FileOutputStream;
    import java.io.IOException;

    /**
    * 基于JDK7之后,实现正确关闭流资源方法
    * try - with - resource
    *
    * @author jingLv
    * @date 2020/10/30
    */
    public class NewFileCopyTest {

    @Test
    public void copyFile() {
    //1 .创建输入输出流
    // 定义输入路径和输出路径
    String originalUrl = "源文件路径";
    String targetUrl = "拷贝文件路径";

    // 初始化输入/输出对象
    try (
    FileInputStream originalFileInputStream = new FileInputStream(originalUrl);
    FileOutputStream targetFileOutputStream = new FileOutputStream(targetUrl);
    ) {
    //2. 执行文件拷贝,读取文件内容,写入到另一个文件中
    // 读取的字节信息
    int content;

    // 迭代,读取/写入字节
    while ((content = originalFileInputStream.read()) != -1) {
    targetFileOutputStream.write(content);
    }
    } catch (IOException e) {
    e.printStackTrace();
    }

    }
    }

try-with-resource简介

  • Java7引入新特性
  • 优雅关闭资源
  • 一种Java语法糖

实战案例:源码分析

查看编译后源码,分析内部原理。

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
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//

package com.java.example.resource;

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import org.testng.annotations.Test;

public class NewFileCopyTest {
public NewFileCopyTest() {
}

@Test
public void copyFile() {
String originalUrl = "源文件路径";
String targetUrl = "拷贝文件路径";

try {
FileInputStream originalFileInputStream = new FileInputStream(originalUrl);
Throwable var4 = null;

try {
FileOutputStream targetFileOutputStream = new FileOutputStream(targetUrl);
Throwable var6 = null;

try {
int content;
try {
while((content = originalFileInputStream.read()) != -1) {
targetFileOutputStream.write(content);
}
} catch (Throwable var31) {
var6 = var31;
throw var31;
}
} finally {
if (targetFileOutputStream != null) {
if (var6 != null) {
try {
targetFileOutputStream.close();
} catch (Throwable var30) {
var6.addSuppressed(var30);
}
} else {
targetFileOutputStream.close();
}
}

}
} catch (Throwable var33) {
var4 = var33;
throw var33;
} finally {
if (originalFileInputStream != null) {
if (var4 != null) {
try {
originalFileInputStream.close();
} catch (Throwable var29) {
var4.addSuppressed(var29);
}
} else {
originalFileInputStream.close();
}
}

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

}
}

try-with-resource使用

  • 多资源自动关闭
  • 实现AutoCloseable接口
  • 避免异常屏蔽

资源关闭顺序问题

  • 先开后关原则

  • 从外到内原则

  • 底层资源单独声明原则

资源关闭特殊情况

  • 资源对象被return的情况下,由调用方关闭
  • ByteArrayInputStream等不需要检查关闭的资源对象
  • 使用Socket获取的InputStream和OutputStream对象不需要关闭