¿Sabías que puede haber un archivo que existe y no existe al mismo tiempo? ¿Sabe que puede eliminar un archivo y seguir utilizándolo? Descubra estos y otros archivos de casos extremos en el desarrollo de software.
En mi artículo anterior sobre casos extremos en el desarrollo de software, estaba escribiendo sobre trampas de texto y les di algunas sugerencias sobre cómo evitarlas. En esta publicación de blog, me gustaría centrarme en los archivos y las operaciones de E/S de archivos.
La API java.io.File proporciona, entre otros, estos 3 métodos:
#existe()
#isDirectory()
#isFile()
Uno puede pensar que, si lo señala una ruta determinada que existe, un objeto es un archivo o un directorio, como en esta pregunta sobre Stack Overflow. Sin embargo, esto no siempre es cierto.
No se menciona explícitamente en los javadocs File#isFile(), pero file **realmente significa **archivo normal. Por lo tanto, pueden existir archivos especiales de Unix como dispositivos, enchufes y tuberías, pero no son archivos en esa definición.
Mira el siguiente fragmento:
import java.io.File val file = File("/dev/null") println("exists: ${file.exists()}") println("isFile: ${file.isFile()}") println("isDirectory: ${file.isDirectory()}")
Como puede ver en la demostración en vivo, puede existir un archivo que no es ni un archivo ni un directorio.
Los enlaces simbólicos también son archivos especiales, pero se tratan de forma transparente en casi todas partes de la (antigua) API java.io. La única excepción es la familia de métodos #getCanonicalPath()/#getCanonicalFile(). La transparencia aquí significa que todas las operaciones se reenvían al objetivo, tal como se realizan directamente en él. Esta transparencia suele ser útil, p. simplemente puede leer o escribir en algún archivo. No le importa la resolución de la ruta del enlace opcional. Sin embargo, también puede dar lugar a algunos casos extraños. Por ejemplo, puede haber un Archivo que existe y no existe al mismo tiempo.
Consideremos un vínculo simbólico colgante. Su objetivo no existe, por lo que todos los métodos de la sección anterior devolverán falso. No obstante, la ruta del archivo fuente todavía está ocupada, p. no puede crear un nuevo archivo en esa ruta. Aquí está el código que demuestra este caso:
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()}")
Y una demostración en vivo.
En la API java.io, para crear un directorio posiblemente inexistente y asegurarse de que exista después, puede usar File#mkdir() (o File#mkdirs() si desea crear directorios principales inexistentes como bueno) y luego Archivo#isDirectory(). Es importante utilizar estos métodos en el orden mencionado. Veamos qué puede pasar si se invierte el orden. Se necesitan dos (o más) subprocesos que realicen las mismas operaciones para demostrar este caso. Aquí usaremos hilos azules y rojos.
(rojo) esDirectorio()? — no, es necesario crear
(azul) esDirectorio()? — no, es necesario crear
(rojo) mkdir()? - éxito
(azul) mkdir()? - fallar
Como puede ver, un hilo azul no pudo crear un directorio. Sin embargo, en realidad fue creado, por lo que el resultado debería ser positivo. Si isDirectory() hubiera llamado al final, el resultado siempre habría sido correcto.
La cantidad de archivos abiertos al mismo tiempo por un proceso UNIX determinado está limitada al valor de RLIMIT_NOFILE. En Android, esto suele ser 1024, pero efectivamente (excluyendo los descriptores de archivos utilizados por el marco) puedes usar incluso menos (durante las pruebas con Actividad vacía en Android 8.0.0, había aproximadamente 970 descriptores de archivos disponibles para usar). ¿Qué pasa si intentas abrir más? Bueno, el archivo no se abrirá. Dependiendo del contexto, puede encontrar una excepción con un motivo explícito (Demasiados archivos abiertos), un mensaje un poco enigmático (por ejemplo, Este archivo no se puede abrir como un descriptor de archivo; probablemente esté comprimido) o simplemente falso como valor de retorno cuando normalmente se espera verdadero. Vea el código que demuestra estos problemas:
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()) } }
Tenga en cuenta que, si usa #apply(), el valor simplemente no se guardará de forma persistente, por lo que no obtendrá ninguna excepción. Sin embargo, será accesible hasta que se elimine el proceso de la aplicación que contiene esa instancia de SharedPreferences. Esto se debe a que las preferencias compartidas también se guardan en la memoria.
Uno podría pensar que los zombis, demonios y otras criaturas similares existen únicamente en la ficción de fantasía y terror. Pero… ¡son reales en informática! Estos términos comunes se refieren a los procesos zombies. De hecho, los archivos de muertos vivientes también se pueden crear fácilmente.
En sistemas operativos tipo Unix, la eliminación de archivos generalmente se implementa mediante la desvinculación. El nombre del archivo desvinculado se elimina del sistema de archivos (suponiendo que sea el último vínculo físico), pero los descriptores de archivos ya abiertos siguen siendo válidos y utilizables. Aún puede leer y escribir en dicho archivo. Aquí está el fragmento:
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()}") }
Y una demostración en vivo.
En primer lugar, recuerde que no podemos olvidarnos del orden de llamada del método adecuado al crear directorios inexistentes. Además, tenga en cuenta que la cantidad de archivos abiertos al mismo tiempo es limitada y no solo se cuentan los archivos abiertos explícitamente por usted. Y por último, pero no menos importante, un truco para eliminar archivos antes del último uso puede brindarte un poco más de flexibilidad.
Publicado originalmente en www.thedroidsonroids.com el 27 de septiembre de 2017.
Descargo de responsabilidad: Todos los recursos proporcionados provienen en parte de Internet. Si existe alguna infracción de sus derechos de autor u otros derechos e intereses, explique los motivos detallados y proporcione pruebas de los derechos de autor o derechos e intereses y luego envíelos al correo electrónico: [email protected]. Lo manejaremos por usted lo antes posible.
Copyright© 2022 湘ICP备2022001581号-3