Please open Telegram to view this post
VIEW IN TELEGRAM
[Совет по Java #062]
Тема:
Проблема: Метод
Это приводит к тому, что разделяемые объекты остаются в частично обновлённом состоянии, а другие потоки, захватившие освобождённые мониторы, могут читать несогласованные данные. Остановка потока в произвольный момент нарушает инварианты объектов, и восстановить состояние системы становится невозможно. Метод был объявлен устаревшим (deprecated) ещё в Java 1.2 и никогда не должен использоваться в production-коде.
Решение: Вместо принудительной остановки используйте кооперативный механизм прерывания. Вызовите
Целевой поток должен периодически проверять статус прерывания с помощью
Дополнительно можно использовать
Объяснение: При вызове
Все мониторы, захваченные этим потоком, принудительно освобождаются, что позволяет другим потокам войти в критическую секцию до того, как изменения были завершены.
Это нарушает атомарность и может привести к трудноотлавливаемым повреждениям данных.
#Java #советы
Тема:
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
Что выведет код?
#Tasks
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
Изучаем до уровня стандартного мидла)
Пишите @Oleborn
Что такое 🤓
Ответ:
System.Logger (Java 9) — это стандартный API для логирования, позволяющий приложениям использовать разные реализации (SLF4J, Log4j2, JUL) через абстракцию.
Получаем логгер через System.getLogger("name"). Предоставляет уровни (INFO, DEBUG, ERROR) и методы log().
Не требует внешних зависимостей. Однако широкого распространения не получил, так как SLF4J уже де-факто стандарт.
Используется в модульной системе и для внутренних нужд JDK.
#собеседование
System.Logger API (Java 9)? Ответ:
Получаем логгер через 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июля
Ру́дольф «Руди» Артур Ма́ркус (англ. 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 предоставляет два ключевых метода для обхода директорий:
Stream API — это фреймворк в Java для обработки последовательностей элементов в функциональном стиле. Он поддерживает ленивые (lazy) промежуточные операции (filter, map, sorted) и энергичные (eager) терминальные операции (count, collect, forEach). Stream не хранит данные, а описывает конвейер вычислений.
Files.list(Path) — содержимое директории (не рекурсивно)
Метод
Важно:
Файловый дескриптор (file descriptor) — это целочисленный идентификатор, который ядро ОС присваивает открытому файлу или ресурсу. В Unix это индекс в таблице открытых файлов процесса. JVM не знает о дескрипторах ОС — GC управляет только памятью кучи, не ресурсами операционной системы.
Фильтрация по расширению: пример вывода всех .txt файлов
В оптимизированной версии
Record — это тип класса в Java, введённый в Java 16 (preview в 14-15), который автоматически генерирует конструктор, геттеры, equals(), hashCode() и toString() на основе объявленных компонентов. record FileInfo(Path name, long size, FileTime modified) создаёт неизменяемый (immutable) класс данных.
Псевдокод: внутренняя работа Files.list()
Ключевой момент:
Files.walk(Path) — рекурсивный обход дерева
Метод
Files.walk(Path, int maxDepth) — ограничение глубины
Метод
#Java #для_новичков #beginner #IO #NIO #Files #Fileslist #Fileswalk
Глава 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 = 1 — start и его прямые потомки (аналогично 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
Порядок обхода и символические ссылки
По умолчанию
Это может привести к:
Бесконечному циклу, если символическая ссылка указывает на предка в дереве (например,
Обходу файловой системы за пределами ожидаемого дерева (например, ссылка на
Для безопасного обхода используйте
FileVisitOption — это enum, определяющий опции обхода дерева.
Псевдокод: внутренняя работа Files.walk()
Важно:
#Java #для_новичков #beginner #IO #NIO #Files #Fileslist #Fileswalk
По умолчанию
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
Параллельный обход: предостережения
Порядок обхода непредсказуем: элементы обрабатываются в произвольном порядке.
Конкурентный доступ к файловой системе: множественные потоки одновременно открывают директории, что может привести к contention (конкуренции) на уровне ОС.
Символические ссылки: при параллельном обходе с
Ошибки обхода: если один поток получает
Рекомендуется использовать параллельный обход только для больших деревьев на быстрых локальных дисках (SSD, NVMe) и только если операция для каждого файла тяжёлая (например, вычисление хэша, а не просто чтение размера).
ForkJoinPool — это пул потоков в Java, реализующий алгоритм work-stealing (воровство задач). Потоки, завершившие свои задачи, "воруют" задачи из очередей других потоков, что обеспечивает равномерную загрузку. commonPool() — это общий пул, используемый по умолчанию для параллельных Stream.
Осторожно: обход может быть дорогим для больших деревьев
Проблема 1: Потребление памяти
Проблема 2: Файловые дескрипторы
Каждая открытая директория в обходе дерева держит файловый дескриптор. Для глубокого дерева с тысячами директорий это может исчерпать лимит дескрипторов процесса (ulimit -n на Unix, обычно 1024–65536). Если лимит исчерпан, последующие
Проблема 3: Время обхода
Обход дерева из миллионов файлов может занять минуты.
Проблема 4: Сортировка и материализация
Если вы применяете
Альтернатива: Files.walkFileTree() для контролируемого обхода
Для сценариев, где требуется точный контроль над обходом (пропуск поддеревьев, обработка ошибок, прерывание), используйте
#Java #для_новичков #beginner #IO #NIO #Files #Fileslist #Fileswalk
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, определяющий действие после обработки узла дерева:
Путь байтов в памяти JVM при обходе директорий
Files.list(): путь данных
При вызове
Объект
Выполняется системный вызов
Если забыть закрыть поток, объект
При итерации по
Если обход короткий (несколько сотен файлов), все объекты
Tenuring — это процесс в JVM, при котором объекты, пережившие несколько циклов Minor GC, перемещаются из Survivor Space в Old Generation. Порог (tenuring threshold) настраивается через опцию
Files.walk(): путь данных
При
Метод создаёт внутренний
Для каждой директории в дереве
Когда
Очередь
Каждый объект
При фильтрации через
GC churn — это ситуация, когда большое количество объектов быстро создаётся и уничтожается, вызывая частые циклы сборки мусора. Это снижает производительность приложения, так как потоки исполнения приостанавливаются (stop-the-world паузы) для работы GC.
Оптимизация: избегаем лишних объектов
Для снижения давления на GC при массовом обходе:
В этом коде нет объектов
Boxing — это процесс автоматической упаковки примитивного типа в соответствующий объект-обёртку (например, long в Long). В Stream API mapToLong() возвращает LongStream, который работает с примитивами и избегает boxing. Но map() с лямбдой, возвращающей Long, вызывает boxing на каждой итерации.
#Java #для_новичков #beginner #IO #NIO #Files #Fileslist #Fileswalk
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
Что выведет код?
#Tasks
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
Варианты ответа:
Anonymous Quiz
11%
2 2
22%
2 0
56%
2, затем исключение IllegalStateException
11%
2, затем исключение IOException
👍2
Что такое 🤓
Ответ:
Thread.onSpinWait() — метод, который подсказывает JVM и процессору, что текущий поток выполняет активное ожидание (busy-wait).
Это позволяет процессору оптимизировать энергопотребление и эффективность, например, переключить ядро в режим пониженного потребления.
Используется в низкоуровневых lock-free алгоритмах (например, ожидание в AbstractQueuedSynchronizer). Обычно вызывается внутри цикла, где ожидается некоторое условие, но блокировка неэффективна.
Улучшает производительность по сравнению с пустым циклом.
#собеседование
Thread.onSpinWait() (Java 9)? Ответ:
Это позволяет процессору оптимизировать энергопотребление и эффективность, например, переключить ядро в режим пониженного потребления.
Используется в низкоуровневых 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июля
Па́вел О́сипович Сухо́й (бел. Павел Восіпавіч Сухі; 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]
Тема:
Проблема: Метод
На разных платформах это может быть CP1251 (Windows), UTF-8 (Linux), MacRoman (macOS) или другие. Это приводит к тому, что одно и то же приложение на разных машинах генерирует разные байтовые последовательности для одинаковых строк, что вызывает ошибки при передаче данных по сети, записи в файлы или взаимодействии с внешними системами. Особенно критично для символов вне ASCII (кириллица, иероглифы) — они могут быть испорчены или потеряны. Даже если сейчас всё работает, при смене окружения (например, при деплое на сервер с другой кодировкой) приложение внезапно ломается, и диагностика затруднена, так как проблема проявляется только в определённых условиях.
Решение: Всегда указывайте кодировку явно с помощью
Предпочтительно использовать константы из
Для проектов, ориентированных на международное взаимодействие, UTF-8 является стандартом де-факто.
Объяснение: Кодировка по умолчанию определяется системным свойством
Однако это глобальная настройка, которая может быть неочевидна для других разработчиков, а её изменение может повлиять на другие части системы (например, чтение файлов). Явное указание кодировки делает код самодокументируемым и устойчивым к изменениям окружения. UTF-8 является предпочтительной кодировкой для обмена данными в современных системах: она поддерживает все символы Unicode, эффективна для текстов на латинице и широко используется в вебе, JSON, XML.
Использование
#Java #советы
Тема:
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
Что выведет код?
#Tasks
public class Task220726 {
public static void main(String[] args) {
String s = "é";
System.out.println(s.getBytes().length);
}
}#Tasks
👍2
👍2
Что такое 🤓
Ответ:
ForkJoinPool.commonPool() — это общий пул потоков для приложения (Java 8), используемый по умолчанию в параллельных стримах (parallelStream()) и CompletableFuture (в некоторых методах).
Размер по умолчанию = количество процессоров − 1.
Не рекомендуется блокировать (спать, ввод-вывод) внутри задач, выполняющихся в commonPool, так как это замедлит другие задачи.
Если ваши задачи могут блокироваться, создавайте отдельный ForkJoinPool. Используйте commonPool для коротких вычислительных задач, не блокирующих IO.
#собеседование
ForkJoinPool.commonPool() и когда его использовать? Ответ:
Размер по умолчанию = количество процессоров − 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июля
Влади́мир Я́ковлевич Кли́мов (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(Path) — построчное чтение текстовых файлов
Метод
Важно:
BufferedReader — это класс в Java, который буферизует чтение символов из потока. Вместо чтения по одному символу или байту он читает блоками (обычно 8 КБ) в буфер в памяти, а затем отдаёт символы из буфера. Это снижает количество системных вызовов read() с O(N) до O(N / buffer_size).
Files.lines(Path, Charset) — с указанием кодировки
Если кодировка не указана, используется
Кодировка (charset, character encoding) — это схема сопоставления байтовых последовательностей символам текста. UTF-8 — переменная длина (1–4 байта на символ), обратно совместима с ASCII. ISO-8859-1 (Latin-1) — фиксированная 1 байт на символ, покрывает западноевропейские языки.
Ленивость и потребление памяти
В отличие от
WatchService — отслеживание изменений в файловой системе
WatchService — это сервис в NIO.2, который использует нативные механизмы ОС для мониторинга изменений в файловой системе. Вместо периодического опроса (polling) он работает по событийной модели: ОС уведомляет JVM, когда происходит зарегистрированное событие.
Polling (опрос) — это стратегия проверки состояния, при которой программа периодически запрашивает ресурс (например, проверяет время модификации файла). Это расходует CPU впустую. Событийная модель (event-driven) — противоположный подход, при котором программа ждёт уведомления от ОС и не потребляет ресурсы в ожидании.
Нативные механизмы ОС
WatchService в Java — это тонкая обёртка над нативными API:
Linux:
macOS:
Windows:
Важно:
Coalescing (слияние событий) — это оптимизация, при которой несколько последовательных событий над одним и тем же файлом объединяются в одно. Например, множественные быстрые записи в файл могут породить только одно событие ENTRY_MODIFY.
Создание WatchService
Регистрация директории
WatchKey — это объект, представляющий регистрацию директории в WatchService. Он содержит ссылку на зарегистрированный путь и очередь событий. Когда WatchKey становится недействительным (например, директория удалена), его метод isValid() возвращает false.
#Java #для_новичков #beginner #IO #NIO #Files #Files_lines #WatchService
Глава 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