首页  >  文章  >  Java  >  要记住的边缘情况。零件文件

要记住的边缘情况。零件文件

PHPz
PHPz原创
2024-08-09 07:05:22456浏览

Edge Cases to Keep in Mind. Part  Files

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

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

一个不是文件的文件

java.io.File API 提供以下 3 种方法:

  • #exists()

  • #isDirectory()

  • #isFile()

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

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

看下面的代码片段:

import java.io.File

val file = File("/dev/null")
println("exists:      ${file.exists()}")
println("isFile:      ${file.isFile()}")
println("isDirectory: ${file.isDirectory()}")

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

存在,还是不存在?

符号链接也是特殊文件,但在(旧)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()。按上述顺序使用这些方法非常重要。让我们看看如果顺序颠倒会发生什么。需要两个(或更多)线程执行相同的操作来演示这种情况。在这里,我们将使用蓝色和红色的线。

  1. (红色)isDirectory()? — 不,需要创建

  2. (蓝色)isDirectory()? — 不,需要创建

  3. (红色)mkdir()? — 成功

  4. (蓝色)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())
    }
}

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

亡灵确实存在

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

In Unix-like operating systems, file deletion is usually implemented by unlinking. The unlinked file name is removed from the file system (assuming that it is the last hardlink) but any already open file descriptors remain valid and usable. You can still read from and write to such a file. Here is the snippet:

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()}")
}

And a live demo.

Wrap up

First of all, remember that we can’t forget about the proper method calling order when creating non-existent directories. Furthermore, keep in mind that a number of files open at the same time is limited and not only files explicitly opened by you are counted. And the last, but not least, a trick with file deletion before the last usage can give you a little bit more flexibility.

Originally published at www.thedroidsonroids.com on September 27, 2017.

以上是要记住的边缘情况。零件文件的详细内容。更多信息请关注PHP中文网其他相关文章!

声明:
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn