JAVA SE API学习 java.io.File
构造函数
File
一共有6个构造函数,其中2个被声明为private
,4个被声明为public
private File(String pathname, int prefixLength)
private File(String child, File parent)
public File(String pathname)
public File(String parent, String child)
public File(File parent, String child)
public File(URI uri)
虽然有6个构造函数,然而这6个构造函数其实都只做了1件事。就是为path
和prefixLength
赋值。
不同之处只是在于对于传入不同参数的处理方式不同而已。
这里简单的使用常用的File(String pathname)
做一个例子:
public File(String pathname) {
if (pathname == null) {
throw new NullPointerException();
}
this.path = fs.normalize(pathname);
this.prefixLength = fs.prefixLength(this.path);
}
这里需要说明2点:
* 对path
赋值的时候使用normalize
函数,主要是为了过滤用户传入字符串中的一些无用信息,生成标准格式的pathname
(如用户输入a.txt
时,其实path
中的值为/a.txt
)。
* prefixLength
和系统相关,在linux下面:当pathname
以/
开头的时候,prefixLength
为1,在win下前缀有2种表达方式:设备号+\\
或者\\\\
,prefixLength
的长度也会对应相应前缀的长度( 这也是方法isAbsolute()
的判断依据)。
(PS:这里也提醒我们在书写自己的代码实现多态的时候,不仅要保证做同一件事情,而且也要保证访问和操作相同的数据)
权限验证的方法
canExecute()
canRead()
canWrite()
这让人很容易联想到linux下面的权限rwx
。在函数内部,他们的逻辑也基本相同,我们久拿出一个canRead()
进行举例。
public boolean canRead() {
SecurityManager security = System.getSecurityManager();
if (security != null) {
security.checkRead(path);
}
if (isInvalid()) {
return false;
}
return fs.checkAccess(this, FileSystem.ACCESS_READ);
}
SecurityManager security = System.getSecurityManager();
这里可以发现。canRead()
中有2层验证机制,首先是java的SecurityManager
类进行权限验证。如果不能获取到SecurityManager
,则调用文件系统本身的验证接口。
SecurityManager
验证机制和本文偏差有点大先不做过深到考虑。文件系统的权限验证比较简单,FileSystem.ACCESS_*
代表对应的16进制数。
@Native public static final int ACCESS_READ = 0x04;
@Native public static final int ACCESS_WRITE = 0x02;
@Native public static final int ACCESS_EXECUTE = 0x01;
很容易推断出验证的时候读(100),写(010),执行(001)各占1位,与系统给出的权限进行&
运算得出结果。
判断文件状态的几种方法
exists()
isFile()
isDirectory()
isHidden()
验证的机制和文件的权限验证类似,定义4个16进制验证码进行位运算。这里就不再重复说明。
/* Constants for simple boolean attributes */
@Native public static final int BA_EXISTS = 0x01;
@Native public static final int BA_REGULAR = 0x02;
@Native public static final int BA_DIRECTORY = 0x04;
@Native public static final int BA_HIDDEN = 0x08;
获取属性的方法
先说第一类命名规则为get + 修饰符/或不加 + Path
:
其中带Canonical
的会抛出异常。
* getPath()
* getAbsolutePath()
* getCanonicalPath()
Path
方法全部返回字符串,一个例子能说明他们返回的区别
src/../aaa_a.txt //getPath()
/Users/asd/Documents/Java/test/src/../aaa_a.txt //getAbsolutePath()
/Users/asd/Documents/Java/test/aaa_a.txt //getCanonicalPath()
他们在实现上的区别主要在于getPath()
只是单纯的对path
私有变量的返还。
而其他两个需要调用文件系统的相关函数。并且有一种内在逻辑就是getCanonicalPath()
是先对
File
对象进行getAbsolutePath()
操作然后才进行下一步处理。
getCanonicalFile()
方法只是对getCanonicalPath()
的调用,然后进行实例化而已。
创建文件的3种方式
- createNewFile()
- File.createTempFile(String prefix, String suffix)
- File.createTempFile(String prefix, String suffix, File directory)
createNewFile()
只是对文件系统创建文件接口的调用。
2个版本的createTempFile
其实是一个函数。File.createTempFile(String prefix, String suffix)
只是将directory
设为null
后对File.createTempFile(String prefix, String suffix, File directory)
的调用。
下面是createTempFile方法
public static File createTempFile(String prefix, String suffix, File directory)
{
if (prefix.length() < 3)
throw new IllegalArgumentException("Prefix string too short");
if (suffix == null)
suffix = ".tmp";
File tmpdir = (directory != null) ? directory
: TempDirectory.location();
SecurityManager sm = System.getSecurityManager();
File f;
do {
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;
}
先快速浏览下代码,这个生成临时文件的基本逻辑就是
f = TempDirectory.generateFile(prefix, suffix, tmpdir);
在这个地方。通过调用TempDirectory的generateFile方法。通过用户传入的参数生成指定的文件File
然后就是确认已经获取了系统操作的实例SecurityManager 并在所选目录有写入权限。
我们再来看看创建临时文件的 generateFile
函数
private static class TempDirectory {
private TempDirectory() { }
// temporary directory location
private static final File tmpdir = new File(AccessController
.doPrivileged(new GetPropertyAction("java.io.tmpdir")));
static File location() {
return tmpdir;
}
// file name generation
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);
}
// Use only the file name from the supplied prefix
prefix = (new File(prefix)).getName();
String name = prefix + Long.toString(n) + suffix;
File f = new File(dir, 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;
}
}
这是一个和File在一个包中的私有类。
其中我们要找的generateFile
函数也在其中。
为了排除用户输入的不能作为文件名字的字符(例如/
)进行了一下转换。
prefix = (new File(prefix)).getName();
最后检查了生成File中的文件名是否是我们创建的文件名(我觉得可能是在验证后缀suffix的合法性),并且验证了路径的有效性。
以上验证全部通过返回了File ;
还有2个创建文件夹的函数:
* mkdir()
* mkdirs()
这里只是提一句mkdirs()
会先调用mkdir()
只有在父级目录不存在的时候才会进行递归调用.
设置文件属性
setExecutable(boolean executable)
setExecutable(boolean executable, boolean ownerOnly)
setLastModified(long time)
setReadable(boolean readable)
setReadable(boolean readable, boolean ownerOnly)
setReadOnly()
setWritable(boolean writable)
setWritable(boolean writable, boolean ownerOnly)
放眼望去,茫茫多的函数挺吓人的,然而翻开他们的实现,基本全是一个套路,参数1 设置是否拥有某个权限,参数2控制作用于全用户还是拥有者自己。需要注意的是所有这些操作都需要写入权限。
(PS:在设计方法的时候相似功能的方法最好也保证传入的参数的数量语义也有相同的规范,这样才能使得代码更加利于维护。)
获取文件列表的几种方法
list()
list(FilenameFilter filter)
listFiles()
listFiles(FileFilter filter)
listFiles(FilenameFilter filter)
以上方法均以list()
为核心,其他方法都是通过list()
方法获取列表后进行相关处理返回的结果集。
其他方法
equals()
比较的时候有一点我们可以学习,就是传入的参数其实是Object类型,然后通过手动的类型检查,这样在控制错误的返回上面更加自由,而不会系统报错。
toString()
是getPath()
的别名。单纯为了实现toString()
接口。
toPath()
的实现使用了synchronized
:
public Path toPath() {
Path result = filePath;
if (result == null) {
synchronized (this) {
result = filePath;
if (result == null) {
result = FileSystems.getDefault().getPath(path);
filePath = result;
}
}
}
return result;
}
这里
result = filePath;
赋值了两次,主要为了解决当程序成功为filePath
赋值后,阻塞在synchronized
进入同步代码块可以直接获取到filePath
而不用重复的调用文件系统函数。
deleteOnExit()
的实现时通过将pathname
添加进DeleteOnExitHook
实现的。具体DeleteOnExitHook
另行说明。
不是用JAVA写出的代码都叫JAVA程序。我将为写出JAVA程序儿努力奋斗!!