Рубрики
Без рубрики

Введение в API файлов Java NIO2

Краткое и практическое руководство по API файлов Java NIO2

Автор оригинала: baeldung.

1. Обзор

В этой статье мы сосредоточимся на новых API ввода-вывода в платформе Java – NIO2 – для выполнения базовых манипуляций с файлами .

Файловые API в NIO2 представляют собой одну из основных новых функциональных областей платформы Java, поставляемой с Java 7, в частности, подмножество нового API файловой системы наряду с API пути .

2. Настройка

Настройка вашего проекта для использования файловых API-интерфейсов-это всего лишь вопрос выполнения этого импорта:

import java.nio.file.*;

Поскольку примеры кода в этой статье, вероятно, будут выполняться в разных средах, давайте рассмотрим домашний каталог пользователя, который будет действителен во всех операционных системах:

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

Класс Files является одной из основных точек входа в пакет java.nio.file . Этот класс предлагает богатый набор API для чтения, записи и управления файлами и каталогами. Методы класса Files работают с экземплярами объектов Path .

3. Проверка файла или каталога

У нас может быть экземпляр Path , представляющий файл или каталог в файловой системе. Независимо от того, существует ли этот файл или каталог, на который он указывает, или нет, доступен или нет, может быть подтверждено файловой операцией.

Для простоты всякий раз, когда мы используем термин file , мы будем ссылаться как на файлы, так и на каталоги, если явно не указано иное.

Чтобы проверить, существует ли файл, мы используем API exists :

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

    assertTrue(Files.exists(p));
}

Чтобы проверить, что файл не существует, мы используем API not Exists :

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

    assertTrue(Files.notExists(p));
}

Мы также можем проверить, является ли файл обычным файлом, например myfile.txt или это просто каталог, мы используем isRegularFile API:

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

    assertFalse(Files.isRegularFile(p));
}

Существуют также статические методы проверки прав доступа к файлам. Чтобы проверить, читаем ли файл, мы используем API isReadable :

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

    assertTrue(Files.isReadable(p));
}

Чтобы проверить, доступен ли он для записи, мы используем API is Writable :

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

    assertTrue(Files.isWritable(p));
}

Аналогично, чтобы проверить, является ли он исполняемым:

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

Когда у нас есть два пути, мы можем проверить, указывают ли они оба на один и тот же файл в базовой файловой системе:

@Test
public void givenSameFilePaths_whenConfirmsIsSame_thenCorrect() {
    Path p1 = Paths.get(HOME);
    Path p2 = Paths.get(HOME);

    assertTrue(Files.isSameFile(p1, p2));
}

4. Создание файлов

API файловой системы предоставляет однострочные операции для создания файлов. Чтобы создать обычный файл, мы используем API CreateFile и передаем ему объект Path , представляющий файл, который мы хотим создать.

Все элементы имени в пути должны существовать, кроме имени файла, в противном случае мы получим исключение 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));
}

В приведенном выше тесте, когда мы сначала проверяем путь, он отсутствует, а затем после операции CreateFile обнаруживается, что он существует.

Для создания каталога мы используем 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));
}

Эта операция требует, чтобы все элементы имени в пути существовали, если нет, мы также получим 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);
}

Однако, если мы хотим создать иерархию каталогов с помощью одного вызова, мы используем метод create Directory . В отличие от предыдущей операции, когда она обнаруживает какие-либо отсутствующие элементы имени в пути, она не создает исключение IOException , она создает их рекурсивно, приводя к последнему элементу:

@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. Создание Временных Файлов

Многие приложения создают след временных файлов в файловой системе во время работы. В результате большинство файловых систем имеют выделенный каталог для хранения временных файлов, созданных такими приложениями.

Новый API файловой системы предоставляет специальные операции для этой цели. Эту операцию выполняет API createTempFile . Он принимает объект пути, префикс файла и суффикс файла:

@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));
}

Этих параметров достаточно для требований, требующих этой операции. Однако, если вам нужно указать конкретные атрибуты файла, существует четвертый параметр аргументов переменной.

Приведенный выше тест создает временный файл в каталоге HOME , добавляя и добавляя предоставленные строки префикса и суффикса соответственно. В итоге мы получим имя файла, например log_8821081429012075286.txt . Длинная числовая строка генерируется системой.

Однако, если мы не предоставим префикс и суффикс, то имя файла будет содержать только длинную числовую строку и расширение по умолчанию .tmp :

@Test
public void givenPath_whenCreatesTempFileWithDefaults_thenCorrect() {
    Path p = Paths.get(HOME + "/");

    Files.createTempFile(p, null, null);
        
    assertTrue(Files.exists(p));
}

Описанная выше операция создает файл с именем 8600179353689423985.tmp .

Наконец, если мы не указываем ни путь, ни префикс, ни суффикс, то операция будет использовать значения по умолчанию во всем. По умолчанию для созданного файла будет использоваться каталог временных файлов, предоставленный файловой системой:

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

    assertTrue(Files.exists(p));
}

В Windows это будет по умолчанию что-то вроде C:\Users\user\AppData\Local\Temp\6100927974988978748.tmp .

Все вышеперечисленные операции можно адаптировать для создания каталогов, а не обычных файлов, используя createTempDirectory вместо createTempFile .

6. Удаление файла

Чтобы удалить файл, мы используем API delete . Для ясности следующий тест сначала гарантирует, что файл еще не существует, затем создает его и подтверждает, что он теперь существует, и, наконец, удаляет его и подтверждает, что он больше не существует:

@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));
}

Однако, если файл не существует в файловой системе, операция удаления завершится ошибкой с IOException :

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

    Files.delete(p);
}

Мы можем избежать этого сценария, используя deleteIfExists , которые завершаются беззвучно, если файл не существует. Это важно, когда эту операцию выполняют несколько потоков, и нам не нужно сообщение об ошибке просто потому, что поток выполнил операцию раньше, чем текущий поток, который потерпел неудачу:

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

    Files.deleteIfExists(p);
}

При работе с каталогами, а не с обычными файлами, мы должны помнить, что операция удаления по умолчанию не работает рекурсивно. Поэтому, если каталог не пуст, он завершится ошибкой с 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. Копирование Файлов

Вы можете скопировать файл или каталог с помощью 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));
}

Копирование завершается неудачно, если целевой файл существует, если не указан параметр REPLACE_EXISTING :

@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);
}

Однако при копировании каталогов содержимое не копируется рекурсивно. Это означает, что если /baeldung содержит /articles.db и /authors.db файлы, копирование /baeldung в новое место создаст пустой каталог.

8. Перемещение файлов

Вы можете переместить файл или каталог с помощью API move . Это во многом похоже на операцию copy . Если операция копирования аналогична операции копировать и вставить в системах, основанных на графическом интерфейсе, то переместить аналогична операции вырезать и вставить :

@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));
}

Операция move завершается неудачно, если целевой файл существует, если не указан параметр REPLACE_EXISTING , как мы сделали с операцией 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. Заключение

В этой статье мы узнали о файловых API в новом API файловой системы (NIO2), который был поставлен как часть Java 7, и увидели большинство важных файловых операций в действии.

Примеры кода, использованные в этой статье, можно найти в проекте статьи Github .