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

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

Наш канал на RUTube - https://rutube.ru/channel/37896292/
Download Telegram
[Совет по Java #062]

Тема: Thread.stop() устарел и опасен (разблокирует все мониторы, оставляя данные в несогласованном состоянии).

Проблема: Метод Thread.stop() принудительно завершает поток, выбрасывая ThreadDeath в целевом потоке. При этом освобождаются все блокировки, которые поток удерживал, но изменения, сделанные в критической секции, не откатываются.

Это приводит к тому, что разделяемые объекты остаются в частично обновлённом состоянии, а другие потоки, захватившие освобождённые мониторы, могут читать несогласованные данные. Остановка потока в произвольный момент нарушает инварианты объектов, и восстановить состояние системы становится невозможно. Метод был объявлен устаревшим (deprecated) ещё в Java 1.2 и никогда не должен использоваться в production-коде.

Решение: Вместо принудительной остановки используйте кооперативный механизм прерывания. Вызовите Thread.interrupt() на потоке, который нужно завершить.

Целевой поток должен периодически проверять статус прерывания с помощью Thread.interrupted() или Thread.currentThread().isInterrupted() и завершать свою работу корректно, освобождая ресурсы и приводя данные в согласованное состояние. Для блокирующих операций (например, wait()sleep()) прерывание выбрасывает InterruptedException, которое следует обработать, прервав выполнение.

Дополнительно можно использовать volatile boolean флаг для сигнализации завершения, но прерывание является стандартным и более интегрированным с JDK.
public class ThreadStopExample {

//Антипаттерн: Thread.stop()
public static void badStop() throws InterruptedException {
Thread t = new Thread(() -> {
while (true) {
System.out.println("Работаю...");
// Если здесь stop() будет вызван, данные могут быть повреждены
}
});
t.start();
Thread.sleep(100);
t.stop(); // Опасно! Освобождает мониторы, оставляя объекты в мусорном состоянии
}

//Правильно: кооперативное завершение через прерывание
public static void goodInterrupt() throws InterruptedException {
Thread t = new Thread(() -> {
while (!Thread.currentThread().isInterrupted()) {
System.out.println("Безопасная работа...");
try {
Thread.sleep(500);
} catch (InterruptedException e) {
// Восстанавливаем статус прерывания и выходим
Thread.currentThread().interrupt();
break;
}
}
System.out.println("Поток корректно завершён");
});
t.start();
Thread.sleep(2000);
t.interrupt(); // Сигнал остановки
t.join();
}

// Альтернатива: volatile флаг
private volatile boolean running = true;

public void safeWithFlag() throws InterruptedException {
Thread t = new Thread(() -> {
while (running) {
System.out.println("Работа с флагом...");
try {
Thread.sleep(300);
} catch (InterruptedException e) {
// При прерывании тоже можем выйти
Thread.currentThread().interrupt();
break;
}
}
});
t.start();
Thread.sleep(1000);
running = false; // Сигнал остановки
t.interrupt(); // Дополнительно для прерывания блокировок
t.join();
}

public static void main(String[] args) throws InterruptedException {
// badStop(); // Не запускайте!
goodInterrupt();
}
}


Объяснение:
 При вызове stop() JVM немедленно завершает поток, не давая ему возможности завершить текущую операцию.

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

Это нарушает атомарность и может привести к трудноотлавливаемым повреждениям данных. interrupt() лишь устанавливает флаг прерывания, не вмешиваясь в выполнение. Поток сам решает, когда и как реагировать на этот сигнал, что позволяет ему корректно завершить работу. Блокирующие методы (например, sleep()wait()join()BlockingQueue.put()) проверяют статус прерывания и выбрасывают InterruptedException, обеспечивая немедленную реакцию на сигнал. В коде с интенсивными вычислениями необходимо явно проверять isInterrupted() в цикле.


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

public class ThreadStopTricky {
private static int value = 0;

public static void main(String[] args) throws Exception {
Thread t = new Thread(() -> {
synchronized (ThreadStopTricky.class) {
value = 1;
try { Thread.sleep(1000); } catch (InterruptedException e) {}
value = 2;
}
});
t.start();
Thread.sleep(50);
t.stop();
synchronized (ThreadStopTricky.class) {
System.out.println(value);
}
}
}


#Tasks
👍2
👍2
This media is not supported in your browser
VIEW IN TELEGRAM
Ребят, продолжаю набирать желающих обучиться Java!

Изучаем до уровня стандартного мидла)

Пишите @Oleborn
Что такое System.Logger API (Java 9)? 🤓

Ответ:

System.Logger (Java 9)
— это стандартный API для логирования, позволяющий приложениям использовать разные реализации (SLF4J, Log4j2, JUL) через абстракцию.

Получаем логгер через System.getLogger("name"). Предоставляет уровни (INFO, DEBUG, ERROR) и методы log().

Не требует внешних зависимостей. Однако широкого распространения не получил, так как SLF4J уже де-факто стандарт.

Используется в модульной системе и для внутренних нужд JDK.



#собеседование
Please open Telegram to view this post
VIEW IN TELEGRAM
👍1
История технологии сегодня — 21 июля

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

Ру́дольф «Руди» Артур Ма́ркус (англ. Rudolph «Rudy» Arthur Marcus; 21 июля 1923, Монреаль, Канада — 16 июля 2026, Пасадина, Калифорния) — американский химик канадского происхождения, основоположник теории переноса электрона в растворах (теория Маркуса), лауреат Нобелевской премии по химии 1992 года («За вклад в теорию реакций переноса электрона в химических системах»).


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

1969 — американский астронавт Нил Армстронг стал первым человеком, ступившим на Луну. Через 19 минут к нему присоединился Базз Олдрин.

1973 — запущен «Марс-4».


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

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

Files.list() и Files.walk() — обход содержимого директорий

Работа с файловой системой в Java NIO.2 предоставляет два ключевых метода для обхода директорий: Files.list() для непосредственного содержимого и Files.walk() для рекурсивного обхода дерева. Оба метода возвращают Stream<Path>, что позволяет использовать всю мощь Stream API для фильтрации, преобразования и агрегации данных. Однако за этой элегантностью скрываются важные нюансы производительности, управления ресурсами и потребления памяти, которые критичны при работе с большими файловыми деревьями.

