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
Ожидание событий: take() vs poll()
Важно: после обработки событий необходимо вызвать
Обработка WatchEvent
Важные нюансы WatchService
Событие CREATE приходит до завершения записи: если процесс создаёт файл и медленно пишет в него,
Событие MODIFY может приходить многократно: одна логическая операция записи может породить серию событий
Символические ссылки: WatchService следует по символическим ссылкам при регистрации. Если зарегистрировать ссылку, события будут приходить для целевой директории.
Рекурсивный мониторинг: WatchService не мониторит поддиректории автоматически. Для рекурсивного мониторинга нужно обходить дерево и регистрировать каждую директорию отдельно, а при появлении новой директории (событие
OVERFLOW: при слишком быстром потоке событий внутренний буфер ОС может переполниться. В этом случае приходит событие
Путь байтов в памяти JVM при построчном чтении
Files.lines(): путь данных
При
Метод создаёт
При итерации по
Чтение байтов из файла через
Декодирование байтов в символы через
Поиск символа перевода строки (
Создание нового объекта
Объект
После обработки строки в терминальной операции (например,
При закрытии
Путь байтов при WatchService
При создании
При регистрации директории (
При вызове
Объекты
При закрытии
Важно:
#Java #для_новичков #beginner #IO #NIO #Files #Files_lines #WatchService
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
Что выведет код?
#Tasks
import java.nio.file.*;
import java.util.*;
import java.util.stream.Stream;
public class FilesLinesTricky {
public static void main(String[] args) throws Exception {
Path file = Paths.get("test.txt");
Files.write(file, Arrays.asList("A", "B", "C"));
try (Stream<String> lines = Files.lines(file)) {
Iterator<String> it = lines.iterator();
System.out.print(it.next());
System.out.print(it.next());
Files.write(file, Arrays.asList("D", "E"), StandardOpenOption.APPEND);
while (it.hasNext()) {
System.out.print(it.next());
}
}
}
}
#Tasks
👍3
👍3
Media is too big
VIEW IN TELEGRAM
Небольшая демонстрация предрелизной логики Devforge.ru 🙈
Жду оценок и мнений!😏
пысы: схемы можно писать через язык Mermaid🤓
Жду оценок и мнений!
пысы: схемы можно писать через язык Mermaid
Please open Telegram to view this post
VIEW IN TELEGRAM
👍3🔥1
Что такое 🤓
Ответ:
ZGC (Java 11 как экспериментальный, стабилен с Java 15) — это низколатентный сборщик мусора, работающий с паузами менее 10 мс (в основном менее 1 мс), даже для терабайтных куч.
Использует механизм colored pointers и load barriers. Работает параллельно с приложением, не останавливая его для полной сборки (concurrent).
Не требует больших тюнингов. Подходит для приложений реального времени, микросервисов с низкими SLI по задержкам.
Альтернатива — Shenandoah (от Red Hat), похожий по идеологии.
#собеседование
ZGC (Z Garbage Collector)? Ответ:
Использует механизм colored pointers и load barriers. Работает параллельно с приложением, не останавливая его для полной сборки (concurrent).
Не требует больших тюнингов. Подходит для приложений реального времени, микросервисов с низкими SLI по задержкам.
Альтернатива — Shenandoah (от Red Hat), похожий по идеологии.
#собеседование
Please open Telegram to view this post
VIEW IN TELEGRAM
👍5
История технологии сегодня — 24 июля
ℹ️ Кто родился в этот день
Шарль Эми́ль Пика́р (фр. Charles Émile Picard; 24 июля 1856, Париж — 11 декабря 1941, там же) — французский математик. Известен фундаментальными результатами в области математического анализа. Его учебник анализа (Traité d’Analyse) долгое время считался классическим. Внёс существенный вклад также в теорию дифференциальных уравнений, теорию функций, топологию, теорию групп. Для линейных дифференциальных уравнений разработал аналог теории Галуа. Часть его трудов посвящены истории и философии математики.
🌐 Знаковые события
1987 — IBM выпустила операционную систему DOS version 3.3.
#Biography #Birth_Date #Events #24июля
Шарль Эми́ль Пика́р (фр. Charles Émile Picard; 24 июля 1856, Париж — 11 декабря 1941, там же) — французский математик. Известен фундаментальными результатами в области математического анализа. Его учебник анализа (Traité d’Analyse) долгое время считался классическим. Внёс существенный вклад также в теорию дифференциальных уравнений, теорию функций, топологию, теорию групп. Для линейных дифференциальных уравнений разработал аналог теории Галуа. Часть его трудов посвящены истории и философии математики.
1987 — IBM выпустила операционную систему DOS version 3.3.
#Biography #Birth_Date #Events #24июля
Please open Telegram to view this post
VIEW IN TELEGRAM
👍3
[Совет по Java #064]
Тема:
Проблема:
Это удобно для кэшей или метаданных, привязанных к объектам. Однако внутри
Решение: При использовании
Если архитектура требует, чтобы значение знало о ключе, следует хранить значение в виде
Альтернативно, можно использовать
Объяснение: В
Сборщик мусора определяет достижимость объектов: если от корней GC (например, статические поля, локальные переменные) нет цепочки сильных ссылок к ключу, ключ считается слабодостижимым. Однако если значение содержит сильную ссылку на ключ, то цепочка
Таким образом, ключ не является слабодостижимым, и запись не удаляется. Это классический случай утечки памяти, который трудно диагностировать, потому что
#Java #советы
Тема:
WeakHashMap хранит ключи через слабые ссылки, но значения — сильные. Значение может удерживать ключ, предотвращая удаление.Проблема:
WeakHashMap предназначена для хранения записей, ключи которых могут быть собраны сборщиком мусора (GC), если на них нет сильных ссылок. Это удобно для кэшей или метаданных, привязанных к объектам. Однако внутри
WeakHashMap значения хранятся через сильные ссылки (обычные ссылки). Если значение содержит ссылку на свой ключ (прямо или косвенно), то образуется цикл: ключ держит значение через map, а значение держит ключ через свою ссылку. В результате ни ключ, ни значение не становятся достижимыми только через слабую ссылку, и запись никогда не удаляется из WeakHashMap. Это приводит к утечке памяти, так как объекты остаются живыми, несмотря на то, что внешние ссылки на ключ отсутствуют. Разработчики часто не учитывают этот нюанс и используют WeakHashMap для кэширования, полагая, что оно автоматически очищается, а затем сталкиваются с неожиданным ростом памяти.Решение: При использовании
WeakHashMap необходимо гарантировать, что значение не содержит ссылки на ключ. Если архитектура требует, чтобы значение знало о ключе, следует хранить значение в виде
WeakReference или использовать отдельную структуру. Альтернативно, можно использовать
WeakHashMap только для простых типов ключей (например, строк), которые не хранятся в значениях. Для сложных кэшей лучше применять библиотеки типа Caffeine или Guava Cache, которые предоставляют более гибкие стратегии очистки и не страдают от этой проблемы. В случае, если значение обязательно должно ссылаться на ключ, можно использовать java.lang.ref.WeakReference для значения и хранить его в WeakHashMap, но тогда нужно самостоятельно обрабатывать удаление.import java.util.WeakHashMap;
import java.lang.ref.WeakReference;
public class WeakHashMapPitfall {
static class Key { /* ... */ }
static class Value {
private final Key key; // ссылка на ключ!
Value(Key key) { this.key = key; }
}
public static void main(String[] args) {
//Антипаттерн: значение ссылается на ключ
WeakHashMap<Key, Value> map = new WeakHashMap<>();
Key key = new Key();
map.put(key, new Value(key)); // значение хранит ссылку на ключ
key = null; // убираем внешнюю ссылку
System.gc(); // попытка сборки
// Запись не будет удалена, так как Value держит Key
System.out.println("Size: " + map.size()); // 1 — утечка!
//Решение: не хранить ссылку на ключ в значении
static class SafeValue {
// без ссылки на Key
}
WeakHashMap<Key, SafeValue> safeMap = new WeakHashMap<>();
Key key2 = new Key();
safeMap.put(key2, new SafeValue());
key2 = null;
System.gc();
// Запись будет удалена
System.out.println("Safe size: " + safeMap.size()); // 0
// Альтернатива: использовать WeakReference для значения
WeakHashMap<Key, WeakReference<Value>> refMap = new WeakHashMap<>();
Key key3 = new Key();
refMap.put(key3, new WeakReference<>(new Value(key3))); // всё равно Value ссылается на Key
// это не решает проблему, если Value ссылается на Key
}
}
Объяснение: В
WeakHashMap запись удаляется, когда ключ становится доступным только через слабую ссылку (т.е. нет сильных ссылок на ключ, кроме самой карты). Сборщик мусора определяет достижимость объектов: если от корней GC (например, статические поля, локальные переменные) нет цепочки сильных ссылок к ключу, ключ считается слабодостижимым. Однако если значение содержит сильную ссылку на ключ, то цепочка
map -> значение -> ключ создает сильную достижимость ключа (через значение). Таким образом, ключ не является слабодостижимым, и запись не удаляется. Это классический случай утечки памяти, который трудно диагностировать, потому что
WeakHashMap работает как ожидается для большинства случаев. #Java #советы
👍3
Что выведет код?
#Tasks
public class Task240726 {
static class Key {
String id;
Key(String id) { this.id = id; }
@Override
public int hashCode() { return id.hashCode(); }
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof Key)) return false;
Key key = (Key) o;
return id.equals(key.id);
}
}
static class Value {
Key ref;
Value(Key ref) { this.ref = ref; }
}
public static void main(String[] args) throws InterruptedException {
WeakHashMap<Key, Value> map = new WeakHashMap<>();
Key key1 = new Key("one");
Key key2 = new Key("two");
map.put(key1, new Value(key1));
map.put(key2, new Value(null));
key1 = null;
key2 = null;
System.gc();
Thread.sleep(1000);
System.out.println(map.size());
System.out.println(map.containsKey(new Key("one")));
System.out.println(map.containsKey(new Key("two")));
}
}#Tasks
👍2
👍2