«1. Обзор
В этой статье мы сосредоточимся на новых API-интерфейсах ввода-вывода на платформе Java — NIO2 — для выполнения основных операций с файлами.
Файловые API в NIO2 представляют собой одну из основных новых функциональных областей платформы Java, поставляемую с Java 7, в частности, подмножество API новой файловой системы наряду с Path API.
2. Настройка
Настройка вашего проекта для использования файловых API — это всего лишь вопрос импорта:
import java.nio.file.*;
Поскольку примеры кода в этой статье, вероятно, будут выполняться в разных средах, давайте handle домашнего каталога пользователя, который будет действителен во всех операционных системах:
private static String HOME = System.getProperty("user.home");
Класс Files является одной из основных точек входа в пакет java.nio.file. Этот класс предлагает богатый набор API для чтения, записи и управления файлами и каталогами. Методы класса Files работают с экземплярами объектов Path.
3. Проверка файла или каталога
У нас может быть экземпляр Path, представляющий файл или каталог в файловой системе. Независимо от того, существует этот файл или каталог, на который он указывает, доступен он или нет, можно подтвердить с помощью файловой операции.
Ради простоты всякий раз, когда мы используем термин файл, мы будем ссылаться как на файлы, так и на каталоги, если явно не указано иное.
Чтобы проверить, существует ли файл, мы используем существующий API:
@Test
public void givenExistentPath_whenConfirmsFileExists_thenCorrect() {
Path p = Paths.get(HOME);
assertTrue(Files.exists(p));
}
Чтобы проверить, что файл не существует, мы используем API notExists:
@Test
public void givenNonexistentPath_whenConfirmsFileNotExists_thenCorrect() {
Path p = Paths.get(HOME + "/inexistent_file.txt");
assertTrue(Files.notExists(p));
}
Мы также можем проверить, существует ли файл является обычным файлом, таким как myfile.txt, или просто каталогом, мы используем API isRegularFile:
@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 isWritable:
@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);
}
Однако, если мы хотим создать иерархию каталогов одним вызовом, мы используем метод createDirectories. В отличие от предыдущей операции, когда она встречает какие-либо отсутствующие элементы имени в пути, она не генерирует исключение 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 удаления. Для ясности следующий тест сначала проверяет, что файл еще не существует, затем создает его и подтверждает, что теперь он существует, и, наконец, удаляет его и подтверждает, что он больше не существует:
@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 копирования:
@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 перемещения. Во многом это похоже на операцию копирования. Если операция копирования аналогична операции копирования и вставки в системах с графическим интерфейсом, то перемещение аналогично операции вырезания и вставки: как мы сделали с операцией копирования:
@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));
}
9. Заключение
@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));
}
В этой статье мы узнали о файловых API в новом API файловой системы (NIO2), который был поставлен как часть Java 7 и видел большинство важных файловых операций в действии.
Примеры кода, использованные в этой статье, можно найти в проекте статьи на Github.
«