Stream API — это фреймворк в Java для обработки последовательностей элементов в функциональном стиле. Он поддерживает ленивые (lazy) промежуточные операции (filter, map, sorted) и энергичные (eager) терминальные операции (count, collect, forEach). Stream не хранит данные, а описывает конвейер вычислений.


Files.list(Path) — содержимое директории (не рекурсивно)

Метод Files.list(Path dir) возвращает Stream<Path>, содержащий прямые потомки указанной директории. Поток ленивый: записи директории читаются по мере запроса элементов из потока, а не все сразу.
public class DirectoryLister {

public void listDirectory(Path dir) throws IOException {
// Stream<Path> — ленивый поток, записи читаются по требованию
try (Stream<Path> stream = Files.list(dir)) {
stream.forEach(path -> {
String type = Files.isDirectory(path) ? "[DIR]" : "[FILE]";
System.out.println(type + " " + path.getFileName());
});
}
}
}

Важно: Files.list() возвращает поток, который держит открытым файловый дескриптор директории до тех пор, пока поток не закрыт. Поэтому обязательно использование try-with-resources — иначе дескриптор утечёт на уровне ОС, даже если объект Stream будет собран GC.

Файловый дескриптор (file descriptor) — это целочисленный идентификатор, который ядро ОС присваивает открытому файлу или ресурсу. В Unix это индекс в таблице открытых файлов процесса. JVM не знает о дескрипторах ОС — GC управляет только памятью кучи, не ресурсами операционной системы.

Фильтрация по расширению: пример вывода всех .txt файлов
public class TxtFileFinder {

public void findTxtFiles(Path dir) throws IOException {
try (Stream<Path> stream = Files.list(dir)) {
stream.filter(Files::isRegularFile) // только файлы, не директории
.filter(this::hasTxtExtension) // фильтр по расширению
.forEach(this::printFileInfo);
}
}

private boolean hasTxtExtension(Path path) {
String fileName = path.getFileName().toString();
// endsWith чувствителен к регистру — на case-sensitive ФС это корректно
return fileName.endsWith(".txt");
}

private void printFileInfo(Path path) {
try {
long size = Files.size(path);
FileTime modified = Files.getLastModifiedTime(path);
System.out.printf("%s (%d bytes, modified: %s)%n",
path.getFileName(), size, modified);
} catch (IOException e) {
System.err.println("Ошибка чтения атрибутов: " + path.getFileName());
}
}

// Альтернатива: чтение атрибутов за один вызов для каждого файла
public void findTxtFilesOptimized(Path dir) throws IOException {
try (Stream<Path> stream = Files.list(dir)) {
stream.filter(Files::isRegularFile)
.filter(p -> p.toString().endsWith(".txt"))
.map(p -> {
try {
BasicFileAttributes attrs = Files.readAttributes(
p, BasicFileAttributes.class
);
return new FileInfo(p.getFileName(), attrs.size(),
attrs.lastModifiedTime());
} catch (IOException e) {
return null;
}
})
.filter(Objects::nonNull)
.forEach(System.out::println);
}
}

private record FileInfo(Path name, long size, FileTime modified) {}
}

В оптимизированной версии readAttributes() читает все атрибуты за один системный вызов вместо отдельных Files.size() и Files.getLastModifiedTime(). Однако map() в Stream API требует, чтобы лямбда не выбрасывала checked-исключения, поэтому приходится оборачивать в try-catch внутри лямбды.

Record — это тип класса в Java, введённый в Java 16 (preview в 14-15), который автоматически генерирует конструктор, геттеры, equals(), hashCode() и toString() на основе объявленных компонентов. record FileInfo(Path name, long size, FileTime modified) создаёт неизменяемый (immutable) класс данных.


Псевдокод: внутренняя работа Files.list()

функция list(Path dir) -> Stream<Path>:
открыть файловый дескриптор директории dir (системный вызов opendir)
создать DirectoryStream для чтения записей
создать Stream, который при запросе следующего элемента:
вызывает DirectoryStream.readNextEntry()
если запись есть — возвращает Path(entry_name)
если записей больше нет — закрывает дескриптор и завершает поток
зарегистрировать onClose для Stream, который закрывает дескриптор
вернуть Stream

Ключевой момент: DirectoryStream читает записи порциями из буфера ядра. Размер буфера зависит от файловой системы и ОС (обычно 4–32 КБ). Это означает, что для директории с миллионом файлов Files.list() не загружает все имена в память сразу — оно читает их порциями по мере итерации потока.


Files.walk(Path) — рекурсивный обход дерева

Метод Files.walk(Path start) выполняет обход дерева файловой системы, начиная с указанного пути, и возвращает Stream<Path> со всеми посещёнными путями. Обход выполняется в порядке pre-order depth-first (прямой порядок, глубина вперёд): сначала обрабатывается родительский узел, затем его потомки.
public void walkDirectory(Path root) throws IOException {
try (Stream<Path> stream = Files.walk(root)) {
stream.forEach(path -> {
int depth = root.relativize(path).getNameCount();
String indent = " ".repeat(depth);
System.out.println(indent + path.getFileName());
});
}
}



Files.walk(Path, int maxDepth) — ограничение глубины

Метод Files.walk(Path start, int maxDepth) ограничивает глубину обхода. maxDepth = 0 означает только сам start. maxDepth = 1start и его прямые потомки (аналогично Files.list() плюс сам корень).
public void walkWithDepthLimit(Path root) throws IOException {
// Обходим только 2 уровня вглубь
try (Stream<Path> stream = Files.walk(root, 2)) {
stream.filter(Files::isRegularFile)
.forEach(p -> System.out.println(p.toAbsolutePath()));
}
}



#Java #для_новичков #beginner #IO #NIO #Files #Fileslist #Fileswalk
👍5
Порядок обхода и символические ссылки

