Introdução à API de arquivos Java NIO2
*1. Visão geral *
Neste artigo, vamos nos concentrar nas novas APIs de E/S na plataforma Java -* NIO2 - para fazer a manipulação básica de arquivos *.
As APIs de arquivos no NIO2 constituem uma das principais novas áreas funcionais da plataforma Java que acompanham o Java 7, especificamente um subconjunto da nova API do sistema de arquivos junto às APIs do Path.
2. Configuração
A configuração do seu projeto para usar APIs de arquivos é apenas uma questão de fazer essa importação:
import java.nio.file.*;
Como os exemplos de código neste artigo provavelmente estarão em execução em ambientes diferentes, vamos identificar o diretório inicial do usuário, que será válido em todos os sistemas operacionais:
private static String HOME = System.getProperty("user.home");
A classe Files é um dos principais pontos de entrada do pacote java.nio.file. Esta classe oferece um rico conjunto de APIs para leitura, gravação e manipulação de arquivos e diretórios. Os métodos da classe Files funcionam em instâncias de objetos Path.
*3. Verificando um arquivo ou diretório *
Podemos ter uma instância Path representando um arquivo ou um diretório no sistema de arquivos. Se esse arquivo ou diretório para o qual está apontando existe ou não, é acessível ou não pode ser confirmado por uma operação de arquivo.
Por uma questão de simplicidade, sempre que usarmos o termo file, nos referiremos a arquivos e diretórios, a menos que seja declarado explicitamente o contrário.
Para verificar se existe um arquivo, usamos a API exists:
@Test
public void givenExistentPath_whenConfirmsFileExists_thenCorrect() {
Path p = Paths.get(HOME);
assertTrue(Files.exists(p));
}
Para verificar se um arquivo não existe, usamos a API notExists:
@Test
public void givenNonexistentPath_whenConfirmsFileNotExists_thenCorrect() {
Path p = Paths.get(HOME + "/inexistent_file.txt");
assertTrue(Files.notExists(p));
}
Também podemos verificar se um arquivo é regular como myfile.txt ou é apenas um diretório, usamos a API isRegularFile:
@Test
public void givenDirPath_whenConfirmsNotRegularFile_thenCorrect() {
Path p = Paths.get(HOME);
assertFalse(Files.isRegularFile(p));
}
Também existem métodos estáticos para verificar as permissões de arquivo. Para verificar se um arquivo é legível, usamos a API isReadable:
@Test
public void givenExistentDirPath_whenConfirmsReadable_thenCorrect() {
Path p = Paths.get(HOME);
assertTrue(Files.isReadable(p));
}
Para verificar se é gravável, usamos a API isWritable:
@Test
public void givenExistentDirPath_whenConfirmsWritable_thenCorrect() {
Path p = Paths.get(HOME);
assertTrue(Files.isWritable(p));
}
Da mesma forma, para verificar se é executável:
@Test
public void givenExistentDirPath_whenConfirmsExecutable_thenCorrect() {
Path p = Paths.get(HOME);
assertTrue(Files.isExecutable(p));
}
Quando temos dois caminhos, podemos verificar se ambos apontam para o mesmo arquivo no sistema de arquivos subjacente:
@Test
public void givenSameFilePaths_whenConfirmsIsSame_thenCorrect() {
Path p1 = Paths.get(HOME);
Path p2 = Paths.get(HOME);
assertTrue(Files.isSameFile(p1, p2));
}
===* 4. Criando arquivos *
A API do sistema de arquivos fornece operações de linha única para criar arquivos. Para criar um arquivo comum, usamos a API createFile e passamos a ele um objeto Path representando o arquivo que queremos criar.
Todos os elementos de nome no caminho devem existir, além do nome do arquivo, caso contrário, obteremos uma _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));
}
No teste acima, quando verificamos o caminho pela primeira vez, ele não existe e, após a operação createFile, ele é encontrado.
Para criar um diretório, usamos a 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));
}
Esta operação requer que todos os elementos de nome no caminho existam; caso contrário, também obtemos uma 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);
}
No entanto, se desejamos criar uma hierarquia de diretórios com uma única chamada, usamos o método createDirectories. Diferente da operação anterior, quando encontra algum elemento de nome ausente no caminho, ele não lança uma IOException, ele os cria recursivamente, levando ao último 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. Criando arquivos temporários *
Muitos aplicativos criam uma trilha de arquivos temporários no sistema de arquivos à medida que são executados. Como resultado, a maioria dos sistemas de arquivos possui um diretório dedicado para armazenar arquivos temporários gerados por esses aplicativos.
A nova API do sistema de arquivos fornece operações específicas para esse fim. A API createTempFile executa esta operação. É necessário um objeto de caminho, um prefixo de arquivo e um sufixo de arquivo:
@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));
}
Esses parâmetros são suficientes para os requisitos que precisam dessa operação. No entanto, se você precisar especificar atributos específicos do arquivo, haverá um quarto parâmetro de argumentos variáveis.
O teste acima cria um arquivo temporário no diretório HOME, pré-pendente e anexando as seqüências de prefixo e sufixo fornecidas, respectivamente. Terminaremos com um nome de arquivo como log_8821081429012075286.txt. A cadeia numérica longa é gerada pelo sistema.
No entanto, se não fornecermos um prefixo e um sufixo, o nome do arquivo incluirá apenas a cadeia numérica longa e uma extensão .tmp padrão:
@Test
public void givenPath_whenCreatesTempFileWithDefaults_thenCorrect() {
Path p = Paths.get(HOME + "/");
Files.createTempFile(p, null, null);
assertTrue(Files.exists(p));
}
A operação acima cria um arquivo com um nome como 8600179353689423985.tmp.
Finalmente, se não fornecermos caminho, prefixo nem sufixo, a operação usará os padrões por toda parte. O local padrão do arquivo criado será o diretório de arquivos temporários fornecido pelo sistema de arquivos:
@Test
public void givenNoFilePath_whenCreatesTempFileInTempDir_thenCorrect() {
Path p = Files.createTempFile(null, null);
assertTrue(Files.exists(p));
}
No Windows, o padrão será algo como C: \ Users \ user \ AppData \ Local \ Temp \ 6100927974988978748.tmp.
Todas as operações acima podem ser adaptadas para criar diretórios em vez de arquivos regulares usando createTempDirectory em vez de createTempFile.
===* 6. Excluindo um arquivo *
Para excluir um arquivo, usamos a API delete. Para fins de clareza, o teste a seguir primeiro garante que o arquivo ainda não exista, depois o cria e confirma que agora existe e, por fim, o exclui e confirma que não existe mais:
@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));
}
No entanto, se um arquivo não existir no sistema de arquivos, a operação de exclusão falhará com uma IOException:
@Test(expected = NoSuchFileException.class)
public void givenInexistentFile_whenDeleteFails_thenCorrect() {
Path p = Paths.get(HOME + "/inexistentFile.txt");
assertFalse(Files.exists(p));
Files.delete(p);
}
Podemos evitar esse cenário usando deleteIfExists, que falha silenciosamente caso o arquivo não exista. Isso é importante quando vários threads estão executando esta operação e não queremos uma mensagem de falha simplesmente porque um thread executou a operação antes do thread atual que falhou:
@Test
public void givenInexistentFile_whenDeleteIfExistsWorks_thenCorrect() {
Path p = Paths.get(HOME + "/inexistentFile.txt");
assertFalse(Files.exists(p));
Files.deleteIfExists(p);
}
Ao lidar com diretórios e arquivos não regulares, devemos lembrar que a operação de exclusão não funciona recursivamente por padrão. Portanto, se um diretório não estiver vazio, ele falhará com uma 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. Copiando arquivos *
Você pode copiar um arquivo ou diretório usando a API copy:
@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));
}
A cópia falhará se o arquivo de destino existir, a menos que a opção REPLACE_EXISTING seja especificada:
@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);
}
No entanto, ao copiar diretórios, o conteúdo não é copiado recursivamente. Isso significa que se / contiver arquivos /articles.db e /autores.db, copiar / para um novo local criará um diretório vazio.
===* 8. Movendo arquivos *
Você pode mover um arquivo ou diretório usando a API move. É, em muitos aspectos, semelhante à operação copy. Se a operação de cópia for análoga a uma operação copy e paste em sistemas baseados em GUI, move será análoga a uma operação cut and paste:
@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));
}
A operação move falhará se o arquivo de destino existir, a menos que a opção REPLACE_EXISTING seja especificada como fizemos com a operação copy:
@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. Conclusão*
Neste artigo, aprendemos sobre APIs de arquivo na nova API do sistema de arquivos (NIO2) que foi enviada como parte do Java 7 e vimos a maioria das operações importantes de arquivo em ação.
Os exemplos de código usados neste artigo podem ser encontrados no Github project.