0
点赞
收藏
分享

微信扫一扫

Java关闭流的多种方法(源码级分析)

千妈小语 2022-02-13 阅读 93

文章较长, 建议收藏观看


目录

1.基本关闭方法

上代码

分析代码:

2.进阶关闭方法try-with-resources

Automatic Resource Management (ARM)

3.最终版本Pro Plus Max(手搓代码)

*手搓代码, 写一个通用的函数, 写成静态的当成工具类便于调用

*批量关闭传进去的流对象

*最终版本Pro Plus Max 


前言:

关闭IO流等是我们操作文件的基本操作

JVM只会帮我们回收堆栈中的内存, 而对于IO流这种物理连接它无能为力, 得我们手动释放

如果不释放可能 会导致内存泄漏


1.基本关闭方法

大家平时关闭流可能是这么做的

try{
    FileInputStream fis = new FileInputStream("hello.txt");//读文件的流

    byte[] buffer = new byte[1024];
    int len;

    while((len = fis.read(buffer)) != -1){
        // 你要做的操作
    }

        // !!! 不建议的写法 !!!
    fis.close();

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

在close()关闭流对象之前

比如说fis.read(buffer)这一行出错了

抛出的异常就会被catch语句捕获, 不再执行fis.read(buffer)下面的语句

这意味着, 我们的流并没有关闭 !


怎么解决呢?

有些同学可能会说, 那我在catch语句里面再写一次 fis.close() 不就得了

这显然不够优雅, 也不符合代码复用的原则


我们的解决方法是这样的

异常处理中, try catch finally是常见的结构

finally代码段, 是异常处理的"善后"操作, 不论是否发生异常, 最后都会执行(finally代码段)

我们把关闭资源的操作, 放到finally代码段即可


上代码

改进版本

FileInputStream fis = null;//读文件的流
try {
    fis = new FileInputStream("hello.txt");

    byte[] buffer = new byte[1024];
    int len;

    while ((len = fis.read(buffer)) != -1) {
        // 你要做的操作
    }
} catch (IOException e) {
    e.printStackTrace();
} finally {
    if (fis != null) {
        try {
            fis.close();
        } catch (IOException e) {
            e.printStackTrace();
        }   
    }
}

乍一看, 还复杂了, 其实都是很有必要的操作, 我们一行行分析

分析代码:

首先看finally代码段

程序可能会在new IO流对象的时候出错,
        if (fis != null)这里判断有没有生成IO流的对象,如果为null,说明还没有生成IO流对象, 也就不用关闭

finally {
    //程序可能会在new IO流对象的时候出错,
    //if (fis != null)这里判断有没有生成IO流的对象,如果为null,说明还没有生成IO流对象
    //也就不用关闭
    if (fis != null) {
        try {        //这里用到try语句是因为close()本身就会抛异常,正常处理即可
            fis.close();
        } catch (IOException e) {
            e.printStackTrace();
        }   
    }
}

再看看开头的代码 

FileInputStream fis = null;//读文件的流
try {
    fis = new FileInputStream("hello.txt");

这里为什么看着这么奇怪?

拿着一个空的对象进去try代码段再赋值 

因为, 我们的IO流资源 最终是要在finally里面关闭的,

如果只是像下面这样写

try{
    FileInputStream fis = new FileInputStream("hello.txt");//读文件的流

finally里面 就访问不到 , 就类似于全局变量的意思

(如果实在理解不了的同学, 可以把代码写在try里面试试, 行不通)

到这里, 我们可以说是写出了  能容纳一定错误的代码


2.进阶关闭方法try-with-resources

上面的代码已经看着近乎完美了, 为什么还有个 2.0呢?

我们举例子

如果我们在项目中要用到这么多 流 , 都要一个个去关闭, 即使有IDEA代码补全, 是不是也有点崩溃?

finally {
    if (ps != null) {
        try {        
            ps.close();
        } catch (IOException e) {
            e.printStackTrace();
        }   
    }
    if (rs != null) {
        try {        
            rs.close();
        } catch (IOException e) {
            e.printStackTrace();
        }   
    }
    if (is != null) {
        try {        
            is.close();
        } catch (IOException e) {
            e.printStackTrace();
        }   
    }
    if (fos != null) {
        try {        
            fos.close();
        } catch (IOException e) {
            e.printStackTrace();
        }   
    }
}

那我们该怎么办呢?


try (InputStream is = new FileInputStream("test")) {
	is.read();
	...
} catch(Exception e) {
	...
} finally {
	//no need to add code to close InputStream(笔者注:不需要添加关闭InputStream的代码)
	//it's close method will be internally called(笔者注:它的关闭方法将被内部调用)
}

这意味着什么?

流资源在使用完成时将自动被关闭.

还是不太理解?

我们看代码


上文的代码:

FileInputStream fis = null;//读文件的流
try {
    fis = new FileInputStream("hello.txt");

    byte[] buffer = new byte[1024];
    int len;

    while ((len = fis.read(buffer)) != -1) {
        // 你要做的操作
    }
} catch (IOException e) {
    e.printStackTrace();
} finally {
    if (fis != null) {
        try {
            fis.close();
        } catch (IOException e) {
            e.printStackTrace();
        }   
    }
}


-----进阶代码----- 

try (FileInputStream fis = new FileInputStream("hello.txt")) {
    byte[] buffer = new byte[1024];
    int len;

    while ((len = fis.read(buffer)) != -1) {
        // 你要做的操作
    }
} catch (IOException e) {
    e.printStackTrace();
}

这对比我们上文给出的代码, 是不是优雅了许多?

我们无需添加关闭InputStream输入流的代码,其close()方法会被自动调用


3.最终版本Pro Plus Max(手搓代码)

??? 还能写??

是的, 这是我前段时间做项目总结出来的

如果上面那个方法真的完美, 就不会有我现在的最终版本Pro Plus Max了(笑)

DataOutputStream dos1 = null;//写文件到本地的

try (DataInputStream dis1 = new DataInputStream(socket.getInputStream()))//获取socket套接字{
    String fileName = dis1.readUTF();//1.从socket读取文件名
    File file = new File(fileName);
    dos1 = new DataOutputStream(new FileOutputStream(file));//写文件的流
    ...
}



......(由于它是在半路创建的流,所以最后只能用常规方法来释放)

if (t != null) {
try {
        t.close();
    } catch (Exception e) {
        e.printStackTrace();
    }
}

上面服务器的代码逻辑就是:

0.客户端给服务器传输文件, 服务器接收(当然包括文件名等必要信息)

1.服务器接收到客户端传来的文件名

2.服务器根据文件名才能 new 一个OutputStream流对象

那么上文所述-----进阶代码----- 

的缺陷就来了:

如果我有一个读取流和写出流, 都想放到try 代码段里是做不到的......

因为写出流outputstream的创建, 要依靠读取流读inputstream取文件名的操作来完成

由于它是在半路创建的流,所以最后只能用常规方法来释放

难受了, 又绕回来了,难道真的没有办法避免了吗?

先不急

我们进入IDEA 查看  FileOutputStream 类

右键显示关系图

我们可以看到这么个关系

 重点关注左边的方法 咦? closeable 是不是和close有关系 ?

果然, close()方法就是从这里来的...

第一处翻译:

第二处翻译: 

它还有一个父亲  AutoCloseable, 我们来看看

我们看下翻译:

意味着, 我们JDK7.0以后, 流资源的关闭, 都和AutoCloseable挂钩了

说得再通俗一点:

 好的, 那话不多说, 

手搓代码, 写一个通用的函数, 写成静态的当成工具类便于调用

其中的函数用到了泛型, 意味传进来的参数是实现了AutoCloseable接口的

public class NetFunction {
    public static <T extends AutoCloseable> void closeStream(T t) {
        if (t != null) {
            try {
                t.close();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
}

这样, 即使流放不进try里面, 也可以愉快的关闭啦.

什么?你还想再少一点...?

把所有的东西都放进去一起关闭?

比如说

NetFunction.closeStream(dos1,dos2,socket);

额, 那就再改改代码

批量关闭传进去的流对象

public static <T extends AutoCloseable> void close(T... ts) {
        for (T t : ts) {
            if (t != null) {
                try {
                    t.close();
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }
    }

怎么样, 是不是满足了所有要求了?

可以批量关闭传进去的流对象

是的, 但是我看编译器的时候强迫症犯了...

 百度了一下

 

 额, 大概就是 使用泛型的时候用可变形参会导致这个问题发生, 加个注解即可

但是我强迫症, 加个注解显然不优雅, 又想消除这一警告

最后沉吟许久, 想到了Java基础知识里面的转型...

最终版本Pro Plus Max 

public static void close(AutoCloseable... t) {
    for (AutoCloseable closeable : t) {
        if (closeable != null) {
            try {
                closeable.close();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
}
NetFunction.close(dos1,dos2,socket);

如果还有更好的办法, 欢迎在评论区指出 

举报

相关推荐

0 条评论