CodeGym /Blogue Java /Random-PT /Arquivos Java, Caminho
John Squirrels
Nível 41
San Francisco

Arquivos Java, Caminho

Publicado no grupo Random-PT
Oi! Hoje falaremos sobre como trabalhar com arquivos e diretórios. Você já sabe como gerenciar o conteúdo dos arquivos: dedicamos muitas lições a isso :) Acho que você acha fácil lembrar algumas classes usadas para esses fins. Na lição de hoje, falaremos especificamente sobre gerenciamento de arquivos: criação, renomeação, etc. Antes do Java 7, todas essas operações eram executadas usando a classe File . Você pode ler sobre isso aqui . Mas no Java 7, os criadores da linguagem decidiram mudar a forma como trabalhamos com arquivos e diretórios. Isso aconteceu porque a classe File tinha várias desvantagens. Por exemplo, ele não tinha o método copy() , que permitia copiar um arquivo de um local para outro (uma habilidade aparentemente essencial). Além disso, oA classe de arquivo tinha alguns métodos que retornavam valores booleanos . Quando há um erro, esse método retorna false. Ele não lança uma exceção, tornando muito difícil identificar erros e diagnosticar suas causas. No lugar da única classe File , surgiram 3 classes: Paths , Path e Files . Bem, para ser preciso, Path é uma interface, não uma classe. Vamos descobrir como eles diferem uns dos outros e por que precisamos de cada um deles. Vamos começar com o mais simples: Paths .

Caminhos

Paths é uma classe muito simples com um único método estático: get() . Ele foi criado apenas para obter um objeto Path da string ou URI passada. Não tem nenhuma outra funcionalidade. Aqui está um exemplo disso no trabalho:

import java.nio.file.Path;
import java.nio.file.Paths;

public class Main {

   public static void main(String[] args) {

       Path testFilePath = Paths.get("C:\\Users\\Username\\Desktop\\testFile.txt");
   }
}
Não é a classe mais complexa, certo? :) Bem, também temos esse tipo de caminho . Vamos descobrir o que é Path e por que é necessário :)

Caminho

Path , em geral, é um análogo redesenhado da classe File . É muito mais fácil trabalhar com o File . Primeiro , muitos métodos utilitários (estáticos) foram removidos e movidos para a classe Files . Em segundo lugar , a ordem foi imposta aos valores de retorno dos métodos da interface Path . Na classe File , os métodos retornavam um String , um booleano ou um File . Não foi fácil descobrir isso. Por exemplo, havia um método getParent() que retornava uma string representando o caminho pai do arquivo atual. Mas também havia umgetParentFile() , que retornou a mesma coisa, mas na forma de um objeto File ! Isso é claramente redundante. Da mesma forma, na interface Path , o método getParent() e outros métodos para trabalhar com arquivos simplesmente retornam um objeto Path . Nenhuma pilha de opções - tudo é fácil e simples. Quais são alguns dos métodos úteis que Path tem? Aqui estão alguns deles e exemplos de como eles funcionam:
  • getFileName() : retorna o nome do arquivo do caminho;

  • getParent() : retorna o diretório "pai" do caminho atual (ou seja, o diretório localizado imediatamente acima na árvore de diretórios);

  • getRoot() : retorna o diretório "raiz", ou seja, o diretório no topo da árvore de diretórios;

  • startsWith() , endsWith() : verifique se o caminho começa/termina com o caminho passado:

    
    import java.nio.file.Path;
    import java.nio.file.Paths;
    
    public class Main {
    
       public static void main(String[] args) {
    
           Path testFilePath = Paths.get("C:\\Users\\Username\\Desktop\\testFile.txt");
    
           Path fileName = testFilePath.getFileName();
           System.out.println(fileName);
    
           Path parent = testFilePath.getParent();
           System.out.println(parent);
    
           Path root = testFilePath.getRoot();
           System.out.println(root);
    
           boolean endWithTxt = testFilePath.endsWith("Desktop\\testFile.txt");
           System.out.println(endWithTxt);
    
           boolean startsWithLalala = testFilePath.startsWith("lalalala");
           System.out.println(startsWithLalala);
       }
    }
    

    Saída do console:

    
    testFile.txt
    C:\Users\Username\Desktop
    C:\
    true
    false
    

    Preste atenção em como o método endsWith() funciona. Ele verifica se o caminho atual termina com o caminho passado . Especificamente, se está no caminho , não na string passada .

    Compare os resultados dessas duas chamadas:

    
    import java.nio.file.Path;
    import java.nio.file.Paths;
    
    public class Main {
    
       public static void main(String[] args) {
    
           Path testFilePath = Paths.get("C:\\Users\\Username\\Desktop\\testFile.txt");
    
           System.out.println(testFilePath.endsWith("estFile.txt"));
           System.out.println(testFilePath.endsWith("Desktop\\testFile.txt"));
       }
    }
    

    Saída do console:

    
    false
    true
    

    O método endsWith() deve receber um caminho genuíno, não apenas um conjunto de caracteres: caso contrário, o resultado será sempre falso, mesmo que o caminho atual realmente termine com essa sequência de caracteres (como é o caso de "estFile.txt " no exemplo acima).

    Além disso, o Path possui um grupo de métodos que simplifica o trabalho com caminhos absolutos (completos) e relativos .

Vejamos estes métodos:
  • boolean isAbsolute() retorna true se o caminho atual for absoluto:

    
    import java.nio.file.Path;
    import java.nio.file.Paths;
    
    public class Main {
    
       public static void main(String[] args) {
    
           Path testFilePath = Paths.get("C:\\Users\\Username\\Desktop\\testFile.txt");
    
           System.out.println(testFilePath.isAbsolute());
       }
    }
    

    Saída do console:

    
    true
    
  • Path normalize() : "normaliza" o caminho atual, removendo dele elementos desnecessários. Você deve saber que em sistemas operacionais populares os símbolos "." (diretório atual) e ".." (diretório pai) são freqüentemente usados ​​para designar caminhos. Por exemplo, " ./Pictures/dog.jpg " significa que o diretório atual tem uma pasta "Pictures", que por sua vez contém um arquivo "dog.jpg".

    Olhe aqui. Se um caminho usando "." ou ".." aparecer em seu programa, o método normalize() irá removê-los e produzir um caminho que não os contenha:

    
    import java.nio.file.Path;
    import java.nio.file.Paths;
    
    public class Main {
    
       public static void main(String[] args) {
    
          
           Path path5 = Paths.get("C:\\Users\\Java\\.\\examples");
          
           System.out.println(path5.normalize());
          
           Path path6 = Paths.get("C:\\Users\\Java\\..\\examples");
           System.out.println(path6.normalize());
       }
    }
    

    Saída do console:

    
    C:\Users\Java\examples
    C:\Users\examples
    
  • Path relativize() : calcula o caminho relativo entre o caminho atual e o passado.

    Por exemplo:

    
    import java.nio.file.Path;
    import java.nio.file.Paths;
    
    public class Main {
    
       public static void main(String[] args) {
    
           Path testFilePath1 = Paths.get("C:\\Users\\Users\\Users\\Users");
           Path testFilePath2 = Paths.get("C:\\Users\\Users\\Users\\Users\\Username\\Desktop\\testFile.txt");
    
           System.out.println(testFilePath1.relativize(testFilePath2));
       }
    }
    

    Saída do console:

    
    Username\Desktop\testFile.txt
    

A lista completa de métodos Path é bastante longa. Você pode encontrá-los todos na documentação do Oracle . Agora passaremos a considerar Arquivos .

arquivos

Files é uma classe utilitária que contém os métodos estáticos retirados da classe File . Arquivos é comparável a Arrays ou Collections . A diferença é que ele trabalha com arquivos, não com arrays ou coleções :) Seu foco é o gerenciamento de arquivos e diretórios. Usando os métodos estáticos da classe Files , podemos criar, excluir e mover arquivos e diretórios. Essas operações são executadas usando osmétodos createFile() (para diretórios, createDirectory() ), move() e delete() . Veja como usá-los:

import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;

import static java.nio.file.StandardCopyOption.REPLACE_EXISTING;

public class Main {

   public static void main(String[] args) throws IOException {

       // Create a file
       Path testFile1 = Files.createFile(Paths.get("C:\\Users\\Username\\Desktop\\testFile111.txt"));
       System.out.println("Was the file created successfully?");
       System.out.println(Files.exists(Paths.get("C:\\Users\\Username\\Desktop\\testFile111.txt")));

       // Create a directory
       Path testDirectory = Files.createDirectory(Paths.get("C:\\Users\\Username\\Desktop\\testDirectory"));
       System.out.println("Was the directory created successfully?");
       System.out.println(Files.exists(Paths.get("C:\\Users\\Username\\Desktop\\testDirectory")));

       // Move the file from the desktop to the testDirectory directory. When you move a folder, you need to indicate its name in the folder!
       testFile1 = Files.move(testFile1, Paths.get("C:\\Users\\Username\\Desktop\\testDirectory\\testFile111.txt"), REPLACE_EXISTING);

       System.out.println("Did our file remain on the desktop?");
       System.out.println(Files.exists(Paths.get("C:\\Users\\Username\\Desktop\\testFile111.txt")));

       System.out.println("Has our file been moved to testDirectory?");
       System.out.println(Files.exists(Paths.get("C:\\Users\\Username\\Desktop\\testDirectory\\testFile111.txt")));

       // Delete a file
       Files.delete(testFile1);
       System.out.println("Does the file still exist?");
       System.out.println(Files.exists(Paths.get("C:\\Users\\Username\\Desktop\\testDirectory\\testFile111.txt")));
   }
}
Aqui primeiro criamos um arquivo ( método Files.createFile() ) na área de trabalho. Em seguida, criamos uma pasta no mesmo local ( método Files.createDirectory() ). Depois disso, movemos o arquivo ( método Files.move() ) da área de trabalho para esta nova pasta e, finalmente, excluímos o arquivo ( método Files.delete() ). Saída do console:

Was the file created successfully? 
true 
Was the directory created successfully? 
true
Did our file remain on the desktop? 
false 
Has our file been moved to testDirectory? 
true 
Does the file still exist? 
false
Observação:assim como os métodos da Pathinterface, muitos métodos da Filesclasse retornam umPath objeto. A maioria dos métodos da Filesclasse também recebe Pathobjetos como entradas. Aqui o Paths.get()método será seu fiel assistente — faça bom uso dele. O que mais é interessante em Files? FileO que realmente faltava à velha classe é um copy()método! Falamos sobre isso no início desta lição. Agora é hora de conhecê-lo!

import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;

import static java.nio.file.StandardCopyOption.REPLACE_EXISTING;

public class Main {

   public static void main(String[] args) throws IOException {

       // Create a file
       Path testFile1 = Files.createFile(Paths.get("C:\\Users\\Username\\Desktop\\testFile111.txt"));
       System.out.println("Was the file created successfully?");
       System.out.println(Files.exists(Paths.get("C:\\Users\\Username\\Desktop\\testFile111.txt")));

       // Create a directory
       Path testDirectory2 = Files.createDirectory(Paths.get("C:\\Users\\Username\\Desktop\\testDirectory2"));
       System.out.println("Was the directory created successfully?");
       System.out.println(Files.exists(Paths.get("C:\\Users\\Username\\Desktop\\testDirectory2")));

       // Copy the file from the desktop to the testDirectory2 directory.
       testFile1 = Files.copy(testFile1, Paths.get("C:\\Users\\Username\\Desktop\\testDirectory2\\testFile111.txt"), REPLACE_EXISTING);

       System.out.println("Did our file remain on the desktop?");
       System.out.println(Files.exists(Paths.get("C:\\Users\\Username\\Desktop\\testFile111.txt")));

       System.out.println("Was our file copied to testDirectory?");
       System.out.println(Files.exists(Paths.get("C:\\Users\\Username\\Desktop\\testDirectory2\\testFile111.txt")));
   }
}
Saída do console:

Was the file created successfully? 
true 
Was the directory created successfully? 
true 
Did our file remain on the desktop? 
true 
Was our file copied to testDirectory? 
true
Agora você sabe como copiar arquivos programaticamente! :) Claro, a Filesclasse permite que você não apenas gerencie um arquivo em si, mas também trabalhe com seu conteúdo. Ele tem o write()método para gravar dados em um arquivo e todos os 3 métodos para ler dados: read(), readAllBytes(), e readAllLines() Vamos nos deter em detalhes no último. Por que aquele? Porque tem um tipo de retorno muito interessante: List<String>! Ou seja, ele nos retorna uma lista de todas as linhas do arquivo. Obviamente, isso torna muito conveniente trabalhar com o conteúdo do arquivo, porque o arquivo inteiro, linha por linha, pode, por exemplo, ser exibido no console usando um loop comum for:

import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.List;

import static java.nio.charset.StandardCharsets.UTF_8;

public class Main {

   public static void main(String[] args) throws IOException {

       List<String> lines = Files.readAllLines(Paths.get("C:\\Users\\Username\\Desktop\\pushkin.txt"), UTF_8);

       for (String s: lines) {
           System.out.println(s);
       }
   }
}
Saída do console:

I still recall the wondrous moment: 
When you appeared before my sight, 
As though a brief and fleeting omen, 
Pure phantom in enchanting light.
Super conveniente! :) Essa habilidade apareceu no Java 7. A Stream API apareceu no Java 8. Ela adiciona alguns elementos de programação funcional ao Java. Incluindo recursos mais avançados de manipulação de arquivos. Imagine que temos a seguinte tarefa: encontrar todas as linhas que começam com a palavra "As", convertê-las para MAIÚSCULAS e exibi-las no console. Como seria uma solução usando a Filesclasse no Java 7? Algo assim:

import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.List;

import static java.nio.charset.StandardCharsets.UTF_8;

public class Main {

   public static void main(String[] args) throws IOException {

       List<String> lines = Files.readAllLines(Paths.get("C:\\Users\\Username\\Desktop\\pushkin.txt"), UTF_8);

       List<String> result = new ArrayList<>();

       for (String s: lines) {
           if (s.startsWith("As")) {
               String upper = s.toUpperCase();
               result.add(upper);
           }
       }

       for (String s: result) {
           System.out.println(s);
       }
   }
}
Saída do console:

AS THOUGH A BRIEF AND FLEETING OMEN, 
PURE PHANTOM IN ENCHANTING LIGHT.
Missão cumprida, mas você não acha que para uma tarefa tão simples nosso código acabou sendo um pouco... prolixo? Usando a API Stream do Java 8, a solução parece muito mais elegante:

import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;

public class Main {

   public static void main(String[] args) throws IOException {

       Stream<String> stream = Files.lines(Paths.get("C:\\Users\\Username\\Desktop\\pushkin.txt"));

       List<String> result  = stream
               .filter(line -> line.startsWith("As"))
               .map(String::toUpperCase)
               .collect(Collectors.toList());
       result.forEach(System.out::println);
   }
}
Obtivemos o mesmo resultado, mas com muito menos código! Além do mais, ninguém pode dizer que perdemos a "legibilidade". Acho que você pode comentar facilmente sobre o que esse código faz, mesmo sem estar familiarizado com a API Stream. Resumindo, um Stream é uma sequência de elementos, sobre os quais você pode realizar várias operações. Obtemos um objeto Stream do Files.lines()método e, em seguida, aplicamos 3 funções a ele:
  1. Usamos o filter()método para selecionar apenas as linhas do arquivo que começam com "As".

  2. Percorremos todas as linhas selecionadas usando o map()método e convertemos cada uma delas em MAIÚSCULAS.

  3. Usamos o collect()método para reunir todas as linhas recebidas em um arquivo List.

Obtemos a mesma saída:

AS THOUGH A BRIEF AND FLEETING OMEN, 
PURE PHANTOM IN ENCHANTING LIGHT.
Agora vamos voltar ao pão com manteiga, ou seja, arquivos :) A última capacidade que consideraremos hoje é percorrer uma árvore de arquivos . Nos sistemas operacionais modernos, a estrutura do arquivo geralmente se parece com uma árvore: tem uma raiz e há ramificações, que podem ter outras ramificações, etc. A raiz e as ramificações são diretórios. Por exemplo, o diretório " С:// " pode ser a raiz. Inclui duas ramificações: " C://Downloads " e " C://Users ". Cada uma dessas ramificações tem duas ramificações: " C://Downloads/Pictures ", " C://Downloads/Video ", " C://Users/JohnSmith ", " C://Users/Pudge2005". E esses ramos por sua vez têm outros ramos, etc. e é por isso que chamamos de árvore. No Linux, a estrutura é semelhante, mas o diretório / éArquivos, Caminho - 2 a raiz. Agora imagine que precisamos começar no diretório raiz , percorra todas as suas pastas e subpastas e encontre arquivos que tenham algum conteúdo específico. Procuraremos arquivos que contenham a linha "Este é o arquivo de que precisamos!" Pegaremos a pasta "testFolder", que está em a área de trabalho, como o diretório raiz. Aqui está seu conteúdo: Arquivos, Caminho - 3As pastas level1-a e level1-b também contêm pastas: Arquivos, Caminho - 4Arquivos, Caminho - 5Não há pastas nessas "pastas de segundo nível", apenas arquivos individuais: Arquivos, Caminho - 6Arquivos, Caminho - 7Os 3 arquivos com o conteúdo de que precisamos recebem nomes explicativos deliberadamente: FileWeNeed1.txt, FileWeNeed2.txt, FileWeNeed3.txt. Estes são precisamente os arquivos que precisamos encontrar usando Java. Como vamos fazer isso? Um método muito poderoso para percorrer uma árvore de arquivos vem em nosso auxílio: Files.walkFileTree (). Aqui está o que precisamos fazer. Primeiro, precisamos de um arquivo FileVisitor. FileVisitoré uma interface especial, na qual são descritos os métodos para percorrer uma árvore de arquivos. Em particular, é onde colocaremos a lógica para ler o conteúdo de um arquivo e verificar se ele contém o texto que precisamos. Aqui está a nossa FileVisitoraparência:

import java.io.IOException;
import java.nio.file.FileVisitResult;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.SimpleFileVisitor;
import java.nio.file.attribute.BasicFileAttributes;
import java.util.List;

public class MyFileVisitor extends SimpleFileVisitor<Path> {

   @Override
   public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {

       List<String> lines = Files.readAllLines(file);
       for (String s: lines) {
           if (s.contains("This is the file we need")) {
               System.out.println("We found a file we need!");
               System.out.println(file.toAbsolutePath());
               break;
           }
       }

       return FileVisitResult.CONTINUE;
   }
}
Nesse caso, nossa classe herda SimpleFileVisitor. Esta é uma classe que implementa FileVisitor, na qual precisamos sobrescrever apenas um método: visitFile(). Aqui definimos o que precisa ser feito com cada arquivo em cada diretório. Se você precisar de uma lógica mais complexa para percorrer a estrutura do arquivo, deverá escrever sua própria implementação de FileVisitor. Você precisaria implementar mais 3 métodos nessa classe:
  • preVisitDirectory(): a lógica a ser executada antes de entrar em uma pasta;

  • visitFileFailed(): a lógica a ser executada se um arquivo não puder ser visitado (sem acesso ou por outros motivos);

  • postVisitDirectory(): a lógica a ser executada após entrar em uma pasta.

Não precisamos de nenhuma lógica executada, então estamos bem com SimpleFileVisitor. A lógica dentro do visitFile()método é bastante simples: leia todas as linhas do arquivo, verifique se elas contêm o conteúdo que precisamos e, em caso afirmativo, imprima o caminho absoluto no console. A única linha que pode causar dificuldade é esta:

return FileVisitResult.CONTINUE;
Na verdade, isso é muito simples. Aqui estamos simplesmente descrevendo o que o programa deve fazer depois que o arquivo for visitado e todas as operações necessárias tiverem sido executadas. No nosso caso, queremos continuar percorrendo a árvore, então escolhemos a CONTINUEopção. Mas, alternativamente, podemos ter um objetivo diferente: em vez de encontrar todos os arquivos que contenham "Este é o arquivo de que precisamos", encontre apenas um desses arquivos . Depois disso, o programa deve terminar. Nesse caso, nosso código seria exatamente o mesmo, mas ao invés de quebrar haveria:

return FileVisitResult.TERMINATE;
Bem, vamos executar nosso código e ver se funciona.

import java.io.IOException;
import java.nio.file.*;

public class Main {

   public static void main(String[] args) throws IOException {

       Files.walkFileTree(Paths.get("C:\\Users\\Username\\Desktop\\testFolder"), new MyFileVisitor());
   }
}
Saída do console:

We found a file we need! 
C:\Users\Username\Desktop\testFolder\FileWeNeed1.txt 
We found a file we need! 
C:\Users\Username\Desktop\testFolder\level1-a\level2-a-a\FileWeNeed2.txt 
We found a file we need! 
C:\Users\Username\Desktop\testFolder\level1-b\level2-b-b\FileWeNeed3.txt
Excelente! Funcionou! :) Você também pode aceitar este pequeno desafio: substitua SimpleFileVisitorpor um FileVisitor, substitua todos os 4 métodos e crie seu próprio propósito para o programa. Por exemplo, você pode escrever um programa que registre todas as suas ações: exiba o nome do arquivo ou pasta antes ou depois de inseri-los. É tudo por agora. Vejo você em breve! :)
Comentários
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION