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
🆒2
Скоро... 🧑‍💻

Ждете?
Please open Telegram to view this post
VIEW IN TELEGRAM
👍7
16. Saga Orchestration: централизованное управление с Camunda

В этом видео мы переходим к Saga Orchestration — централизованному управлению распределёнными транзакциями. Вместо того чтобы встраивать Camunda в существующие сервисы, мы создаём выделенный workflow-service, который становится единственным владельцем бизнес-процесса. Kafka остаётся транспортом, Camunda становится мозгом.

BPM Service:
- принимает решения, а не просто пересылает сообщения;
- управляет порядком шагов и компенсациями;
- обрабатывает таймауты на уровне BPMN;
- предоставляет полную видимость процесса через Camunda Cockpit.

Ключевые темы видео:

Command vs Event — принципиальное различие
Жизненный цикл процесса в Camunda (Process Definition → Instance → Waiting → Completed)
Message Correlation — как Camunda находит нужный процесс среди тысяч других
BPMN-диаграмма с Event-Based Gateway (ветки успеха и ошибки)
JavaDelegate для публикации команд в Kafka Компенсирующие транзакции и таймауты без единой строчки кода Camunda
Cockpit — мониторинг бизнес-процессов

Исходный код проекта на GitHub наверняка заслуживает Ваших звезд! 🙂

Ссылка на Youtube
Ссылка на Рутьюб

Смотрите, ставьте лайки, подписывайтесь на каналы!✌️

Жду ваших реакций и оценок
🙂
Please open Telegram to view this post
VIEW IN TELEGRAM
👍6
История технологии сегодня — 13 июля

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

Ариста́рх Аполло́нович Белопо́льский (1 июля [13 июля] 1854, Москва — 16 мая 1934, Пулково) — русский и советский астроном и астрофизик. Разработал метод и сконструировал прибор, с помощью которого первым получил экспериментальное доказательство существования эффекта Доплера применительно к световым волнам; применил эффект Доплера, проявляющийся в виде смещения спектральных линий в оптических спектрах, для исследований в астроспектроскопии; в числе первых определил элементы орбит нескольких переменных и спектрально-двойных звёзд, исследовал спектры новых звёзд и солнечной поверхности, краев и короны; — лучевые скорости небесных светил, один из пионеров в фотографировании их спектров с помощью спектрографов; обнаружил периодическое изменение лучевой скорости у цефеид; всесторонние исследования комет, — вращения около оси ВенерыЮпитера и колец Сатурна.


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

1897 — Гульельмо Маркони получил патент на радио в США.


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

Глава 2. Современный NIO.2 (java.nio.file)

Создание файлов и директорий

Класс Files предоставляет набор статических методов для создания файловых объектов на диске. Эти методы отличаются от конструкторов старого java.io.File тем, что работают непосредственно с файловой системой и выбрасывают конкретные исключения при ошибках. В отличие от File.createNewFile(), который возвращал boolean без объяснения причин отказа, методы NIO.2 сообщают точно, что пошло не так: файл уже существует, нет прав на запись, родительская директория отсутствует и т.д.

Файловая система — это способ организации, хранения, именования, доступа и управления файлами на устройстве хранения данных. Она определяет структуру директорий, права доступа, метаданные файлов и механизмы их поиска.


Создание пустого файла: Files.createFile(Path)


Метод Files.createFile(Path path, FileAttribute<?>... attrs) создаёт новый пустой файл по указанному пути. Если файл уже существует — выбрасывает FileAlreadyExistsException. Если родительская директория не существует — выбрасывает NoSuchFileException.
import java.nio.file.*;
import java.io.IOException;
import java.nio.file.attribute.PosixFilePermission;
import java.nio.file.attribute.PosixFilePermissions;
import java.util.Set;

public class FileCreator {

public void createEmptyFile(Path path) throws IOException {
// Проверяем, существует ли родительская директория
Path parent = path.getParent();
if (parent != null && Files.notExists(parent)) {
throw new NoSuchFileException(
"Родительская директория не существует: " + parent
);
}

// Создаём файл с правами доступа (только Unix)
Set<PosixFilePermission> perms = PosixFilePermissions.fromString("rw-r--r--");
FileAttribute<Set<PosixFilePermission>> attr = PosixFilePermissions.asFileAttribute(perms);

Files.createFile(path, attr);
}
}

Параметр FileAttribute<?>... attrs — это varargs массив атрибутов файла, которые устанавливаются атомарно при создании. На POSIX-системах через него передаются права доступа. На Windows этот параметр игнорируется, так как модель прав Windows основана на ACL (Access Control List, список контроля доступа), а не на POSIX-битах.

Атомарность создания означает, что файл либо создаётся полностью с указанными атрибутами, либо не создаётся вообще. Другие процессы не увидят файл без атрибутов. Это критично для безопасности: например, если вы создаёте файл с секретными данными и хотите, чтобы он сразу имел ограниченные права rw------- (только владелец).

Важное отличие от File.createNewFile(): Files.createFile() гарантирует, что файл создан именно вашим вызовом. Если между проверкой exists() и вызовом createFile() другой процесс создал файл, createFile() выбросит исключение, а не молча вернёт false. Это устраняет race condition (состояние гонки) — ситуацию, когда результат операции зависит от порядка выполнения конкурирующих потоков или процессов.


Создание одной директории: Files.createDirectory(Path)

Метод Files.createDirectory(Path dir, FileAttribute<?>... attrs) создаёт одну директорию. Все промежуточные директории в пути уже должны существовать. Если директория существует — FileAlreadyExistsException. Если родитель отсутствует — NoSuchFileException.
public void createSingleDirectory(Path dir) throws IOException {
// Создаём только конечную директорию
// Предполагаем, что /var/log уже существует
Path appLogs = Paths.get("/var/log/myapp");
Files.createDirectory(appLogs);
}

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


Создание вложенных директорий: Files.createDirectories(Path)

Метод Files.createDirectories(Path dir, FileAttribute<?>... attrs) создаёт директорию и все несуществующие родительские директории в пути. Это аналог команды mkdir -p в Unix. Если конечная директория уже существует — метод молча возвращает управление, не выбрасывая исключение. Если путь существует, но не является директорией — выбрасывает FileAlreadyExistsException.
public void createNestedStructure() throws IOException {
Path deepPath = Paths.get("data", "books", "2024", "reviews");

// Создаёт data/, data/books/, data/books/2024/, data/books/2024/reviews/
Files.createDirectories(deepPath);

// Проверяем, что создано
System.out.println("Создана структура: " + deepPath.toAbsolutePath());
System.out.println("Количество сегментов: " + deepPath.getNameCount());
}

createDirectories() — идемпотентный метод: повторный вызов с тем же путём не приводит к ошибке. Это делает его идеальным для инициализации структуры приложения при старте.

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


#Java #для_новичков #beginner #IO #NIO #Files #copy
👍6
Создание временных файлов и директорий

Files.createTempFile(String prefix, String suffix)
Создаёт временный файл в системной временной директории (java.io.tmpdir). Имя генерируется атомарно и гарантированно уникально в пределах JVM-процесса.
Path tempFile = Files.createTempFile("report_", ".tmp");
// Результат: /tmp/report_1234567890.tmp

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

Files.createTempFile(Path dir, String prefix, String suffix)

Создаёт временный файл в указанной директории, а не в системной временной.
Path customDir = Paths.get("/app/temp");
Files.createDirectories(customDir);
Path tempInCustom = Files.createTempFile(customDir, "session_", ".dat");

Это важно, когда системная временная директория (/tmp на Linux) монтируется в tmpfs (файловая система в памяти) с ограниченным размером, а ваши временные файлы большие. В таком случае лучше использовать директорию на диске.

tmpfs — это виртуальная файловая система, хранящая данные в оперативной памяти (RAM) вместо диска. Она быстрая, но ограничена объёмом ОЗУ. /tmp на многих Linux-дистрибутивах монтируется как tmpfs.

Files.createTempDirectory(String prefix)

Создаёт временную директорию в системной временной директории.
Path tempDir = Files.createTempDirectory("batch_processing_");
// Результат: /tmp/batch_processing_1234567890/


Files.createTempDirectory(Path dir, String prefix)

Создаёт временную директорию в указанной родительской директории.
Path workDir = Paths.get("/var/app/work");
Path tempWorkDir = Files.createTempDirectory(workDir, "job_");



Копирование файлов: Files.copy()

Базовое копирование: Files.copy(Path source, Path target, CopyOption... options)
Метод копирует файл или директорию по указанному пути. Для директории копируется только сама директория как пустая сущность, без содержимого. Для рекурсивного копирования дерева требуется ручной обход.
Path src = Paths.get("/data/report.txt");
Path dst = Paths.get("/backup/report.txt");

Files.copy(src, dst); // FileAlreadyExistsException, если dst существует


StandardCopyOption.REPLACE_EXISTING
Разрешает перезапись существующего файла назначения. Без этой опции Files.copy() выбросит FileAlreadyExistsException.
Files.copy(src, dst, StandardCopyOption.REPLACE_EXISTING);


StandardCopyOption.COPY_ATTRIBUTES

Копирует метаданные файла: временные метки (creation time, last modified time, last access time) и POSIX-права (на Unix). Без этой опции новый файл получает текущее время и права по умолчанию (umask).
umask (user file-creation mode mask) — это маска прав доступа в Unix, которая определяет, какие биты прав сбрасываются при создании нового файла. Например, umask 022 означает, что у новых файлов не будут прав на запись для группы и остальных.
Files.copy(src, dst, 
StandardCopyOption.REPLACE_EXISTING,
StandardCopyOption.COPY_ATTRIBUTES
);


StandardCopyOption.ATOMIC_MOVE

Не применяется к copy(), только к move(). При передаче в copy() выбросит UnsupportedOperationException.

Копирование из потока: Files.copy(InputStream in, Path target, CopyOption... options)
Читает все байты из InputStream и записывает их в файл. Поток закрывается автоматически. Удобно для загрузки данных из сети или других источников.
import java.net.URL;

public void downloadFile(String urlString, Path target) throws IOException {
URL url = new URL(urlString);
try (InputStream in = url.openStream()) {
// Копируем всё содержимое потока в файл
// CREATE — создать файл, если нет
// REPLACE_EXISTING — перезаписать, если есть
long bytesCopied = Files.copy(in, target,
StandardCopyOption.REPLACE_EXISTING
);
System.out.println("Скопировано байт: " + bytesCopied);
}
// InputStream закрыт автоматически try-with-resources
}



Метод возвращает количество скопированных байт как long. Это полезно для логирования и проверки целостности.

Копирование в поток: Files.copy(Path source, OutputStream out)
Читает все байты из файла и записывает их в OutputStream. Поток не закрывается автоматически — управление потоком остаётся на вызывающей стороне.
public void streamFileToResponse(Path file, OutputStream responseOut) throws IOException {
// Отправляем файл в HTTP-ответ
// responseOut управляется внешним кодом (сервлет, фреймворк)
long bytesSent = Files.copy(file, responseOut);
responseOut.flush(); // явно сбрасываем буфер
}

Этот метод эффективен, так как внутри использует нативный механизм zero-copy (нулевое копирование) через transferTo() файлового канала, если поток поддерживает запись в канал. Zero-copy означает, что данные передаются от диска к сетевому сокету без промежуточного копирования в пользовательское пространство JVM, что снижает нагрузку на CPU и память.


Копирование директорий: Files.copy() не рекурсивно
Критически важно понимать: Files.copy() для директории копирует только саму директорию как пустую сущность. Все файлы и поддиректории внутри не копируются. Для полного копирования дерева требуется ручной обход.
import java.nio.file.*;
import java.io.IOException;
import java.util.stream.Stream;

public class DirectoryCopier {

public void copyDirectory(Path source, Path target) throws IOException {
// Проверяем, что источник — директория
if (!Files.isDirectory(source)) {
throw new NotDirectoryException(source.toString());
}

// Создаём корневую директорию назначения
// createDirectories идемпотентен — не бросает исключение, если уже есть
Files.createDirectories(target);

// Обходим дерево источника
try (Stream<Path> walk = Files.walk(source)) {
walk.forEach(srcPath -> {
try {
// Вычисляем относительный путь от корня источника
Path relative = source.relativize(srcPath);
// Резолвим его относительно корня назначения
Path dstPath = target.resolve(relative);

if (Files.isDirectory(srcPath)) {
// Создаём директорию
Files.createDirectories(dstPath);
} else {
// Копируем файл
Files.copy(srcPath, dstPath,
StandardCopyOption.REPLACE_EXISTING,
StandardCopyOption.COPY_ATTRIBUTES
);
}
} catch (IOException e) {
// Оборачиваем в unchecked для использования внутри forEach
throw new UncheckedIOException(e);
}
});
}
}
}

В этом коде Files.walk(source) возвращает поток всех путей в дереве источника, включая корень. source.relativize(srcPath) вычисляет относительный путь от корня, а target.resolve(relative) строит соответствующий путь в дереве назначения. Порядок обхода гарантирован — walk() обходит в порядке pre-order (сначала родитель, потом дети), поэтому директории создаются до файлов внутри них.

Pre-order обход — это стратегия обхода дерева, при которой узел обрабатывается до его потомков. В контексте файловой системы это означает, что директория создаётся перед файлами внутри неё.

#Java #для_новичков #beginner #IO #NIO #Files #copy
👍6
Путь байтов в памяти JVM при создании и копировании

