Java for Beginner
870 subscribers
1.01K photos
275 videos
14 files
1.69K links
Канал от новичков для новичков!
Изучайте Java вместе с нами!
Здесь мы обмениваемся опытом и постоянно изучаем что-то новое!

Наш YouTube канал - https://www.youtube.com/@Java_Beginner-Dev

Наш канал на RUTube - https://rutube.ru/channel/37896292/
Download Telegram
История технологии сегодня — 14 июля

ℹ️ Кто родился в этот день

Джей Райт Фо́ррестер (англ. Jay Wright Forrester; 14 июля 1918, Анселмо, Небраска — 16 ноября 2016, Конкорд, Массачусетс) — американский инженер и системолог, разработчик теории системной динамики. Был разработчиком одного из первых универсальных компьютеров Whirlwind I («Вихрь-1») по заказу ВМС США, в основанной им лаборатории цифровых компьютеров при МТИ. В рамках этих исследований им в 1950 году была предложена одна из схем рабочей памяти на ферритовых сердечниках, получившей широкое распространение на ЭВМ второго поколения.


🌐 Знаковые события

1954 — Принстонский университет в США впервые объявил о сдаче компьютеров в частное пользование.

1983 — вышла компьютерная игра Mario Bros.


#Biography #Birth_Date #Events #14июля
Please open Telegram to view this post
VIEW IN TELEGRAM
👍3
[Совет по Java #060]

Тема: CountDownLatch нельзя переиспользовать после достижения нуля.

Проблема: CountDownLatch — это синхронизатор, позволяющий одному или нескольким потокам ожидать, пока набор операций в других потоках не завершится. Счетчик уменьшается вызовами countDown() и, достигнув нуля, освобождает все ожидающие потоки. Однако после этого счетчик не может быть сброшен — он остается на нуле, и любые последующие вызовы await() будут немедленно возвращать управление, не блокируя потоки.

Это делает CountDownLatch одноразовым инструментом. Разработчики, не знакомые с этим ограничением, могут попытаться повторно использовать экземпляр для нескольких фаз синхронизации, что приведет к неверной логике или состояниям гонки. В сценариях, где требуется многократная синхронизация групп потоков (например, в циклах итераций или периодических вычислениях), попытка повторно использовать один и тот же CountDownLatch приводит к тому, что вторая фаза начинается немедленно, без ожидания всех потоков, что нарушает корректность.

Решение: Для многоразовой синхронизации используйте CyclicBarrier, который позволяет группе потоков ожидать друг друга на барьере, и после достижения барьера автоматически сбрасывается для следующего использования. CyclicBarrier может принимать опциональное действие, выполняемое при достижении барьера (например, агрегация данных).

Если требуется накопление событий с возможностью сброса, можно использовать Semaphore с подсчетом разрешений, но это менее удобно. Для сложных сценариев с несколькими фазами применяйте Phaser, который предоставляет более гибкую и масштабируемую синхронизацию с динамической регистрацией сторон.
public class LatchVsBarrier {

public static void main(String[] args) throws InterruptedException {
//CountDownLatch одноразовый
CountDownLatch latch = new CountDownLatch(3);

// Первая фаза: ожидаем 3 потока
for (int i = 0; i < 3; i++) {
new Thread(() -> {
// работа
latch.countDown();
}).start();
}
latch.await(); // ждем завершения первой фазы
System.out.println("Первая фаза завершена");

// Попытка второй фазы — бессмысленна, latch уже на нуле
// Новые потоки, вызывающие latch.await(), пройдут без ожидания
// latch.countDown() не имеет эффекта

//CyclicBarrier переиспользуется
CyclicBarrier barrier = new CyclicBarrier(3, () ->
System.out.println("Барьер достигнут, выполняется действие"));

Runnable task = () -> {
try {
// Первая итерация
System.out.println(Thread.currentThread().getName() + " готов к фазе 1");
barrier.await();
// работа после барьера
System.out.println(Thread.currentThread().getName() + " завершил фазу 1");

// Вторая итерация — барьер сброшен автоматически
System.out.println(Thread.currentThread().getName() + " готов к фазе 2");
barrier.await();
System.out.println(Thread.currentThread().getName() + " завершил фазу 2");
} catch (InterruptedException | BrokenBarrierException e) {
Thread.currentThread().interrupt();
}
};

for (int i = 0; i < 3; i++) {
new Thread(task).start();
}
}
}


Объяснение:
 CountDownLatch спроектирован как одноразовый "затвор". Его внутренний счетчик не имеет метода reset(), и после достижения нуля состояние фиксируется. Это сделано для простоты и производительности в сценариях "один раз дождаться завершения". CyclicBarrier же имеет встроенный механизм сброса: когда последний поток достигает барьера, все освобождаются, и барьер автоматически перезапускается. Он также предоставляет метод reset() для ручного сброса в случае ошибок. CyclicBarrier поддерживает опциональное барьерное действие, которое выполняется перед освобождением потоков — это удобно для агрегации промежуточных результатов.

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


#Java #советы
👍4
Что выведет код?

import java.util.concurrent.CountDownLatch;

public class Task140726 {
public static void main(String[] args) throws InterruptedException {
CountDownLatch latch = new CountDownLatch(1);
latch.countDown();

for (int i = 0; i < 3; i++) {
latch.await();
System.out.print(i + " ");
}
System.out.print(latch.getCount());
}
}


#Tasks
👍3
👍3
Что такое AsynchronousFileChannel и AsynchronousSocketChannel? 🤓

Ответ:

Асинхронные каналы в NIO.2 (Java 7). AsynchronousFileChannel позволяет выполнять операции чтения/записи неблокирующе, передавая CompletionHandler или используя Future. 

AsynchronousSocketChannel — аналогично для сетевых сокетов. Они используют пулы потоков (AsynchronousChannelGroup) и позволяют работать с высокой нагрузкой без создания множества потоков. Применяются в высокопроизводительных серверах, хотя сейчас чаще используют Netty или NIO + селекторы.



#собеседование
Please open Telegram to view this post
VIEW IN TELEGRAM
👍5
По-моему это шедевр...
🤣4
История технологии сегодня — 15 июля

ℹ️ Кто родился в этот день

Рем Ви́кторович Хохло́в (15 июля 1926, Ливны, Орловская губерния — 8 августа 1977, Москва) — советский физик, один из основоположников нелинейной оптики. Признание в научном мире Хохлову принесли два изданных в 1961 году труда: «К теории ударных радиоволн в нелинейных линиях» и «О распространении волн в нелинейных диспергирующих линиях». Первая монография заложила основы теоретической нелинейной акустики, вторая сформировала основание теории распространения электромагнитных волн в нелинейных средах.

Леон Макс Ле́дерман (англ. Leon Max Lederman; 15 июля 1922, Нью-Йорк, США — 3 октября 2018, Рексберг, Айдахо, США) — американский физик-экс­пе­ри­мен­та­тор, специалист в области физики высоких энергий, нобелевский лауреат 1988 года за открытие мюонного нейтрино.

Бертрам Невилл Брокха́уз (англ. Bertram Neville Brockhouse; 15 июля 1918, Летбридж, Канада — 13 октября 2003, Гамильтон, Канада) — канадский учёный, физик-ядерщик, лауреат Нобелевской премии по физике в 1994 г. «за создание нейтронной спектроскопии» (совместно с Клиффордом Шаллом).


🌐 Знаковые события

1687 — Исаак Ньютон опубликовал в Лондоне «Philosophiae naturalis principia mathematica» с изложением открытий автора в области прикладной математики, астрономии и физики, в том числе и теорию всемирного тяготения (5 июля по ст.ст.[8]).

2006 — основан сервис микроблоггинга Twitter.

1983 — в Японии вышли игровые приставки NES и SG-1000.


#Biography #Birth_Date #Events #15июля
Please open Telegram to view this post
VIEW IN TELEGRAM
👍3
Раздел 11. Работа с файлами, I/O и сетью (NIO.2)

Глава 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.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 при перемещении и удалении

Перемещение: путь данных

При 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
Что выведет код?

import java.nio.file.*;
import java.io.*;

public class Task150726 {
public static void main(String[] args) throws IOException {
Path source = Paths.get("file.txt");
Path target = Paths.get("target.txt");

Files.createFile(source);
Files.createFile(target);

try {
Files.move(source, target);
System.out.println("Moved");
} catch (IOException e) {
System.out.println(e.getClass().getSimpleName());
}

System.out.println(Files.exists(source));
}
}


#Tasks
👍4
Что такое FileAttribute и PosixFileAttributes в NIO? 🤓

Ответ:

В NIO.2 можно получать атрибуты файлов без обращения к ОС через Files.getFileAttributeView(). 

FileAttribute — общий интерфейс для любого атрибута (например, "posix:permissions"). PosixFileAttributes расширяет его для UNIX-систем, давая доступ к владельцу, группе, правам доступа (rwx).

Использование: Files.readAttributes(path, PosixFileAttributes.class).

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



#собеседование
Please open Telegram to view this post
VIEW IN TELEGRAM
👍5
Привет)

