"Si un ouvrier veut bien faire son travail, il doit d'abord affûter ses outils." - Confucius, "Les Entretiens de Confucius. Lu Linggong"
Page de garde > La programmation > Cas extrêmes à garder à l’esprit. Fichiers de pièces

Cas extrêmes à garder à l’esprit. Fichiers de pièces

Publié le 2024-08-14
Parcourir:879

Edge Cases to Keep in Mind. Part  Files

Saviez-vous qu'il peut y avoir un fichier qui existe et qui n'existe pas en même temps ? Savez-vous que vous pouvez supprimer un fichier tout en l'utilisant ? Découvrez ces fichiers et d'autres cas extrêmes dans le développement de logiciels.

Dans mon article précédent sur les cas extrêmes dans le développement de logiciels, j'écrivais sur les pièges de texte et je vous ai donné quelques suggestions sur la façon de les éviter. Dans cet article de blog, je voudrais me concentrer sur les fichiers et les opérations d'E/S sur fichiers.

Un fichier qui n'est pas un fichier

L'API java.io.File fournit, entre autres, ces 3 méthodes :

  • #existe()

  • #isDirectory()

  • #isFile()

On peut penser que, s'il est pointé par un chemin donné qui existe, un objet est soit un fichier, soit un répertoire — comme dans cette question sur Stack Overflow. Cependant, ce n'est pas toujours vrai.

Ce n'est pas explicitement mentionné dans les javadocs File#isFile(), mais file **là signifie vraiment **fichier normal. Ainsi, des fichiers Unix spéciaux tels que des périphériques, des sockets et des tuyaux peuvent exister, mais ils ne sont pas des fichiers dans cette définition.

Regardez l'extrait suivant :

import java.io.File

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

Comme vous pouvez le voir sur la démo live, un fichier qui n'est ni un fichier ni un répertoire peut exister.

Exister ou ne pas exister ?

Les liens symboliques sont également des fichiers spéciaux mais ils sont traités de manière transparente presque partout dans l'(ancienne) API java.io. La seule exception est la famille de méthodes #getCanonicalPath()/#getCanonicalFile(). La transparence signifie ici que toutes les opérations sont transmises à la cible, tout comme elles sont effectuées directement sur elle. Une telle transparence est généralement utile, par ex. vous pouvez simplement lire ou écrire dans un fichier. Vous ne vous souciez pas de la résolution facultative du chemin de lien. Cependant, cela peut aussi conduire à des cas étranges. Par exemple, il peut y avoir un fichier qui existe et qui n'existe pas en même temps.

Considérons un lien symbolique pendant. Sa cible n'existe pas, donc toutes les méthodes de la section précédente renverront false. Néanmoins, le chemin du fichier source est toujours occupé, par ex. vous ne pouvez pas créer un nouveau fichier sur ce chemin. Voici le code illustrant ce cas :

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

Et une démo en direct.

L'ordre compte

Dans l'API java.io, pour créer un répertoire éventuellement inexistant et vous assurer qu'il existe par la suite, vous pouvez utiliser File#mkdir() (ou File#mkdirs() si vous souhaitez créer des répertoires parents inexistants comme bien) puis File#isDirectory(). Il est important d’utiliser ces méthodes dans l’ordre mentionné. Voyons ce qui pourrait arriver si l’ordre était inversé. Deux (ou plus) threads effectuant les mêmes opérations sont nécessaires pour démontrer ce cas. Ici, nous utiliserons des fils bleus et rouges.

  1. (rouge) isDirectory() ? — non, il faut créer

  2. (bleu) isDirectory() ? — non, il faut créer

  3. (rouge) mkdir() ? - succès

  4. (bleu) mkdir() ? - échouer

Comme vous pouvez le voir, un fil de discussion bleu n'a pas réussi à créer un répertoire. Cependant, il a bel et bien été créé, le résultat devrait donc être positif. Si isDirectory() avait appelé à la fin, le résultat aurait toujours été correct.

La limitation cachée

Le nombre de fichiers ouverts en même temps par un processus UNIX donné est limité à la valeur de RLIMIT_NOFILE. Sur Android, il s'agit généralement de 1 024, mais en réalité (à l'exclusion des descripteurs de fichiers utilisés par le framework), vous pouvez en utiliser encore moins (lors des tests avec une activité vide sur Android 8.0.0, il y avait environ 970 descripteurs de fichiers disponibles à utiliser). Que se passe-t-il si vous essayez d’en ouvrir davantage ? Eh bien, le fichier ne sera pas ouvert. Selon le contexte, vous pouvez rencontrer une exception avec une raison explicite (Trop de fichiers ouverts), un message un peu énigmatique (par exemple Ce fichier ne peut pas être ouvert en tant que descripteur de fichier ; il est probablement compressé) ou simplement faux comme valeur de retour alors que vous vous attendez normalement à vrai. Consultez le code illustrant ces problèmes :

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

Notez que si vous utilisez #apply(), la valeur ne sera tout simplement pas enregistrée de manière persistante — vous n'obtiendrez donc aucune exception. Cependant, il sera accessible jusqu'à ce que le processus d'application contenant cette instance SharedPreferences soit supprimé. En effet, les préférences partagées sont également enregistrées dans la mémoire.

Les morts-vivants existent vraiment

On pourrait penser que les zombies, les goules et autres créatures similaires n'existent que dans les fictions fantastiques et d'horreur. Mais… ils sont réels en informatique ! Ces termes courants font référence aux processus zombies. En fait, les fichiers de morts-vivants peuvent également être facilement créés.

Dans les systèmes d'exploitation de type Unix, la suppression de fichiers est généralement mise en œuvre par dissociation. Le nom du fichier non lié est supprimé du système de fichiers (en supposant qu'il s'agit du dernier lien physique), mais tous les descripteurs de fichiers déjà ouverts restent valides et utilisables. Vous pouvez toujours lire et écrire dans un tel fichier. Voici l'extrait :

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

Et une démo en direct.

Conclure

Tout d'abord, rappelez-vous que nous ne pouvons pas oublier l'ordre d'appel approprié des méthodes lors de la création de répertoires inexistants. De plus, gardez à l’esprit que le nombre de fichiers ouverts en même temps est limité et que seuls les fichiers explicitement ouverts par vous sont pris en compte. Et le dernier, mais non le moindre, une astuce avec la suppression de fichiers avant la dernière utilisation peut vous donner un peu plus de flexibilité.

Publié à l'origine sur www.thedroidsonroids.com le 27 septembre 2017.

Déclaration de sortie Cet article est reproduit sur : https://dev.to/koral/edge-cases-to-keep-in-mind-part-2-files-53?1 En cas de violation, veuillez contacter [email protected] pour le supprimer
Dernier tutoriel Plus>

Clause de non-responsabilité: Toutes les ressources fournies proviennent en partie d'Internet. En cas de violation de vos droits d'auteur ou d'autres droits et intérêts, veuillez expliquer les raisons détaillées et fournir une preuve du droit d'auteur ou des droits et intérêts, puis l'envoyer à l'adresse e-mail : [email protected]. Nous nous en occuperons pour vous dans les plus brefs délais.

Copyright© 2022 湘ICP备2022001581号-3