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

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

Наш канал на RUTube - https://rutube.ru/channel/37896292/
Download Telegram
Что такое ForkJoinPool.commonPool() и когда его использовать? 🤓

Ответ:

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

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

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

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



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

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

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


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

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

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

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

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


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

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

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

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


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

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

public class LineReader {

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

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

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

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

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

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

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


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

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

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


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

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

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


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

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

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

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

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

public class WatchServiceFactory {

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

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

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

public class DirectoryRegistrar {

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

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

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


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

public class EventPoller {

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

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

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

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

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

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

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


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

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

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

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


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


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

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

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

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

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

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


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

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

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


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


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

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


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

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
Варианты ответа:
Anonymous Quiz
30%
ABC
50%
ABCDE
0%
AB
20%
ABDE
👍3
Media is too big
VIEW IN TELEGRAM
Небольшая демонстрация предрелизной логики Devforge.ru 🙈

Жду оценок и мнений! 😏

пысы: схемы можно писать через язык Mermaid 🤓
Please open Telegram to view this post
VIEW IN TELEGRAM
👍3🔥1
Что такое ZGC (Z Garbage Collector)? 🤓

Ответ:

ZGC (Java 11 как экспериментальный, стабилен с Java 15)
— это низколатентный сборщик мусора, работающий с паузами менее 10 мс (в основном менее 1 мс), даже для терабайтных куч.

Использует механизм 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июля
Please open Telegram to view this post
VIEW IN TELEGRAM
👍3
[Совет по Java #064]

Тема: 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
Что выведет код?

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
Варианты ответа:
Anonymous Quiz
57%
1 true false
0%
1 false true
29%
2 true true
14%
0 false false
👍2
Что такое switch с паттернами (Pattern Matching for Switch)? 🤓

Ответ:

Начиная с Java 17 (preview с Java 17, финал в Java 21), switch может сопоставлять не только константы, но и типы объектов с автоматическим приведением.

Пример: 
String result = switch (obj)
{
case Integer i -> "Integer: " + i;
case String s -> "String: " + s;
case null -> "null"; default -> "Unknown";
};.


Также поддерживаются охранные условия (guards): case Point p when p.x() > 0 -> ....

Это делает switch полноценным инструментом для работы с иерархиями типов, уменьшая количество instanceof и приведений.



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

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

Джон Баннистер Гуденаф (англ. John Bannister Goodenough; 25 июля 1922, Йена, Тюрингия[вд] — 25 июня 2023, Остин) — американский учёный, специалист в области физики и химии твёрдого тела и материаловедения. Известен своим вкладом в разработку первых литий-ионных аккумуляторов.


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

1907 — русский физик Борис Розинг подал заявку на патент на «Способ электрической передачи изображений на расстояние», то есть телевидение.


#Biography #Birth_Date #Events #25июля
Please open Telegram to view this post
VIEW IN TELEGRAM
👍2
История технологии сегодня — 26 июля

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

Джеймс Эфрэйм Лавлок (англ. James Ephraim Lovelock; 26 июля 1919, Летчуэрт, Хартфордшир — 26 июля 2022[3], Эбботсбери[вд], Юго-Западная Англия) — британский учёный, независимый исследователь, биофизик, химик, футуролог. Приобрёл известность как создатель Гипотезы Геи (согласно которой планета Земля функционирует как суперорганизм), а также как активный сторонник использования атомной энергии.

Исаак Бэббитт (англ. Isaac Babbitt; 26 июля 1799, Тонтон, Массачусетс, США — 26 мая 1862, Сомервилл, Массачусетс, США) — американский изобретатель. В 1839 году он изобрел подшипник из металлического сплава на основе олова с низким коэффициентом трения. Этот металл был назван по его имени баббитом, и до сих пор широко используется в подшипниках.


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

1925 — впервые поднялся в воздух самолёт «К-1» конструкции К. А. Калинина — первый отечественный пассажирский самолёт, рекомендованный к серийному выпуску.

1989 — 24-летнему американскому студенту Роберту Моррису предъявляется обвинение в создании и запуске компьютерного вируса. Моррис становится первым создателем вируса, подвергнутым уголовному преследованию.


#Biography #Birth_Date #Events #26июля
Please open Telegram to view this post
VIEW IN TELEGRAM
👍2
Как отдыхать от кода, чтобы не потерять к нему интерес.

Было время, когда я изучая программирование и готовясь к собеседованиям, просиживал за изучением Java по 12-15 часов в день. Это был цейтнот и время поджимало, поэтому я не жалел себя.
И прекрасно помню, как вставая от компа, я понимал, что вообще не соображаю. Мозг, как онемевший орган, переставал реагировать на внешние раздражители.


Учиться в таком режиме было тяжело, но быстро. А ощущение отупения накапливалось и с каждым днем было все сложнее заставить себя учиться. Все сложнее было воспринимать. и что важнее - осознавать что-то новое.

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

Вот мои советы:

1. Программирование — это умственный стресс и нагрузка. Значит, отдых должен быть противоположным.


Многие думают: «Отдохну, полежу, сериальчик посмотрю». Это может сработать разок, но по сути — это просто пассивная стимуляция. Усталый мозг не отключается, он продолжает переваривать входящие, только уже из Netflix.

В психологии есть понятие когнитивной разрядки (cognitive offloading) — это когда мозг сознательно уходит от абстрактных нагрузок к чему-то телесному и простому.

Поэтому, будут крайне полезны:
🔵 Регулярные пешие прогулки, без телефона в руках.
🔵 Физнагрузки (бег, плавание, даже уборка) — вообще мастхев для программистов.

Я в свою очередь стараюсь заниматься хотя-бы через день. Ничего лучше так не прочищает мозг, чем 5-километровая пробежка по парку
😉.


2. Отдых не должен быть побегом. Он должен быть паузой.

Плохо, когда однажды перенагрузив мозг, ты вновь пытаешься это повторить, а он сопротивляется. Ты прямо-таки можешь ощущать его сопротивление, нежелание осознавать прочитанное.
И ты пытаешься сбежать от программирования в какие-то рандомные развлечения. Это может вызвать ненужные тебе переживания, самоосуждение, тут и до "темного попутчика"
(о котором мы говорили в статье) недалеко.

Поэтому правильный, а что немаловажно, своевременный отдых — это контекстный отдых, когда ты всё ещё как бы рядом с ИТ, но без задачи что-то выдать.

Примеры:
🔵Чтение биографий учёных, разработчиков — это вдохновляет и не грузит.
🔵Хобби: пайка электроники, музыка, геймдев или гейминг — лёгкое смежное времяпрепровождение.
🔵Визуальные виды деятельности: рисование, моделирование, pixel art.

В психологии это описывается как ресторативная активность (restorative experiences) — действия, которые возвращают ощущение контроля и удовольствия от процесса.

И твое «Хочу программировать» останется внутри даже в перерыве от него, ведь ты не перенапрягаешься.



3. Мини-проекты без цели.

Порой, когда ты перегружен "сложным" программированием и чувствуешь, что уже не вывозишь, полезно переключиться на "развлекательное" программирование - написать что-то простое и веселое.

Попробуй написать:
🔵Телеграм - бота, который будет постить твои рандомные мысли.
🔵Код, сортирующий папки по настроению (буквально по цветам или дням недели).
🔵Программу, делающую любую глупость, но красиво ☺️.

Это и есть "игра с собой" (self-determined play) — процесс, в котором мотивация рождается не из цели, а из самого процесса. В котором ты отдыхаешь, но при этом не теряешь связи с программированием и получаешь удовольствие от процесса.


4. Общение в среде программирования.

Иногда, после высокой умственной нагрузки, достаточно оставаться рядом с темой. Расслабиться и послушать подкасты, посмотреть чужие лайвкодинги.
А можно найти сообщество (похожее на наше
☺️) и программистов, с которыми пообсуждать околопрограммерские вопросы.

Это вызывает ощущение сопричастности.

В когнитивной психологии это называется пассивное обучение (incidental learning) — когда ты усваиваешь что-то из обсуждения, даже не замечая.
А это в свою очередь, поддерживает интерес и мотивацию (
Deci & Ryan, 2000).


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


А как вы боретесь с усталостью? Пишите в комментариях! 💪


#motivation
Please open Telegram to view this post
VIEW IN TELEGRAM
👍8🔥1
История технологии сегодня — 27 июля

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

Сэр Демис Хассабис (родился 27 июля 1976 г.) — британский исследователь в области искусственного интеллекта (ИИ) и предприниматель. Он является генеральным директором и соучредителем Google DeepMind и Isomorphic Labs, а также советником правительства Великобритании по вопросам ИИ. В 2024 году Хассабис и Джон М. Джампер были совместно удостоены Нобелевской премии по химии за свой вклад в исследования ИИ в области предсказания структуры белков.


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

1985 — первый полёт спортивно-пилотажного акробатического самолёта СУ-26М, в конструкции которого широко применены композитные материалы.


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

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

SeekableByteChannel — произвольный доступ к файлу. Символические и жёсткие ссылки

До этого момента мы работали с файлами через высокоуровневые абстракции: читали целиком, писали целиком, копировали, перемещали. Но файловая система — это в первую очередь последовательность байтов на диске, и иногда нужно читать или писать не с начала, а с произвольной позиции.

Для этого в NIO.2 существует интерфейс SeekableByteChannel. Он объединяет возможности чтения, записи и позиционирования в единый канал, заменяя устаревший RandomAccessFile.

Вторая часть посвящена ссылкам в файловой системе — механизму, который позволяет одним данным соответствовать несколько имён. Символические и жёсткие ссылки — фундаментальная концепция Unix-файловых систем, и их непонимание часто приводит к багам при обходе деревьев, подсчёте размеров и резервном копировании.


Часть 1. SeekableByteChannel

SeekableByteChannel — это интерфейс в пакете java.nio.channels, расширяющий ByteChannel. ByteChannel в свою очередь объединяет ReadableByteChannel и WritableByteChannel, то есть поддерживает и чтение, и запись. Добавление приставки "Seekable" означает, что канал позволяет изменять текущую позицию чтения/записи — "перематывать" указатель в произвольное место файла.

Канал (channel) в Java NIO — это абстракция для соединения с сущностью, способной выполнять операции ввода-вывода: файл, сокет, пайп. Каналы работают с буферами (ByteBuffer), а не с потоками байтов. Это позволяет использовать прямой (direct) ввод-вывод, минуя промежуточное копирование в кучу JVM.

Интерфейс SeekableByteChannel декларирует три ключевых метода, отсутствующих в обычном ByteChannel:
position() — возвращает текущую позицию в файле.
position(long newPosition) — устанавливает новую позицию.
size() — возвращает текущий размер файла в байтах.


Открытие канала: Files.newByteChannel()

Метод Files.newByteChannel(Path path, OpenOption... options) возвращает SeekableByteChannel для указанного пути. Это фабричный метод, который делегирует создание канала текущему FileSystemProvider.
import java.nio.file.*;
import java.nio.channels.SeekableByteChannel;
import java.nio.ByteBuffer;
import java.io.IOException;
import java.nio.charset.StandardCharsets;

public class ChannelOpener {

public void openForRead(Path file) throws IOException {
// Открываем только для чтения
try (SeekableByteChannel channel = Files.newByteChannel(file, StandardOpenOption.READ)) {
System.out.println("Размер файла: " + channel.size());
System.out.println("Текущая позиция: " + channel.position());
}
}

public void openForWrite(Path file) throws IOException {
// Открываем для чтения и записи, создаём если нет, обрезаем если есть
try (SeekableByteChannel channel = Files.newByteChannel(file,
StandardOpenOption.READ,
StandardOpenOption.WRITE,
StandardOpenOption.CREATE,
StandardOpenOption.TRUNCATE_EXISTING)) {

// Записываем данные
ByteBuffer buffer = ByteBuffer.wrap("Hello, World".getBytes(StandardCharsets.UTF_8));
channel.write(buffer);
}
}
}


StandardOpenOption — режимы открытия

StandardOpenOption — это enum, определяющий, как именно открывать или создавать файл. Каждая опция соответствует флагу системного вызова open() (POSIX):
READ — открыть для чтения. Файл должен существовать. Соответствует флагу O_RDONLY.
WRITE — открыть для записи. Файл должен существовать. Соответствует флагу O_WRONLY (в комбинации с READ даёт O_RDWR).
APPEND — все операции записи производятся в конец файла, независимо от текущей позиции. Соответствует флагу O_APPEND. Позиция после записи не изменяется (остаётся там, где была до записи).
CREATE — создать файл, если он не существует. Соответствует O_CREAT.
CREATE_NEW — создать файл, но выбросить исключение, если он уже существует. Соответствует O_CREAT | O_EXCL. Атомарная операция.
TRUNCATE_EXISTING — если файл существует и открыт для записи, обрезать его размер до 0. Соответствует O_TRUNC.
DELETE_ON_CLOSE — удалить файл при закрытии канала. Полезно для временных файлов.
SYNC — каждая операция записи синхронизирует данные и метаданные с диском. Соответствует O_SYNC. Сильно снижает производительность, но гарантирует durability.
DSYNC — синхронизирует только данные, не метаданные. Соответствует O_DSYNC.
SPARSE — создать разреженный файл (sparse file), если файловая система поддерживает.

Разреженный файл (sparse file) — это файл, в котором последовательности нулевых байтов не занимают место на диске. Файловая система хранит только информацию о "дырах" (holes), а не сами нули. При чтении таких областей возвращаются нулевые байты. Разреженные файлы экономят дисковое пространство при работе с большими файлами, большая часть которых пуста.

Durability (стойкость) — свойство системы хранения, гарантирующее, что записанные данные сохранятся даже при сбое питания. SYNC и DSYNC принудительно сбрасывают данные из кэша страниц ОС на физическое устройство хранения.


Чтение и запись с произвольной позиции
public class RandomAccessExample {

public void readFromPosition(Path file, long position, int length) throws IOException {
try (SeekableByteChannel channel = Files.newByteChannel(file, StandardOpenOption.READ)) {
// Устанавливаем позицию
channel.position(position);

ByteBuffer buffer = ByteBuffer.allocate(length);
int bytesRead = channel.read(buffer);

buffer.flip(); // переключаем буфер из режима записи в режим чтения
byte[] data = new byte[bytesRead];
buffer.get(data);

System.out.println("Прочитано с позиции " + position + ": " + new String(data));
}
}

public void writeAtPosition(Path file, long position, String data) throws IOException {
try (SeekableByteChannel channel = Files.newByteChannel(file,
StandardOpenOption.READ, StandardOpenOption.WRITE)) {

channel.position(position);

ByteBuffer buffer = ByteBuffer.wrap(data.getBytes(StandardCharsets.UTF_8));
channel.write(buffer);

// После записи позиция сдвинулась на количество записанных байт
System.out.println("Новая позиция: " + channel.position());
}
}

public void appendToFile(Path file, String data) throws IOException {
// Способ 1: через APPEND опцию
try (SeekableByteChannel channel = Files.newByteChannel(file,
StandardOpenOption.WRITE, StandardOpenOption.APPEND)) {
ByteBuffer buffer = ByteBuffer.wrap(data.getBytes(StandardCharsets.UTF_8));
channel.write(buffer);
}

// Способ 2: через position(size()) — ручное позиционирование в конец
try (SeekableByteChannel channel = Files.newByteChannel(file,
StandardOpenOption.READ, StandardOpenOption.WRITE)) {
channel.position(channel.size()); // переходим в конец
ByteBuffer buffer = ByteBuffer.wrap(data.getBytes(StandardCharsets.UTF_8));
channel.write(buffer);
}
}
}


ByteBuffer
— это класс в Java NIO, представляющий блок памяти, через который производится ввод-вывод. Буфер имеет три ключевых свойства: capacity (ёмкость), limit (предел) и position (текущая позиция). Метод flip() переключает буфер из режима записи (write) в режим чтения (read): устанавливает limit = position, position = 0.


#Java #для_новичков #beginner #IO #NIO #SeekableByteChannel
👍4
Сравнение с RandomAccessFile

Класс java.io.RandomAccessFile существует с Java 1.0 и предоставляет аналогичный функционал.

Однако у него есть архитектурные недостатки:
Не работает с Path: RandomAccessFile принимает только String или File, не Path. Для использования с NIO.2 требуется конвертация.
Не поддерживает символические ссылки корректно: RandomAccessFile всегда следует по ссылкам, нет опции NOFOLLOW_LINKS.
Не интегрирован с NIO: RandomAccessFile работает со своими методами read(), write(), не с ByteBuffer. Хотя с Java 1.4 у него появился метод getChannel(), возвращающий FileChannel.
Менее гибкая модель опций: RandomAccessFile имеет только два режима — "r" (read) и "rw" (read-write). Нет поддержки APPEND, CREATE_NEW, SYNC и других флагов.
Не поддерживает кастомные FileSystem: RandomAccessFile работает только с дефолтной файловой системой. SeekableByteChannel через FileSystemProvider может работать с ZIP, сетевыми ФС и т.д.
// Старый способ (RandomAccessFile)
try (RandomAccessFile raf = new RandomAccessFile("data.bin", "rw")) {
raf.seek(100); // позиционирование
raf.write("data".getBytes());
}

// Новый способ (SeekableByteChannel)
try (SeekableByteChannel channel = Files.newByteChannel(Paths.get("data.bin"),
StandardOpenOption.READ, StandardOpenOption.WRITE)) {
channel.position(100);
channel.write(ByteBuffer.wrap("data".getBytes()));
}

FileChannel — это конкретная реализация SeekableByteChannel в Java, работающая с файлами локальной файловой системы. Он предоставляет дополнительные методы: map() (memory-mapped files), transferTo()/transferFrom() (zero-copy), lock() (файловые блокировки), force() (синхронизация с диском).


Часть 2. Символические и жёсткие ссылки

В файловых системах Unix имя файла — это не сам файл, а ссылка на структуру данных, хранящую метаданные и указатели на блоки. Эта структура называется inode. Директория — это просто список пар "имя — номер inode". Ссылка — это запись в этом списке.

inode (index node) — структура данных в файловой системе Unix, содержащая всё, кроме имени файла и его содержимого: тип файла, права доступа, владелец, группа, размер, временные метки, счётчик ссылок и указатели на блоки данных. Номер inode уникален в пределах файловой системы (раздела диска).


Жёсткая ссылка (hard link)

Жёсткая ссылка — это дополнительное имя для существующего файла, указывающее на тот же inode.

Все жёсткие ссылки на файл равноправны: нет "оригинала" и "копий". Каждая жёсткая ссылка увеличивает счётчик ссылок (link count) в inode на 1.

Когда файл удаляется через unlink() (системный вызов, лежащий в основе Files.delete()), ядро уменьшает счётчик ссылок. Если счётчик достигает 0 и нет открытых файловых дескрипторов на этот inode, блоки данных помечаются как свободные. Если счётчик 0, но дескрипторы открыты, файл становится "невидимым" в файловой системе, но продолжает существовать до закрытия всех дескрипторов.

Ограничения жёстких ссылок:
Нельзя создать на директорию (за исключением системных . и ..). Это предотвращает циклы в файловой системе.
Нельзя создать между разными файловыми системами (разделами диска), так как inode уникален только в пределах одной ФС.
Нельзя создать на несуществующий файл.
import java.nio.file.*;
import java.io.IOException;

public class HardLinkManager {

public void createHardLink(Path existingFile, Path newLink) throws IOException {
// Проверяем, что исходный файл существует и на той же файловой системе
if (!Files.isRegularFile(existingFile)) {
throw new IllegalArgumentException("Исходный файл не существует или не является обычным файлом");
}

// createLink создаёт жёсткую ссылку
Files.createLink(newLink, existingFile);

// Проверяем: оба пути указывают на один inode
Object key1 = Files.readAttributes(existingFile, BasicFileAttributes.class).fileKey();
Object key2 = Files.readAttributes(newLink, BasicFileAttributes.class).fileKey();

if (key1 != null && key1.equals(key2)) {
System.out.println("Жёсткая ссылка создана. Оба пути указывают на inode " + key1);
}
}
}


Files.createLink(Path link, Path existing) выполняет системный вызов link() (POSIX), который создаёт новую запись в директории, указывающую на тот же inode, что и existing. Это атомарная операция на уровне файловой системы.


Символическая ссылка (symbolic link, symlink)

Символическая ссылка — это специальный файл, содержащий путь (строку) к другому файлу или директории. В отличие от жёсткой ссылки, символическая ссылка имеет свой собственный inode и не влияет на счётчик ссылок целевого файла. Она может указывать на несуществующий объект (висячая ссылка, dangling symlink), на директорию, на файл в другой файловой системе или даже на саму себя.

Висячая ссылка (dangling symlink) — символическая ссылка, целевой объект которой был удалён или перемещён. При попытке доступа к такой ссылке системный вызов вернёт ENOENT (No such file or directory).

Символические ссылки могут быть абсолютными (начинаются с /, указывают на фиксированный путь в файловой системе) или относительными (указывают относительно директории, в которой находится ссылка).

Относительные ссылки предпочтительны, так как сохраняют работоспособность при перемещении дерева.
public class SymlinkManager {

public void createSymlink(Path link, Path target) throws IOException {
// Создаём символическую ссылку
// target может быть абсолютным или относительным
Files.createSymbolicLink(link, target);
}

public void createRelativeSymlink(Path linkDir, Path targetFile) throws IOException {
// Строим относительный путь от linkDir до targetFile
Path relativeTarget = linkDir.relativize(targetFile);
Path link = linkDir.resolve("shortcut");

Files.createSymbolicLink(link, relativeTarget);
}

public void inspectSymlink(Path link) throws IOException {
if (!Files.isSymbolicLink(link)) {
System.out.println("Это не символическая ссылка");
return;
}

// Читаем целевой путь, хранящийся в ссылке
Path target = Files.readSymbolicLink(link);
System.out.println("Ссылка указывает на: " + target);

// Проверяем, существует ли целевой объект
if (Files.exists(link)) {
System.out.println("Целевой объект существует");

// Читаем атрибуты целевого объекта (следуем по ссылке)
BasicFileAttributes attrs = Files.readAttributes(link, BasicFileAttributes.class);
System.out.println("Тип цели: " + (attrs.isDirectory() ? "директория" : "файл"));
} else {
System.out.println("Висячая ссылка — цель не существует");
}

// Читаем атрибуты самой ссылки (не следуем по ней)
BasicFileAttributes linkAttrs = Files.readAttributes(
link, BasicFileAttributes.class, LinkOption.NOFOLLOW_LINKS
);
System.out.println("Размер самой ссылки: " + linkAttrs.size() + " байт (длина пути)");
}
}

Files.readSymbolicLink(Path link) возвращает Path, содержащий строку, записанную в символической ссылке. Это не резолвит путь — просто читает содержимое ссылки. Размер символической ссылки (возвращаемый Files.size()) равен длине строки пути в байтах, а не размеру целевого файла.


#Java #для_новичков #beginner #IO #NIO #SeekableByteChannel
👍4
Опасности символических ссылок при обходе дерева

Символические ссылки на директории создают риск бесконечного цикла при рекурсивном обходе. Если директория A содержит символическую ссылку на свою родительскую директорию, обход зайдёт в родителя, снова найдёт A, снова зайдёт в ссылку — и так до переполнения стека или исчерпания ресурсов.
public class SafeTreeWalker {

// ОПАСНО: может зациклиться, если есть symlink на предка
public void dangerousWalk(Path root) throws IOException {
try (Stream<Path> stream = Files.walk(root, FileVisitOption.FOLLOW_LINKS)) {
stream.forEach(System.out::println); // риск бесконечного цикла
}
}

// БЕЗОПАСНО: не следуем по символическим ссылкам
public void safeWalk(Path root) throws IOException {
try (Stream<Path> stream = Files.walk(root)) { // без FOLLOW_LINKS
stream.forEach(System.out::println);
}
}

// БЕЗОПАСНО: следуем по ссылкам, но отслеживаем посещённые inode
public void safeWalkWithCycleDetection(Path root) throws IOException {
Set<Object> visitedInodes = new HashSet<>();

Files.walkFileTree(root, EnumSet.of(FileVisitOption.FOLLOW_LINKS), Integer.MAX_VALUE,
new SimpleFileVisitor<Path>() {
@Override
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) {
Object key = attrs.fileKey();
if (key != null) {
if (visitedInodes.contains(key)) {
System.out.println("Обнаружен цикл через ссылку: " + file);
return FileVisitResult.SKIP_SIBLINGS;
}
visitedInodes.add(key);
}
System.out.println(file);
return FileVisitResult.CONTINUE;
}
}
);
}
}


В безопасном варианте с отслеживанием fileKey() (пара device_id + inode) мы можем обнаружить, что уже посещали этот inode, и пропустить его. Это защищает от циклов через жёсткие ссылки и символические ссылки на директории.


Сравнение жёстких и символических ссылок

Жёсткая ссылка:
Указывает напрямую на inode.
Не может указывать на директорию (кроме системных).
Не может пересекать границы файловых систем.
Не может указывать на несуществующий файл.
Увеличивает счётчик ссылок в inode.
Не имеет собственного размера (размер равен размеру файла).
Удаление одной ссылки не влияет на другие.

Символическая ссылка:
Указывает на путь (строку), имеет свой inode.
Может указывать на директорию.
Может пересекать границы файловых систем.
Может указывать на несуществующий файл (висячая ссылка).
Не влияет на счётчик ссылок целевого файла.
Имеет размер, равный длине строки пути.
При удалении целевого файла становится висячей.


Путь байтов в памяти JVM при работе с SeekableByteChannel и ссылками

SeekableByteChannel: путь данных

При Files.newByteChannel(path, StandardOpenOption.READ, StandardOpenOption.WRITE):
Объект Path в куче преобразуется в байты пути в нативной памяти. Выполняется системный вызов open() с флагами O_RDWR. Ядро ОС проверяет права доступа, находит или создаёт inode, открывает файловый дескриптор и возвращает его в пространство процесса.
JVM создаёт объект FileChannel (реализация SeekableByteChannel в OpenJDK) в куче (Heap), в Young Generation.

Этот объект содержит:
Целочисленный файловый дескриптор (хранится как int в поле объекта).
Ссылку на FileSystem.
Флаги режима открытия.
Кэшированное значение позиции (для уменьшения системных вызовов lseek).
При вызове channel.position(100):
Если кэшированная позиция уже 100, системный вызов не выполняется.
Иначе выполняется lseek(fd, 100, SEEK_SET) (POSIX), который перемещает указатель чтения-записи файлового дескриптора. Это операция только над метаданными дескриптора, данные на диске не читаются.

При channel.read(buffer):
Выполняется системный вызов readv() или pread() (если позиция задана явно). Данные читаются из кэша страниц ОС (page cache) в нативную память, а затем копируются в ByteBuffer.
Если ByteBuffer — direct (создан через ByteBuffer.allocateDirect()), данные копируются напрямую в нативную память буфера, минуя кучу JVM. Это zero-copy на уровне JVM: нет промежуточного массива byte[] в куче.
Если ByteBuffer — heap (создан через ByteBuffer.allocate()), JVM выделяет временный direct буфер в нативной памяти, копирует туда данные из ОС, а затем копирует из нативного буфера в массив byte[] в куче. Это лишнее копирование, но необходимо, так как системные вызовы работают только с нативной памятью.

Direct ByteBuffer — это буфер, размещённый в нативной памяти (вне кучи JVM). Он создаётся через ByteBuffer.allocateDirect(size) и управляется JVM, но не подлежит обычной сборке мусора. Память освобождается, когда буфер становится недостижимым и Cleaner (специальный механизм JVM) вызывает free() для нативной памяти.

При channel.write(buffer):
Данные из ByteBuffer копируются в кэш страниц ОС (для direct буфера — напрямую, для heap — через временный direct буфер).
Запись в кэш страниц — это операция в памяти, быстрая. Физическая запись на диск происходит позже, когда ядро решает сбросить "грязные" страницы (dirty pages) на устройство хранения.
Если открыт с SYNC или DSYNC, после записи выполняется fsync() или fdatasync(), который принудительно сбрасывает кэш на диск. Это блокирующая операция, зависящая от скорости диска.
Объект FileChannel остаётся в куче до закрытия. Если он локальная переменная в try-with-resources, он становится мусором после выхода из блока и собирается Minor GC. Файловый дескриптор закрывается через close(), а не через GC.


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

При Files.createSymbolicLink(link, target):
Объекты link и target (оба Path) находятся в куче. Их строковые представления извлекаются.
Выполняется системный вызов symlink(targetPath, linkPath) (POSIX). Ядро ОС:
Создаёт новый inode типа "символическая ссылка".
Записывает строку targetPath в блоки данных этого inode.
Добавляет запись в родительскую директорию link, связывающую имя с новым inode.
Никаких новых объектов в куче JVM не создаётся. Метод возвращает Path link (тот же объект, что был передан).

При Files.createLink(newLink, existing):
Выполняется системный вызов link(existingPath, newLinkPath) (POSIX).
Ядро находит inode существующего файла, увеличивает его счётчик ссылок на 1, и добавляет новую запись в директорию newLink, указывающую на тот же inode.
Никаких объектов в куче не создаётся. Операция полностью атомарна на уровне файловой системы.


Работа GC с каналами и ссылками

Объекты SeekableByteChannel и Path при создании ссылок — это обычные объекты в куче.

Они управляются стандартным механизмом GC:
Создаются в Eden (Young Generation).
При короткоживущем использовании (внутри метода с try-with-resources) собираются при следующей Minor GC.
Если канал открыт и сохранён в долгоживущем поле (например, пул соединений к файлам), объект переходит в Old Generation и живёт до Major GC.
Важно: ByteBuffer.allocateDirect() создаёт буфер в нативной памяти. Эта память не входит в кучу и не управляется GC напрямую. Однако сам объект DirectByteBuffer (обёртка в куче) управляется GC. Когда обёртка собирается GC, внутренний механизм Cleaner вызывает нативный метод для освобождения нативной памяти.

Cleaner — это механизм в JVM (класс java.lang.ref.Cleaner с Java 9, ранее sun.misc.Cleaner), который позволяет регистрировать callback-метод, вызываемый при сборке мусора определённого объекта. Используется для освобождения нативных ресурсов, ассоциированных с Java-объектами, например, нативной памяти DirectByteBuffer.

Однако Cleaner не гарантирует немедленное освобождение — нативная память может оставаться занятой до следующей сборки мусора. Поэтому для критичных ресурсов (файловые дескрипторы, каналы) всегда используется явное закрытие через close(), а не полагание на GC и Cleaner.


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