你知道吗,可能有一个文件同时存在和不存在?您是否知道,您可以删除文件并仍然使用它?发现软件开发中的这些和其他文件边缘情况。
在我之前关于软件开发中的边缘情况的文章中,我写了有关文本陷阱的文章,并给了您一些建议,以及如何避免它们。在这篇博文中,我想重点讨论文件和文件 i/o 操作。
一个不是文件的文件
java.io.file api 提供了以下 3 种方法:
#exists()
#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<assetfiledescriptor>() 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()) } } </assetfiledescriptor>
请注意,如果你使用#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中文网其它相关文章!