По умолчанию Files.walk() следует по символическим ссылкам (follows symlinks). Это означает, что если директория содержит символическую ссылку на другую директорию, обход зайдёт внутрь этой целевой директории.

Это может привести к:
Бесконечному циклу, если символическая ссылка указывает на предка в дереве (например, ln -s .. loop).
Обходу файловой системы за пределами ожидаемого дерева (например, ссылка на /etc).

Для безопасного обхода используйте FileVisitOption.FOLLOW_LINKS явно, только если это нужно, или обходите ссылки как файлы:
public class SafeWalker {

// Безопасный обход: символические ссылки обрабатываются как файлы, не следуем по ним
public void walkNoFollowLinks(Path root) throws IOException {
// По умолчанию walk НЕ следует по ссылкам — это поведение по умолчанию
// Но явно можно указать:
try (Stream<Path> stream = Files.walk(root)) {
stream.forEach(path -> {
if (Files.isSymbolicLink(path)) {
System.out.println("[LINK] " + path.getFileName());
}
});
}
}

// Обход с явным следованием по ссылкам (осторожно!)
public void walkFollowingLinks(Path root) throws IOException {
try (Stream<Path> stream = Files.walk(root, FileVisitOption.FOLLOW_LINKS)) {
stream.forEach(System.out::println);
}
}
}

FileVisitOption — это enum, определяющий опции обхода дерева. FOLLOW_LINKS указывает, что символические ссылки на директории должны трактоваться как директории и обходиться рекурсивно. Без этой опции ссылки трактуются как обычные файлы.

Псевдокод: внутренняя работа Files.walk()
функция walk(Path start, int maxDepth) -> Stream<Path>:
создать очередь (Deque) для обхода в ширину/глубину
поместить start в очередь с глубиной 0
создать Stream, который при запросе элемента:
извлекает следующий путь из очереди
возвращает его как элемент Stream
если это директория И глубина < maxDepth:
открыть DirectoryStream для этой директории
для каждой записи:
поместить entry в очередь с глубиной + 1
зарегистрировать onClose для закрытия всех открытых DirectoryStream
вернуть Stream

Важно: Files.walk() открывает DirectoryStream для каждой директории в дереве. Все эти потоки держат открытые файловые дескрипторы до закрытия основного Stream<Path>. Поэтому try-with-resources обязателен — иначе при аварийном завершении обхода дескрипторы останутся открытыми.


#Java #для_новичков #beginner #IO #NIO #Files #Fileslist #Fileswalk
👍5
Параллельный обход: предостережения

stream.parallel() распределяет обход по потокам ForkJoinPool.commonPool(). Это может ускорить обход на многопроцессорных системах, но есть риски:
Порядок обхода непредсказуем: элементы обрабатываются в произвольном порядке.
Конкурентный доступ к файловой системе: множественные потоки одновременно открывают директории, что может привести к contention (конкуренции) на уровне ОС.
Символические ссылки: при параллельном обходе с FOLLOW_LINKS риск зацикливания возрастает.
Ошибки обхода: если один поток получает AccessDeniedException, это не прерывает другие потоки, но обработка ошибок усложняется.

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

ForkJoinPool — это пул потоков в Java, реализующий алгоритм work-stealing (воровство задач). Потоки, завершившие свои задачи, "воруют" задачи из очередей других потоков, что обеспечивает равномерную загрузку. commonPool() — это общий пул, используемый по умолчанию для параллельных Stream.



Осторожно: обход может быть дорогим для больших деревьев

Проблема 1: Потребление памяти

Files.walk() использует внутреннюю очередь для хранения путей, ожидающих обхода. Для сильно ветвящегося дерева (например, директория с миллионом файлов на первом уровне) эта очередь может занять значительный объём памяти. Хотя поток ленивый, внутренний буфер между DirectoryStream и Stream может накапливать пути.

Проблема 2: Файловые дескрипторы
Каждая открытая директория в обходе дерева держит файловый дескриптор. Для глубокого дерева с тысячами директорий это может исчерпать лимит дескрипторов процесса (ulimit -n на Unix, обычно 1024–65536). Если лимит исчерпан, последующие opendir() вызовы вернут EMFILE (too many open files), что JVM преобразует в IOException.

Проблема 3: Время обхода
Обход дерева из миллионов файлов может занять минуты. Stream не предоставляет механизма прерывания (cancellation) стандартными средствами. Прерывание потока через Thread.interrupt() не прерывает внутренний обход — он продолжит открывать директории и читать записи.

Проблема 4: Сортировка и материализация
Если вы применяете sorted() к результату Files.walk(), весь поток материализуется в список в памяти перед сортировкой. Для большого дерева это может привести к OutOfMemoryError.

// ОПАСНО: материализует все пути в память
Files.walk(root).sorted().forEach(...); // OOM при миллионе файлов

// Безопасно: обход без сортировки
Files.walk(root).forEach(...); // ленивый, память ограничена буфером


Альтернатива: Files.walkFileTree() для контролируемого обхода


Для сценариев, где требуется точный контроль над обходом (пропуск поддеревьев, обработка ошибок, прерывание), используйте Files.walkFileTree() с FileVisitor:
import java.nio.file.*;
import java.nio.file.attribute.BasicFileAttributes;
import java.io.IOException;
import java.util.concurrent.atomic.AtomicLong;

public class ControlledTreeWalker implements FileVisitor<Path> {

private final AtomicLong totalSize = new AtomicLong(0);
private final long maxSizeToProcess; // прервать после достижения лимита
private volatile boolean cancelled = false;

public ControlledTreeWalker(long maxSize) {
this.maxSizeToProcess = maxSize;
}

@Override
public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) {
if (cancelled) {
return FileVisitResult.TERMINATE; // прервать обход
}
// Пропускаем скрытые директории
try {
if (Files.isHidden(dir)) {
return FileVisitResult.SKIP_SUBTREE; // не заходить внутрь
}
} catch (IOException e) {
// Продолжаем, если не удалось проверить
}
return FileVisitResult.CONTINUE;
}

@Override
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) {
if (cancelled) {
return FileVisitResult.TERMINATE;
}
if (attrs.isRegularFile()) {
totalSize.addAndGet(attrs.size());
if (totalSize.get() >= maxSizeToProcess) {
cancelled = true;
return FileVisitResult.TERMINATE;
}
}
return FileVisitResult.CONTINUE;
}

@Override
public FileVisitResult visitFileFailed(Path file, IOException exc) {
// Логируем ошибку, но продолжаем обход
System.err.println("Ошибка доступа к файлу: " + file + " — " + exc.getMessage());
return FileVisitResult.CONTINUE;
}

@Override
public FileVisitResult postVisitDirectory(Path dir, IOException exc) {
if (exc != null) {
System.err.println("Ошибка обхода директории: " + dir + " — " + exc.getMessage());
}
return FileVisitResult.CONTINUE;
}

public long getTotalSize() {
return totalSize.get();
}

public static long calculateSizeWithLimit(Path root, long limit) throws IOException {
ControlledTreeWalker walker = new ControlledTreeWalker(limit);
Files.walkFileTree(root, walker);
return walker.getTotalSize();
}
}



#Java #для_новичков #beginner #IO #NIO #Files #Fileslist #Fileswalk
👍6
AtomicLong — это класс в пакете java.util.concurrent.atomic, предоставляющий атомарные операции над long без блокировок (lock-free). Он использует примитивы CAS (Compare-And-Swap) на уровне процессора для обеспечения потокобезопасности. В отличие от synchronized, atomic-классы не требуют захвата монитора и более эффективны при высокой конкуренции.

FileVisitResult — это enum, определяющий действие после обработки узла дерева:
CONTINUE — продолжить обход.
TERMINATE — прервать обход полностью.
SKIP_SUBTREE — не заходить в текущую директорию (для preVisitDirectory).
SKIP_SIBLINGS — пропустить остальные элементы текущей директории.
Files.walkFileTree() не использует Stream API и не материализует дерево в памяти. Он обходит рекурсивно, вызывая callback'и, и управляет стеком вызовов самостоятельно. Это более предсказуемо по памяти, но менее гибко для функциональных преобразований (filter, map, reduce).


Путь байтов в памяти JVM при обходе директорий

Files.list(): путь данных
При вызове Files.list(dir):
Объект Path (директория) находится в куче (Heap). JVM извлекает его строковое представление и кодирует в байты пути в нативной памяти.
Выполняется системный вызов opendir() (POSIX) или FindFirstFile() (Windows). Ядро ОС открывает директорию, выделяет файловый дескриптор и возвращает его в пространство процесса. Дескриптор — это целое число, хранящееся в нативной памяти JVM (не в куче), в структуре DirectoryStream.
DirectoryStream — это объект в куче, содержащий ссылку на нативный дескриптор. Он создаётся в Young Generation, в Eden. При закрытии потока (вызов close() или try-with-resources) выполняется системный вызов closedir(), который освобождает дескриптор на уровне ОС.
Если забыть закрыть поток, объект DirectoryStream становится мусором и собирается Minor GC (или Major GC, если он пережил несколько циклов). Однако GC не знает о нативном дескрипторе — он освобождает только память кучи. Дескриптор остаётся открытым до завершения процесса или исчерпания лимита дескрипторов. Это классическая утечка нативных ресурсов.
При итерации по Stream<Path> каждая запись директории читается через readdir() (POSIX) или FindNextFile() (Windows). Запись возвращается как структура в нативной памяти, из которой JVM извлекает имя файла и создаёт новый объект Path в куче. Этот Path создаётся в Young Generation.
Если обход короткий (несколько сотен файлов), все объекты Path создаются в Eden и собираются при следующей Minor GC. Если обход длительный (миллионы файлов), объекты Path могут пережить несколько циклов Minor GC и мигрировать в Survivor Space, а затем в Old Generation через механизм tenuring (старение).
Tenuring — это процесс в JVM, при котором объекты, пережившие несколько циклов Minor GC, перемещаются из Survivor Space в Old Generation. Порог (tenuring threshold) настраивается через опцию -XX:MaxTenuringThreshold (по умолчанию 15 в G1 GC).


Files.walk(): путь данных

При Files.walk(root):
Метод создаёт внутренний FileTreeWalker — объект в куче, который управляет обходом дерева. Он содержит очередь (ArrayDeque<Path>) для хранения путей, ожидающих обхода.
Для каждой директории в дереве FileTreeWalker открывает DirectoryStream, что порождает новый файловый дескриптор. Все эти дескрипторы остаются открытыми до закрытия основного Stream.
Когда Stream запрашивает следующий элемент, FileTreeWalker извлекает путь из очереди. Если это директория и глубина позволяет, он открывает её DirectoryStream, читает записи и добавляет их в очередь. Каждый добавленный путь — это новый объект Path в куче.
Очередь FileTreeWalker может накапливать пути. Для дерева с фактором ветвления B (среднее число потомков на узел) и глубиной D максимальный размер очереди примерно B * D. Для B=100 и D=10 это 1000 объектов Path — незначительно. Но для B=10000 (директория с 10000 файлов) на первом уровне очередь может содержать 10000 объектов Path.
Каждый объект Path в OpenJDK (реализация UnixPath или WindowsPath) хранит массив строк-сегментов и ссылку на FileSystem. Размер объекта — примерно 40–80 байт плюс массив сегментов. Для 10000 файлов это 400–800 КБ только на объекты Path — приемлемо для современных JVM. Но для миллиона файлов на одном уровне — 40–80 МБ, что может вызвать давление на Old Generation.
При фильтрации через stream.filter(Files::isRegularFile) каждый отфильтрованный элемент (директория) всё равно создаётся как объект Path, но не проходит дальше по конвейеру. Объект становится мусором и собирается Minor GC. Однако если фильтрация редко пропускает элементы (например, ищем один файл среди миллиона), большинство объектов Path создаются и тут же уничтожаются — это создаёт высокую нагрузку на Minor GC (GC churn).

GC churn — это ситуация, когда большое количество объектов быстро создаётся и уничтожается, вызывая частые циклы сборки мусора. Это снижает производительность приложения, так как потоки исполнения приостанавливаются (stop-the-world паузы) для работы GC.


Оптимизация: избегаем лишних объектов

Для снижения давления на GC при массовом обходе:
// Оптимизация: используем walkFileTree вместо Stream для минимизации объектов
public long fastSizeCalculation(Path root) throws IOException {
long[] total = new long[1]; // массив примитивов — нет boxing, нет объектов

Files.walkFileTree(root, new SimpleFileVisitor<Path>() {
@Override
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) {
total[0] += attrs.size(); // прямой доступ к примитиву
return FileVisitResult.CONTINUE;
}
});

return total[0];
}

В этом коде нет объектов Stream, нет лямбд (которые компилируются в синтетические классы и требуют загрузки в Metaspace), нет boxing'а Long. Единственные объекты в куче — SimpleFileVisitor (один на весь обход) и BasicFileAttributes для каждого файла (лёгкие, собираются Minor GC). Это самый эффективный способ с точки зрения памяти и GC.

Boxing — это процесс автоматической упаковки примитивного типа в соответствующий объект-обёртку (например, long в Long). В Stream API mapToLong() возвращает LongStream, который работает с примитивами и избегает boxing. Но map() с лямбдой, возвращающей Long, вызывает boxing на каждой итерации.



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

import java.io.IOException;
import java.nio.file.*;
import java.util.stream.Stream;

public class Task210726 {
public static void main(String[] args) throws IOException {
Path dir = Paths.get("test");
Files.createDirectory(dir);
Files.createFile(dir.resolve("a.txt"));
Files.createFile(dir.resolve("b.txt"));

Stream<Path> stream = Files.list(dir);
System.out.print(stream.count() + " ");
System.out.print(stream.count());
}
}


#Tasks
👍2
Что такое Thread.onSpinWait() (Java 9)? 🤓

Ответ:

Thread.onSpinWait()
 — метод, который подсказывает JVM и процессору, что текущий поток выполняет активное ожидание (busy-wait).

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

Используется в низкоуровневых lock-free алгоритмах (например, ожидание в AbstractQueuedSynchronizer). Обычно вызывается внутри цикла, где ожидается некоторое условие, но блокировка неэффективна.

Улучшает производительность по сравнению с пустым циклом.



#собеседование
Please open Telegram to view this post
VIEW IN TELEGRAM
👍4
История технологии сегодня — 22 июля

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

Па́вел О́сипович Сухо́й (бел. Павел Восіпавіч Сухі; 10 [22] июля 1895, Глубокое, Дисненский уезд, Виленская губерния, Российская империя (ныне Витебская область, Беларусь) — 15 сентября 1975МоскваСССР) — советский авиаконструктор, доктор технических наук, один из основателей советской реактивной и сверхзвуковой авиации.

Гу́став Лю́двиг Герц (нем. Gustav Ludwig Hertz; 22 июля 1887, Гамбург — 30 октября 1975, Берлин) — немецкий физик, лауреат Нобелевской премии по физике в 1925 году «за открытие законов соударения электрона с атомом» (совместно с Джеймсом Франком).

Габриэ́ль Ламе́ (фр. Gabriel Lamé; 22 июля 1795, Тур — 1 мая 1870, Париж) — французский математикмеханикфизик и инженерчлен-корреспондент Петербургской академии наук (1829), член Парижской академии наук (1843), профессор Политехнической школы (1832—1863) и Парижского университета (1848—1863). В 1820—1831 годах работал в России, в петербургском Институте Корпуса инженеров путей сообщения. Основные труды — по математической физике и теории упругости. Разработал общую теорию криволинейных координат (1833), ввёл так называемые коэффициенты Ламе (1859) и специальный класс функций (1839, функции Ламе). В честь него названы параметры Ламе в теории упругости.


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

1961 — первый полёт первого советского экраноплана «СМ-1».

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


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

Тема: String.getBytes() использует платформенно-зависимую кодировку.

Проблема: Метод String.getBytes() без аргументов преобразует строку в массив байт, используя системную кодировку по умолчанию, которая определяется операционной системой и настройками JVM (file.encoding).

На разных платформах это может быть CP1251 (Windows), UTF-8 (Linux), MacRoman (macOS) или другие. Это приводит к тому, что одно и то же приложение на разных машинах генерирует разные байтовые последовательности для одинаковых строк, что вызывает ошибки при передаче данных по сети, записи в файлы или взаимодействии с внешними системами. Особенно критично для символов вне ASCII (кириллица, иероглифы) — они могут быть испорчены или потеряны. Даже если сейчас всё работает, при смене окружения (например, при деплое на сервер с другой кодировкой) приложение внезапно ломается, и диагностика затруднена, так как проблема проявляется только в определённых условиях.

Решение: Всегда указывайте кодировку явно с помощью String.getBytes(Charset) или String.getBytes(String charsetName).

Предпочтительно использовать константы из StandardCharsets (Java 7+), например, StandardCharsets.UTF_8. Это гарантирует одинаковое поведение на всех платформах и делает намерения разработчика явными. Аналогичное правило применяется к конструктору new String(byte[], Charset) и другим методам преобразования между строками и байтами.

Для проектов, ориентированных на международное взаимодействие, UTF-8 является стандартом де-факто.
import java.nio.charset.StandardCharsets;
import java.io.UnsupportedEncodingException;

public class StringGetBytes {

public static void main(String[] args) throws UnsupportedEncodingException {
String text = "Привет, мир!";

//Антипаттерн: платформенно-зависимая кодировка
byte[] badBytes = text.getBytes();
System.out.println("Длина (default): " + badBytes.length);
// На Windows (CP1251) результат может быть другим, чем на Linux (UTF-8)

//Решение: явная кодировка UTF-8
byte[] goodBytes = text.getBytes(StandardCharsets.UTF_8);
System.out.println("Длина (UTF-8): " + goodBytes.length);

// Также при обратном преобразовании
String restored = new String(goodBytes, StandardCharsets.UTF_8);
System.out.println("Восстановлено: " + restored);

// Для старых версий Java (до 7) используйте строковое имя
// byte[] bytes = text.getBytes("UTF-8");
// Но StandardCharsets предпочтительнее (типобезопасно)

// Демонстрация различий: символы вне ASCII кодируются по-разному
String euro = "€"; // Символ евро
byte[] utf8 = euro.getBytes(StandardCharsets.UTF_8); // 3 байта (E2 82 AC)
byte[] win1251 = euro.getBytes("Windows-1251"); // 1 байт (80) или не поддерживается
System.out.println("UTF-8 length: " + utf8.length);
System.out.println("Windows-1251 length: " + win1251.length);
}
}


Объяснение:
 Кодировка по умолчанию определяется системным свойством file.encoding и может быть изменена при запуске JVM (например, -Dfile.encoding=UTF-8).

Однако это глобальная настройка, которая может быть неочевидна для других разработчиков, а её изменение может повлиять на другие части системы (например, чтение файлов). Явное указание кодировки делает код самодокументируемым и устойчивым к изменениям окружения. UTF-8 является предпочтительной кодировкой для обмена данными в современных системах: она поддерживает все символы Unicode, эффективна для текстов на латинице и широко используется в вебе, JSON, XML.

Использование StandardCharsets.UTF_8 исключает проверяемое исключение UnsupportedEncodingException, так как UTF-8 гарантированно поддерживается всеми реализациями JVM.


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

public class Task220726 {
public static void main(String[] args) {
String s = "é";
System.out.println(s.getBytes().length);
}
}


#Tasks
👍2
👍2
Что такое ForkJoinPool.commonPool() и когда его использовать? 🤓

Ответ:

ForkJoinPool.commonPool() 
— это общий пул потоков для приложения (Java 8), используемый по умолчанию в параллельных стримах (parallelStream()) и CompletableFuture (в некоторых методах).

Размер по умолчанию = количество процессоров − 1.

Не рекомендуется блокировать (спать, ввод-вывод) внутри задач, выполняющихся в commonPool, так как это замедлит другие задачи.

Если ваши задачи могут блокироваться, создавайте отдельный ForkJoinPool. Используйте commonPool для коротких вычислительных задач, не блокирующих IO.



#собеседование
Please open Telegram to view this post
VIEW IN TELEGRAM
👍4
История технологии сегодня — 23 июля

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

Влади́мир Я́ковлевич Кли́мов (1892—1962) — советский учёный в области авиационного моторостроенияконструктор авиационных двигателейгенерал-майор инженерно-авиационной службы (1944), академик АН СССР (1953). В ходе научных исследований и конструкторской работы Климовым были разработаны и внедрены специальная закрытая система жидкостного охлаждения поршневых авиационных двигателей под давлением, воздушный нагнетатель с двухскоростным приводом, усовершенствованная система газораспределения, система питания топливовоздушной смесью мощных и высокооборотных авиадвигателей, предложен ряд оригинальных решений в конструкциях ТРД. Климов внёс существенный вклад в развитие теории смазки, в решение проблем уравновешивания поршневых авиадвигателей и др. вопросов двигателестроения.


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

1874 — Александр Николаевич Лодыгин получил привилегию № 1619 на лампу накаливания. Его изобретение было запатентовано и в нескольких европейских странах, Петербургская АН присудила ему в этом году Ломоносовскую премию, а в конце года было создано «Товарищество электрического освещения А. Н. Лодыгин и Ко».

1903 — спустя месяц после образования корпорации «Ford Motor Company» она выпустила в продажу свой первый автомобиль «модель А». В течение первого года своего существования компания произвела 1700 автомобилей. Первый автомобиль, выставленный на продажу, был описан как «наиболее совершенная машина на рынке, которую в состоянии водить даже 15-летний мальчик». Первый автомобиль был продан доктору Е. Пфеннингу (англ. Ernst Pfenning) из Чикаго, который купил машину месяц спустя после регистрации компании, к большой радости обеспокоенных акционеров, нервно наблюдавших за уменьшением банковского баланса до $223.

1980 — запуск космического корабля Союз-37, в составе его экипажа в космос отправился первый космонавт из Азиивьетнамец Фам Туан (Phạm Tuân).

2015 — с помощью телескопа «Кеплер» найдена первая экзопланета земного типа, которая находится в зоне обитаемости солнцеподобной звезды — Kepler-452 b.


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

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

Files.lines() и WatchService — построчное чтение и мониторинг файловой системы

Два механизма в Java NIO.2 решают противоположные задачи: Files.lines() читает содержимое файла построчно, а WatchService наблюдает за изменениями в файловой системе без активного чтения. Первый экономит память при обработке больших текстовых файлов, второй — CPU при ожидании событий. Оба требуют строгого управления ресурсами, так как работают с нативными дескрипторами ОС, невидимыми для GC.


Files.lines(Path) — построчное чтение текстовых файлов

Метод Files.lines(Path path) возвращает Stream<String>, который лениво читает файл построчно. Каждая строка декодируется из байтов файла в символы с использованием кодировки по умолчанию (StandardCharsets.UTF_8).
import java.nio.file.*;
import java.io.IOException;
import java.util.stream.Stream;

public class LineReader {

public void readLines(Path file) throws IOException {
// Stream<String> — ленивое чтение, строки появляются по мере обхода
try (Stream<String> lines = Files.lines(file)) {
lines.forEach(line -> System.out.println(line));
}
// BufferedReader закрыт автоматически try-with-resources
}
}

Важно: Files.lines() открывает BufferedReader под капотом, который держит файловый дескриптор открытым. Поток Stream<String> должен быть закрыт через try-with-resources или явный close(). Если поток не закрыт, дескриптор останется открытым даже после того, как Stream будет собран GC.

BufferedReader — это класс в Java, который буферизует чтение символов из потока. Вместо чтения по одному символу или байту он читает блоками (обычно 8 КБ) в буфер в памяти, а затем отдаёт символы из буфера. Это снижает количество системных вызовов read() с O(N) до O(N / buffer_size).

Files.lines(Path, Charset) — с указанием кодировки
import java.nio.charset.StandardCharsets;

