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
История технологии сегодня — 20 июля

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

Григо́рий Васи́льевич Кисунько́ (укр. Григорій Васильович Кісунько; 20 июля 1918, Бельманка, Екатеринославская губерния — 11 октября 1998, Москва) — советский учёный в области радиоэлектроники. Один из создателей советской системы противоракетной обороны.

Герд Карл Би́нниг (нем. Gerd Karl Binnig; род. 20 июля 1947) — немецкий физик, в 1986 году совместно с Генрихом Рорером получил Нобелевскую премию по физике за изобретение сканирующего туннельного микроскопа. Разработки Биннига расширили возможности нанотехнологий, позволив не только визуализировать отдельные атомы, но и манипулировать ими.

Гео́ргий Э́рихович Лангема́к (8 (20) июля 1898, Старобельск, Старобельский уезд, Харьковская губерния, Российская империя — 11 января 1938, Москва, РСФСР, СССР) российский и советский учёный, один из пионеров ракетной техники и один из создателей первых реактивных снарядов в СССР. Основоположник исследований по конструированию реактивных снарядов на бездымном порохе, открыл так называемый закон подобия, знание которого позволило определять оптимальную геометрию сопла реактивного двигателя без длительных дорогостоящих экспериментов — аналитическим расчётом. Ввёл в русский язык термин «космонавтика».


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

1807 — Нисефор Ньепс с братом получили патент на один из ранних двигателей внутреннего сгоранияпиреолофор[англ.].

1969 — «Аполлон-11» совершил первую в истории пилотируемую посадку на Луну.


#Biography #Birth_Date #Events #20июля
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥3
Вакансия мечты, не? 🚽
Please open Telegram to view this post
VIEW IN TELEGRAM
1
[Совет по 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