ホームページ  >  記事  >  Java  >  留意すべきエッジケース。パーツファイル

留意すべきエッジケース。パーツファイル

PHPz
PHPzオリジナル
2024-08-09 07:05:22396ブラウズ

Edge Cases to Keep in Mind. Part  Files

ファイルには存在するものと存在しないものがあることをご存知ですか?ファイルを削除してもまだ使用できることをご存知ですか?ソフトウェア開発におけるこれらおよびその他のファイルのエッジケースを発見してください。

ソフトウェア開発におけるエッジケースに関する前回の記事では、テキストトラップについて書き、それを回避する方法についていくつかの提案をしました。このブログ投稿では、ファイルとファイル I/O 操作に焦点を当てたいと思います。

ファイルではないファイル

java.io.File API は、特に次の 3 つのメソッドを提供します:

  • #exists()

  • #isDirectory()

  • #isFile()

Stack Overflow のこの質問のように、存在する特定のパスによってオブジェクトが指されている場合、オブジェクトはファイルまたはディレクトリのいずれかであると考える人もいるかもしれません。ただし、これが常に真実であるとは限りません。

File#isFile() Javadoc では明示的に言及されていませんが、ファイル **実際には **通常のファイルを意味します。したがって、デバイス、ソケット、パイプなどの特別な 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() です。これらのメソッドを記載された順序で使用することが重要です。順序が逆の場合に何が起こるかを見てみましょう。このケースを実証するには、同じ操作を実行する 2 つ (またはそれ以上) のスレッドが必要です。ここでは、青と赤の糸を使用します。

  1. (赤)はDirectory()? — いいえ、

  2. を作成する必要があります
  3. (青)はDirectory()? — いいえ、

  4. を作成する必要があります
  5. (赤) mkdir()? — 成功

  6. (青) mkdir()? — 失敗

ご覧のとおり、青いスレッドはディレクトリの作成に失敗しました。しかし、実際に作成されたものであるため、結果はプラスになるはずです。 isDirectory() が最後に呼び出されていれば、結果は常に正しいものになっていたでしょう。

隠れた限界

特定の UNIX プロセスによって同時にオープンされるファイルの数は、RLIMIT_NOFILE の値に制限されます。 Android では、これは通常 1024 ですが、実際には (フレームワークで使用されるファイル記述子を除く)、さらに少ない数を使用できます (Android 8.0.0 で空のアクティビティを使用したテスト中、使用可能なファイル記述子は約 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 中国語 Web サイトの他の関連記事を参照してください。

声明:
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。