public void readWithEncoding(Path file) throws IOException {
// Явное указание кодировки — обязательно для файлов не в UTF-8
try (Stream<String> lines = Files.lines(file, StandardCharsets.ISO_8859_1)) {
lines.forEach(System.out::println);
}
}

Если кодировка не указана, используется Charset.defaultCharset(), которая зависит от ОС и локали. На Linux обычно UTF-8, на Windows — Windows-1252 или CP1251. Неявное использование дефолтной кодировки — распространённый источник багов при переносе приложения между платформами.

Кодировка (charset, character encoding) — это схема сопоставления байтовых последовательностей символам текста. UTF-8 — переменная длина (1–4 байта на символ), обратно совместима с ASCII. ISO-8859-1 (Latin-1) — фиксированная 1 байт на символ, покрывает западноевропейские языки.


Ленивость и потребление памяти

Files.lines() — ленивый поток. Строка читается из файла только когда терминальная операция запрашивает следующий элемент. Это позволяет обрабатывать файлы любого размера с константным потреблением памяти:
public void processHugeFile(Path file) throws IOException {
try (Stream<String> lines = Files.lines(file)) {
// Фильтрация и подсчёт — каждая строка обрабатывается и отбрасывается
long errorCount = lines.filter(line -> line.contains("ERROR"))
.count();
System.out.println("Ошибок: " + errorCount);
// Память не зависит от размера файла — только от длины одной строки
}
}

В отличие от Files.readAllLines(), который загружает весь файл в List<String> (потенциально гигабайты в куче), Files.lines() держит в памяти только текущую строку и буфер BufferedReader (8 КБ).


WatchService — отслеживание изменений в файловой системе

WatchService — это сервис в NIO.2, который использует нативные механизмы ОС для мониторинга изменений в файловой системе. Вместо периодического опроса (polling) он работает по событийной модели: ОС уведомляет JVM, когда происходит зарегистрированное событие.

Polling (опрос) — это стратегия проверки состояния, при которой программа периодически запрашивает ресурс (например, проверяет время модификации файла). Это расходует CPU впустую. Событийная модель (event-driven) — противоположный подход, при котором программа ждёт уведомления от ОС и не потребляет ресурсы в ожидании.


Нативные механизмы ОС

WatchService в Java — это тонкая обёртка над нативными API:
Linux: inotify — система уведомлений о событиях файловой системы на уровне ядра. Каждый WatchService создаёт экземпляр inotify через системный вызов inotify_init().
macOS: FSEvents — фреймворк уведомлений о изменениях файловой системы. Работает на уровне всей ФС, а не отдельных директорий, что даёт другую семантику событий.
Windows: ReadDirectoryChangesW — WinAPI функция для асинхронного чтения изменений директории.

Важно: WatchService не гарантирует доставку всех событий и не гарантирует порядок. Некоторые события могут быть пропущены при высокой нагрузке, и события могут приходить пачками (coalescing).

Coalescing (слияние событий) — это оптимизация, при которой несколько последовательных событий над одним и тем же файлом объединяются в одно. Например, множественные быстрые записи в файл могут породить только одно событие ENTRY_MODIFY.

Создание WatchService
import java.nio.file.*;

public class WatchServiceFactory {

public WatchService createWatchService() throws IOException {
// Получаем WatchService для файловой системы по умолчанию
WatchService watchService = FileSystems.getDefault().newWatchService();
return watchService;
}
}

FileSystems.getDefault().newWatchService() создаёт нативный дескриптор inotify (Linux), регистрирует callback в FSEvents (macOS) или создаёт объект ожидания ReadDirectoryChangesW (Windows). Этот дескриптор существует в нативной памяти вне кучи JVM.

Регистрация директории
import java.nio.file.*;

public class DirectoryRegistrar {

public void registerDirectory(Path dir, WatchService watchService) throws IOException {
// Регистрируем директорию на отслеживание создания, удаления и модификации
WatchKey key = dir.register(watchService,
StandardWatchEventKinds.ENTRY_CREATE, // создание файла/директории
StandardWatchEventKinds.ENTRY_DELETE, // удаление
StandardWatchEventKinds.ENTRY_MODIFY // изменение содержимого или метаданных
);

// WatchKey — это токен, идентифицирующий зарегистрированную директорию
// Его можно использовать для отмены регистрации
System.out.println("Директория зарегистрирована: " + dir);
}
}

WatchKey — это объект, представляющий регистрацию директории в WatchService. Он содержит ссылку на зарегистрированный путь и очередь событий. Когда WatchKey становится недействительным (например, директория удалена), его метод isValid() возвращает false.


#Java #для_новичков #beginner #IO #NIO #Files #Files_lines #WatchService
👍5
Ожидание событий: take() vs poll()
import java.nio.file.*;
import java.util.concurrent.TimeUnit;

public class EventPoller {

// Блокирующее ожидание: поток ждёт, пока не появится событие
public void blockingWait(WatchService watchService) throws InterruptedException {
// take() блокирует поток до появления события
// Прерывается только при InterruptedException или закрытии WatchService
WatchKey key = watchService.take();
processKey(key);
}

// Неблокирующая проверка: возвращает null, если событий нет
public void nonBlockingPoll(WatchService watchService) {
WatchKey key = watchService.poll();
if (key != null) {
processKey(key);
} else {
System.out.println("Нет новых событий");
}
}

// Ожидание с таймаутом
public void timedPoll(WatchService watchService) throws InterruptedException {
// Ждём 5 секунд, если событий нет — возвращаем null
WatchKey key = watchService.poll(5, TimeUnit.SECONDS);
if (key != null) {
processKey(key);
}
}

private void processKey(WatchKey key) {
for (WatchEvent<?> event : key.pollEvents()) {
WatchEvent.Kind<?> kind = event.kind();
Path fileName = (Path) event.context(); // имя файла, вызвавшего событие

System.out.println("Событие: " + kind.name() + " для " + fileName);
}
// Сбрасываем ключ, чтобы получать новые события
boolean valid = key.reset();
if (!valid) {
System.out.println("Ключ больше не валиден — директория удалена");
}
}
}