Ничего нового, просто хотел выразить свою благодарность той паре-тройке человек, которые ставят лайки на каждом посте в канале. 🙏

Вы лучшие, спасибо! ❤️
Please open Telegram to view this post
VIEW IN TELEGRAM
👍15
История технологии сегодня — 16 июля

ℹ️ Кто родился в этот день

Бори́с Васи́льевич Бу́нкин (1922—2007) — советский и российский учёный, конструктор и организатор производства зенитных ракетных комплексов для ПВО. С 1968 по 1998 год — генеральный конструктор предприятия НПО «Алмаз», осуществляющего разработку и серийное производство зенитных ракетных комплексов, составляющих основу вооружения отечественных войск ПВОС-75С-125С-300С-400.

Фриц Цернике (нид. Frits Zernike; 16 июля 1888 — 10 марта 1966)голландский физик, лауреат Нобелевской премии по физике 1953 года «За обоснование фазово-контрастного метода, особенно за изобретение фазово-контрастного микроскопа».


🌐 Знаковые события

1867 — Француз Жозеф Монье, бывший садовник, получил патент на армированный бетон, ставший главным материалом при возведении высотных зданий.

1969 — с космодрома на мысе Канаверал стартовала РН «Сатурн-V» с космическим кораблём «Аполлон-11». Через несколько дней посадочный модуль «Орёл» совершит первую пилотируемую посадку на Луну.