Создание файла: путь данных
Когда вы вызываете Files.createFile(path):
Объект Path уже находится в куче (Heap). Строковые сегменты пути — это String-объекты, хранящие массивы символов (char[]) в куче.
Метод createFile() вызывает FileSystemProvider.createFile(). Провайдер извлекает байтовое представление пути: на Unix это массив байтов в кодировке UTF-8, на Windows — UTF-16LE (wchar_t[]).
Байты пути копируются из кучи JVM в нативную память через JNI. Системный вызов open() с флагом O_CREAT | O_EXCL (POSIX) атомарно создаёт файл, если он не существует. Флаг O_EXCL гарантирует, что если файл уже есть, вызов вернёт ошибку EEXIST — JVM преобразует её в FileAlreadyExistsException.
Вновь созданный файл представлен в ядре ОС структурой inode (Unix) или записью в MFT (NTFS). Права доступа устанавливаются через fchmod() или аналогичный вызов, если были переданы FileAttribute.
Возвращаемое значение createFile() — это объект Path, переданный в метод (тот же самый объект, без копирования). Никаких новых объектов в куче не создаётся, за исключением возможного внутреннего объекта исключения, если операция не удалась.


Копирование файла: путь данных

При Files.copy(source, target):
Источник и назначение — объекты Path в куче. Метод определяет FileStore (хранилище) для обоих путей через FileSystemProvider.getFileStore().
Если источник и назначение на одном FileStore (один раздел диска), некоторые провайдеры могут использовать оптимизацию reflink (copy-on-write копирование) — вместо физического копирования блоков данных создаётся новая ссылка на те же блоки. Изменение любой из копий приведёт к физическому копированию изменённых блоков. Это поддерживается файловыми системами Btrfs и XFS на Linux.
Если оптимизация недоступна, выполняется классическое копирование: данные читаются из источника через кэш страниц ОС в нативную память, затем записываются в назначение. При этом данные проходят через пространство пользователя (user space) JVM — два системных вызова на каждый блок: read() и write().

Для больших файлов внутренняя реализация Files.copy() использует буфер фиксированного размера (обычно 8 КБ или 32 КБ). Этот буфер — массив byte[] в куче, который создаётся один раз и переиспользуется. При копировании файла размером 1 ГБ буфер создаётся один раз, а не для каждого блока, что минимизирует давление на GC.
Если передан COPY_ATTRIBUTES, после копирования данных выполняется дополнительный системный вызов для установки метаданных: utimensat() для временных меток, fchmod() для прав.
Все временные объекты — буфер копирования, массивы байтов пути — создаются в Young Generation. Если операция быстрая (маленький файл), они уничтожаются при следующей Minor GC. Если файл большой и операция длительная, буфер может пережить несколько циклов Minor GC и быть перемещён в Survivor Space, а затем в Old Generation при достижении порога возраста.

Survivor Space — это одно из двух пространств (S0 или S1) в Young Generation, куда перемещаются объекты, пережившие Minor GC. Объекты "прыгают" между S0 и S1, и при достижении определённого количества циклов сборки (tenuring threshold) перемещаются в Old Generation.


Создание директорий: путь данных

Files.createDirectories() выполняет итерацию по сегментам пути. Для каждого сегмента:
Строится промежуточный Path (в куче) — это лёгкий объект, создаваемый быстро.
Выполняется системный вызов mkdir(). Если директория уже существует — ошибка EEXIST, которую метод игнорирует (идемпотентность).
Если переданы FileAttribute, применяются chmod().
Промежуточные объекты Path становятся мусором сразу после итерации. При создании глубокой иерархии (data/books/2024/reviews) создаётся 4 временных объекта Path — все они собираются при первой Minor GC.

#Java #для_новичков #beginner #IO #NIO #Files #copy
👍6
Что выведет код?

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

public class Task130726 {
public static void main(String[] args) {
boolean created1 = false;
try {
File file = new File("parent/child.txt");
created1 = file.createNewFile();
} catch (IOException e) {
created1 = false;
}
System.out.print(created1 + " ");

boolean created2 = false;
try {
Path path = Paths.get("parent/child2.txt");
Files.createFile(path);
created2 = true;
} catch (IOException e) {
created2 = false;
}
System.out.print(created2);
}
}


#Tasks
👍2
Варианты ответа:
Anonymous Quiz
62%
true true
0%
true false
23%
false true
15%
false false
👍3
Что такое WatchService в NIO? 🤓

Ответ:

java.nio.file.WatchService (Java 7)
— сервис для отслеживания изменений в файловой системе (создание, удаление, изменение файлов/директорий).

Регистрируется с помощью Path.register(WatchService, WatchEvent.Kind...). Получает события через take() или poll().

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

Реализует эффективный механизм уведомлений на уровне ядра (не требует опроса).



#собеседование
Please open Telegram to view this post
VIEW IN TELEGRAM
👍5
История технологии сегодня — 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