开发者

Java中的FileInputStream是否需要close问题

目录
  • FileInputStream 类简介
    • FileInputStream 的 finalize() 方法
  • 实际测试
    • 结论
  • 会有其他问题吗
    • 主动 close 的方式
      • 总结

        FileInputStream 类简介

        FileInputStream 类在 Java 中非常常用,用来读取文件流的。而这种读取方式往往会涉及到流的关闭 close。

        如果不关闭 FileInputStream 流会有问题吗?会导致内存泄漏吗?

        FileInputStream 的 finalize() 方法

        Java 中每个 Object 都有 finalize() 方法,用来在 gc 的时候调用。

        而 FileInputStream 类中的 finalize() 方法中有一点特别的,它重写了这个方法,并且在里面进行了 close()。

        如下代码:

          /**
          * Ensures that the <code>close</code> method of this file input stream is
          * called when there are no more references to it.
          *
          * @excep编程tion IOException if an I/O error occurs.
          * @see    java.io.FileInputStream#close()
          */
          protected void finalize() throws IOException {
            if ((fd != null) && (fd != FileDescriptor.in)) {
              /* if fd is shared, the references in FileDescriptor
              * will ensure that finalizer is only called when
              * safe to do so. All references using the fd have
              * become unreachable. We can call close()
              */
              close();
            }
          }

        可以看到,只要触发了 gc,那么就会调用 finalize() 方法,那么就会自动 close 流。当被 close 之后,也就不会发生内存泄漏了。

        那么,不主动关闭,并且不主动触发 System.gc() 的话,它会被 JVM 回收吗?

        实际测试

        为了更加直观地看到是否调用了 finalize() 方法,这里新建一个 MyFileInputStream 类 extends FileInputStream,为的是重写 FileInputStream 的 finalize() 方法,给里面加入一行打印输出。

        代码如下:

        import java.io.File;
        import java.io.FileInputStream;
        import java.io.FileNotFoundException;
        import java.io.IOException;
        
        /**
        * a class extends FileInputStream to test method finalize()
        *
        * @author sleepybear - https://blog.csdn.net/qq_36670734
        * @date 2022/1/9 21:02
        */
        public class MyFileInputStream extends FileInputStream {
          public MyFileInputStream(File file) throws FileNotFoundException {
            super(file);
          }
        
          /**
          * 重写了 finalize 方法,把父类的 finalize 中的方法复制到此处,并且在其中添加打印信息
          *
          * @throws IOException 异常
          */
          @Override
          protected void finalize() throws IOException {
            if ((this.getFD() != null) && (this.getFD() != FileDescriptor.in)) {
              /* if fd is shared, the references in FileDescriptor
              * will ensure that finalizer is only called when
              * safe to do so. All references using the fd have
              * become unreachable. We can call close()
              */
              close();
              System.out.println("finalize and close");
            } else {
              System.out.println("only call finalize");
            }
          }
        }

        可以看到,只要执行了 finalize() 方法,那么就会打印一行 “finalize”。

        然后新建测试类,如下代码:

        import java.io.File;
        import java.io.IOException;
        import java.util.concurrent.TimeUnit;
        
        /**
        * 测试 MyFileInputStream 的 Finalize
        *
        * @author slee编程客栈pybear - https://blog.csdn.net/qq_36670734
        * @date 2022/1/9 21:10
        */
        public class TestFinalize {
          /**
          * 计数
          */
          public static int count = 0;
        
          public static void main(String[] args) throws InterruptedException {
            listFiles("D://Work//");
            System.out.println("遍历结束,等待 2 秒");
            TimeUnit.MILLISECONDS.sleep(1000 * 2L);
            System.out.println("显式调用 gc");
            System.gc();
            TimeUnit.MILLISECONDS.sleep(1000 * 2L);
            System.out.println("结束");
          }
        
          /**
          * 递归遍历所有文件,若遍历到 2000 的整数倍,那么输出这个文件的 md5
          *
          * @param path 路径
          */
          public static void listFiles(String path) {
            if (path == null || path.length() == 0) {
              return;
            }
        
            File file = new File(path);
            if (!file.exists()) {
              return;
            }
        
            if (file.isDirectory()) {
              // 遇到文件夹,往里面继续递归
              File[] files = file.listFiles();
              if (files != null && files.length > 0) {
                for (File dir : files) {
                  listFiles(dir.getAbsolutePath());
                }
              }
            }
        
            if (file.isFile()) {
              // 遇到是文件,那么计数 +1
              count++;
              if (count % 2000 == 0) {
                // 如果是 2000 的整数倍,那么打印文件的 md5,md5 打印需要用到 commons-codec-1.15.jar
                try {
                  // 这里直接 new MyFileInputStream 并没有显式 close,同时工具方法里面也没有调用 close()
                  String md5 = org.apache.commons.codec.digest.DigestUtils.md5Hex(new MyFileInputStream(file));
                  System.out.println("count = " + count + ", md5 = " + md5);
                } catch (IOException e) {
                  e.printStackTrace();
                }
              }
            }
          }
        }

        运行代码,得到如下的结果:

        count = 2000, md5 = da6a56cda0772e7b321621472f1ca9ce

        count = 4000, md5 = 8fc19cf5f7675179ed14abce784e29da

        count = 6000, md5 = f93186d553b72e168f84ef9870285a17

        count = 8000, md5 = 6af44868883a83b7ae4a0c3529ebc6ef

        count = 10000, md5 = f570fdda290a62db840538c460e46510

        count = 12000, md5 = 26ae171433b7c355223fa43f9d605110

        count = 14000, md5 = c5f924cee8c51c19825af3713044b67a

        count = 16000, md5 = deda72e7ef14674a49295f460301b4cf

        count = 18000, md5 = 08753370d8c5bbda239e4d349892730c

        count = 20000, md5 = df1213e1584803bf0a549c5a1c14f111

        count = 22000, md5 = 9751d0dbc67c75cb446bdaf2d2434f66

        count = 24000, md5 = 962cf50af21894734a78493e2f4df31b

        count = 26000, md5 = f9556c74d758838e9c2202921de50056

        count = 28000, md5 = 2a2699c13ff46dd34305bc5c7b780b52

        count = 30000, md5 = 71af55db4adf6c7b2e667f031a809e91

        count = 32000, md5 = bdef65ff9a12c5808b491f4b0be362d1

        count = 34000, md5 = 9f1da8e150bfe5077c7ab82b3727aba0

        count = 36000, md5 = 648422e1e6b89a1e331e407e6c7fc652

        count = 38000, md5 = d1d9e7a656db7d0a4e79110fdb3f3462

        count = 40000, md5 = 50b6c156bf30447d4f0b891677b75617

        count = 42000, md5 = 1be6de12ec79e310675f1b1e5f1e891c

        count = 44000, md5 = 027ca2c40a3d9b2d8f7818673cb6329c

        count = 46000, md5 = 07e1a13fc5e3e5fdd3cacf998e57eaa8

        count = 48000, md5 = c3bf74579b053ccdd5bb6bed7c8c5ab1

        count = 50000, md5 = 78a2a70250a4df4f21523e0b94d6bca4

        count = 52000, md5 = 769f5ea0d0a2c2b89d82a2bf2dbdbedd

        count = 54000, md5 = c092d2f664c726da95f058018134bdfb

        count = 56000, md5 = dc4d6f6ac6212f91d0aba93d692564c4

        count = 58000, md5 = 217926c75b000f1dea20d319e9aeebbf

        count = 60000, md5 = b437b7e80f6c52a42c3e4fe74be49c48

        count = 62000, md5 = 9a92a6cf85e5513550ab801e12867dc9

        count = 64000, md5 = b92bc3f149a121039aa8fe6a05f47b35

        count = 66000, md5 = 064fd7ca2040cb21615e205b1f294e19

        count = 68000, md5 = b4d20b20526769ef73702108c519cf25

        count = 70000, md5 = 2436edd2550c69c1c2a519edfee2b853

        finalize and close

        finalize and close

        finalize and close

        finalize and close

        finalize and close

        finalize and close

        finalize and close

        finalize and close

        finalize and close

        finalize and close

        finalize and close

        finalize and close

        finalize and close

        finalize and close

        finalize and close

        finalize and close

        finalize and close

        finalize and close

        finalize and close

        finalize and close

        finalize and close

        finalize and close

        finalize and close

        finalize and close

        finalize and close

        finalize and close

        finalize and close

        finalize and close

        finalize and close

        finalize and close

        finalize and close

        finalize and close

        finalize and close

        finalize and close

        finalize and close

        count = 72000, md5 = 6513a7abfe9f456814216f4040d82f2e

        count = 74000, md5 = 7b1a34f38c218fa59b775543e1d2784f

        count = 76000, md5 = 5e02f191cec09d5a938233dd43bec96d

        count = 78000, md5 = 8e80d1b1e0c42af8937fc570e94145d4

        count = 80000, md5 = 42c2b3d42c7966e47a1de23e3c89eca6

        count = 82000, md5 = ce45fc5afea20d7e5a4fc79ddc3b04cb

        count = 84000, md5 = 51bd1280db5b549968b855f7d25f7075

        count = 86000, md5 = c90629033ec6b8fbfe5f17f63a44cfa3

        count = 88000, md5 = 71b5e1bdc7f76444fb0fe88bc0e3a669

        count = 90000, md5 = a930f1957f273fd5cb0d7fc984096ce4

        count = 92000, md5 = badfe45e2c5917dcec2d0be1a03583de

        count = 94000, md5 = 9e74b89c1f8e8ecfb00b84001b45ddd7

        count = 96000, md5 = 7b4464b79280f9ac23e4b241508aa2d1

        count = 98000, md5 = d79fabe6804056b0a6572e95f7c970c0

        count = 100000, md5 = bcb8f86c91f8e106bdce40584ddba54b

        count = 102000, md5 = 8f578f7a6dbcbde77d375a2e7c98ceee

        count = 104000, md5 = aa39503815c583a38b409c92e04e5d14

        count = 106000, md5 = 34ec7897529f1b00a78b13DB223f907b

        count = 108000, md5 = ea83e11ece4e7fdcc23bd9214f486ae3

        count = 110000, md5 = 05d69a87ebf4133795aad1aae4a99ebb

        count = 112000, md5 = bcc781b71ff6b10a57923af9fcc85b38

        count = 114000, md5 = 5e468c6233db3f6a4311ebafa6e35eb6

        count = 116000, md5 = 365a5b3af1dd522ed7c9a8b106f90224

        count = 118000, md5 = 9117c65ed9ff083256a86af10ab88d65

        count = 120000, md5 = 97f24779279cfe2fa1d573ef83c21009

        count = 122000, md5 = ba6b12f0b76b192d9dd74965919bad95

        count = 124000, md5 = b54c8105da76b3f13dcf78495bc2ff52

        count = 126000, md5 = 648422e1e6b89a1e331e407e6c7fc652

        count = 128000, md5 = eb115942b0abf38b881353debe4bdb92

        count = 130000, md5 = e469a50bf7cfd9f1fb95f7d5badc1641

        count = 132000, md5 = 2be1bd409def8cfc4f32a49af4bf5e9f

        count = 134000, md5 = e3a20ac5df9d81a2ce301886fdfc0f07

        count = 136000, md5 = 77269649674cc0fdf4b379a523a3a829

        count = 138000, md5 = aead3fc8f94编程5488ab71cf64271f46c54

        count = 140000, md5 = e7098eafd97649e4a77edea89d0d22ab

        count = 142000, md5 = 28d392b26bbd86fb3312960b82572060

        count = 144000, md5 = f37ea0c388bec909255c602adc0cdfe4

        count = 146000, md5 = d91659c455a4f66f9a16c8f287ce9fc9

        count = 148000, md5 = 338364a0d43b018333d3143a466e1cf2

        遍历结束,等待 2 秒

        显式调用 gc

        finalize and close

        finalize and close

        finalize and close

        finalize and close

        finalize and close

        finalize and close

        finalize and cl开发者_Python培训ose

        finalize and close

        finalize and close

        finalhttp://www.devze.comize and close

        finalize and close

        finalize and close

        finalize and close

        finalize and close

        finalize and close

        finalize and close

        finalize and close

        finalize and close

        finalize and close

        finalize and close

        finalize and close

        finalize and close

        finalize and close

        finalize and close

        finalize and close

        finalize and close

        finalize and close

        finalize and close

        finalize and close

        fwww.devze.cominalize and close

        finalize and close

        finalize and close

        finalize and close

        finalize and close

        finalize and close

        finalize and close

        finalize and close

        finalize and close

        finalize and close

        结论

        上述测试可以看到,即使没有主动 close 流,MyFileInputStream 类(或者是 FileInputStream 类)在结束之后,也会自动被回收。在回收的时候调用 finalize() 方法自行 close 流。

        所以在使用 FileInputStream 类,不显式调用 close 方法的话,一般不会造成内存泄漏。

        会有其他问题吗

        不主动 close 流的话,文件句柄还占用着,如果 JVM 没有及时清理这些流的话,那么可能导致资源泄露问题,可能会因为过多打开文件导致 CPU 和 RAM 占用居高,甚至无法再打开新的文件进行读写。

        主动 close 的方式

          FileInputStream fileInputStream = null;
          try {
            fileInputStream = new FileInputStream(file);
            fileInputStream.read();
          } catch (IOException e) {
            e.printStackTrace();
          } finally {
            if (fileInputStream != null) {
              fileInputStream.close();
            }
          }

        当 Java 1.7 之后,可以使用 try-with-resources 方式自动 close 流

          try (FileInputStream fileInputStream = new FileInputStream(file)) {
            fileInputStream.read();
          } catch (IOException e) {
            e.printStackTrace();
          }

        总结

        以上为个人经验,希望能给大家一个参考,也希望大家多多支持我们。

        0

        上一篇:

        下一篇:

        精彩评论

        暂无评论...
        验证码 换一张
        取 消

        最新开发

        开发排行榜