java filesystem_JDK不同操作系统的FileSystem(unix-like)上篇

前言

我们知道不同的操作系统有各自的文件系统,这些文件系统又存在很多差异,而Java 因为是跨平台的,所以它必须要统一处理这些不同平台文件系统之间的差异,才能往上提供统一的入口。

关于FileSystem类

JDK 里面抽象出了一个 FileSystem 来表示文件系统,不同的操作系统通过继承该类实现各自的文件系统,比如 Windows NT/2000 操作系统则为 WinNTFileSystem,而 unix-like 操作系统为 UnixFileSystem。

需要注意的一点是,WinNTFileSystem类 和 UnixFileSystem类并不是在同一个 JDK 里面,也就是说它们是分开的,你只能在 Windows 版本的 JDK 中找到 WinNTFileSystem,而在 unix-like 版本的 JDK 中找到 UnixFileSystem,同样地,其他操作系统也有自己的文件系统实现类。

这里分成两个系列分析 JDK 对两种(Windows 和 unix-like )操作系统的文件系统的实现类,前面已经讲了 Windows操作系统,对应为 WinNTFileSystem 类。这里接着讲 unix-like 操作系统,对应为 UnixFileSystem 类。篇幅所限,分为上中下篇,此为中篇。

继承结构

--java.lang.Object

--java.io.FileSystem

--java.io.UnixFileSystem

类定义

class UnixFileSystem extends FileSystem

主要属性

slash 表示斜杠符号。

colon 表示冒号符号。

javaHome 表示Java Home目录。

cache 用于缓存标准路径。

javaHomePrefixCache 用于缓存标准路径前缀。

private final char slash;

private final char colon;

private final String javaHome;

private ExpiringCache cache = new ExpiringCache();

private ExpiringCache javaHomePrefixCache = new ExpiringCache();

主要方法

parentOrNull 方法

该方法用于获取路径的父目录,它的思路其实很简单,就是从路径的最后一个字符开始向前寻找路径分隔符(斜杠),正常情况下,找到第一个路径分隔符的位置,再从头开始截取到该位置即可。但可能存在.或..的情况,所以如果连续遇到两个.则返回 null;如果路径以.结尾也是返回 null;如果路径没有父目录或以分隔符结尾都返回null。

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 {

++nonDotCount;

adjacentDots = 0;

}

--idx;

}

return null;

}

getBooleanAttributes方法

这是一个本地方法,它主要的作用是可以用来判断 File 对象对应的文件或目录是否存在,判断 File 对象对应的是不是文件,判断 File 对象对应的是不是目录,判断 File 对象是不是隐藏文件或目录。

从实现可以看到是通过 getBooleanAttributes0 本地方法获取“是否存在”、“是否为文件”和“是否为目录”三个属性值,但“是否为隐藏”则无法从本地方法获取到,而是通过判断文件名是否以.开头来判断是否隐藏,因为在 unix-like 中约定以此开头的都是隐藏文件或目录。

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);

}

但这里为什么返回的是一个 int 类型呢?因为这里为了高效利用数据,用位作为不同属性的标识,分别为

0x01、0x02、0x04、0x08,分别代表是否存在、是否为文件、是否为目录和是否为隐藏文件或目录。

public native int getBooleanAttributes0(File f);

本地方法的主要逻辑是通过 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)) {

int fmt = mode & S_IFMT;

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;

}

static jboolean

statMode(const char *path, int *mode)

{

struct stat64 sb;

if (stat64(path, &sb) == 0) {

*mode = sb.st_mode;

return JNI_TRUE;

}

return JNI_FALSE;

}

checkAccess方法

这是一个本地方法,它主要的作用是判断某个文件或目录是否可读、是否可写、是否可执行。这里同样用位标识这些属性,分别用0x01、0x02、0x04表示可执行、可写、可读。

public native boolean checkAccess(File f, int access);

本地方法的实现主要通过 access 函数,可以看到可读、可写和可执行三种情况对应的mode分别为R_OK、W_OK、X_OK。拥有对应权限则返回0,否则返回-1。最后往Java层返回一个 boolean 值。

JNIEXPORT jboolean JNICALL

Java_java_io_UnixFileSystem_checkAccess(JNIEnv *env, jobject this,

jobject file, jint a)

{

jboolean rv = JNI_FALSE;

int mode = 0;

switch (a) {

case java_io_FileSystem_ACCESS_READ:

mode = R_OK;

break;

case java_io_FileSystem_ACCESS_WRITE:

mode = W_OK;

break;

case java_io_FileSystem_ACCESS_EXECUTE:

mode = X_OK;

break;

default: assert(0);

}

WITH_FIELD_PLATFORM_STRING(env, file, ids.path, path) {

if (access(path, mode) == 0) {

rv = JNI_TRUE;

}

} END_PLATFORM_STRING(env, path);

return rv;

}

getLastModifiedTime方法

该方法用于获取文件或目录的最后修改时间,本地方法逻辑是通过 stat64 函数获取对应路径文件属性,然后获取结构体的 st_mtime 成员即可。

public native long getLastModifiedTime(File f);

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;

}

这里为什么能通过 ids 直接获取到路径呢?且看下面,其实在 Java 层的 UnixFileSystem 类被加载时就会执行一个代码块,initIDs 方法会将 File 类的 path 属性关联到 ids 中,进而可以根据该属性获取到路径。

