目錄
1、separator / pathSeparatorChar
2、構造方法
3、isAbsolute / getAbsolutePath / getCanonicalPath
4、exists / isDirectory / isFile / isHidden / lastModified / length
5、createNewFile / createTempFile / delete / deleteOnExit
6、mkdir / mkdirs
7、list / listFiles / listRoots
File表示檔案系統中的一個檔案,提供了檔案操作的相關API,其底層實作都是通過FileSystem實作的。FileSystem表示底層作業系統的一個檔案系統,windows下的實作是WinNTFileSystem,類Unix系統下的實作是UnixFileSystem,我們重點關注後者的實作細節。
1、separator / pathSeparatorChar
這兩個是File類的public static屬性,分别表示路徑分隔符和多個路徑字元串的分隔符,其定義如下:
//路徑分隔符,對應系統屬性file.separator
public static final String separator = "" + separatorChar;
public static final char separatorChar = fs.getSeparator();
//多個路徑字元串的分隔符,對應系統屬性path.separator
public static final String pathSeparator = "" + pathSeparatorChar;
public static final char pathSeparatorChar = fs.getPathSeparator();
//fs表示檔案系統的實作
private static final FileSystem fs = DefaultFileSystem.getFileSystem();
//slash和colon都是UnixFileSystem的私有屬性,在構造方法中完成初始化
public char getSeparator() {
return slash;
}
public char getPathSeparator() {
return colon;
}
public UnixFileSystem() {
//讀取對應的系統屬性
slash = AccessController.doPrivileged(
new GetPropertyAction("file.separator")).charAt(0);
colon = AccessController.doPrivileged(
new GetPropertyAction("path.separator")).charAt(0);
javaHome = AccessController.doPrivileged(
new GetPropertyAction("java.home"));
}
window下DefaultFileSystem的實作如下:
類Unix下DefaultFileSystem的實作如下,其源碼在jdk\src\solaris\classes\java\io目錄下:
測試用例如下:
import java.io.File;
public class FileTest {
public static void main(String[] args) {
//windows下檔案路徑
// File file=new File("D:\\export\\data\\test.txt");
//Linux下的檔案路徑
File file=new File("/export/data/test.txt");
System.out.println(file.getName());
System.out.println("separator->"+File.separator);
System.out.println("pathSeparator->"+File.pathSeparator);
}
window下輸出如下:
Linux下輸出如下:
2、構造方法
public File(String pathname) {
if (pathname == null) {
throw new NullPointerException();
}
//将檔案路徑轉換成正常格式
this.path = fs.normalize(pathname);
//擷取路徑字首的長度,Linux下如果以/開始則prefixLength是1,否則是0,
//windows下需要計算包含前面盤符在内的字首的長度
this.prefixLength = fs.prefixLength(this.path);
}
public File(String parent, String child) {
if (child == null) {
throw new NullPointerException();
}
if (parent != null) {
if (parent.equals("")) {
//父路徑為空,則使用預設的父路徑,Linux下是/
//resolve傳回該父路徑下某個子路徑的真實路徑
this.path = fs.resolve(fs.getDefaultParent(),
fs.normalize(child));
} else {
this.path = fs.resolve(fs.normalize(parent),
fs.normalize(child));
}
} else {
this.path = fs.normalize(child);
}
this.prefixLength = fs.prefixLength(this.path);
}
public File(File parent, String child) {
if (child == null) {
throw new NullPointerException();
}
if (parent != null) {
//邏輯同上
if (parent.path.equals("")) {
this.path = fs.resolve(fs.getDefaultParent(),
fs.normalize(child));
} else {
this.path = fs.resolve(parent.path,
fs.normalize(child));
}
} else {
this.path = fs.normalize(child);
}
this.prefixLength = fs.prefixLength(this.path);
}
public File(URI uri) {
//檢查uri是否符合file類uri 規範
if (!uri.isAbsolute())
throw new IllegalArgumentException("URI is not absolute");
if (uri.isOpaque())
throw new IllegalArgumentException("URI is not hierarchical");
String scheme = uri.getScheme();
if ((scheme == null) || !scheme.equalsIgnoreCase("file"))
throw new IllegalArgumentException("URI scheme is not \"file\"");
if (uri.getAuthority() != null)
throw new IllegalArgumentException("URI has an authority component");
if (uri.getFragment() != null)
throw new IllegalArgumentException("URI has a fragment component");
if (uri.getQuery() != null)
throw new IllegalArgumentException("URI has a query component");
String p = uri.getPath();
if (p.equals(""))
throw new IllegalArgumentException("URI path component is empty");
//計算路徑
p = fs.fromURIPath(p);
if (File.separatorChar != '/')
p = p.replace('/', File.separatorChar);
this.path = fs.normalize(p);
this.prefixLength = fs.prefixLength(this.path);
}
//去掉路徑中多餘的/
public String normalize(String pathname) {
int n = pathname.length();
char prevChar = 0;
for (int i = 0; i < n; i++) {
char c = pathname.charAt(i);
if ((prevChar == '/') && (c == '/'))
//如果是連續兩個//
return normalize(pathname, n, i - 1);
prevChar = c;
}
//如果最後一個字元是/
if (prevChar == '/') return normalize(pathname, n, n - 1);
return pathname;
}
//計算路徑中字首字元串的長度
public int prefixLength(String pathname) {
if (pathname.length() == 0) return 0;
//如果以/開頭則傳回1,否則傳回0
return (pathname.charAt(0) == '/') ? 1 : 0;
}
//計算某個父路徑下子路徑的完整路徑
public String resolve(String parent, String child) {
//child是空,則直接傳回parent
if (child.equals("")) return parent;
if (child.charAt(0) == '/') {
//parent是/
if (parent.equals("/")) return child;
return parent + child;
}
if (parent.equals("/")) return parent + child;
return parent + '/' + child;
}
//去掉路徑中多餘的/
private String normalize(String pathname, int len, int off) {
if (len == 0) return pathname;
int n = len;
//倒着周遊,找到第一個不是/的字元
while ((n > 0) && (pathname.charAt(n - 1) == '/')) n--;
if (n == 0) return "/";
StringBuffer sb = new StringBuffer(pathname.length());
//截取0到off之間的字元串
if (off > 0) sb.append(pathname.substring(0, off));
char prevChar = 0;
//周遊off到n之間的字元
for (int i = off; i < n; i++) {
char c = pathname.charAt(i);
//如果是連續多個/則跳過後面的/
if ((prevChar == '/') && (c == '/')) continue;
sb.append(c);
prevChar = c;
}
return sb.toString();
}
public String fromURIPath(String path) {
String p = path;
//去掉末尾多餘的/
if (p.endsWith("/") && (p.length() > 1)) {
// "/foo/" --> "/foo", but "/" --> "/"
p = p.substring(0, p.length() - 1);
}
return p;
}
其中涉及URI的校驗,需要了解URI的格式規範,如下圖:
測試用例如下:
import java.io.File;
import java.net.URI;
public class FileTest {
public static void main(String[] args) {
File file=new File("//export//data///test.txt/");
System.out.println("exist->"+file.exists()+",path->"+file.getPath());
file=new File("//export/data","/test.txt/");
System.out.println("exist->"+file.exists()+",path->"+file.getPath());
file=new File(new File("//export/data///"),"test.txt/");
System.out.println("exist->"+file.exists()+",path->"+file.getPath());
//冒号後面不能帶兩個//,會被認為是URI中帶有登入使用者名等權限資訊,校驗失敗
file=new File(URI.create("file:/export///data//test.txt//"));
System.out.println("exist->"+file.exists()+",path->"+file.getPath());
}
}
輸出如下:
3、isAbsolute / getAbsolutePath / getCanonicalPath
isAbsolute判斷目前檔案的路徑是否絕對路徑,getAbsolutePath擷取絕對路徑,如果不是絕對路徑則擷取在user.dir下的絕對路徑;getCanonicalPath 擷取目前檔案路徑的規範化标準化的路徑,會替換掉路徑中包含的./ 和 ../,其實作如下:
//是否絕對路徑
public boolean isAbsolute() {
return fs.isAbsolute(this);
}
//擷取目前檔案的絕對路徑
public String getAbsolutePath() {
return fs.resolve(this);
}
//擷取目前檔案路徑的規範路徑,會處理掉路徑中的./或者../
public String getCanonicalPath() throws IOException {
if (isInvalid()) {
throw new IOException("Invalid file path");
}
return fs.canonicalize(fs.resolve(this));
}
final boolean isInvalid() {
if (status == null) {
//如果路徑中包含空字元則認為是無效路徑
status = (this.path.indexOf('\u0000') < 0) ? PathStatus.CHECKED
: PathStatus.INVALID;
}
return status == PathStatus.INVALID;
}
//UnixFileSystem的實作
//是否絕對路徑
public boolean isAbsolute(File f) {
return (f.getPrefixLength() != 0);
}
//擷取絕對路徑
public String resolve(File f) {
if (isAbsolute(f)) return f.getPath();
//如果不是絕對路徑,則擷取在user.dir下的絕對路徑
return resolve(System.getProperty("user.dir"), f.getPath());
}
public String canonicalize(String path) throws IOException {
//父類FileSystem的靜态屬性,預設為true,通過屬性sun.io.useCanonCaches配置
if (!useCanonCaches) {
//是本地方法
return canonicalize0(path);
} else {
//cache是ExpiringCache執行個體,一個支援緩存key自動過期的Map
String res = cache.get(path);
if (res == null) {
String dir = null;
String resDir = null;
//父類FileSystem的靜态屬性,預設為true,通過屬性sun.io.useCanonPrefixCache配置
if (useCanonPrefixCache) {
// Note that this can cause symlinks that should
// be resolved to a destination directory to be
// resolved to the directory they're contained in
dir = parentOrNull(path);
if (dir != null) {
//javaHomePrefixCache也是ExpiringCache執行個體
resDir = javaHomePrefixCache.get(dir);
if (resDir != null) {
// Hit only in prefix cache; full path is canonical
String filename = path.substring(1 + dir.length());
res = resDir + slash + filename;
//将解析結果添加到緩存中
cache.put(dir + slash + filename, res);
}
}
}
if (res == null) {
res = canonicalize0(path);
cache.put(path, res);
//javaHome是系統屬性java.home的值
if (useCanonPrefixCache &&
dir != null && dir.startsWith(javaHome)) {
resDir = parentOrNull(res);
// Note that we don't allow a resolved symlink
// to elsewhere in java.home to pollute the
// prefix cache (java.home prefix cache could
// just as easily be a set at this point)
if (resDir != null && resDir.equals(dir)) {
File f = new File(res);
if (f.exists() && !f.isDirectory()) {
javaHomePrefixCache.put(dir, resDir);
}
}
}
}//第二個res等于null
}//第一個res等于null
return res;
}
}
//擷取path的父路徑,盡可能避免canonicalize0方法中抛出異常
static String parentOrNull(String path) {
if (path == null) return null;
char sep = File.separatorChar;
int last = path.length() - 1;
int idx = last;
//連續的.字元的個數
int adjacentDots = 0;
//不是.和分隔符的字元的個數
int nonDotCount = 0;
//從最後一個字元開始往前周遊
while (idx > 0) {
char c = path.charAt(idx);
if (c == '.') {
if (++adjacentDots >= 2) {
//路徑中包含..
return null;
}
} else if (c == sep) {
if (adjacentDots == 1 && nonDotCount == 0) {
//路徑中包含/.
return null;
}
if (idx == 0 ||
idx >= last - 1 ||
path.charAt(idx - 1) == sep) {
//第一個字元或者倒數的兩個字元包含/,或者連續兩個//
return null;
}
return path.substring(0, idx);
} else {
//不是.和分隔符,計數加1
++nonDotCount;
adjacentDots = 0;
}
//周遊下一個字元
--idx;
}
return null;
}
其中父類FileSystem的useCanonCaches和useCanonPrefixCache屬性定義如下:
canonicalize0本地方法的實作在jdk\src\solaris\native\java\io\UnixFileSystem_md.c中,如下圖:
canonicalize C方法的實作在同目錄下的canonicalize_md.c中,可以自行參考,其中的核心就是對路徑做規範化标準化處理的C realpath函數。測試用例如下:
@Test
public void test() throws Exception {
//絕對路徑
File file=new File("D:\\code\\test.txt");
System.out.println("isAbsolute:"+file.isAbsolute());
System.out.println("getAbsolutePath:"+file.getAbsolutePath());
}
@Test
public void test2() throws Exception {
//相對路徑
File file=new File("../test.txt");
System.out.println("isAbsolute:"+file.isAbsolute());
System.out.println("getAbsolutePath:"+file.getAbsolutePath());
System.out.println("user.dir:"+System.getProperty("user.dir"));
System.out.println("getCanonicalPath:"+file.getCanonicalPath());
}
其中第二個測試用例輸出如下:
4、exists / isDirectory / isFile / isHidden / lastModified / length
這幾個方法都是擷取檔案屬性,判斷檔案是否存在,檔案類型,是否隐藏,最後一次修改時間和檔案的大小,其實作如下:
public boolean exists() {
SecurityManager security = System.getSecurityManager();
if (security != null) {
security.checkRead(path);
}
if (isInvalid()) {
return false;
}
return ((fs.getBooleanAttributes(this) & FileSystem.BA_EXISTS) != 0);
}
public boolean isDirectory() {
SecurityManager security = System.getSecurityManager();
if (security != null) {
security.checkRead(path);
}
if (isInvalid()) {
return false;
}
return ((fs.getBooleanAttributes(this) & FileSystem.BA_DIRECTORY)
!= 0);
}
public boolean isFile() {
SecurityManager security = System.getSecurityManager();
if (security != null) {
security.checkRead(path);
}
if (isInvalid()) {
return false;
}
return ((fs.getBooleanAttributes(this) & FileSystem.BA_REGULAR) != 0);
}
public boolean isHidden() {
SecurityManager security = System.getSecurityManager();
if (security != null) {
security.checkRead(path);
}
if (isInvalid()) {
return false;
}
return ((fs.getBooleanAttributes(this) & FileSystem.BA_HIDDEN) != 0);
}
public long lastModified() {
SecurityManager security = System.getSecurityManager();
if (security != null) {
security.checkRead(path);
}
if (isInvalid()) {
return 0L;
}
//本地方法實作
return fs.getLastModifiedTime(this);
}
//擷取檔案的位元組數,如果不存在則傳回0
public long length() {
SecurityManager security = System.getSecurityManager();
if (security != null) {
security.checkRead(path);
}
if (isInvalid()) {
return 0L;
}
//本地方法實作
return fs.getLength(this);
}
//UnixFileSystem的實作
public int getBooleanAttributes(File f) {
//本地方法實作
int rv = getBooleanAttributes0(f);
String name = f.getName();
// 以. 開頭的檔案認為是隐藏檔案
boolean hidden = (name.length() > 0) && (name.charAt(0) == '.');
return rv | (hidden ? BA_HIDDEN : 0);
}
其中涉及的FileSystem的幾個常量的定義如下:
其中涉及的本地方法的實作都在UnixFileSystem_md.c中,其核心是擷取檔案屬性的stat64函數,如下:
JNIEXPORT jint JNICALL
Java_java_io_UnixFileSystem_getBooleanAttributes0(JNIEnv *env, jobject this,
jobject file)
{
jint rv = 0;
WITH_FIELD_PLATFORM_STRING(env, file, ids.path, path) {
int mode;
if (statMode(path, &mode)) { //傳回true表示檔案存在
int fmt = mode & S_IFMT;
//stat的結果轉換
rv = (jint) (java_io_FileSystem_BA_EXISTS
| ((fmt == S_IFREG) ? java_io_FileSystem_BA_REGULAR : 0) //如果是檔案
| ((fmt == S_IFDIR) ? java_io_FileSystem_BA_DIRECTORY : 0)); //如果是檔案夾
}
} END_PLATFORM_STRING(env, path);
return rv;
}
JNIEXPORT jlong JNICALL
Java_java_io_UnixFileSystem_getLastModifiedTime(JNIEnv *env, jobject this,
jobject file)
{
jlong rv = 0;
WITH_FIELD_PLATFORM_STRING(env, file, ids.path, path) {
struct stat64 sb;
if (stat64(path, &sb) == 0) {
rv = 1000 * (jlong)sb.st_mtime;
}
} END_PLATFORM_STRING(env, path);
return rv;
}
JNIEXPORT jlong JNICALL
Java_java_io_UnixFileSystem_getLength(JNIEnv *env, jobject this,
jobject file)
{
jlong rv = 0;
WITH_FIELD_PLATFORM_STRING(env, file, ids.path, path) {
struct stat64 sb;
if (stat64(path, &sb) == 0) {
rv = sb.st_size;
}
} END_PLATFORM_STRING(env, path);
return rv;
}
static jboolean
statMode(const char *path, int *mode)
{
struct stat64 sb;
//stat64是标準C函數,用于擷取檔案屬性,ls指令的底層實作就是該函數
if (stat64(path, &sb) == 0) {
*mode = sb.st_mode;
return JNI_TRUE;
}
return JNI_FALSE;
}
5、createNewFile / createTempFile / delete / deleteOnExit
createNewFile會建立一個新檔案,如果目前File對象對應的檔案不存在的話,如果存在則傳回false。createTempFile會建立一個在指定目錄下的臨時檔案,臨時檔案的檔案名是通過prefix、随機數、suffix生成,傳回的File對應的檔案肯定是原來不存在的,注意createTempFile本身并不删除臨時檔案,需要程式顯示調用delete方法删除或者放在作業系統臨時目錄下,由作業系統負責删除。delete和deleteOnExit都是用于删除檔案,差別在于前者是立即删除,後者是在JVM退出時通過回調鈎子方法執行的删除,實際的删除動作還是delete方法完成的。
//如果目标路徑的檔案不存在則建立一個新的
public boolean createNewFile() throws IOException {
SecurityManager security = System.getSecurityManager();
if (security != null) security.checkWrite(path);
if (isInvalid()) {
throw new IOException("Invalid file path");
}
//是一個本地方法
return fs.createFileExclusively(path);
}
//在指定目錄下建立一個臨時檔案,檔案名由prefix 和 suffix指定,不宜過長,如果超長會被自動截斷
//如果suffix為null,則預設為.tmp,如果directory為null,則預設在系統的臨時目錄下建立檔案,Unix下是/tmp或者/var/tmp
public static File createTempFile(String prefix, String suffix,
File directory)
throws IOException
{
if (prefix.length() < 3) //字首不小于3
throw new IllegalArgumentException("Prefix string too short");
if (suffix == null)
suffix = ".tmp"; //字尾預設為.tmp
//檔案路徑預設為系統的臨時檔案夾目錄
File tmpdir = (directory != null) ? directory
: TempDirectory.location();
SecurityManager sm = System.getSecurityManager();
File f;
do {
//生成一個随機檔案名的File對象,此時未實際建立檔案
f = TempDirectory.generateFile(prefix, suffix, tmpdir);
if (sm != null) {
try {
//檢查通路權限
sm.checkWrite(f.getPath());
} catch (SecurityException se) {
// don't reveal temporary directory location
if (directory == null)
throw new SecurityException("Unable to create temporary file");
throw se;
}
}
//檢查檔案是否存在,如果存在則繼續生成一個新檔案名的檔案
} while ((fs.getBooleanAttributes(f) & FileSystem.BA_EXISTS) != 0);
if (!fs.createFileExclusively(f.getPath())) //如果建立檔案失敗,則抛出異常
throw new IOException("Unable to create temporary file");
return f;
}
public static File createTempFile(String prefix, String suffix)
throws IOException
{
return createTempFile(prefix, suffix, null);
}
public boolean delete() {
SecurityManager security = System.getSecurityManager();
if (security != null) {
security.checkDelete(path);
}
if (isInvalid()) {
return false;
}
return fs.delete(this);
}
public void deleteOnExit() {
SecurityManager security = System.getSecurityManager();
if (security != null) {
security.checkDelete(path);
}
if (isInvalid()) {
return;
}
//在JVM退出時執行删除
DeleteOnExitHook.add(path);
}
//File的靜态内部類
private static class TempDirectory {
private TempDirectory() { }
//擷取臨時檔案夾路徑
private static final File tmpdir = new File(AccessController
.doPrivileged(new GetPropertyAction("java.io.tmpdir")));
//傳回系統臨時檔案夾路徑
static File location() {
return tmpdir;
}
//使用SecureRandom而非Random生成随機數,可避免因為種子問題導緻生成的随機數序列一緻的問題
private static final SecureRandom random = new SecureRandom();
//在指定目錄下生成一個臨時檔案
static File generateFile(String prefix, String suffix, File dir)
throws IOException
{
long n = random.nextLong();
if (n == Long.MIN_VALUE) {
n = 0; // corner case
} else {
n = Math.abs(n);
}
//擷取字首,這裡通過File的構造方法去掉了prefix中可能的多餘的/
prefix = (new File(prefix)).getName();
//生成随機的檔案名
String name = prefix + Long.toString(n) + suffix;
File f = new File(dir, name);
//如果name中包含多餘的字元或者是非法路徑則抛出異常
if (!name.equals(f.getName()) || f.isInvalid()) {
if (System.getSecurityManager() != null)
throw new IOException("Unable to create temporary file");
else
throw new IOException("Unable to create temporary file, " + f);
}
return f;
}
}
//UnixFileSystem實作
public boolean delete(File f) {
//清空路徑解析的緩存
cache.clear();
javaHomePrefixCache.clear();
//本地方法實作
return delete0(f);
}
其中涉及的本地方法的實作都在UnixFileSystem_md.c中,其核心是負責打開和建立檔案的open64函數和删除檔案的remove函數,如下:
JNIEXPORT jboolean JNICALL
Java_java_io_UnixFileSystem_createFileExclusively(JNIEnv *env, jclass cls,
jstring pathname)
{
jboolean rv = JNI_FALSE;
WITH_PLATFORM_STRING(env, pathname, path) {
FD fd;
/* The root directory always exists */
if (strcmp (path, "/")) {
//O_CREAT下如果檔案不存在則建立
fd = handleOpen(path, O_RDWR | O_CREAT | O_EXCL, 0666);
if (fd < 0) {
if (errno != EEXIST) //如果不是因為檔案已存在導緻的失敗,則抛出異常
JNU_ThrowIOExceptionWithLastError(env, path);
} else {
if (close(fd) == -1) //關閉fd失敗,抛出異常
JNU_ThrowIOExceptionWithLastError(env, path);
rv = JNI_TRUE;
}
}
} END_PLATFORM_STRING(env, path);
return rv;
}
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) {
//remove是C函數,用于移除檔案
if (remove(path) == 0) {//移除成功,傳回true
rv = JNI_TRUE;
}
} END_PLATFORM_STRING(env, path);
return rv;
}
FD
handleOpen(const char *path, int oflag, int mode) {
FD fd;
//open64是一個C函數,用于打開檔案,根據oflag不同會有不同的行為
RESTARTABLE(open64(path, oflag, mode), fd);
if (fd != -1) {
//open64執行成功
struct stat64 buf64;
int result;
//fstat64是C函數,擷取檔案屬性
RESTARTABLE(fstat64(fd, &buf64), result);
if (result != -1) {
if (S_ISDIR(buf64.st_mode)) {
//如果目錄檔案存在且是檔案夾
close(fd);
errno = EISDIR;
fd = -1;
}
} else {
//執行fstat64異常
close(fd);
fd = -1;
}
}
return fd;
}
測試用例如下:
@Test
public void test3() throws Exception {
File file=new File("D:\\code\\test2.txt");
System.out.println("exist->"+file.exists());
System.out.println("createNewFile->"+file.createNewFile());
System.out.println("exist after create->"+file.exists());
System.out.println("createNewFile two->"+file.createNewFile());
System.out.println("delete ->"+file.delete());
System.out.println("delete two->"+file.delete());
}
@Test
public void test4() throws Exception {
for(int i=0;i<5;i++) {
//字尾預設是.tmp,檔案路徑預設是系統的臨時目錄
File file = File.createTempFile("tst", null);
System.out.println("exists->" + file.exists() + ",path->" + file.getPath());
}
}
6、mkdir / mkdirs
mkdir用于建立一個檔案夾,mkdirs用于建立從父目錄到目前目錄的多個檔案夾,如果建立失敗則傳回false,如果建立成功或者本身已經存在則傳回true,其實作如下:
//建立檔案夾,如果建立失敗則傳回false
public boolean mkdir() {
SecurityManager security = System.getSecurityManager();
if (security != null) {
security.checkWrite(path);
}
if (isInvalid()) {
return false;
}
//本地方法實作
return fs.createDirectory(this);
}
//建立多個檔案夾,如果建立失敗則傳回false
public boolean mkdirs() {
if (exists()) { //檔案已存在
return false;
}
if (mkdir()) { //直接建立檔案夾失敗
return true;
}
File canonFile = null;
try {
//擷取标準化路徑對應的檔案
canonFile = getCanonicalFile();
} catch (IOException e) {
return false;
}
//擷取父檔案夾對應的File
File parent = canonFile.getParentFile();
//parent不為空,則調用其mkdirs,此處實際是一個遞歸
return (parent != null && (parent.mkdirs() || parent.exists()) &&
canonFile.mkdir());
}
public File getCanonicalFile() throws IOException {
String canonPath = getCanonicalPath();
return new File(canonPath, fs.prefixLength(canonPath));
}
其中createDirectory本地方法的實作在UnixFileSystem_md.c中,其核心是mkdir函數,如下:
JNIEXPORT jboolean JNICALL
Java_java_io_UnixFileSystem_createDirectory(JNIEnv *env, jobject this,
jobject file)
{
jboolean rv = JNI_FALSE;
WITH_FIELD_PLATFORM_STRING(env, file, ids.path, path) {
//0777表示建立的檔案是所有使用者都可讀可寫可執行的
if (mkdir(path, 0777) == 0) {
rv = JNI_TRUE;
}
} END_PLATFORM_STRING(env, path);
return rv;
}
7、list / listFiles / listRoots
list和listFiles都是擷取目前File下的子檔案或者子目錄,差別在于前者傳回檔案名,後者傳回檔案名對應的File對象,可以添加FilenameFilter 或者FileFilter 實作過濾掉不滿足條件的檔案。listRoots傳回根目錄對應的File,Unix下傳回 / 對應的File對象,Windows下傳回所有磁盤分區對應的File對象。
public String[] list() {
SecurityManager security = System.getSecurityManager();
if (security != null) {
security.checkRead(path);
}
if (isInvalid()) { //路徑無效,傳回null
return null;
}
//本地方法
return fs.list(this);
}
public String[] list(FilenameFilter filter) {
String names[] = list();
if ((names == null) || (filter == null)) {
return names;
}
List<String> v = new ArrayList<>();
for (int i = 0 ; i < names.length ; i++) {
//執行過濾邏輯
if (filter.accept(this, names[i])) {
v.add(names[i]);
}
}
return v.toArray(new String[v.size()]);
}
public File[] listFiles() {
String[] ss = list();
if (ss == null) return null;
int n = ss.length;
File[] fs = new File[n];
for (int i = 0; i < n; i++) {
//将list方法檔案的檔案名轉換成File對象
fs[i] = new File(ss[i], this);
}
return fs;
}
public File[] listFiles(FilenameFilter filter) {
String ss[] = list();
if (ss == null) return null;
ArrayList<File> files = new ArrayList<>();
for (String s : ss)
//執行過濾邏輯,将檔案名轉換成File對象
if ((filter == null) || filter.accept(this, s))
files.add(new File(s, this));
return files.toArray(new File[files.size()]);
}
public File[] listFiles(FileFilter filter) {
String ss[] = list();
if (ss == null) return null;
ArrayList<File> files = new ArrayList<>();
for (String s : ss) {
File f = new File(s, this);
//執行過濾邏輯,将檔案名轉換成File對象
if ((filter == null) || filter.accept(f))
files.add(f);
}
return files.toArray(new File[files.size()]);
}
public static File[] listRoots() {
return fs.listRoots();
}
//UnixFileSystem的實作
public File[] listRoots() {
try {
SecurityManager security = System.getSecurityManager();
if (security != null) {
security.checkRead("/");
}
return new File[] { new File("/") };
} catch (SecurityException x) {
return new File[0];
}
}
其中list本地方法的實作在 UnixFileSystem_md.c中,其核心是打開目錄檔案的opendir函數,負責逐一讀取目錄下子目錄或者子檔案的readdir64_r函數以及關閉目錄對象Dir的closedir函數,如下:
JNIEXPORT jobjectArray JNICALL
Java_java_io_UnixFileSystem_list(JNIEnv *env, jobject this,
jobject file)
{
DIR *dir = NULL;
struct dirent64 *ptr;
struct dirent64 *result;
int len, maxlen;
jobjectArray rv, old;
jclass str_class;
//擷取String對應的Class
str_class = JNU_ClassString(env);
CHECK_NULL_RETURN(str_class, NULL);
WITH_FIELD_PLATFORM_STRING(env, file, ids.path, path) {
//opendir是C函數,打開某個檔案目錄
dir = opendir(path);
} END_PLATFORM_STRING(env, path);
//目錄不存在,傳回null
if (dir == NULL) return NULL;
//建立一個dirent64數組
ptr = malloc(sizeof(struct dirent64) + (PATH_MAX + 1));
if (ptr == NULL) {
//記憶體不足,抛出異常
JNU_ThrowOutOfMemoryError(env, "heap allocation failed");
//closedir是C函數,關閉目錄
closedir(dir);
return NULL;
}
/* Allocate an initial String array */
len = 0;
maxlen = 16;
//建立一個初始長度為16的String數組
rv = (*env)->NewObjectArray(env, maxlen, str_class, NULL);
//建立失敗,跳轉到goto
if (rv == NULL) goto error;
/* Scan the directory */
//readdir64_r是C函數,用于讀取下一個目錄,讀取的結果放到ptr數組中
while ((readdir64_r(dir, ptr, &result) == 0) && (result != NULL)) {
jstring name;
if (!strcmp(ptr->d_name, ".") || !strcmp(ptr->d_name, "..")) //跳過. 和 ..
continue;
if (len == maxlen) {
//數組滿了,執行擴容
old = rv;
//建立一個擴容一倍的數組
rv = (*env)->NewObjectArray(env, maxlen <<= 1, str_class, NULL);
if (rv == NULL) goto error;
//數組拷貝
if (JNU_CopyObjectArray(env, rv, old, len) < 0) goto error;
//删除本地引用old
(*env)->DeleteLocalRef(env, old);
}
//建立Java字元串,d_name表示檔案名
name = JNU_NewStringPlatform(env, ptr->d_name);
if (name == NULL) goto error;
//儲存到Java數組中,len加1,并删除本地引用
(*env)->SetObjectArrayElement(env, rv, len++, name);
(*env)->DeleteLocalRef(env, name);
}
//關閉目錄,釋放記憶體
closedir(dir);
free(ptr);
/* 根據實際的結果大小,重新建立一個String數組 */
old = rv;
rv = (*env)->NewObjectArray(env, len, str_class, NULL);
if (rv == NULL) {
return NULL;
}
//數組拷貝
if (JNU_CopyObjectArray(env, rv, old, len) < 0) {
return NULL;
}
return rv;
error:
closedir(dir);
free(ptr);
return NULL;
}
測試用例如下:
@Test
public void test5() throws Exception {
File file=new File("D:\\code");
System.out.println(Arrays.toString(file.list()));
System.out.println(Arrays.toString(file.list(new FilenameFilter() {
@Override
public boolean accept(File dir, String name) {
System.out.println("FilenameFilter dir->"+dir+",name->"+name);
return name.contains("priv");
}
})));
System.out.println(Arrays.toString(file.listFiles(new FileFilter() {
@Override
public boolean accept(File pathname) {
System.out.println("FileFilter file->"+pathname.getPath());
return pathname.getPath().contains("pub");
}
})));
}
結果如下: