这个算是一个非常通用并且常见的API了,但是其实这个方法在删除文件的时候其实是有一些限制的。如下,我们可以看见这个API返回值代表着删除是否成功:
public boolean delete() {
SecurityManager security = System.getSecurityManager();
if (security != null) {
security.checkDelete(path);
}
if (isInvalid()) {
return false;
}
return fs.delete(this);
}
继续追溯,我们能看到这里的fs的值:
/**
* The FileSystem object representing the platform's local file system.
*/
private static final FileSystem fs = DefaultFileSystem.getFileSystem();
哈哈,继续,继续,看看这个到底是什么?
class DefaultFileSystem {
/**
* Return the FileSystem object for Unix-based platform.
*/
public static FileSystem getFileSystem() {
return new UnixFileSystem();
}
}
可以看到返回的是Java所虚拟化的UnixFileSystem类,即JVM所模拟的一个Unix-like文件系统类。于是我们暂时得到了,实际上我们调用File#delete(),会执行到UnixFileSystem#delete(File)。继续到UnixFileSystem.java中查询delete()的实现。可以看到其间接调用了native层的delete0(File)方法。OK,继续追踪!
// Android-changed: Added thread policy check
public boolean delete(File f) {
// Keep canonicalization caches in sync after file deletion
// and renaming operations. Could be more clever than this
// (i.e., only remove/update affected entries) but probably
// not worth it since these entries expire after 30 seconds
// anyway.
cache.clear();
javaHomePrefixCache.clear();
BlockGuard.getThreadPolicy().onWriteToDisk();
return delete0(f);
}
private native boolean delete0(File f);
找到UnixFileSystem_md.c类,其中就定义了这个native delete0方法:
JNIEXPORT jboolean JNICALL
Java_java_io_UnixFileSystem_delete0(JNIEnv *env, jobject this,
jobject file)
{
jboolean rv = JNI_FALSE;
WITH_FIELD_PLATFORM_STRING(env, file, ids.path, path) {
//调用unix remove接口以删除具体的文档
if (remove(path) == 0) {
rv = JNI_TRUE;
}
} END_PLATFORM_STRING(env, path);
return rv;
}
可以看到该方法返回的就是unix的remove方法的结果,根据APUE Page.94所言,
当调用Unix-like系统remove() API时,应该同时满足如下两个条件:
1. 对于该文件的链接数为0,我们应用内所创建的临时文件肯定是不会有硬链接的
2. 不存在进程打开了该文件
实际在进行文件的删除操作时,只有当链接计数达到0时,该文件的内容才可被删除。另一个条件也会阻止删除文件的内容–只要有进程打开了该文件,其内容也不能删除。关闭一个文件时,内核首先检查打开该文件的进程个数;如果这个技术达到0,内核再去检查其链接计数:如果计数也是0,那么就删除该文件的内容。
我们就能知道最开始所说的限制是什么了,作为android开发,应用运行的时候一般不会去创建硬链接,所以第一个限制一般不会出问题。但是第二个条件,如果在调用File#close()时,有流正在准备或者正在继续输入输出操作,那么File#delete()的调用就会失败,返回false。
下面是一个demo,进行了一次实验,开启了一个额外的输出流,如果在delete的时候仍存在对该文件的使用,delete操作会失败!
import java.io.*;
import java.net.URL;
public class Test {
private static void downloadUsingStream1(String urlStr, String file) throws IOException {
URL url = new URL(urlStr);
final File newFile = new File(file);
InputStream bis = url.openStream();
FileOutputStream fis = new FileOutputStream(newFile);
//重点关注这里!!!!!我额外开启了一个输出流,但是在调用delete的时候没有释放
FileOutputStream fos = new FileOutputStream(newFile);
byte[] buffer = new byte[1024];
int count = 0;
int writeCount = 0;
try {
while ((count = bis.read(buffer, 0, 1024)) != -1) {
fis.write(buffer, 0, count);
if (++writeCount >= 60) {
throw new IOException("123");
}
}
} catch (IOException e) {
fis.close();
bis.close();
boolean isDeleteSuccessful = newFile.delete();
System.out.println("" + isDeleteSuccessful);
}
fis.close();
bis.close();
}
public static void main(String[] args) {
String url = "file:" + File.separator + "D:" + File.separator + "game.jpg";
try {
downloadUsingStream1(url, "./game1.jpg");
} catch (IOException e) {
e.printStackTrace();
}
}
}
至于为什么仅仅开启一个输出流就能达成这个目的呢?因为实际输出流在创建的时候就已经开启了文件访问标记。