Introduzione alla Java NIO2 File API

1. Panoramica

In questo articolo, ci concentreremo sulle nuove API I / O nella piattaforma Java - NIO2 - per la manipolazione di file di base .

Le API di file in NIO2 costituiscono una delle principali nuove aree funzionali della piattaforma Java fornita con Java 7, in particolare un sottoinsieme della nuova API del file system insieme alle API Path.

2. Configurazione

Configurare il tuo progetto per utilizzare le API di file è solo questione di eseguire questa importazione:

import java.nio.file.*;

Poiché gli esempi di codice in questo articolo verranno probabilmente eseguiti in ambienti diversi, otteniamo un handle sulla directory home dell'utente, che sarà valido su tutti i sistemi operativi:

private static String HOME = System.getProperty("user.home");

La classe Files è uno dei punti di ingresso principali del pacchetto java.nio.file . Questa classe offre un ricco set di API per la lettura, la scrittura e la manipolazione di file e directory. I metodi della classe Files funzionano su istanze di oggetti Path .

3. Controllo di un file o di una directory

Possiamo avere un'istanza Path che rappresenta un file o una directory sul file system. Se il file o la directory a cui punta esiste o meno, è accessibile o meno può essere confermato da un'operazione sul file.

Per motivi di semplicità, ogni volta che usiamo il termine file , faremo riferimento sia a file che a directory se non diversamente specificato.

Per verificare se esiste un file, si usa il esiste API:

@Test public void givenExistentPath_whenConfirmsFileExists_thenCorrect() { Path p = Paths.get(HOME); assertTrue(Files.exists(p)); }

Per verificare che un file non esista, utilizziamo l' API notExists :

@Test public void givenNonexistentPath_whenConfirmsFileNotExists_thenCorrect() { Path p = Paths.get(HOME + "/inexistent_file.txt"); assertTrue(Files.notExists(p)); }

Possiamo anche verificare se un file è un file normale come myfile.txt o è solo una directory, utilizziamo l' API isRegularFile :

@Test public void givenDirPath_whenConfirmsNotRegularFile_thenCorrect() { Path p = Paths.get(HOME); assertFalse(Files.isRegularFile(p)); }

Esistono anche metodi statici per verificare le autorizzazioni dei file. Per verificare se un file è leggibile, utilizziamo l' API isReadable :

@Test public void givenExistentDirPath_whenConfirmsReadable_thenCorrect() { Path p = Paths.get(HOME); assertTrue(Files.isReadable(p)); }

Per verificare se è scrivibile, utilizziamo l' API isWritable :

@Test public void givenExistentDirPath_whenConfirmsWritable_thenCorrect() { Path p = Paths.get(HOME); assertTrue(Files.isWritable(p)); }

Allo stesso modo, per verificare se è eseguibile:

@Test public void givenExistentDirPath_whenConfirmsExecutable_thenCorrect() { Path p = Paths.get(HOME); assertTrue(Files.isExecutable(p)); }

Quando abbiamo due percorsi, possiamo verificare se entrambi puntano allo stesso file nel file system sottostante:

@Test public void givenSameFilePaths_whenConfirmsIsSame_thenCorrect() { Path p1 = Paths.get(HOME); Path p2 = Paths.get(HOME); assertTrue(Files.isSameFile(p1, p2)); }

4. Creazione di file

L'API del file system fornisce operazioni a riga singola per la creazione di file. Per creare un file regolare, utilizziamo l' API createFile e gli passiamo un oggetto Path che rappresenta il file che vogliamo creare.

Tutti gli elementi del nome nel percorso devono esistere, a parte il nome del file, altrimenti avremo una IOException:

@Test public void givenFilePath_whenCreatesNewFile_thenCorrect() { String fileName = "myfile_" + UUID.randomUUID().toString() + ".txt"; Path p = Paths.get(HOME + "/" + fileName); assertFalse(Files.exists(p)); Files.createFile(p); assertTrue(Files.exists(p)); }

Nel test precedente, quando controlliamo per la prima volta il percorso, è inesistente, quindi dopo l' operazione createFile , risulta essere esistente.

Per creare una directory, utilizziamo l' API createDirectory :

@Test public void givenDirPath_whenCreatesNewDir_thenCorrect() { String dirName = "myDir_" + UUID.randomUUID().toString(); Path p = Paths.get(HOME + "/" + dirName); assertFalse(Files.exists(p)); Files.createDirectory(p); assertTrue(Files.exists(p)); assertFalse(Files.isRegularFile(p)); assertTrue(Files.isDirectory(p)); }

Questa operazione richiede che tutti gli elementi del nome nel percorso esistano, in caso contrario, otteniamo anche una IOException :

