W3docs

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

Создание файлов и директорий в Java с помощью File.createNewFile, Files.createFile и Files.createDirectories.

«Создать новый пустой файл» и «создать дерево директорий» — две простейшие операции с файловой системой, однако Java предоставляет четыре перекрывающихся способа выполнить каждую из них. Различия важны: это разница между «падает, если файл существует» и «молча перезаписывает», между mkdir и mkdir -p, между устаревшим методом, возвращающим boolean, и современным, выбрасывающим исключение.

Эта глава охватывает четыре способа создания:

  • File.createNewFile() — устаревшее создание файла.
  • File.mkdir() / File.mkdirs() — устаревшее создание директорий.
  • Files.createFile(path) — современное атомарное «создать или завершить с ошибкой».
  • Files.createDirectory(path) / Files.createDirectories(path) — современное создание директорий.

А также вспомогательные методы для временных файлов (Files.createTempFile, Files.createTempDirectory) и флаги OpenOption, позволяющие писателям создавать файлы неявно.

File.createNewFile() — устаревший метод, возвращает boolean

File f = new File("data/users.txt");
boolean created = f.createNewFile();      // true if it actually created the file
                                          // false if it already existed
                                          // throws IOException if the parent dir is missing

Контракт — атомарная проверка и создание: ОС гарантирует, что ни один другой процесс не сможет создать файл по тому же пути между проверкой существования и самим созданием. Это делает createNewFile примитивной блокировкой в некоторых устаревших идиомах «PID-файла»: if (!f.createNewFile()) throw new IllegalStateException("already running");

Чего он не делает:

  • Он не создаёт отсутствующие родительские директории. new File("does/not/exist/file.txt").createNewFile() выбрасывает IOException.
  • Возвращает false (а не исключение), если файл уже существует.

Если вам нужно лишь, чтобы файл существовал по завершении вызова, возврат false вполне приемлем. Если вам нужен гарантированно новый файл (семантика блокировки), false — сигнал для перехода к другому пути выполнения.

File.mkdir() и File.mkdirs()

new File("logs").mkdir();           // creates "logs" — fails if "." has no perms or parent missing
new File("a/b/c").mkdirs();          // creates "a", "a/b", and "a/b/c" — like `mkdir -p`

Оба возвращают boolean, оба теряют информацию о причине сбоя. mkdir падает, если родительская директория отсутствует; mkdirs — нет. Оба возвращают true только при успешном создании директории — если она уже существует, возвращается false. В сочетании с проблемой «нет информации об ошибке» это пример устаревшего API, который обычно оборачивают в вспомогательный метод:

File dir = new File("data");
if (!dir.exists() && !dir.mkdirs()) throw new IOException("cannot create " + dir);

Современный Files.createDirectories(path) — это однострочная замена.

Files.createFile(path) — современный метод, выбрасывает исключение

Files.createFile — это аналог File.createNewFile() из пакета java.nio.file с одним важным отличием: он выбрасывает исключение вместо возврата boolean.

Path p = Path.of("data/users.txt");
Files.createFile(p);                          // creates an empty regular file
                                              // throws FileAlreadyExistsException if it exists
                                              // throws NoSuchFileException if the parent is missing

FileAlreadyExistsException — то, что вы перехватываете, если важен факт существования файла; NoSuchFileException — то, что вы перехватываете (или предотвращаете с помощью createDirectories), если родительская директория может отсутствовать. Типы исключений — конкретные подклассы IOException, поэтому обобщённый catch (IOException e) по-прежнему работает.

Можно передать аргументы FileAttribute для установки прав доступа POSIX в момент создания в Unix — чаще всего это используется для гарантии того, что секретные файлы (приватные ключи, токены) создаются с правами 0600:

import static java.nio.file.attribute.PosixFilePermissions.*;
var attr = asFileAttribute(fromString("rw-------"));
Files.createFile(Path.of("/tmp/secret"), attr);   // born with 0600 permissions, atomically

(На Windows этот вызов выбрасывает UnsupportedOperationException, поскольку там нет модели прав доступа POSIX — добавьте проверку платформы, если ориентируетесь на обе системы.)

Files.createDirectory и Files.createDirectories

Та же разница, что между mkdir и mkdir -p, только на основе исключений:

Files.createDirectory(Path.of("logs"));          // one level deep; parent must exist
Files.createDirectories(Path.of("a/b/c"));        // creates every missing ancestor

createDirectory выбрасывает FileAlreadyExistsException, если целевой путь уже существует и не является директорией; если это директория — тоже выбрасывает (обычно нежелательное поведение).

createDirectories — более удобный вариант: он ничего не делает, если все директории уже существуют, и создаёт то, чего не хватает. Он не выбрасывает исключение, если путь уже существует как директория. Это делает его идемпотентным — безопасным для вызова при старте без проверки exists().

Временные файлы и директории

Для тестов, временного хранилища и ситуаций «мне нужно куда-то положить это на несколько минут» JDK предоставляет Files.createTempFile и Files.createTempDirectory:

Path scratch = Files.createTempFile("session-", ".log");          // /tmp/session-3829387.log
Path workdir = Files.createTempDirectory("export-");               // /tmp/export-1827392

Оба выбирают уникальное имя в системной временной директории, оба возвращают Path к созданному объекту и оба создают объект с ограниченными правами доступа в Unix. Префикс и суффикс — это подсказки, к которым JDK добавляет уникальное значение; выбрать точное имя нельзя (в этом и смысл: другой вызывающий не может его предсказать и перезаписать ваш файл).

Временные файлы не удаляются автоматически. Нужно либо:

  • Вызвать Files.deleteIfExists(path) по завершении работы; либо
  • Вызвать path.toFile().deleteOnExit(), чтобы запланировать удаление при завершении JVM (не срабатывает при принудительном завершении); либо
  • Открыть файл с StandardOpenOption.DELETE_ON_CLOSE, если он нужен только на время открытого потока.

Писатели создают файлы неявно

В большинстве случаев вызов «создать файл» вообще не нужен — писатель создаёт его за вас. Files.newBufferedWriter, Files.write и Files.writeString принимают varargs OpenOption..., определяющие поведение при наличии или отсутствии файла:

import static java.nio.file.StandardOpenOption.*;
Files.writeString(path, "hello\n", CREATE, WRITE, TRUNCATE_EXISTING);
Files.writeString(path, "more\n",  CREATE, WRITE, APPEND);
Files.writeString(path, "new\n",   CREATE_NEW);     // fails if file exists
  • CREATE — создать, если отсутствует; иначе открыть существующий.
  • CREATE_NEW — создать; выбросить FileAlreadyExistsException, если файл уже существует. Та же семантика, что у Files.createFile.
  • TRUNCATE_EXISTING — очистить содержимое файла при открытии (поведение по умолчанию для writeString без режима добавления).
  • APPEND — записывать в конец без усечения.

По умолчанию Files.writeString (без опций) использует CREATE, WRITE, TRUNCATE_EXISTING — то есть «создать или перезаписать». Files.newBufferedWriter по умолчанию ведёт себя так же. Для режима добавления нужно указать его явно.

Практический пример: все способы создания рядом

Программа ниже строит небольшое дерево директорий с нуля в системной временной папке, используя оба API и несколько режимов открытия. Каждый шаг выводит, что изменилось; последний блок демонстрирует, что происходит при повторном выполнении операции над уже существующим путём.

java— editable, runs on the server

Что стоит вынести из запуска:

  • Первый legacy.createNewFile() вернул true (создан); второй — false (уже существует). boolean не сообщает, что именно произошло — нужно помнить соглашение.
  • deep.mkdirs() успешно выполнился один раз и вернул false при повторном вызове. Этот false неотличим от «нет прав доступа» или «родительская директория отсутствует» — именно эту проблему отсутствия информации об ошибке решает Files.
  • Files.createFile на существующем пути выбросил FileAlreadyExistsException. Тип исключения конкретен, поэтому реальный обработчик может отличить «уже существует» от «нет прав доступа» без разбора строк.
  • Files.createDirectories, вызванный дважды подряд, не причинил никакого вреда при втором вызове. Именно это свойство делает его правильным выбором для кода инициализации: не нужна проверка, просто вызываем.
  • Files.writeString(log, "line 1\n") создал файл, потому что CREATE входит в опции по умолчанию. Второй и третий вызовы явно использовали APPEND, и файл накопил три строки. Четвёртый вызов использовал CREATE_NEW и отказался перезаписывать. Значения по умолчанию рассчитаны на случай «перезаписать новым содержимым»; режим добавления нужно указывать явно.
  • Files.createTempFile(root, "scratch-", ".tmp") создал имя вроде scratch-1827392.tmp — ваш префикс и суффикс плюс уникальный фрагмент, выбранный JVM, чтобы два параллельных вызова никогда не конфликтовали.
  • При очистке обход root выполняется в обратном порядке, чтобы дочерние файлы и директории удалялись раньше родительских. Files.delete отказывается удалять непустую директорию; такой порядок — это и есть реализация ручного rm -rf.

Что дальше

Вы умеете создавать файлы; следующая глава, Чтение файлов в Java, посвящена их чтению — сначала с помощью современных однострочников (Files.readString, Files.readAllLines, Files.lines), затем с помощью классического стека декораторов FileReader / BufferedReader / Scanner, чтобы в последующих главах был фундамент для работы с устаревшим кодом.

Практика

Практика
Вам нужен однострочник, который создаёт директорию вместе с любыми отсутствующими родительскими директориями и **ничего не делает**, если директория уже существует. Какой вызов подходит?
Вам нужен однострочник, который создаёт директорию вместе с любыми отсутствующими родительскими директориями и **ничего не делает**, если директория уже существует. Какой вызов подходит?
Was this page helpful?