你知道吗,可能有一个文件同时存在和不存在?您是否知道,您可以删除文件并仍然使用它?发现软件开发中的这些和其他文件边缘情况。

在我之前关于软件开发中的边缘情况的文章中,我写了有关文本陷阱的文章,并给了您一些建议,以及如何避免它们。在这篇博文中,我想重点讨论文件和文件 i/o 操作。

一个不是文件的文件

java.io.file api 提供了以下 3 种方法:

exists()

点击下载“C盘瘦身工具,一键清理C盘”;

isdirectory()

isfile()

人们可能会认为,如果它由存在的给定路径指向,则对象要么是文件,要么是目录 - 就像 stack overflow 上的这个问题一样。然而,这并不总是正确的。

file#isfile() javadocs 中没有明确提及,但文件 确实意味着 常规文件。因此,特殊的 unix 文件(如设备、套接字和管道)可能存在,但它们不是该定义中的文件。

看下面的片段:

import java.io.file

val file = file("/dev/null")
println("exists: ${file.exists()}")
println("isfile: ${file.isfile()}")
println("isdirectory: ${file.isdirectory()}")
登录后复制

正如您在现场演示中看到的,可能存在既不是文件也不是目录的 file。

存在,还是不存在?

符号链接也是特殊文件,但在(旧)java.io api 中几乎所有地方都以透明方式处理它们。唯一的例外是 #getcanonicalpath()/#getcanonicalfile() 方法系列。这里的透明意味着所有操作都转发到目标,就像直接在目标上执行一样。这种透明度通常很有用,例如您可以只读取或写入某个文件。您不关心可选的链接路径分辨率。然而,这也可能会导致一些奇怪的情况。例如,可能有一个文件同时存在和不存在。

让我们考虑一个悬挂的符号链接。它的目标不存在,因此上一节中的所有方法都将返回 false。尽管如此,源文件路径仍然被占用,例如您无法在该路径上创建新文件。这是演示此案例的代码:

import java.nio.file.files
import java.nio.file.path
import java.nio.file.paths

val path = paths.get("foo")
files.createsymboliclink(path, path)
println("exists : ${path.tofile().exists()}")
println("isfile : ${path.tofile().isfile()}")
println("isdirectory : ${path.tofile().isdirectory()}")
println("createnewfile: ${path.tofile().createnewfile()}")
登录后复制

还有现场演示。

顺序很重要

在 java.io api 中,要创建一个可能不存在的目录并确保它之后存在,可以使用 file#mkdir() (如果您还想创建不存在的父目录,则可以使用 file#mkdirs() )然后是 file#isdirectory()。按上述顺序使用这些方法非常重要。让我们看看如果顺序颠倒会发生什么。需要两个(或更多)线程执行相同的操作来演示这种情况。在这里,我们将使用蓝色和红色的线。

(红色) isdirectory()? — 不,需要创建
(蓝色) isdirectory()? — 不,需要创建
(红色)mkdir()? ——成功
(蓝色)mkdir()? ——失败

如您所见,蓝色线程无法创建目录。但它确实是被创造出来的,所以结果应该是积极的。如果 isdirectory() 在最后调用,结果总是正确的。

隐藏的限制

给定 unix 进程同时打开的文件数量限制为 rlimit_nofile 的值。在 android 上,这通常是 1024,但实际上(不包括框架使用的文件描述符)您可以使用更少(在 android 8.0.0 上使用空 activity 进行测试期间,大约有 970 个文件描述符可供使用)。如果您尝试打开更多会发生什么?好吧,文件不会被打开。根据上下文,您可能会遇到具有明确原因的异常(打开文件太多),一点点神秘的消息(例如此文件无法作为文件描述符打开;它可能被压缩)或者当您通常期望 true 时,将 false 作为返回值。请参阅演示这些问题的代码:

package pl.droidsonroids.edgetest

import android.content.res.assetfiledescriptor
import android.support.test.instrumentationregistry
import org.junit.assert
import org.junit.test

class toomanyopenfilestest {
//asset named "test" required
@test
fun toomanyopenfilesdemo() {
val context = instrumentationregistry.getcontext()
val assets = context.assets
val descriptors = mutablelistof()
try {
for (i in 0..1024) {
descriptors.add(assets.openfd("test"))
}
} catch (e: exception) {
e.printstacktrace() //java.io.filenotfoundexception: this file can not be opened as a file descriptor; it is probably compressed
}
try {
context.openfileoutput("test", 0)
} catch (e: exception) {
e.printstacktrace() //java.io.filenotfoundexception: /data/user/0/pl.droidsonroids.edgetest/files/test (too many open files)
}

    val sharedpreferences = context.getsharedpreferences("test", 0)
    assert.asserttrue(sharedpreferences.edit().putboolean("test", true).commit())
}

}
登录后复制

请注意,如果你使用#apply(),该值将不会被持久保存——所以你不会得到任何异常。但是,在持有该 sharedpreferences 实例的应用程序进程被终止之前,它将可以访问。那是因为共享偏好也保存在内存中。

亡灵确实存在

人们可能认为僵尸、食尸鬼和其他类似的生物只存在于奇幻和恐怖小说中。但是……它们在计算机科学中是真实存在的!这些常见术语指的是僵尸进程。其实亡灵文件也可以轻松创建。

在类unix操作系统中,文件删除通常是通过取消链接来实现的。未链接的文件名将从文件系统中删除(假设它是最后一个硬链接),但任何已打开的文件描述符仍然有效且可用。您仍然可以读取和写入此类文件。这是片段:

import java.io.BufferedReader
import java.io.File
import java.io.FileReader

val file = File("test")
file.writeText("this is file content")

BufferedReader(FileReader(file)).use {
println("deleted?: ${file.delete()}")
println("content?: ${it.readLine()}")
}
登录后复制

还有现场演示。

包起来

首先,请记住,在创建不存在的目录时,我们不能忘记正确的方法调用顺序。此外,请记住,同时打开的文件数量是有限的,并且不仅计算您明确打开的文件。最后但并非最不重要的一点是,在最后一次使用之前删除文件的技巧可以为您提供更多的灵活性。

最初于2017年9月27日发布于www.thedroidsonroids.com。

    以上就是要记住的边缘情况零件文件的详细内容,更多请关注php中文网其它相关文章!