@Test(expected = NoSuchFileException.class) public void givenDirPath_whenFailsToCreateRecursively_thenCorrect() { String dirName = "myDir_" + UUID.randomUUID().toString() + "/subdir"; Path p = Paths.get(HOME + "/" + dirName); assertFalse(Files.exists(p)); Files.createDirectory(p); }

Tuttavia, se desideriamo creare una gerarchia di directory con una singola chiamata, utilizziamo il metodo createDirectories . A differenza dell'operazione precedente, quando incontra elementi di nome mancanti nel percorso, non genera un'IOException , ma li crea in modo ricorsivo fino all'ultimo elemento:

@Test public void givenDirPath_whenCreatesRecursively_thenCorrect() { Path dir = Paths.get( HOME + "/myDir_" + UUID.randomUUID().toString()); Path subdir = dir.resolve("subdir"); assertFalse(Files.exists(dir)); assertFalse(Files.exists(subdir)); Files.createDirectories(subdir); assertTrue(Files.exists(dir)); assertTrue(Files.exists(subdir)); }

5. Creazione di file temporanei

Molte applicazioni creano una traccia di file temporanei nel file system durante l'esecuzione. Di conseguenza, la maggior parte dei file system dispone di una directory dedicata per memorizzare i file temporanei generati da tali applicazioni.

La nuova API del file system fornisce operazioni specifiche per questo scopo. L' API createTempFile esegue questa operazione. Richiede un oggetto percorso, un prefisso file e un suffisso file:

@Test public void givenFilePath_whenCreatesTempFile_thenCorrect() { String prefix = "log_"; String suffix = ".txt"; Path p = Paths.get(HOME + "/"); Files.createTempFile(p, prefix, suffix); assertTrue(Files.exists(p)); }

Questi parametri sono sufficienti per i requisiti che richiedono questa operazione. Tuttavia, se è necessario specificare attributi specifici del file, esiste un quarto parametro di argomenti della variabile.

Il test precedente crea un file temporaneo nella directory HOME , in attesa e aggiungendo rispettivamente le stringhe di prefisso e suffisso fornite. Finiremo con un nome di file come log_8821081429012075286.txt . La lunga stringa numerica viene generata dal sistema.

Tuttavia, se non forniamo un prefisso e un suffisso, il nome del file includerà solo la lunga stringa numerica e un'estensione .tmp predefinita :

@Test public void givenPath_whenCreatesTempFileWithDefaults_thenCorrect() { Path p = Paths.get(HOME + "/"); Files.createTempFile(p, null, null); assertTrue(Files.exists(p)); }

L'operazione precedente crea un file con un nome come 8600179353689423985.tmp .

Infine, se non forniamo né il percorso, né il prefisso né il suffisso, l'operazione utilizzerà i valori predefiniti in tutto. La posizione predefinita del file creato sarà la directory dei file temporanei fornita dal file system:

@Test public void givenNoFilePath_whenCreatesTempFileInTempDir_thenCorrect() { Path p = Files.createTempFile(null, null); assertTrue(Files.exists(p)); }

Su Windows, verrà impostato per impostazione predefinita su qualcosa come C: \ Users \ user \ AppData \ Local \ Temp \ 6100927974988978748.tmp .

Tutte le operazioni precedenti possono essere adattate per creare directory piuttosto che file regolari utilizzando createTempDirectory invece di createTempFile .

6. Eliminazione di un file

Per eliminare un file, utilizziamo l' API di eliminazione . Per motivi di chiarezza, il seguente test assicura prima che il file non esista già, quindi lo crea e conferma che ora esiste e infine lo elimina e conferma che non è più esistente:

@Test public void givenPath_whenDeletes_thenCorrect() { Path p = Paths.get(HOME + "/fileToDelete.txt"); assertFalse(Files.exists(p)); Files.createFile(p); assertTrue(Files.exists(p)); Files.delete(p); assertFalse(Files.exists(p)); }

Tuttavia, se un file non è presente nel file system, l'operazione di eliminazione avrà esito negativo con una IOException :

@Test(expected = NoSuchFileException.class) public void givenInexistentFile_whenDeleteFails_thenCorrect() { Path p = Paths.get(HOME + "/inexistentFile.txt"); assertFalse(Files.exists(p)); Files.delete(p); }

Possiamo evitare questo scenario utilizzando deleteIfExists che fallisce silenziosamente nel caso in cui il file non esiste. Questo è importante quando più thread stanno eseguendo questa operazione e non vogliamo un messaggio di errore semplicemente perché un thread ha eseguito l'operazione prima del thread corrente che non è riuscito:

@Test public void givenInexistentFile_whenDeleteIfExistsWorks_thenCorrect() { Path p = Paths.get(HOME + "/inexistentFile.txt"); assertFalse(Files.exists(p)); Files.deleteIfExists(p); }

