Wussten Sie, dass es eine Datei geben kann, die gleichzeitig existiert und nicht existiert? Wussten Sie, dass Sie eine Datei löschen und weiterhin verwenden können? Entdecken Sie diese und andere Dateirandfälle in der Softwareentwicklung.
In meinem vorherigen Artikel über Grenzfälle in der Softwareentwicklung habe ich über Textfallen geschrieben und Ihnen einige Vorschläge gegeben, wie Sie diese vermeiden können. In diesem Blogbeitrag möchte ich mich auf Dateien und Datei-E/A-Vorgänge konzentrieren.
Die java.io.File API bietet unter anderem diese 3 Methoden:
#exists()
#isDirectory()
#isFile()
Man könnte denken, dass ein Objekt entweder eine Datei oder ein Verzeichnis ist, wenn es durch einen bestimmten vorhandenen Pfad angezeigt wird – wie in dieser Frage zum Stapelüberlauf. Dies ist jedoch nicht immer wahr.
Es wird in den Javadocs File#isFile() nicht explizit erwähnt, aber Datei **dort bedeutet eigentlich **normale Datei. Daher können spezielle Unix-Dateien wie Geräte, Sockets und Pipes existieren, sie sind jedoch keine Dateien in dieser Definition.
Sehen Sie sich den folgenden Ausschnitt an:
import java.io.File val file = File("/dev/null") println("exists: ${file.exists()}") println("isFile: ${file.isFile()}") println("isDirectory: ${file.isDirectory()}")
Wie Sie in der Live-Demo sehen können, kann eine Datei existieren, die weder eine Datei noch ein Verzeichnis ist.
Symbolische Links sind ebenfalls spezielle Dateien, werden jedoch fast überall in der (alten) java.io-API transparent behandelt. Die einzige Ausnahme ist die Methodenfamilie #getCanonicalPath()/#getCanonicalFile(). Transparenz bedeutet hier, dass alle Vorgänge an das Ziel weitergeleitet werden, so wie sie direkt auf diesem ausgeführt werden. Eine solche Transparenz ist in der Regel nützlich, z.B. Sie können einfach aus einer Datei lesen oder in diese schreiben. Die optionale Linkpfadauflösung ist Ihnen egal. Allerdings kann es auch zu seltsamen Fällen kommen. Beispielsweise kann es eine Datei geben, die gleichzeitig existiert und nicht existiert.
Betrachten wir einen baumelnden symbolischen Link. Sein Ziel existiert nicht, daher geben alle Methoden aus dem vorherigen Abschnitt „false“ zurück. Dennoch ist der Quelldateipfad noch belegt, z.B. Sie können in diesem Pfad keine neue Datei erstellen. Hier ist der Code, der diesen Fall demonstriert:
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()}")
Und eine Live-Demo.
Um in der java.io-API ein möglicherweise nicht vorhandenes Verzeichnis zu erstellen und sicherzustellen, dass es anschließend existiert, können Sie File#mkdir() (oder File#mkdirs(), wenn Sie nicht vorhandene übergeordnete Verzeichnisse erstellen möchten) verwenden na ja) und dann File#isDirectory(). Es ist wichtig, diese Methoden in der angegebenen Reihenfolge anzuwenden. Mal sehen, was passieren kann, wenn die Reihenfolge umgekehrt wird. Um diesen Fall zu veranschaulichen, sind zwei (oder mehr) Threads erforderlich, die dieselben Vorgänge ausführen. Hier verwenden wir blaue und rote Fäden.
(red) isDirectory()? – Nein, muss erstellt werden
(blau) isDirectory()? – Nein, muss erstellt werden
(red) mkdir()? — Erfolg
(blau) mkdir()? – scheitern
Wie Sie sehen, konnte ein blauer Thread kein Verzeichnis erstellen. Es wurde jedoch tatsächlich erstellt, sodass das Ergebnis positiv sein sollte. Hätte isDirectory() am Ende aufgerufen, wäre das Ergebnis immer korrekt gewesen.
Die Anzahl der gleichzeitig von einem bestimmten UNIX-Prozess geöffneten Dateien ist auf den Wert von RLIMIT_NOFILE begrenzt. Unter Android beträgt dieser normalerweise 1024, aber effektiv (mit Ausnahme der vom Framework verwendeten Dateideskriptoren) können Sie noch weniger verwenden (bei Tests mit leerer Aktivität unter Android 8.0.0 standen etwa 970 Dateideskriptoren zur Verwendung zur Verfügung). Was passiert, wenn Sie versuchen, mehr zu öffnen? Nun, die Datei wird nicht geöffnet. Abhängig vom Kontext kann es zu einer Ausnahme mit einem expliziten Grund (Zu viele geöffnete Dateien) oder einer etwas rätselhaften Meldung (z. B. Diese Datei kann nicht als Dateideskriptor geöffnet werden) kommen. es ist wahrscheinlich komprimiert) oder einfach false als Rückgabewert, wenn Sie normalerweise true erwarten. Sehen Sie sich den Code an, der diese Probleme demonstriert:
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()) } }
Beachten Sie, dass der Wert bei Verwendung von #apply() einfach nicht dauerhaft gespeichert wird – Sie erhalten also keine Ausnahme. Es bleibt jedoch zugänglich, bis der App-Prozess, der diese SharedPreferences-Instanz enthält, beendet wird. Denn auch geteilte Präferenzen werden im Speicher gespeichert.
Man könnte meinen, dass Zombies, Ghule und andere ähnliche Kreaturen nur in Fantasy- und Horrorromanen existieren. Aber... in der Informatik gibt es sie wirklich! Solche gebräuchlichen Begriffe beziehen sich auf die Zombie-Prozesse. Tatsächlich können auch Untotendateien problemlos erstellt werden.
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.
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.
Das obige ist der detaillierte Inhalt vonRandfälle, die Sie im Hinterkopf behalten sollten. Teiledateien. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!