private static native void initIDs();

static {

initIDs();

}

static struct {

jfieldID path;

} ids;

JNIEXPORT void JNICALL

Java_java_io_UnixFileSystem_initIDs(JNIEnv *env, jclass cls)

{

jclass fileClass = (*env)->FindClass(env, "java/io/File");

if (!fileClass) return;

ids.path = (*env)->GetFieldID(env, fileClass,

"path", "Ljava/lang/String;");

}

getLength方法

该方法用于获取文件或目录的长度,本地方法逻辑是通过 stat64 函数获取对应路径文件属性,其中路径同样是通过 ids 获得,然后获取结构体的 st_size 成员即可。

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;

}

setPermission方法

该方法主要用于设置 File 对象的访问权限,核心逻辑是通过 chmod 函数来实现,具体逻辑如下:

1. 对于 owneronly 变量,表示是否仅修改文件所有者的权限。

2. 如果设为 ACCESS_READ,则根据 owneronly 变量,将 chmod 函数对应的参数值设为 S_IRUSR 或 S_IRUSR | S_IRGRP | S_IROTH。

3. 如果设为 ACCESS_WRITE,则根据 owneronly 变量,将 chmod 函数对应的参数值设为 S_IWUSR 或 S_IWUSR | S_IWGRP | S_IWOTH。

4. 如果设为 ACCESS_EXECUTE,则根据 owneronly 变量,将 chmod 函数对应的参数值设为 S_IXUSR 或 S_IXUSR | S_IXGRP | S_IXOTH。

5. 通过 stat64 函数获取路径对应文件的属性,并根据 enable 变量来赋予新值,最后通过 chmod 函数完成权限的修改。

JNIEXPORT jboolean JNICALL

Java_java_io_UnixFileSystem_setPermission(JNIEnv *env, jobject this,

jobject file,

jint access,

jboolean enable,

jboolean owneronly)

{

jboolean rv = JNI_FALSE;

WITH_FIELD_PLATFORM_STRING(env, file, ids.path, path) {

int amode = 0;

int mode;

switch (access) {

case java_io_FileSystem_ACCESS_READ:

if (owneronly)

amode = S_IRUSR;

else

amode = S_IRUSR | S_IRGRP | S_IROTH;

break;

case java_io_FileSystem_ACCESS_WRITE:

if (owneronly)

amode = S_IWUSR;

else

amode = S_IWUSR | S_IWGRP | S_IWOTH;

break;

case java_io_FileSystem_ACCESS_EXECUTE:

if (owneronly)

amode = S_IXUSR;

else

amode = S_IXUSR | S_IXGRP | S_IXOTH;

break;

default:

assert(0);

}

if (statMode(path, &mode)) {

if (enable)

mode |= amode;

else

mode &= ~amode;

if (chmod(path, mode) >= 0) {

rv = JNI_TRUE;

}

}

} END_PLATFORM_STRING(env, path);

return rv;

}

static jboolean

statMode(const char *path, int *mode)

{

struct stat64 sb;

if (stat64(path, &sb) == 0) {

*mode = sb.st_mode;

return JNI_TRUE;

}

return JNI_FALSE;

}

createFileExclusively方法

该方法用于创建文件,本地方法逻辑是,

1. 如果是根目录则不做任何操作,因为根目录肯定存在的。

2. 通过 handleOpen 函数完成创建的逻辑,该方法其实通过调用 open64 函数创建文件,其中传入的参数为 O_RDWR | O_CREAT | O_EXCL,表示以读写模式打开,并且如果文件不存在则创建,如果文件已存在则返回-1。

3. 创建成功后通过 fstat64 函数获取文件属性,用 S_ISDIR 函数判断如果是目录则关闭文件并且设置 errno 为 EISDIR。

4. 如果 handleOpen 函数返回了-1,则如果 errno 不为 EEXIST 则抛出错误,也就是说文件已经存在的话则不抛出错误。

5. 最后创建文件成功后关闭它,返回成功。

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;

if (strcmp (path, "/")) {

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)

JNU_ThrowIOExceptionWithLastError(env, path);

rv = JNI_TRUE;

}

}

} END_PLATFORM_STRING(env, path);

return rv;

}

FD

handleOpen(const char *path, int oflag, int mode) {

FD fd;

RESTARTABLE(open64(path, oflag, mode), fd);

if (fd != -1) {

struct stat64 buf64;

int result;

RESTARTABLE(fstat64(fd, &buf64), result);

if (result != -1) {

if (S_ISDIR(buf64.st_mode)) {

close(fd);

errno = EISDIR;

fd = -1;

}

} else {

close(fd);

fd = -1;

}

}

return fd;

}

delete方法

该方法用于删除 File 对象指定路径,需要将标准路径缓存和标准路径前缀缓存都清掉,然后调用本地方法 delete0 执行删除操作。

public boolean delete(File f) {

cache.clear();

javaHomePrefixCache.clear();

return delete0(f);

}

private native boolean delete0(File f);

本地方法实际上就是调用了 remove 函数对指定路径文件进行删除。

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) {

if (remove(path) == 0) {

rv = JNI_TRUE;

}

} END_PLATFORM_STRING(env, path);

return rv;

}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值