Quando si ha a che fare con directory e file non regolari, è necessario ricordare che l'operazione di eliminazione non funziona in modo ricorsivo per impostazione predefinita. Quindi, se una directory non è vuota, fallirà con una IOException :

@Test(expected = DirectoryNotEmptyException.class) public void givenPath_whenFailsToDeleteNonEmptyDir_thenCorrect() { Path dir = Paths.get( HOME + "/emptyDir" + UUID.randomUUID().toString()); Files.createDirectory(dir); assertTrue(Files.exists(dir)); Path file = dir.resolve("file.txt"); Files.createFile(file); Files.delete(dir); assertTrue(Files.exists(dir)); }

7. Copia dei file

Puoi copiare un file o una directory utilizzando l' API di copia :

@Test public void givenFilePath_whenCopiesToNewLocation_thenCorrect() { Path dir1 = Paths.get( HOME + "/firstdir_" + UUID.randomUUID().toString()); Path dir2 = Paths.get( HOME + "/otherdir_" + UUID.randomUUID().toString()); Files.createDirectory(dir1); Files.createDirectory(dir2); Path file1 = dir1.resolve("filetocopy.txt"); Path file2 = dir2.resolve("filetocopy.txt"); Files.createFile(file1); assertTrue(Files.exists(file1)); assertFalse(Files.exists(file2)); Files.copy(file1, file2); assertTrue(Files.exists(file2)); }

The copy fails if the target file exists unless the REPLACE_EXISTING option is specified:

@Test(expected = FileAlreadyExistsException.class) public void givenPath_whenCopyFailsDueToExistingFile_thenCorrect() { Path dir1 = Paths.get( HOME + "/firstdir_" + UUID.randomUUID().toString()); Path dir2 = Paths.get( HOME + "/otherdir_" + UUID.randomUUID().toString()); Files.createDirectory(dir1); Files.createDirectory(dir2); Path file1 = dir1.resolve("filetocopy.txt"); Path file2 = dir2.resolve("filetocopy.txt"); Files.createFile(file1); Files.createFile(file2); assertTrue(Files.exists(file1)); assertTrue(Files.exists(file2)); Files.copy(file1, file2); Files.copy(file1, file2, StandardCopyOption.REPLACE_EXISTING); }

However, when copying directories, the contents are not copied recursively. This means that if /baeldung contains /articles.db and /authors.db files, copying /baeldung to a new location will create an empty directory.

8. Moving Files

You can move a file or directory by using the move API. It is in most ways similar to the copy operation. If the copy operation is analogous to a copy and paste operation in GUI based systems, then move is analogous to a cut and paste operation:

@Test public void givenFilePath_whenMovesToNewLocation_thenCorrect() { Path dir1 = Paths.get( HOME + "/firstdir_" + UUID.randomUUID().toString()); Path dir2 = Paths.get( HOME + "/otherdir_" + UUID.randomUUID().toString()); Files.createDirectory(dir1); Files.createDirectory(dir2); Path file1 = dir1.resolve("filetocopy.txt"); Path file2 = dir2.resolve("filetocopy.txt"); Files.createFile(file1); assertTrue(Files.exists(file1)); assertFalse(Files.exists(file2)); Files.move(file1, file2); assertTrue(Files.exists(file2)); assertFalse(Files.exists(file1)); }

L' operazione di spostamento non riesce se il file di destinazione esiste a meno che l' opzione REPLACE_EXISTING non sia specificata proprio come abbiamo fatto con l' operazione di copia :

@Test(expected = FileAlreadyExistsException.class) public void givenFilePath_whenMoveFailsDueToExistingFile_thenCorrect() { Path dir1 = Paths.get( HOME + "/firstdir_" + UUID.randomUUID().toString()); Path dir2 = Paths.get( HOME + "/otherdir_" + UUID.randomUUID().toString()); Files.createDirectory(dir1); Files.createDirectory(dir2); Path file1 = dir1.resolve("filetocopy.txt"); Path file2 = dir2.resolve("filetocopy.txt"); Files.createFile(file1); Files.createFile(file2); assertTrue(Files.exists(file1)); assertTrue(Files.exists(file2)); Files.move(file1, file2); Files.move(file1, file2, StandardCopyOption.REPLACE_EXISTING); assertTrue(Files.exists(file2)); assertFalse(Files.exists(file1)); }

9. Conclusione

In questo articolo, abbiamo appreso le API dei file nella nuova API del file system (NIO2) fornita come parte di Java 7 e abbiamo visto la maggior parte delle operazioni importanti sui file in azione.

Gli esempi di codice utilizzati in questo articolo sono disponibili nel progetto Github dell'articolo.