1993 — в Лондоне прошла презентация Amiga CD32 — первой 32-битной игровой консоли, использующей CD-ROM.

2000 — принято решение об увеличении числа доменов Интернета.


#Biography #Birth_Date #Events #16июля
Please open Telegram to view this post
VIEW IN TELEGRAM
👍3
[Совет по Java #061]

Тема: Thread.yield() — подсказка планировщику, но может быть проигнорирована. Не используйте для синхронизации.

Проблема: Метод Thread.yield() призван сигнализировать планировщику потоков, что текущий поток готов уступить процессорное время другим потокам.

Однако это всего лишь подсказка (hint) для JVM и операционной системы. Планировщик может проигнорировать её, особенно в средах с вытесняющей многозадачностью, где решение о переключении принимается системой.

Некоторые разработчики используют yield() в циклах ожидания (busy-wait) для "уменьшения нагрузки" на процессор, полагая, что это предотвратит зависание. Это ошибочная практика: она не гарантирует прогресс, не обеспечивает взаимное исключение и не защищает от гонок данных. Более того, в многопроцессорных системах вызов yield() может вообще не изменить поведение, так как другие потоки могут выполняться на других ядрах.

Использование yield() для синхронизации — это антипаттерн, ведущий к недетерминированным ошибкам, которые проявляются только под нагрузкой или на определённых конфигурациях железа.

Решение: Для координации потоков используйте механизмы синхронизации из java.util.concurrent или встроенные synchronizedwait()/notify().

Если нужно временно освободить процессор, применяйте Thread.sleep(1) с минимальной задержкой — это более предсказуемый способ, хотя тоже не идеален.

Для реализации эффективного ожидания используйте LockSupport.park() или Condition.await(). Для уступки в вычислительных циклах с интенсивным использованием CPU можно использовать yield() в исключительных случаях, но только после тщательного анализа и с чётким пониманием, что это не влияет на корректность.

В подавляющем большинстве сценариев yield() не нужен, а его использование свидетельствует о попытке "замаскировать" проблемы синхронизации.
public class ThreadYield {
private boolean flag = false;

//Антипаттерн: yield() в цикле ожидания
public void badWait() {
while (!flag) {
Thread.yield(); // Плохо: может никогда не сработать, а если сработает, то не факт
}
// Действие после флага
}

//Правильно: использование Lock или wait/notify
private final Object lock = new Object();

public void goodWait() throws InterruptedException {
synchronized (lock) {
while (!flag) {
lock.wait(); // Блокируется до уведомления
}
}
}

public void setFlagTrue() {
synchronized (lock) {
flag = true;
lock.notifyAll();
}
}
}


Объяснение:
 Спецификация Java не даёт никаких гарантий относительно того, что yield() действительно вызовет переключение контекста.

Разные реализации JVM и операционные системы трактуют его по-разному: на Windows это обычно приводит к немедленному переключению, на Linux может быть проигнорировано.

Полагаться на него для синхронизации — значит внести в код фактор случайности. Правильные механизмы (wait/notify, Lock, Semaphore, CountDownLatch) обеспечивают детерминированное освобождение ресурсов и гарантируют, что поток будет возобновлён только когда условие действительно выполнено.

Даже Thread.sleep(0) часто работает аналогично yield(), но тоже не рекомендуется.


#Java #советы
👍6
Потом удивляемся почему нет рабочих мест для погромистов. Грууусть...
🗿5🤓2
Что выведет код?

public class Task160726 {
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(() -> {
for (int i = 0; i < 10; i++) {
System.out.print("A");
Thread.yield();
}
});
Thread t2 = new Thread(() -> {
for (int i = 0; i < 10; i++) {
System.out.print("B");
Thread.yield();
}
});
t1.start();
t2.start();
t1.join();
t2.join();
}
}


#Tasks
👍3
Что такое ProcessHandle в Java 9? 🤓

Ответ:

ProcessHandle (Java 9)
— это улучшенный API для управления процессами ОС из Java.

Позволяет получить дескриптор текущего процесса (ProcessHandle.current()), получить PID, родительский процесс, дочерние процессы, а также завершить процесс (destroy(), destroyForcibly()).

Можно также подписаться на завершение процесса через onExit(), возвращающее CompletableFuture<ProcessHandle>. Это улучшение по сравнению с устаревшим Runtime.exec() и Process позволяет более удобно управлять внешними процессами.



#собеседование
Please open Telegram to view this post
VIEW IN TELEGRAM
👍4