Jerarquies de fitxers

Com ja hem explicat al principi per organitzar les dades que emmagatzemes en un disc pots organitzar els fitxers en directoris: un directori principal pot incloure altres directoris o subdirectoris, donant lloc a una jerarquia de fitxers.

Per exemple, si observes la jerarquia del sistema de fitxers a Linux, el directori arrel / inclou tots els altres fitxers i directoris, independentment del seu emmagatzematge en diferents dispositius físics. .

La classe Path proporciona funcions per treballar amb un Path quan aquest representa un directori.

Fitxers i directoris

Un fitxer és una secció de dades anomenada en un suport d'emmagatzematge. És la unitat fonamental d'interacció de dades en els sistemes operatius.

Un directori és una entitat d'un sistema de fitxers que ajuda a organitzar fitxers. Atès que un sistema de fitxers típic conté un nombre massiu de fitxers, els directoris ajuden a l'organització agrupant-los.

També podeu interactuar amb ells com a Fileentitats.

Kotlin ofereix diversos mètodes per treballar amb directoris i fitxers. Per exemple, podeu utilitzar la java.iobiblioteca. Anem a repassar alguns dels mètodes més populars:

File.isDirectorycomprova si Fileés un directori.

File.isFileverifica si Fileés realment un fitxer.

File.exists()comprova l'existència del fitxer.

File.resolve(String)retorna un fitxer amb el nom Stringen un directori.

File.resolve(File)retorna un fitxer Filed'un directori.

File.createNewFile()crea un fitxer nou.

File.mkdir()crea un nou directori.

Aquí teniu un exemple per il·lustrar:

val outDir = File("outDir") println(outDir.exists()) // false outDir.mkdir() println(outDir.exists()) // true println(outDir.isDirectory) // true

val file = outDir.resolve("newFile.txt") // outDir/newFile.txt println(file.exists()) // false file.createNewFile()
println(file.exists()) // true println(file.isFile) // true

Mètodes per iterar a través de jerarquies de fitxers

Podeu navegar per la jerarquia de fitxers mitjançant els java.io.Filemètodes:

  • File.getParentFile()retorna una instància java.io.Fileque representa el directori principal d'un fitxer, o nullsi el fitxer no té cap pare (indicant que és l'arrel).

File.getParent()retorna una representació de cadena del directori principal d'un fitxer, o nullsi el fitxer no té cap pare.

File.listFiles()retorna una matriu de fitxers ubicats en un directori determinat, o nullsi la instància no és un directori.

File.list()retorna una matriu de fitxers i directoris al directori especificat per aquest nom de ruta abstracte.

El kotlin.ioproporciona mètodes especials que us permeten passar per tota la jerarquia de fitxers. Examinem tres mètodes fonamentals:

