2016-03-14 40 views
24

Attualmente sto cercando di eliminare ricorsivamente una directory ... Stranamente il pezzo più breve del codice sono stato in grado di trovare è il seguente costrutto, impiegando un ad hoc classe interna e in un modello visitatore ...java.nio: directory ricorsiva più concisa cancellare

Path rootPath = Paths.get("data/to-delete"); 

try { 
    Files.walkFileTree(rootPath, new SimpleFileVisitor<Path>() { 
    @Override 
    public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException { 
     System.out.println("delete file: " + file.toString()); 
     Files.delete(file); 
     return FileVisitResult.CONTINUE; 
    } 

    @Override 
    public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException { 
     Files.delete(dir); 
     System.out.println("delete dir: " + dir.toString()); 
     return FileVisitResult.CONTINUE; 
    } 
    }); 
} catch(IOException e){ 
    e.printStackTrace(); 
} 

Fonte: here

Questo si sente terribilmente goffo e prolisso, dato che le nuove API nio rimuovere tanto disordine e boilerplate ...

C'è un modo più breve per ottenere una cancellazione di directory forzata e ricorsiva?

Sto cercando puri nativi Java 1.8 metodi, quindi si prega di non creare un collegamento a librerie esterne ...

+0

* Questo si sente terribilmente goffo e verbose * Perché ? Questo è un ottimo modo per farlo. E Java 8 'Files.walk' non ti darà l'opportunità di farlo. – Tunaki

+1

Perché questo costringe l'utente a ridefinire una semplice cancellazione ricorsiva ... Perché questo richiede 15 righe di codice ... Che ne dici di qualcosa come "Files.deleteRecursively (Path)", o forse qualche flag opzionale? – fgysin

+0

La risposta è che semplicemente non esiste in NIO.2 integrato. Potresti avere un approccio ricorsivo con 'Files.list' ma è lo stesso e preferirei la soluzione che hai. – Tunaki

risposta

68

è possibile combinare NIO 2 e l'API Stream.

Path rootPath = Paths.get("/data/to-delete"); 
Files.walk(rootPath, FileVisitOption.FOLLOW_LINKS) 
    .sorted(Comparator.reverseOrder()) 
    .map(Path::toFile) 
    .peek(System.out::println) 
    .forEach(File::delete); 
  • Files.walk - restituire tutti i file/directory sotto rootPath compreso
  • .sorted - ordinare l'elenco in ordine inverso, quindi la directory stessa viene dopo le sottodirectory comprese e file
  • .map - Mappa del Path a File
  • .peek - c'è solo per mostrare quale voce viene elaborata
  • .forEach - chiama il metodo .delete() su ogni oggetto File

EDIT

Ecco alcune cifre.
La directory /data/to-delete conteneva l'rt.jar decompresso di jdk1.8.0_73 e una versione recente di activemq.

files: 36,427 
dirs : 4,143 
size : 514 MB 

Times in millisecondi

    int. SSD  ext. USB3 
NIO + Stream API 1,126  11,943 
FileVisitor   1,362  13,561 

Entrambe le versioni sono stati giustiziati senza stampare i nomi dei file. Il fattore più limitante è il drive. Non l'implementazione.

EDIT

Alcuni dati supplementare su lLa opzione FileVisitOption.FOLLOW_LINKS.

Assumere seguente file e directory struttura

/data/dont-delete/bar 
/data/to-delete/foo 
/data/to-delete/dont-delete -> ../dont-delete 

Utilizzando

Files.walk(rootPath, FileVisitOption.FOLLOW_LINKS) 

seguirà i collegamenti simbolici e file /tmp/dont_delete/bar sarebbero cancellate.

Utilizzando

Files.walk(rootPath) 

non seguiranno i collegamenti simbolici e file /tmp/dont_delete/bar non sarebbero eliminati.

NOTA: Non utilizzare mai il codice come copia e incolla senza capire cosa fa.

+0

Hai provato questo? La logica di ordinamento dovrebbe essere più complicata rispetto all'ordinamento delle stringhe. Inoltre, sono abbastanza sicuro che le prestazioni siano davvero pessime: l'ordinamento in ordine inverso richiede di caricare tutto lo Stream in memoria. Se l'albero delle directory è profondo, questo può essere piuttosto problematico. – Tunaki

+0

@Tunaki Era finito per un albero di directory di '> 3.600' directory e'> 21.000' file in meno di 10 secondi (incluso il tempo di compilazione dello snippet). E i nomi sono ordinati in ordine naturale. Poiché il nome della directory è sempre più breve del file contenuto, sarà dopo il file in ordine inverso. Se le prestazioni basate su un albero di directory enorme con una quantità enorme di file è una preoccupazione, è possibile rimuovere prima tutti i file e quindi tutte le directory. In tal caso l'ordine non avrebbe importanza. – SubOptimal

+0

@Tunaki Ho fatto un rapido test con la prima rimozione di tutti i file e quindi tutte le directory -> risultato ci vuole quasi il doppio del tempo. Perché devi attraversare due volte tutte le directory. – SubOptimal

3

la seguente soluzione non ha bisogno della conversione da percorso del file oggetti:

Path rootPath = Paths.get("/data/to-delete");  
final List<Path> pathsToDelete = Files.walk(rootPath).sorted(Comparator.reverseOrder()).collect(Collectors.toList()); 
for(Path path : pathsToDelete) { 
    Files.deleteIfExists(path); 
} 
0

Se è necessario utilizzare esclusivamente Java 7 con NIO

Path path = Paths.get("./target/logs"); 
Files.walkFileTree(path, new SimpleFileVisitor<Path>() { 
    @Override 
    public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException { 
    Files.delete(file); 
    return FileVisitResult.CONTINUE; 
    } 

    @Override 
    public FileVisitResult postVisitDirectory(Path dir, IOException exc) 
     throws IOException { 
    Files.delete(dir); 
    return FileVisitResult.CONTINUE; 
    } 
});