take() — блокирующий метод, который переводит поток в состояние WAITING. Поток не потребляет CPU в этом состоянии. ОС пробуждает поток, когда приходит событие inotify/FSEvents/ReadDirectoryChangesW.
poll() — неблокирующий. Если событий нет, немедленно возвращает null. Полезен в циклах, где нужно делать другую работу между проверками.
poll(timeout, TimeUnit) — блокирует на указанное время, затем возвращает null или WatchKey.

Важно: после обработки событий необходимо вызвать key.reset(). Это сбрасывает состояние ключа и позволяет WatchService снова наполнять его событиями. Если reset() не вызван, ключ перестаёт получать новые события. Если reset() возвращает false, ключ больше не валиден (обычно потому, что директория удалена).


Обработка WatchEvent
WatchEvent<?> event = ...;

// Тип события: ENTRY_CREATE, ENTRY_DELETE, ENTRY_MODIFY
WatchEvent.Kind<?> kind = event.kind();

// Имя файла или директории, вызвавшей событие (относительно зарегистрированной директории)
Path context = (Path) event.context();

// Количество событий данного типа для этого файла (может быть > 1 при coalescing)
int count = event.count();


event.context()
возвращает Path, но это не абсолютный путь — это имя файла относительно зарегистрированной директории. Для получения абсолютного пути нужно резолвить через watchable() ключа:
Path dir = (Path) key.watchable();  // зарегистрированная директория
Path absolute = dir.resolve(context); // полный путь


Важные нюансы WatchService

Событие CREATE приходит до завершения записи: если процесс создаёт файл и медленно пишет в него, ENTRY_CREATE приходит сразу после появления inode. Файл может быть ещё пустым или заблокирован на запись. Решение — ожидание готовности (как в waitForFileReady()) или использование временных имён с последующим атомарным переименованием.

Событие MODIFY может приходить многократно: одна логическая операция записи может породить серию событий ENTRY_MODIFY. Приложение должно быть готово к дедупликации или игнорированию промежуточных событий.

Символические ссылки: WatchService следует по символическим ссылкам при регистрации. Если зарегистрировать ссылку, события будут приходить для целевой директории.

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

OVERFLOW: при слишком быстром потоке событий внутренний буфер ОС может переполниться. В этом случае приходит событие OVERFLOW, и часть событий теряется. Приложение должно обрабатывать это как сигнал к полному пересканированию директории.


Путь байтов в памяти JVM при построчном чтении

Files.lines(): путь данных

При Files.lines(file):
Метод создаёт BufferedReader — объект в куче, который оборачивает InputStreamReader, который оборачивает FileInputStream. Каждый из этих объектов — в куче. FileInputStream содержит файловый дескриптор (целое число в нативной памяти).
BufferedReader выделяет буфер char[] размером 8192 символов (по умолчанию) в Young Generation. Этот буфер переиспользуется для всех операций чтения.
При итерации по Stream<String> каждый вызов readLine() выполняет:
Чтение байтов из файла через FileInputStream.read() в буфер BufferedReader.
Декодирование байтов в символы через CharsetDecoder (для UTF-8 — переменная длина, 1–4 байта на символ).
Поиск символа перевода строки (\n, \r\n или \r).
Создание нового объекта String для строки.
Объект String создаётся в Young Generation (Eden). Если строка длинная (например, CSV-строка с тысячей символов), она может быть размещена сразу в Old Generation как humongous object (в G1 GC, если размер превышает половину региона).
После обработки строки в терминальной операции (например, count() или forEach()) ссылка на String исчезает. Объект становится мусором и собирается Minor GC. Буфер BufferedReader остаётся живым до закрытия потока.
При закрытии Stream (через try-with-resources) вызывается BufferedReader.close(), который закрывает FileInputStream, который выполняет системный вызов close() для освобождения файлового дескриптора. Если поток не закрыт, дескриптор утечёт.


Путь байтов при WatchService


При создании WatchService:
FileSystems.getDefault().newWatchService() создаёт объект WatchService в куче. На Linux это обёртка над InotifyWatchService, который выполняет системный вызов inotify_init().
inotify_init() создаёт экземпляр inotify в ядре Linux и возвращает файловый дескриптор. Этот дескриптор хранится в нативной памяти JVM, ассоциированной с объектом WatchService.
При регистрации директории (dir.register(...)) выполняется inotify_add_watch(), который добавляет "watch" (наблюдение) за директорией в ядре. Ядро начинает отслеживать события для этой директории и всех её прямых потомков.
inotify в ядре использует собственный буфер событий (обычно 16 КБ по умолчанию, настраивается через /proc/sys/fs/inotify/max_queued_events). Если событий больше, чем вмещает буфер, приходит OVERFLOW.
При вызове watchService.take() поток Java блокируется на системном вызове read() от дескриптора inotify. Поток переводится в состояние WAITING — он не потребляет CPU. Когда событие появляется, ядро пробуждает поток, read() возвращает структуру события, и JVM создаёт объекты WatchKey и WatchEvent в куче.
Объекты WatchKey и WatchEvent создаются в Young Generation. WatchKey может переживать несколько циклов событий, если его не сбросить через reset(). WatchEvent — одноразовый объект, который становится мусором после обработки.
При закрытии WatchService (watchService.close()) выполняется inotify_rm_watch() для всех зарегистрированных наблюдений и close() для дескриптора inotify. Это освобождает ресурсы ядра. Если WatchService не закрыт, дескриптор inotify остаётся открытым до завершения процесса.

Важно: inotify на Linux не рекурсивен. WatchService регистрирует наблюдение только для указанной директории, не для поддиректорий. Для рекурсивного мониторинга Java обходит дерево и создаёт отдельный inotify_add_watch() для каждой директории. Каждый watch потребляет ресурсы ядра (inode наблюдения), и их количество ограничено (/proc/sys/fs/inotify/max_user_watches, обычно 8192).


#Java #для_новичков #beginner #IO #NIO #Files #Files_lines #WatchService
👍4