最近在看安全代码规范建议中提到关于如何删除创建的临时文件,推荐使用jdk7中的Files的函数,通过参数StandardOpenOption.DELETE_ON_CLOSE来控制
代码示例
BufferedWriter writer = Files.newBufferedWriter(tempFile, Charset.forName("UTF8"), StandardOpenOption.DELETE_ON_CLOSE)
只要当文件close掉,这个文件就会自动被系统删除,这样可以避免在一些场景下忘了删除产生的临时文件。
StandardOpenOption.DELETE_ON_CLOSE参数
我们来看代码StandardOpenOption.DELETE_ON_CLOSE参数在linux下究竟控制了什么,我们在java代码中看到了参数的控制阀
if (flags.deleteOnClose) { try { if (dfd >= 0) { unlinkat(dfd, path.asByteArray(), 0); } else { unlink(path); } } catch (UnixException ignore) { // best-effort } }
java的unlink函数
static void unlink(UnixPath path) throws UnixException { NativeBuffer buffer = copyToNativeBuffer(path); try { unlink0(buffer.address()); } finally { buffer.release(); } }
JNI unlink0函数
JNIEXPORT void JNICALL Java_sun_nio_fs_UnixNativeDispatcher_unlink0(JNIEnv* env, jclass this, jlong pathAddress) { const char* path = (const char*)jlong_to_ptr(pathAddress); /* EINTR not listed as a possible error */ if (unlink(path) == -1) { throwUnixException(env, errno); } }
最后调用了系统unlink函数
unlink rm unlinkat remove的区别
我们通常会使用shell rm去删除文件,让我们先看看rm做了什么,通过strace来查看系统调用strace rm filepath会看到最后调用的函数unlinkat
unlink 和 unlinkat 的区别
unlinkat 可以通过参数AT_REMOVEDIR删除空的目录,其他的是和unlink一样
unlinkat(AT_FDCWD, "/tmp/test", AT_REMOVEDIR)
remove 和 unlink的区别
remove是unlink 和 rmdir 合集,区别也是在删除目录上
可以看到说参数StandardOpenOption.DELETE_ON_CLOSE 在linux上是调用了unlink删除了文件名,那么也就意味着你对这个文件的任何读写操作都是无效的。
Linux和windows的区别
参数StandardOpenOption.DELETE_ON_CLOSE在Linux和windows是不一样的实现,windows上只有调用了writer.close() 这个文件才会被删除,也就是windows上的open函数本身就带有文件属性FILE_FLAG_DELETE_ON_CLOSE,当显式的调用close,自然就会删除该文件,而如果进程退出也会删除这个文件。
总结:
参数StandardOpenOption.DELETE_ON_CLOSE对Linux 来说是无效的操作,虽然保证了文件的删除,但也违反了本身API的协议(需要调用close才能真实的删除文件)。
而且本身也没有实际意义,因为文件已经被删除了,那就意味着本身也无法向文件中写任何内容,那么创建临时文件的意义也不存在了。这个函数在windows上是实现的,而Linux中没有类似的内核函数去实现这样的功能。
File函数的方法deleteOnExit
File.deleteOnExit()
这个方法在jvm退出时添加了一个钩子,退出时删除文件。但是如果jvm没有正常退出呢?比如我们常用的信号退出,就没有办法正常删除文件。
StandardOpenOption.DELETE_ON_CLOSE这是需要在内核函数中去实现这个属性,而不是在jvm 应用层去实现,而jvm 在针对Linux 直接调用unlink 也是避免了问题的复杂化。