Работа с HTTP-методами в Spring: GET, POST, PUT, DELETE
HTTP-методы (или «глаголы») — это действия, которые клиент может выполнять на ресурсе в веб-приложении. В контексте RESTful архитектуры, они представляют собой стандартные операции для взаимодействия с ресурсами.
Spring предоставляет удобный механизм работы с HTTP-методами через аннотации контроллеров, такие как @GetMapping, @PostMapping, @PutMapping, @DeleteMapping, которые упрощают реализацию RESTful API.
Настройка HTTP-методов в Spring
Общий пример контроллера
HTTP-методы
1. GET — Чтение данных
Назначение: Возвращает данные с сервера (например, список пользователей, детали одного пользователя).
Идемпотентный метод: Повторный вызов GET не изменяет состояние ресурса.
Пример запроса:
Реализация в Spring:
Особенности:
Используется аннотация @GetMapping.
Данные возвращаются в формате JSON или XML в зависимости от заголовка Accept.
2. POST — Создание нового ресурса
Назначение: Создает новый ресурс на сервере.
Не идемпотентный метод: Повторный вызов создаёт новые записи.
Пример запроса:
Реализация в Spring:
Особенности:
Используется аннотация @PostMapping.
Данные передаются через тело запроса (@RequestBody).
В ответе часто возвращается код 201 Created и URL нового ресурса.
3. PUT — Обновление ресурса
Назначение: Обновляет существующий ресурс или создаёт его, если он не существует.
Идемпотентный метод: Повторный вызов приводит к тому же результату.
Пример запроса:
Реализация в Spring:
Особенности:
Используется аннотация @PutMapping.
Данные для обновления передаются через тело запроса.
В ответе обычно возвращается 200 OK с обновлённым ресурсом.
4. DELETE — Удаление ресурса
Назначение: Удаляет ресурс на сервере.
Идемпотентный метод: Повторный вызов приводит к тому же результату (ресурс уже удалён).
Пример запроса:
Реализация в Spring:
Особенности:
Используется аннотация @DeleteMapping.
В ответе часто возвращается 204 No Content.
#Java #Training #Spring #GET #PUT #POST #DELETE
HTTP-методы (или «глаголы») — это действия, которые клиент может выполнять на ресурсе в веб-приложении. В контексте RESTful архитектуры, они представляют собой стандартные операции для взаимодействия с ресурсами.
Spring предоставляет удобный механизм работы с HTTP-методами через аннотации контроллеров, такие как @GetMapping, @PostMapping, @PutMapping, @DeleteMapping, которые упрощают реализацию RESTful API.
Настройка HTTP-методов в Spring
Общий пример контроллера
@RestController
@RequestMapping("/api/users")
public class UserController {
@GetMapping("/{id}")
public User getUser(@PathVariable Long id) {
return new User(id, "John Doe", "john@example.com");
}
@PostMapping
public ResponseEntity<User> createUser(@RequestBody User user) {
user.setId(1L); // Пример генерации ID
return ResponseEntity.status(HttpStatus.CREATED).body(user);
}
@PutMapping("/{id}")
public ResponseEntity<User> updateUser(@PathVariable Long id, @RequestBody User user) {
user.setId(id);
return ResponseEntity.ok(user);
}
@DeleteMapping("/{id}")
public ResponseEntity<Void> deleteUser(@PathVariable Long id) {
return ResponseEntity.noContent().build();
}
}
HTTP-методы
1. GET — Чтение данных
Назначение: Возвращает данные с сервера (например, список пользователей, детали одного пользователя).
Идемпотентный метод: Повторный вызов GET не изменяет состояние ресурса.
Пример запроса:
GET /api/users/1 HTTP/1.1
Host: example.com
Реализация в Spring:
@GetMapping("/{id}")
public User getUser(@PathVariable Long id) {
return new User(id, "John Doe", "john@example.com");
}Особенности:
Используется аннотация @GetMapping.
Данные возвращаются в формате JSON или XML в зависимости от заголовка Accept.
2. POST — Создание нового ресурса
Назначение: Создает новый ресурс на сервере.
Не идемпотентный метод: Повторный вызов создаёт новые записи.
Пример запроса:
POST /api/users HTTP/1.1
Content-Type: application/json
Host: example.com
{
"name": "Jane Doe",
"email": "jane@example.com"
}
Реализация в Spring:
@PostMapping
public ResponseEntity<User> createUser(@RequestBody User user) {
user.setId(1L); // Симуляция сохранения с генерацией ID
return ResponseEntity.status(HttpStatus.CREATED).body(user);
}
Особенности:
Используется аннотация @PostMapping.
Данные передаются через тело запроса (@RequestBody).
В ответе часто возвращается код 201 Created и URL нового ресурса.
3. PUT — Обновление ресурса
Назначение: Обновляет существующий ресурс или создаёт его, если он не существует.
Идемпотентный метод: Повторный вызов приводит к тому же результату.
Пример запроса:
PUT /api/users/1 HTTP/1.1
Content-Type: application/json
Host: example.com
{
"name": "John Smith",
"email": "john.smith@example.com"
}
Реализация в Spring:
@PutMapping("/{id}")
public ResponseEntity<User> updateUser(@PathVariable Long id, @RequestBody User user) {
user.setId(id); // Симуляция обновления ресурса
return ResponseEntity.ok(user);
}Особенности:
Используется аннотация @PutMapping.
Данные для обновления передаются через тело запроса.
В ответе обычно возвращается 200 OK с обновлённым ресурсом.
4. DELETE — Удаление ресурса
Назначение: Удаляет ресурс на сервере.
Идемпотентный метод: Повторный вызов приводит к тому же результату (ресурс уже удалён).
Пример запроса:
DELETE /api/users/1 HTTP/1.1
Host: example.com
Реализация в Spring:
@DeleteMapping("/{id}")
public ResponseEntity<Void> deleteUser(@PathVariable Long id) {
return ResponseEntity.noContent().build();
}Особенности:
Используется аннотация @DeleteMapping.
В ответе часто возвращается 204 No Content.
#Java #Training #Spring #GET #PUT #POST #DELETE
👍1
Примеры сложных сценариев
1. Обработка ошибок
Если ресурс не найден, можно вернуть соответствующий HTTP-ответ:
2. Валидация данных
Использование @Valid для проверки корректности входящих данных:
Пример ошибок валидации:
3. Поддержка разных форматов данных
Spring поддерживает автоматическое преобразование данных в зависимости от заголовков запроса:
JSON (application/json)
XML (application/xml)
Пример настройки:
#Java #Training #Spring #GET #PUT #POST #DELETE
1. Обработка ошибок
Если ресурс не найден, можно вернуть соответствующий HTTP-ответ:
@GetMapping("/{id}")
public ResponseEntity<User> getUser(@PathVariable Long id) {
return userService.findById(id)
.map(ResponseEntity::ok)
.orElse(ResponseEntity.status(HttpStatus.NOT_FOUND).build());
}2. Валидация данных
Использование @Valid для проверки корректности входящих данных:
@PostMapping
public ResponseEntity<User> createUser(@Valid @RequestBody User user) {
user.setId(1L);
return ResponseEntity.status(HttpStatus.CREATED).body(user);
}
Пример ошибок валидации:
{
"timestamp": "2024-12-11T12:00:00",
"status": 400,
"errors": [
{
"field": "name",
"message": "Name is required"
}
]
}3. Поддержка разных форматов данных
Spring поддерживает автоматическое преобразование данных в зависимости от заголовков запроса:
JSON (application/json)
XML (application/xml)
Пример настройки:
@GetMapping(value = "/{id}", produces = {MediaType.APPLICATION_JSON_VALUE, MediaType.APPLICATION_XML_VALUE})
public User getUser(@PathVariable Long id) {
return new User(id, "John Doe", "john@example.com");
}#Java #Training #Spring #GET #PUT #POST #DELETE
👍1
Раздел 11. Работа с файлами, I/O и сетью (NIO.2)
Глава 2. Современный NIO.2 (java.nio.file)
Перемещение и переименование файлов и директорий
Перемещение и удаление — это операции, которые завершают жизненный цикл файла в файловой системе.
В отличие от копирования, которое создаёт новую копию данных, перемещение часто не требует физического перемещения байтов на диске — достаточно изменить метаданные в файловой системе.
Удаление, в свою очередь, освобождает inode и блоки данных, но реальное освобождение дискового пространства зависит от того, есть ли другие ссылки на те же данные. Класс
inode (index node) — это структура данных в файловых системах Unix, хранящая метаданные файла: права доступа, владелец, размер, временные метки и указатели на блоки данных. Имя файла — это просто ссылка на inode в директории.
Files.move(Path source, Path target) — перемещение и переименование
Метод
Разница между копированием и перемещением
Копирование (
Перемещение в пределах одного
Перемещение между разными
FileStore — абстракция над хранилищем файловой системы (раздел диска, том, сетевой ресурс). Каждый раздел имеет свой
Опции перемещения
StandardCopyOption.REPLACE_EXISTING
Разрешает перезапись существующего файла или директории в точке назначения. Без этой опции
Важно: при перезаписи директории целевая директория должна быть пустой. Если
StandardCopyOption.ATOMIC_MOVE
Выполняет атомарное перемещение. Это означает, что операция либо завершится целиком, либо не произойдёт вообще. Другие процессы никогда не увидят промежуточного состояния, где ни источник, ни назначение не существуют.
Ограничения
Источник и назначение должны находиться на одном
Нельзя комбинировать с
Если назначение существует и передан
Атомарность в контексте файловых систем означает неделимость операции. Для
Пример: перемещение обработанного файла в архивную папку
В этом примере
#Java #для_новичков #beginner #IO #NIO #Files #move #delete
Глава 2. Современный NIO.2 (java.nio.file)
Перемещение и переименование файлов и директорий
Перемещение и удаление — это операции, которые завершают жизненный цикл файла в файловой системе.
В отличие от копирования, которое создаёт новую копию данных, перемещение часто не требует физического перемещения байтов на диске — достаточно изменить метаданные в файловой системе.
Удаление, в свою очередь, освобождает inode и блоки данных, но реальное освобождение дискового пространства зависит от того, есть ли другие ссылки на те же данные. Класс
Files предоставляет атомарные и безопасные методы для этих операций, каждый из которых имеет чёткую семантику ошибок.inode (index node) — это структура данных в файловых системах Unix, хранящая метаданные файла: права доступа, владелец, размер, временные метки и указатели на блоки данных. Имя файла — это просто ссылка на inode в директории.
Files.move(Path source, Path target) — перемещение и переименование
Метод
Files.move(Path source, Path target, CopyOption... options) выполняет перемещение файла или директории из source в target. На уровне файловой системы это может быть либо переименование в пределах одного раздела (атомарная операция, изменяющая только записи в директориях), либо копирование с последующим удалением источника (если источник и назначение на разных разделах или файловых системах).import java.nio.file.*;
import java.io.IOException;
public class FileMover {
public void renameFile(Path oldName, Path newName) throws IOException {
// Простое переименование в той же директории
Files.move(oldName, newName);
}
public void moveToArchive(Path file, Path archiveDir) throws IOException {
// Перемещение в другую директорию
Path target = archiveDir.resolve(file.getFileName());
Files.move(file, target);
}
}
Разница между копированием и перемещением
Копирование (
Files.copy) создаёт независимую копию данных. После копирования источник и назначение — это два разных файла с разными inode, каждый занимает своё дисковое пространство. Изменение одного не влияет на другой.Перемещение в пределах одного
FileStore (одного раздела диска) — это операция над метаданными. Файловая система изменяет запись в родительской директории источника (удаляет ссылку на inode) и добавляет запись в родительскую директорию назначения. Сами данные на диске не перемещаются. inode остаётся тем же, временные метки данных не меняются. Это выполняется за один системный вызов rename() и является атомарным.Перемещение между разными
FileStore (разные разделы, разные диски, сетевые ФС) невозможно как атомарная операция над метаданными. JVM выполняет копирование данных из источника в назначение, а затем удаляет источник. Это неатомарно: если копирование прошло успешно, но удаление источника не удалось (например, файл заблокирован), вы получите частичный результат — данные продублированы, а исходный файл остался на месте.FileStore — абстракция над хранилищем файловой системы (раздел диска, том, сетевой ресурс). Каждый раздел имеет свой
FileStore, и перемещение между разными FileStore требует физического копирования данных.Опции перемещения
StandardCopyOption.REPLACE_EXISTING
Разрешает перезапись существующего файла или директории в точке назначения. Без этой опции
Files.move() выбросит FileAlreadyExistsException, если target уже существует.Path src = Paths.get("/data/report_v1.txt");
Path dst = Paths.get("/data/report.txt");
// Перезаписываем старый отчёт новым
Files.move(src, dst, StandardCopyOption.REPLACE_EXISTING);Важно: при перезаписи директории целевая директория должна быть пустой. Если
target — непустая директория, даже с REPLACE_EXISTING будет выброшено DirectoryNotEmptyException.StandardCopyOption.ATOMIC_MOVE
Выполняет атомарное перемещение. Это означает, что операция либо завершится целиком, либо не произойдёт вообще. Другие процессы никогда не увидят промежуточного состояния, где ни источник, ни назначение не существуют.
Path src = Paths.get("/data/temp_report.txt");
Path dst = Paths.get("/data/report.txt");
// Атомарное переименование: читатели либо видят старый файл, либо новый
Files.move(src, dst, StandardCopyOption.ATOMIC_MOVE);Ограничения
ATOMIC_MOVE:Источник и назначение должны находиться на одном
FileStore. Если это не так, выбрасывается AtomicMoveNotSupportedException.Нельзя комбинировать с
COPY_ATTRIBUTES — атомарное перемещение не копирует атрибуты, оно переносит inode целиком, вместе со всеми его метаданными.Если назначение существует и передан
REPLACE_EXISTING, перезапись тоже будет атомарной.Атомарность в контексте файловых систем означает неделимость операции. Для
rename() атомарность гарантируется на уровне ядра ОС: запись в структуры директорий защищена блокировками, и другие процессы видят либо старое имя, либо новое, но не промежуточное состояние.Пример: перемещение обработанного файла в архивную папку
import java.nio.file.*;
import java.io.IOException;
import java.time.Instant;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
public class ArchiveMover {
private static final DateTimeFormatter DATE_FORMAT =
DateTimeFormatter.ofPattern("yyyy-MM-dd").withZone(ZoneId.systemDefault());
public void archiveProcessedFile(Path processedFile, Path archiveRoot) throws IOException {
// Проверяем, что источник существует и является файлом
if (!Files.isRegularFile(processedFile)) {
throw new NoSuchFileException("Файл не найден или не является обычным файлом: " + processedFile);
}
// Создаём архивную директорию с текущей датой: /archive/2024-07-14/
String today = DATE_FORMAT.format(Instant.now());
Path datedArchive = archiveRoot.resolve(today);
Files.createDirectories(datedArchive);
// Формируем целевой путь
String fileName = processedFile.getFileName().toString();
Path target = datedArchive.resolve(fileName);
// Если файл с таким именем уже есть в архиве — добавляем счётчик
Path finalTarget = resolveUniqueName(target);
// Атомарное перемещение
Files.move(processedFile, finalTarget, StandardCopyOption.ATOMIC_MOVE);
System.out.println("Файл перемещён в архив: " + finalTarget.toAbsolutePath());
}
private Path resolveUniqueName(Path target) {
if (Files.notExists(target)) {
return target;
}
// Генерируем уникальное имя: file.txt -> file_1.txt, file_2.txt
String fileName = target.getFileName().toString();
int lastDot = fileName.lastIndexOf('.');
String base = lastDot > 0 ? fileName.substring(0, lastDot) : fileName;
String ext = lastDot > 0 ? fileName.substring(lastDot) : "";
Path parent = target.getParent();
int counter = 1;
Path candidate;
do {
candidate = parent.resolve(base + "_" + counter + ext);
counter++;
} while (Files.exists(candidate));
return candidate;
}
public static void main(String[] args) {
try {
Path file = Paths.get("/app/inbox/order_12345.xml");
Path archive = Paths.get("/app/archive");
new ArchiveMover().archiveProcessedFile(file, archive);
} catch (AtomicMoveNotSupportedException e) {
System.err.println("Атомарное перемещение невозможно — источник и архив на разных разделах");
// Fallback: копирование + удаление
} catch (IOException e) {
System.err.println("Ошибка архивирования: " + e.getMessage());
}
}
}
В этом примере
ATOMIC_MOVE гарантирует, что в процессе перемещения читатели директории inbox либо видят файл на месте, либо не видят его вообще (уже в архиве). Промежуточного состояния "файл исчез" не возникает. Если архив находится на другом разделе, ловим AtomicMoveNotSupportedException и применяем fallback-стратегию.#Java #для_новичков #beginner #IO #NIO #Files #move #delete
👍5
Удаление файлов и директорий
Files.delete(Path) — строгое удаление
Метод
Строгость этого метода полезна, когда вы ожидаете, что файл существует, и хотите знать, если это не так. Например, при очистке временного файла, который обязательно должен был создаться предыдущим шагом.
Files.deleteIfExists(Path) — условное удаление
Метод
Этот метод идеален для идемпотентной очистки: можно вызывать многократно без ошибок.
DirectoryNotEmptyException
Оба метода
Обход дерева с удалением всех файлов
Для рекурсивного удаления директории со всем содержимым используется обход дерева в обратном порядке: сначала удаляются файлы и пустые поддиректории на самом глубоком уровне, затем — родительские директории.
Почему
UncheckedIOException — это обёртка вокруг
Пример: удаление всех .tmp файлов во временной папке
В этом примере
DirectoryStream — это интерфейс в NIO.2, представляющий поток записей директории. В отличие от
#Java #для_новичков #beginner #IO #NIO #Files #move #delete
Files.delete(Path) — строгое удаление
Метод
Files.delete(Path path) удаляет файл, директорию или символическую ссылку. Если объект не существует — выбрасывает NoSuchFileException. Если путь — непустая директория — выбрасывает DirectoryNotEmptyException. Если нет прав на удаление — AccessDeniedException.Path file = Paths.get("/tmp/old_data.tmp");
Files.delete(file); // NoSuchFileException, если файла нетСтрогость этого метода полезна, когда вы ожидаете, что файл существует, и хотите знать, если это не так. Например, при очистке временного файла, который обязательно должен был создаться предыдущим шагом.
Files.deleteIfExists(Path) — условное удаление
Метод
Files.deleteIfExists(Path path) удаляет файл, если он существует. Возвращает true, если файл был удалён, false — если файла не было. Не выбрасывает исключение при отсутствии файла, но всё ещё выбрасывает DirectoryNotEmptyException и AccessDeniedException.Path tempFile = Paths.get("/tmp/session_abc123.tmp");
boolean wasDeleted = Files.deleteIfExists(tempFile);
if (wasDeleted) {
System.out.println("Временный файл удалён");
} else {
System.out.println("Файл уже был удалён или не существовал");
}Этот метод идеален для идемпотентной очистки: можно вызывать многократно без ошибок.
DirectoryNotEmptyException
Оба метода
delete() и deleteIfExists() не удаляют непустые директории. Это ограничение на уровне файловой системы: удаление директории требует сначала удалить все её содержимое. Файловая система хранит список записей в директории, и ядро ОС отказывает в удалении, пока список не пуст.Path dir = Paths.get("/tmp/myapp");
try {
Files.delete(dir); // DirectoryNotEmptyException, если внутри есть файлы
} catch (DirectoryNotEmptyException e) {
// Нужно сначала удалить содержимое
}Обход дерева с удалением всех файлов
Для рекурсивного удаления директории со всем содержимым используется обход дерева в обратном порядке: сначала удаляются файлы и пустые поддиректории на самом глубоком уровне, затем — родительские директории.
import java.nio.file.*;
import java.io.IOException;
import java.util.Comparator;
import java.util.stream.Stream;
public class TreeDeleter {
public void deleteDirectoryRecursively(Path root) throws IOException {
if (!Files.exists(root)) {
return; // Нечего удалять
}
// walk возвращает Stream<Path> в порядке pre-order (родитель до детей)
// sorted(reverseOrder()) переворачивает: сначала глубокие пути, потом корень
try (Stream<Path> walk = Files.walk(root)) {
walk.sorted(Comparator.reverseOrder())
.forEach(path -> {
try {
Files.delete(path);
} catch (IOException e) {
// Оборачиваем в UncheckedIOException для использования в forEach
throw new UncheckedIOException(
"Не удалось удалить: " + path, e
);
}
});
} catch (UncheckedIOException e) {
// Распаковываем обратно в checked исключение
throw e.getCause();
}
}
}
Почему
Comparator.reverseOrder()? Метод Files.walk() обходит дерево в порядке pre-order: сначала корень, потом его дети. Если мы будем удалять в этом порядке, попытка удалить корневую директорию первой выбросит DirectoryNotEmptyException, так как дети ещё не удалены. reverseOrder() сортирует пути по строковому представлению в обратном порядке, что гарантирует, что более глубокие пути (с большим количеством сегментов) обрабатываются раньше.UncheckedIOException — это обёртка вокруг
IOException, наследующая RuntimeException. Она нужна потому, что функциональные интерфейсы в Java (например, Consumer в forEach) не могут выбрасывать checked-исключения. Оборачивая IOException в UncheckedIOException, мы пробрасываем его через лямбду, а затем распаковываем вне.Пример: удаление всех .tmp файлов во временной папке
import java.nio.file.*;
import java.io.IOException;
import java.util.stream.Stream;
public class TempCleaner {
private static final Path TEMP_DIR = Paths.get(System.getProperty("java.io.tmpdir"));
public void cleanTempFiles() throws IOException {
// Проверяем, что TEMP_DIR существует и является директорией
if (!Files.isDirectory(TEMP_DIR)) {
throw new NotDirectoryException(TEMP_DIR.toString());
}
// Обходим только верхний уровень временной директории
// newDirectoryStream — более лёгкая альтернатива walk для одного уровня
try (DirectoryStream<Path> stream = Files.newDirectoryStream(TEMP_DIR)) {
for (Path entry : stream) {
if (Files.isRegularFile(entry) && entry.toString().endsWith(".tmp")) {
try {
Files.delete(entry);
System.out.println("Удалён: " + entry.getFileName());
} catch (IOException e) {
// Логируем, но продолжаем удаление остальных
System.err.println("Не удалось удалить " + entry.getFileName()
+ ": " + e.getMessage());
}
}
}
}
}
// Альтернатива: удаление .tmp файлов рекурсивно во всём поддереве
public void cleanTempFilesRecursive(Path root) throws IOException {
try (Stream<Path> walk = Files.walk(root)) {
walk.filter(Files::isRegularFile)
.filter(p -> p.toString().endsWith(".tmp"))
.forEach(p -> {
try {
Files.delete(p);
System.out.println("Удалён: " + p);
} catch (IOException e) {
System.err.println("Ошибка удаления " + p + ": " + e.getMessage());
}
});
}
}
// Удаление .tmp файлов старше N дней
public void cleanOldTempFiles(int daysOld) throws IOException {
long cutoff = System.currentTimeMillis() - (daysOld * 24 * 60 * 60 * 1000L);
try (Stream<Path> walk = Files.walk(TEMP_DIR)) {
walk.filter(Files::isRegularFile)
.filter(p -> p.toString().endsWith(".tmp"))
.filter(p -> {
try {
return Files.getLastModifiedTime(p).toMillis() < cutoff;
} catch (IOException e) {
return false; // Не удалось прочитать — пропускаем
}
})
.forEach(p -> {
try {
Files.delete(p);
System.out.println("Удалён старый файл: " + p.getFileName());
} catch (IOException e) {
System.err.println("Ошибка: " + e.getMessage());
}
});
}
}
public static void main(String[] args) {
try {
TempCleaner cleaner = new TempCleaner();
cleaner.cleanTempFiles();
cleaner.cleanOldTempFiles(7); // удалить .tmp старше 7 дней
} catch (IOException e) {
System.err.println("Ошибка очистки: " + e.getMessage());
}
}
}
В этом примере
newDirectoryStream() — более лёгкая альтернатива Files.walk() для обхода только одного уровня. Она не строит дерево и не буферизует пути, а возвращает итератор, который читает директорию порциями. Это экономит память при работе с директориями, содержащими миллионы файлов.DirectoryStream — это интерфейс в NIO.2, представляющий поток записей директории. В отличие от
Stream<Path>, он не поддерживает промежуточные операции (filter, map), но эффективнее для простого обхода, так как не создаёт промежуточных объектов потока.#Java #для_новичков #beginner #IO #NIO #Files #move #delete
👍6
Путь байтов в памяти JVM при перемещении и удалении
Перемещение: путь данных
При
Объекты
Если источник и назначение на одном
Удаляется запись в родительской директории источника.
Создаётся запись в родительской директории назначения, указывающая на тот же inode.
Блоки данных на диске не перемещаются.
Поскольку данные не копируются, в куче JVM не создаётся никаких буферов или массивов байтов. Объекты
Если
Если источник и назначение на разных
При этом:
Создаётся буфер
Данные копируются блоками: чтение в буфер, запись из буфера.
После успешного копирования источник удаляется через
Буфер становится мусором и собирается Minor GC.
Удаление: путь данных
При
Объект
Выполняется системный вызов
Удаляет запись в родительской директории.
Уменьшает счётчик ссылок (reference count) на inode на 1.
Если счётчик ссылок достигает 0 и нет открытых файловых дескрипторов на этот inode, блоки данных помечаются как свободные.
Если счётчик 0, но дескрипторы открыты (например, другой процесс читает файл), блоки остаются занятыми до закрытия всех дескрипторов. Файл становится "невидимым" в файловой системе, но продолжает занимать место на диске.
Никаких новых объектов в куче JVM не создаётся. Операция полностью работает с метаданными файловой системы.
При удалении директории через
Рекурсивное удаление: путь данных и GC
При рекурсивном удалении через
Альтернатива без материализации — обход вручную через
FileVisitor — это интерфейс в NIO.2, определяющий callback-методы для обхода дерева:
#Java #для_новичков #beginner #IO #NIO #Files #move #delete
Перемещение: путь данных
При
Files.move(source, target, ATOMIC_MOVE):Объекты
source и target — это Path в куче. Их строковые представления извлекаются и кодируются в байты пути в нативной кодировке.Если источник и назначение на одном
FileStore, выполняется системный вызов rename() (POSIX) или MoveFileEx() (Windows). Этот вызов работает исключительно с метаданными файловой системы:Удаляется запись в родительской директории источника.
Создаётся запись в родительской директории назначения, указывающая на тот же inode.
Блоки данных на диске не перемещаются.
Поскольку данные не копируются, в куче JVM не создаётся никаких буферов или массивов байтов. Объекты
source и target остаются в куче как обычные объекты Path. Если они локальные переменные метода, они становятся мусором после выхода из метода и собираются при следующей Minor GC.Если
REPLACE_EXISTING передан и целевой файл существует, файловая система сначала удаляет целевой файл (уменьшает счётчик ссылок на его inode), а затем выполняет rename(). Это тоже атомарно на уровне ядра.Если источник и назначение на разных
FileStore, ATOMIC_MOVE выбросит AtomicMoveNotSupportedException до выполнения каких-либо операций. Без ATOMIC_MOVE JVM выполняет fallback: копирование через буфер в куче + удаление источника. При этом:
Создаётся буфер
byte[] в Young Generation (обычно 8–32 КБ).Данные копируются блоками: чтение в буфер, запись из буфера.
После успешного копирования источник удаляется через
unlink().Буфер становится мусором и собирается Minor GC.
Удаление: путь данных
При
Files.delete(path):Объект
Path в куче преобразуется в байты пути в нативной памяти.Выполняется системный вызов
unlink() (POSIX) или DeleteFile() (Windows). Ядро ОС:Удаляет запись в родительской директории.
Уменьшает счётчик ссылок (reference count) на inode на 1.
Если счётчик ссылок достигает 0 и нет открытых файловых дескрипторов на этот inode, блоки данных помечаются как свободные.
Если счётчик 0, но дескрипторы открыты (например, другой процесс читает файл), блоки остаются занятыми до закрытия всех дескрипторов. Файл становится "невидимым" в файловой системе, но продолжает занимать место на диске.
Никаких новых объектов в куче JVM не создаётся. Операция полностью работает с метаданными файловой системы.
При удалении директории через
rmdir() (POSIX) ядро проверяет, что директория содержит только записи . и .. (текущая и родительская). Если есть другие записи — возвращается ENOTEMPTY, который JVM преобразует в DirectoryNotEmptyException.Рекурсивное удаление: путь данных и GC
При рекурсивном удалении через
Files.walk():Files.walk(root) создаёт Stream<Path>, который лениво обходит дерево. Каждый вызов walk() порождает внутренние объекты итератора директории в куче.sorted(Comparator.reverseOrder()) материализует весь поток в список для сортировки. Это означает, что все пути дерева загружаются в память одновременно. Для дерева из миллиона файлов это создаст миллион объектов Path в куче, что может привести к OutOfMemoryError.Альтернатива без материализации — обход вручную через
FileVisitor:public void deleteRecursivelySafe(Path root) throws IOException {
// FileVisitor — интерфейс для обхода дерева с callback-методами
Files.walkFileTree(root, new SimpleFileVisitor<Path>() {
@Override
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs)
throws IOException {
Files.delete(file);
return FileVisitResult.CONTINUE;
}
@Override
public FileVisitResult postVisitDirectory(Path dir, IOException exc)
throws IOException {
if (exc != null) {
throw exc; // ошибка при обходе поддиректории
}
Files.delete(dir);
return FileVisitResult.CONTINUE;
}
});
}FileVisitor — это интерфейс в NIO.2, определяющий callback-методы для обхода дерева:
preVisitDirectory (перед входом в директорию), visitFile (при обнаружении файла), visitFileFailed (при ошибке доступа к файлу), postVisitDirectory (после выхода из директории). SimpleFileVisitor — адаптер с пустыми реализациями по умолчанию.Files.walkFileTree() не материализует всё дерево в память. Он обходит директории рекурсивно, вызывая callback'и по мере продвижения. Это безопасно для огромных деревьев, так как в памяти хранится только стек текущего пути обхода, а не всё дерево целиком. Объекты Path создаются и сразу становятся мусором после выхода из callback'а, собираясь Minor GC.#Java #для_новичков #beginner #IO #NIO #Files #move #delete
👍5