File.walk(direction): FileTreeWalkproporciona els fitxers i directoris d'aquest directori que podeu visitar; heu d'especificar la manera com repetireu (amunt o avall de l'estructura de la jerarquia).

File.walkBottomUp(): FileTreeWalkofereix els directoris i fitxers d'aquest directori que podeu visitar. Utilitza una cerca en profunditat, visitant directoris després d'haver visitat tots els seus fitxers.

File.walkTopDown(): FileTreeWalkofereix els directoris i fitxers d'aquest directori perquè els visiteu. S'utilitza una cerca en profunditat i es visiten els directoris abans que tots els seus fitxers.

La FileTreeWalkclasse està dissenyada per iterar a través d'un sistema de fitxers determinat. Us permet recórrer tots els fitxers d'un directori. El mètode iterador retorna un iterador que travessa els fitxers. Podeu recórrer aquesta estructura o convertir-la en una llista amb la funció toList().

A la secció següent, mostrarem aquests mètodes utilitzant un exemple simple de jerarquia de fitxers.

Un exemple de jerarquia

Considereu una jerarquia de fitxers amb el directori arrel anomenat Files. Conté dos subdirectoris: CompletedProjectsi Music. També contenen subdirectoris: el HelloWorlddirectori conté dos fitxers relacionats amb el projecte, el JCalculatordirectori només conté un fitxer i el Soundtracksdirectori està buit.

Aquí teniu un diagrama que representa aquesta jerarquia:

En els segments següents, utilitzarem aquesta jerarquia de fitxers com a exemple per il·lustrar com maniobrar el sistema de fitxers.

Path

Quan crees un objecte Path aquest por representar un fitxer o un directori: és la teva responsabilitat saber que representa i utilitzar les funcions corresponents.

Desde el punt de vista del sistema operatiu només hi ha fitxers: un directori és un fitxer "especial" que ell gestiona a la seva manera.

Per aquest motiu desde el punt de vista del sistema operatiu el nom deFile està molt bé, i a principis dels anys 90 quan es va dissenyar Java era un nom adequat.

Però desde el punt de vista d'un programa són dues coses molt diferents, i el que tenen en comú es que comparteixen la mateixa forma d'accés: un "path".

Funcions d'extensió

La llibreria estàndar de Kotlin proporciona algunes funcions que extenen la classe `java.nio.file.Path.

Pots trobar la llista completa a la documentació oficial.

Per exemple, podem obtenir una llista del contingut que està directament indexat per un path que representa un directori (pot ser un fitxer o un directori):

Path("~/Dowloads").listDirectoryEntries()`

Si necessites copiar un fitxer a un altre path pots utilitza la funció copyTo() tal com es mostra a continuació:

Path("source.txt").copyTo(Path("destination.txt")

Si vols moure un fitxer o un directori, pots utilitzar la funció copyTo():

Path("source.txt").moveTo(Path("destination.txt")

Si mirem més a fons la implemetació de moveTo:

public inline fun Path.moveTo(target: Path, overwrite: Boolean = false): Path {
    val options = if (overwrite) arrayOf<CopyOption>(StandardCopyOption.REPLACE_EXISTING) else emptyArray()
    return Files.move(this, target, *options)
}

Pots veure que la funcionalitat de moure ja està implementada en la classe Files, però utilitzant parametres per defecte, moveTo és molt més fàcil d'utilitzar en la majoria dels casos.

Per exemple, si volem reemplaçar un fitxer si aquest existeix, és més fàcil escriure aquest codi:

path.moveTo(dst, overwrite=true)

Que no pas aquest:

Files.move(dst, arrayOf<CopyOption>(StandardCopyOptions.REPLACE_EXISTING)

L'operador div(), que s'invoca amb el simbol / et permet especificar un path de manera molt semblant a como ho faries amb una URI.

Amb aquest operador és molt fàcil de navegar pel sistema de fitxers tal com es mostar en l'exemple que tens a continuació:

fun softDelete(path: String) {
    val fileToDelete = Path(path)

    val destinationDirectory = fileToDelete.parent / ".deleted"
    if (destinationDirectory.notExists()) {
        destinationDirectory.createDirectory()
    }

    fileToDelete.moveTo(destinationDirectory / fileToDelete.name)
}

Aquesta funció, enlloc de borrar un fitxer, el mou a un directori que té el nom .deleted.

Gestionar fitxers de manera recursiva

El paquet kotlin.io.path[https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.io.path/#package-kotliniopath] inclous funcions que et permeten gestionar fitxers de manera recursiva.

Alguns exemples són:

  • walk(): per recorrer tot el contingut accesible des d'aquest path.
  • copyToRecursively(): per copiar tot el contingut accesible des d'aquest path a una altre path.

A continuació tens un exemple:

fun main() {
    val sourcePath = Path("sourceDir/file.txt")
    val destinationPath = Path("destDir/file.txt")

    // List all files in the 'sourceDir' directory
    val filesInDir = Path("sourceDir").listDirectoryEntries()
    println("Files in sourceDir: $filesInDir")

    // Copy the file from 'sourceDir' to 'destDir'
    sourcePath.copyTo(destinationPath, overwrite = true)
    println("File copied from sourceDir to destDir.")

    // Move the file within 'destDir' to a new file
    val movedPath = destinationPath.moveTo(Paths.get("destDir/movedFile.txt"), overwrite = true)
    println("File moved to: $movedPath")

    // Write text to the moved file
    movedPath.writeText("Hello, world!")
    println("Text written to file.")

    // Read the text from the file
    val text = movedPath.readText()
    println("Text read from file: $text")

    // Read the lines from the file as a collection
    val lines = movedPath.readLines()
    println("Lines read from file: $lines")

    // Walk all files and directories within 'destDir'
    val walkedPaths = Path("destDir").walk().toList()
    println("Walked paths: $walkedPaths")

    // Copy 'sourceDir' to 'copyDir' recursively
    Path("sourceDir").copyToRecursively(
        target = Paths.get("copyDir"),
        overwrite = true,
        followLinks = true,
    )
    println("Directory copied recursively.")
}

Files in sourceDir: File copied from sourceDir to destDir. File moved to: destDir/movedFile.txt Text written to file. Text read from file: Hello, world! Lines read from file: [Hello, world!] Walked paths: [destDir, destDir/movedFile.txt] Directory copied recursively.

Funciona amb jerarquies de fitxers

Obtenció del contingut del directori i el pare del directori /fitxer amb Fitxer i Ruta

La Files.listFiles()funció imprimeix el contingut (fitxers i directoris) d'un directori escollit també podeu utilitzar Path.listDirectoryEntries().

// with File val helloWorld = File("/Files/CompletedProjects/HelloWorld") val helloWorldFilesNames = helloWorld.listFiles().map{it.name} // [Doc.pdf, Reviews.txt]

val reviews = File("/Files/CompletedProjects/HelloWorld/Reviews.txt") val reviewsFiles = reviews.listFiles() // null

val soundtracks = File("/Files/Music/SoundTracks") val soundtracksFiles = soundtracks.listFiles() // []

// with Path val helloWorld = Path("/Files/CompletedProjects/HelloWorld") val helloWorldFilesNames = helloWorld.listDirectoryEntries().map { it.name } // [Doc.pdf, Reviews.txt]

val reviews = Path("/Files/CompletedProjects/HelloWorld/Reviews.txt") val reviewsFiles = if (reviews.isDirectory()) reviews.listDirectoryEntries() else emptyList() // []

val soundtracks = Path("/Files/Music/SoundTracks") val soundtracksFiles = if (soundtracks.isDirectory()) soundtracks.listDirectoryEntries() else emptyList() // []

reviewsFilesés nullperquè reviewsno és un directori en absolut i no pot incloure altres fitxers o subdirectoris. D'altra banda, soundtracksés un directori sense fitxers, de manera que el resultat és una matriu buida.

La propietat File.parentor Path.parentus dóna el nom del pare d'un fitxer o directori.

La propietat File.parentFileor Path.parentFileproporciona el pare d'un fitxer o directori com a File.

// with File val files = File("/Files") print(files.parent) // the result is "/" print(files.parentFile.name) // the result is ""

val reviews = File("/Files/CompletedProjects/HelloWorld/Reviews.txt") print(reviews.parent) // the result is "/Files/CompletedProjects/HelloWorld" print(reviews.parentFile.name) // the result is "HelloWorld"

val root = File("/") print(root.parent) // the result is "null" print(root.parentFile.name) // throws java.lang.NullPointerException

// with Path val files = Path("/Files") println(files.parent) // the result is "/" println(files.parent.fileName) // the result is ""

val reviews = Path("/Files/CompletedProjects/HelloWorld/Reviews.txt") println(reviews.parent) // the result is "/Files/CompletedProjects/HelloWorld" println(reviews.parent.fileName) // the result is "HelloWorld"

val root = Path("/") println(root.parent) // the result is "null" println(root.parent.fileName) // This line won't be executed because root.parent is null

Si un directori és l'arrel d'una jerarquia de fitxers, obtindreu null. Aneu amb compte de no activar excepcions!

Iteració en ambdues direccions

Abans hem esmentat el File.walk(direction)mètode per passar per la jerarquia de fitxers . L' directionatribut descriu la manera com travessem la nostra jerarquia de fitxers; pot ser FileWalkDirection.BOTTOM_UPo bé FileWalkDirection.TOP_DOWN. A més, podeu utilitzar Path.walk()i utilitzar la seqüència o l'opció iterable per realitzar la direcció.

// with File val files: File = File("/Files") println("TOP_DOWN: ") files.walk(FileWalkDirection.TOP_DOWN).forEach

println("BOTTOM_UP: ") files.walk(FileWalkDirection.BOTTOM_UP).forEach

// with Path val dir: Path = Path("/Files") println("TOP_DOWN: ") dir.walk().forEach

println("BOTTOM_UP: ") dir.walk().asIterable().reversed().forEach

La sortida d'aquest programa serà:

TOP_DOWN: /Files /Files/Music /Files/Music/SoundTracks /Files/CompletedProjects /Files/CompletedProjects/HelloWorld /Files/CompletedProjects/HelloWorld/Doc.pdf /Files/CompletedProjects/HelloWorld/Reviews.txt /Files/CompletedProjects/JCalculator /Files/CompletedProjects/JCalculator/Doc.pdf BOTTOM_UP: /Files/Music/SoundTracks /Files/Music /Files/CompletedProjects/HelloWorld/Doc.pdf /Files/CompletedProjects/HelloWorld/Reviews.txt /Files/CompletedProjects/HelloWorld /Files/CompletedProjects/JCalculator/Doc.pdf /Files/CompletedProjects/JCalculator /Files/CompletedProjects /Files

Podeu arribar al mateix resultat mitjançant aquests mètodes:

File.walkBottomUp()(el mateix que File.walk(FileWalkDirection.BOTTOM_UP));

File.walkTopDown()(el mateix que File.walk(FileWalkDirection.TOP_DOWN)).

Per tant, aquests tres mètodes ens permeten recórrer tota l'estructura de fitxers de forma recursiva.

Treball amb jerarquies

Suposant que tenim una instància de java.io.Filenamed completedProjects, que fa referència al CompletedProjectsdirectori. Ara obtindrem els dos subdirectoris que contenen les dades del projecte.

// with File val completedProjects: File = File("/Files/CompletedProjects") val projects = completedProjects.walk() projects.maxDepth(1) // HelloWorld and JCalculator

// with Path val completedProjects: Path = Path("/Files/CompletedProjects") val projects = Files.walk(completedProjects, 1)

No es promet l'ordre específic dels fitxers de la matriu. Per trobar el HelloWorldprojecte, recorrerem l'arbre de fitxers:

val helloWorldProject: File = projects.first

També podeu obtenir el HelloWorlddirectori utilitzant el File.listFiles()mètode:

val helloWorldProject: File = completedProjects.listFiles().first

Només suposem que no és nullper simplificar el nostre codi amb finalitats educatives. Però sempre és millor comprovar inicialment, ja que la jerarquia real pot canviar.

Ara, intentem navegar fins al Soundtracksdirectori a partir del Reviews.txtfitxer:

val reviews = File("/Files/CompletedProjects/HelloWorld/Reviews.txt") var parent = reviews.parentFile while (parent.name != "Files"){ parent = parent.parentFile }

val soundTracks: File = parent.walkTopDown().first

Còpia de fitxers

Si necessiteu copiar un fitxer, utilitzeu la copyTo()funció:

// with File val fileIn = File("newFile.txt") val fileOut = File("copyNewFile") fileIn.copyTo(fileOut)

// with Path val fileIn = Path("newFile.txt") val fileOut = Path("copyNewFile.txt") fileIn.copyTo(fileOut)

Tingueu en compte que si necessiteu sobreescriure el fitxer, haureu d'incloure un overwriteparàmetre:

// With File val fileIn = File("newFile.txt") val fileOut = File("copyNewFile") fileIn.copyTo(fileOut, overwrite = true)

// With Path val fileIn = Path("newFile.txt") val fileOut = Path("copyNewFile.txt") fileIn.copyTo(fileOut, overwrite = true)

Per copiar tot un directori de forma recursiva, podeu utilitzar la copyRecursively()funció:

// with File val fileIn = File("outDir") val fileOut = File("newDir") fileIn.copyRecursively(fileOut)

// with Path val pathIn = Path("outDir") val pathOut = Path("newDir") pathIn.copyToRecursively(pathOut)

Recordeu que si necessiteu sobreescriure carpetes i fitxers, també heu d'introduir un overwriteparàmetre o seguir els enllaços (només a Path) amb followLinks l'opció:

// with File val fileIn = File("outDir") val fileOut = File("newDir") fileIn.copyRecursively(fileOut, overwrite = true)

// with Path val pathIn = Path("outDir") val pathOut = Path("newDir") pathIn.copyToRecursively(pathOut, overwrite = true, followLinks = true)

Activitats

1.- Què passa quan fas servir src.copyTo(dst, overwrite = true)?

  1. Esborra dst i després copia src a la mateixa ubicació
  2. Mou src a dst sense sobreescriure
  3. Copia src a dst sense sobreescriure els fitxers existents
  4. Copia src a dst i sobreescriu els fitxers existents

  1. Copia src a dst i sobreescriu els fitxers existents

2.- Modifica el codi per mostrar el directori principal del fitxer reviews.

func main(args: Array<String>) {
   val reviews = File("/home/david/reviews.txt")
}

func main(args: Array<String>) {
   val reviews = File("/home/david/reviews.txt")
   print(reviews.parent)
}

3.- Quin mètode pots utilitzar per recórrer una jerarquia de fitxers de baix a dalt?

  1. File.walk(FileWalkDirection.TOP_DOWN)
  2. File.walkBottomUp()
  3. File.walk(FileWalkDirection.BOTTOM_UP)
  4. File.walkTopDown()

  1. `File.walkBottomUp()

4.- Reordena el codi per copiar newFile.txt a copyNewFile.txt; sobreescriu si cal.

func main(args: Array<String>) {
   val fileIn = File("input.txt")
   val out = File("output.txt")
   in.copyTo(out, overwrite = true)
}

5.- Què espereu helloWorldFilesNamesque sigui la sortida al fragment de codi següent?

val helloWorld = File("/Files/CompletedProjects/HelloWorld") val helloWorldFilesNames = helloWorld.listFiles().map

["Doc.pdf", "Revisions.txt"] nul [] X ["HelloWorld"]