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

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

Наш канал на RUTube - https://rutube.ru/channel/37896292/
Download Telegram
По-моему это шедевр...
🤣4
История технологии сегодня — 15 июля

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

Рем Ви́кторович Хохло́в (15 июля 1926, Ливны, Орловская губерния — 8 августа 1977, Москва) — советский физик, один из основоположников нелинейной оптики. Признание в научном мире Хохлову принесли два изданных в 1961 году труда: «К теории ударных радиоволн в нелинейных линиях» и «О распространении волн в нелинейных диспергирующих линиях». Первая монография заложила основы теоретической нелинейной акустики, вторая сформировала основание теории распространения электромагнитных волн в нелинейных средах.

Леон Макс Ле́дерман (англ. Leon Max Lederman; 15 июля 1922, Нью-Йорк, США — 3 октября 2018, Рексберг, Айдахо, США) — американский физик-экс­пе­ри­мен­та­тор, специалист в области физики высоких энергий, нобелевский лауреат 1988 года за открытие мюонного нейтрино.

Бертрам Невилл Брокха́уз (англ. Bertram Neville Brockhouse; 15 июля 1918, Летбридж, Канада — 13 октября 2003, Гамильтон, Канада) — канадский учёный, физик-ядерщик, лауреат Нобелевской премии по физике в 1994 г. «за создание нейтронной спектроскопии» (совместно с Клиффордом Шаллом).


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

1687 — Исаак Ньютон опубликовал в Лондоне «Philosophiae naturalis principia mathematica» с изложением открытий автора в области прикладной математики, астрономии и физики, в том числе и теорию всемирного тяготения (5 июля по ст.ст.[8]).

2006 — основан сервис микроблоггинга Twitter.

1983 — в Японии вышли игровые приставки NES и SG-1000.


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

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

Перемещение и переименование файлов и директорий

Перемещение и удаление — это операции, которые завершают жизненный цикл файла в файловой системе.

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

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

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


Files.move(Path source, Path target) — перемещение и переименование

Метод Files.move(Path source, Path target, CopyOption... options) выполняет перемещение файла или директории из source в target. На уровне файловой системы это может быть либо переименование в пределах одного раздела (атомарная операция, изменяющая только записи в директориях), либо копирование с последующим удалением источника (если источник и назначение на разных разделах или файловых системах).
import java.nio.file.*;
import java.io.IOException;

public class FileMover {

public void renameFile(Path oldName, Path newName) throws IOException {
// Простое переименование в той же директории
Files.move(oldName, newName);
}

public void moveToArchive(Path file, Path archiveDir) throws IOException {
// Перемещение в другую директорию
Path target = archiveDir.resolve(file.getFileName());
Files.move(file, target);
}
}



Разница между копированием и перемещением

Копирование (Files.copy) создаёт независимую копию данных. После копирования источник и назначение — это два разных файла с разными inode, каждый занимает своё дисковое пространство. Изменение одного не влияет на другой.

Перемещение в пределах одного FileStore (одного раздела диска) — это операция над метаданными. Файловая система изменяет запись в родительской директории источника (удаляет ссылку на inode) и добавляет запись в родительскую директорию назначения. Сами данные на диске не перемещаются. inode остаётся тем же, временные метки данных не меняются. Это выполняется за один системный вызов rename() и является атомарным.

Перемещение между разными FileStore (разные разделы, разные диски, сетевые ФС) невозможно как атомарная операция над метаданными. JVM выполняет копирование данных из источника в назначение, а затем удаляет источник. Это неатомарно: если копирование прошло успешно, но удаление источника не удалось (например, файл заблокирован), вы получите частичный результат — данные продублированы, а исходный файл остался на месте.

FileStore — абстракция над хранилищем файловой системы (раздел диска, том, сетевой ресурс). Каждый раздел имеет свой FileStore, и перемещение между разными FileStore требует физического копирования данных.


Опции перемещения


StandardCopyOption.REPLACE_EXISTING
Разрешает перезапись существующего файла или директории в точке назначения. Без этой опции Files.move() выбросит FileAlreadyExistsException, если target уже существует.
Path src = Paths.get("/data/report_v1.txt");
Path dst = Paths.get("/data/report.txt");

// Перезаписываем старый отчёт новым
Files.move(src, dst, StandardCopyOption.REPLACE_EXISTING);

Важно: при перезаписи директории целевая директория должна быть пустой. Если target — непустая директория, даже с REPLACE_EXISTING будет выброшено DirectoryNotEmptyException.

StandardCopyOption.ATOMIC_MOVE
Выполняет атомарное перемещение. Это означает, что операция либо завершится целиком, либо не произойдёт вообще. Другие процессы никогда не увидят промежуточного состояния, где ни источник, ни назначение не существуют.
Path src = Paths.get("/data/temp_report.txt");
Path dst = Paths.get("/data/report.txt");

// Атомарное переименование: читатели либо видят старый файл, либо новый
Files.move(src, dst, StandardCopyOption.ATOMIC_MOVE);


Ограничения ATOMIC_MOVE:
Источник и назначение должны находиться на одном FileStore. Если это не так, выбрасывается AtomicMoveNotSupportedException.
Нельзя комбинировать с COPY_ATTRIBUTES — атомарное перемещение не копирует атрибуты, оно переносит inode целиком, вместе со всеми его метаданными.
Если назначение существует и передан REPLACE_EXISTING, перезапись тоже будет атомарной.

Атомарность в контексте файловых систем означает неделимость операции. Для rename() атомарность гарантируется на уровне ядра ОС: запись в структуры директорий защищена блокировками, и другие процессы видят либо старое имя, либо новое, но не промежуточное состояние.

Пример: перемещение обработанного файла в архивную папку
import java.nio.file.*;
import java.io.IOException;
import java.time.Instant;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;

public class ArchiveMover {

private static final DateTimeFormatter DATE_FORMAT =
DateTimeFormatter.ofPattern("yyyy-MM-dd").withZone(ZoneId.systemDefault());

public void archiveProcessedFile(Path processedFile, Path archiveRoot) throws IOException {
// Проверяем, что источник существует и является файлом
if (!Files.isRegularFile(processedFile)) {
throw new NoSuchFileException("Файл не найден или не является обычным файлом: " + processedFile);
}

// Создаём архивную директорию с текущей датой: /archive/2024-07-14/
String today = DATE_FORMAT.format(Instant.now());
Path datedArchive = archiveRoot.resolve(today);
Files.createDirectories(datedArchive);

// Формируем целевой путь
String fileName = processedFile.getFileName().toString();
Path target = datedArchive.resolve(fileName);

// Если файл с таким именем уже есть в архиве — добавляем счётчик
Path finalTarget = resolveUniqueName(target);

// Атомарное перемещение
Files.move(processedFile, finalTarget, StandardCopyOption.ATOMIC_MOVE);

System.out.println("Файл перемещён в архив: " + finalTarget.toAbsolutePath());
}

private Path resolveUniqueName(Path target) {
if (Files.notExists(target)) {
return target;
}

// Генерируем уникальное имя: file.txt -> file_1.txt, file_2.txt
String fileName = target.getFileName().toString();
int lastDot = fileName.lastIndexOf('.');
String base = lastDot > 0 ? fileName.substring(0, lastDot) : fileName;
String ext = lastDot > 0 ? fileName.substring(lastDot) : "";
Path parent = target.getParent();

int counter = 1;
Path candidate;
do {
candidate = parent.resolve(base + "_" + counter + ext);
counter++;
} while (Files.exists(candidate));

return candidate;
}

public static void main(String[] args) {
try {
Path file = Paths.get("/app/inbox/order_12345.xml");
Path archive = Paths.get("/app/archive");
new ArchiveMover().archiveProcessedFile(file, archive);
} catch (AtomicMoveNotSupportedException e) {
System.err.println("Атомарное перемещение невозможно — источник и архив на разных разделах");
// Fallback: копирование + удаление
} catch (IOException e) {
System.err.println("Ошибка архивирования: " + e.getMessage());
}
}
}

В этом примере ATOMIC_MOVE гарантирует, что в процессе перемещения читатели директории inbox либо видят файл на месте, либо не видят его вообще (уже в архиве). Промежуточного состояния "файл исчез" не возникает. Если архив находится на другом разделе, ловим AtomicMoveNotSupportedException и применяем fallback-стратегию.


#Java #для_новичков #beginner #IO #NIO #Files #move #delete
👍5
Удаление файлов и директорий

Files.delete(Path) — строгое удаление

Метод Files.delete(Path path) удаляет файл, директорию или символическую ссылку. Если объект не существует — выбрасывает NoSuchFileException. Если путь — непустая директория — выбрасывает DirectoryNotEmptyException. Если нет прав на удаление — AccessDeniedException.
Path file = Paths.get("/tmp/old_data.tmp");
Files.delete(file); // NoSuchFileException, если файла нет


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


Files.deleteIfExists(Path) — условное удаление

Метод Files.deleteIfExists(Path path) удаляет файл, если он существует. Возвращает true, если файл был удалён, false — если файла не было. Не выбрасывает исключение при отсутствии файла, но всё ещё выбрасывает DirectoryNotEmptyException и AccessDeniedException.
Path tempFile = Paths.get("/tmp/session_abc123.tmp");
boolean wasDeleted = Files.deleteIfExists(tempFile);
if (wasDeleted) {
System.out.println("Временный файл удалён");
} else {
System.out.println("Файл уже был удалён или не существовал");
}

Этот метод идеален для идемпотентной очистки: можно вызывать многократно без ошибок.

DirectoryNotEmptyException

Оба метода delete() и deleteIfExists() не удаляют непустые директории. Это ограничение на уровне файловой системы: удаление директории требует сначала удалить все её содержимое. Файловая система хранит список записей в директории, и ядро ОС отказывает в удалении, пока список не пуст.
Path dir = Paths.get("/tmp/myapp");
try {
Files.delete(dir); // DirectoryNotEmptyException, если внутри есть файлы
} catch (DirectoryNotEmptyException e) {
// Нужно сначала удалить содержимое
}



Обход дерева с удалением всех файлов


Для рекурсивного удаления директории со всем содержимым используется обход дерева в обратном порядке: сначала удаляются файлы и пустые поддиректории на самом глубоком уровне, затем — родительские директории.
import java.nio.file.*;
import java.io.IOException;
import java.util.Comparator;
import java.util.stream.Stream;

public class TreeDeleter {

public void deleteDirectoryRecursively(Path root) throws IOException {
if (!Files.exists(root)) {
return; // Нечего удалять
}

// walk возвращает Stream<Path> в порядке pre-order (родитель до детей)
// sorted(reverseOrder()) переворачивает: сначала глубокие пути, потом корень
try (Stream<Path> walk = Files.walk(root)) {
walk.sorted(Comparator.reverseOrder())
.forEach(path -> {
try {
Files.delete(path);
} catch (IOException e) {
// Оборачиваем в UncheckedIOException для использования в forEach
throw new UncheckedIOException(
"Не удалось удалить: " + path, e
);
}
});
} catch (UncheckedIOException e) {
// Распаковываем обратно в checked исключение
throw e.getCause();
}
}
}


Почему Comparator.reverseOrder()? Метод Files.walk() обходит дерево в порядке pre-order: сначала корень, потом его дети. Если мы будем удалять в этом порядке, попытка удалить корневую директорию первой выбросит DirectoryNotEmptyException, так как дети ещё не удалены. reverseOrder() сортирует пути по строковому представлению в обратном порядке, что гарантирует, что более глубокие пути (с большим количеством сегментов) обрабатываются раньше.

UncheckedIOException — это обёртка вокруг IOException, наследующая RuntimeException. Она нужна потому, что функциональные интерфейсы в Java (например, Consumer в forEach) не могут выбрасывать checked-исключения. Оборачивая IOException в UncheckedIOException, мы пробрасываем его через лямбду, а затем распаковываем вне.

Пример: удаление всех .tmp файлов во временной папке
import java.nio.file.*;
import java.io.IOException;
import java.util.stream.Stream;

public class TempCleaner {

private static final Path TEMP_DIR = Paths.get(System.getProperty("java.io.tmpdir"));

public void cleanTempFiles() throws IOException {
// Проверяем, что TEMP_DIR существует и является директорией
if (!Files.isDirectory(TEMP_DIR)) {
throw new NotDirectoryException(TEMP_DIR.toString());
}

// Обходим только верхний уровень временной директории
// newDirectoryStream — более лёгкая альтернатива walk для одного уровня
try (DirectoryStream<Path> stream = Files.newDirectoryStream(TEMP_DIR)) {
for (Path entry : stream) {
if (Files.isRegularFile(entry) && entry.toString().endsWith(".tmp")) {
try {
Files.delete(entry);
System.out.println("Удалён: " + entry.getFileName());
} catch (IOException e) {
// Логируем, но продолжаем удаление остальных
System.err.println("Не удалось удалить " + entry.getFileName()
+ ": " + e.getMessage());
}
}
}
}
}

// Альтернатива: удаление .tmp файлов рекурсивно во всём поддереве
public void cleanTempFilesRecursive(Path root) throws IOException {
try (Stream<Path> walk = Files.walk(root)) {
walk.filter(Files::isRegularFile)
.filter(p -> p.toString().endsWith(".tmp"))
.forEach(p -> {
try {
Files.delete(p);
System.out.println("Удалён: " + p);
} catch (IOException e) {
System.err.println("Ошибка удаления " + p + ": " + e.getMessage());
}
});
}
}

// Удаление .tmp файлов старше N дней
public void cleanOldTempFiles(int daysOld) throws IOException {
long cutoff = System.currentTimeMillis() - (daysOld * 24 * 60 * 60 * 1000L);

try (Stream<Path> walk = Files.walk(TEMP_DIR)) {
walk.filter(Files::isRegularFile)
.filter(p -> p.toString().endsWith(".tmp"))
.filter(p -> {
try {
return Files.getLastModifiedTime(p).toMillis() < cutoff;
} catch (IOException e) {
return false; // Не удалось прочитать — пропускаем
}
})
.forEach(p -> {
try {
Files.delete(p);
System.out.println("Удалён старый файл: " + p.getFileName());
} catch (IOException e) {
System.err.println("Ошибка: " + e.getMessage());
}
});
}
}

public static void main(String[] args) {
try {
TempCleaner cleaner = new TempCleaner();
cleaner.cleanTempFiles();
cleaner.cleanOldTempFiles(7); // удалить .tmp старше 7 дней
} catch (IOException e) {
System.err.println("Ошибка очистки: " + e.getMessage());
}
}
}

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

DirectoryStream — это интерфейс в NIO.2, представляющий поток записей директории. В отличие от Stream<Path>, он не поддерживает промежуточные операции (filter, map), но эффективнее для простого обхода, так как не создаёт промежуточных объектов потока.


#Java #для_новичков #beginner #IO #NIO #Files #move #delete
👍6
Путь байтов в памяти JVM при перемещении и удалении

Перемещение: путь данных

При Files.move(source, target, ATOMIC_MOVE):
Объекты source и target — это Path в куче. Их строковые представления извлекаются и кодируются в байты пути в нативной кодировке.
Если источник и назначение на одном FileStore, выполняется системный вызов rename() (POSIX) или MoveFileEx() (Windows). Этот вызов работает исключительно с метаданными файловой системы:
Удаляется запись в родительской директории источника.
Создаётся запись в родительской директории назначения, указывающая на тот же inode.
Блоки данных на диске не перемещаются.
Поскольку данные не копируются, в куче JVM не создаётся никаких буферов или массивов байтов. Объекты source и target остаются в куче как обычные объекты Path. Если они локальные переменные метода, они становятся мусором после выхода из метода и собираются при следующей Minor GC.
Если REPLACE_EXISTING передан и целевой файл существует, файловая система сначала удаляет целевой файл (уменьшает счётчик ссылок на его inode), а затем выполняет rename(). Это тоже атомарно на уровне ядра.
Если источник и назначение на разных FileStore, ATOMIC_MOVE выбросит AtomicMoveNotSupportedException до выполнения каких-либо операций. Без ATOMIC_MOVE JVM выполняет fallback: копирование через буфер в куче + удаление источника.

При этом:
Создаётся буфер byte[] в Young Generation (обычно 8–32 КБ).
Данные копируются блоками: чтение в буфер, запись из буфера.
После успешного копирования источник удаляется через unlink().
Буфер становится мусором и собирается Minor GC.


Удаление: путь данных
При Files.delete(path):
Объект Path в куче преобразуется в байты пути в нативной памяти.
Выполняется системный вызов unlink() (POSIX) или DeleteFile() (Windows). Ядро ОС:
Удаляет запись в родительской директории.
Уменьшает счётчик ссылок (reference count) на inode на 1.
Если счётчик ссылок достигает 0 и нет открытых файловых дескрипторов на этот inode, блоки данных помечаются как свободные.
Если счётчик 0, но дескрипторы открыты (например, другой процесс читает файл), блоки остаются занятыми до закрытия всех дескрипторов. Файл становится "невидимым" в файловой системе, но продолжает занимать место на диске.
Никаких новых объектов в куче JVM не создаётся. Операция полностью работает с метаданными файловой системы.
При удалении директории через rmdir() (POSIX) ядро проверяет, что директория содержит только записи . и .. (текущая и родительская). Если есть другие записи — возвращается ENOTEMPTY, который JVM преобразует в DirectoryNotEmptyException.

Рекурсивное удаление: путь данных и GC
При рекурсивном удалении через Files.walk():
Files.walk(root) создаёт Stream<Path>, который лениво обходит дерево. Каждый вызов walk() порождает внутренние объекты итератора директории в куче.
sorted(Comparator.reverseOrder()) материализует весь поток в список для сортировки. Это означает, что все пути дерева загружаются в память одновременно. Для дерева из миллиона файлов это создаст миллион объектов Path в куче, что может привести к OutOfMemoryError.

Альтернатива без материализации — обход вручную через FileVisitor:
public void deleteRecursivelySafe(Path root) throws IOException {
// FileVisitor — интерфейс для обхода дерева с callback-методами
Files.walkFileTree(root, new SimpleFileVisitor<Path>() {
@Override
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs)
throws IOException {
Files.delete(file);
return FileVisitResult.CONTINUE;
}

@Override
public FileVisitResult postVisitDirectory(Path dir, IOException exc)
throws IOException {
if (exc != null) {
throw exc; // ошибка при обходе поддиректории
}
Files.delete(dir);
return FileVisitResult.CONTINUE;
}
});
}

FileVisitor — это интерфейс в NIO.2, определяющий callback-методы для обхода дерева: preVisitDirectory (перед входом в директорию), visitFile (при обнаружении файла), visitFileFailed (при ошибке доступа к файлу), postVisitDirectory (после выхода из директории). SimpleFileVisitor — адаптер с пустыми реализациями по умолчанию.

Files.walkFileTree() не материализует всё дерево в память. Он обходит директории рекурсивно, вызывая callback'и по мере продвижения. Это безопасно для огромных деревьев, так как в памяти хранится только стек текущего пути обхода, а не всё дерево целиком. Объекты Path создаются и сразу становятся мусором после выхода из callback'а, собираясь Minor GC.


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

import java.nio.file.*;
import java.io.*;

public class Task150726 {
public static void main(String[] args) throws IOException {
Path source = Paths.get("file.txt");
Path target = Paths.get("target.txt");

Files.createFile(source);
Files.createFile(target);

try {
Files.move(source, target);
System.out.println("Moved");
} catch (IOException e) {
System.out.println(e.getClass().getSimpleName());
}

System.out.println(Files.exists(source));
}
}


#Tasks
👍4
Что такое FileAttribute и PosixFileAttributes в NIO? 🤓

Ответ:

В NIO.2 можно получать атрибуты файлов без обращения к ОС через Files.getFileAttributeView(). 

FileAttribute — общий интерфейс для любого атрибута (например, "posix:permissions"). PosixFileAttributes расширяет его для UNIX-систем, давая доступ к владельцу, группе, правам доступа (rwx).

Использование: Files.readAttributes(path, PosixFileAttributes.class).

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



#собеседование
Please open Telegram to view this post
VIEW IN TELEGRAM
👍5
Привет)

Ничего нового, просто хотел выразить свою благодарность той паре-тройке человек, которые ставят лайки на каждом посте в канале. 🙏

Вы лучшие, спасибо! ❤️
Please open Telegram to view this post
VIEW IN TELEGRAM
👍15
История технологии сегодня — 16 июля

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

Бори́с Васи́льевич Бу́нкин (1922—2007) — советский и российский учёный, конструктор и организатор производства зенитных ракетных комплексов для ПВО. С 1968 по 1998 год — генеральный конструктор предприятия НПО «Алмаз», осуществляющего разработку и серийное производство зенитных ракетных комплексов, составляющих основу вооружения отечественных войск ПВОС-75С-125С-300С-400.

Фриц Цернике (нид. Frits Zernike; 16 июля 1888 — 10 марта 1966)голландский физик, лауреат Нобелевской премии по физике 1953 года «За обоснование фазово-контрастного метода, особенно за изобретение фазово-контрастного микроскопа».


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

1867 — Француз Жозеф Монье, бывший садовник, получил патент на армированный бетон, ставший главным материалом при возведении высотных зданий.

1969 — с космодрома на мысе Канаверал стартовала РН «Сатурн-V» с космическим кораблём «Аполлон-11». Через несколько дней посадочный модуль «Орёл» совершит первую пилотируемую посадку на Луну.

1993 — в Лондоне прошла презентация Amiga CD32 — первой 32-битной игровой консоли, использующей CD-ROM.

2000 — принято решение об увеличении числа доменов Интернета.


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

Тема: Thread.yield() — подсказка планировщику, но может быть проигнорирована. Не используйте для синхронизации.

Проблема: Метод Thread.yield() призван сигнализировать планировщику потоков, что текущий поток готов уступить процессорное время другим потокам.

Однако это всего лишь подсказка (hint) для JVM и операционной системы. Планировщик может проигнорировать её, особенно в средах с вытесняющей многозадачностью, где решение о переключении принимается системой.

Некоторые разработчики используют yield() в циклах ожидания (busy-wait) для "уменьшения нагрузки" на процессор, полагая, что это предотвратит зависание. Это ошибочная практика: она не гарантирует прогресс, не обеспечивает взаимное исключение и не защищает от гонок данных. Более того, в многопроцессорных системах вызов yield() может вообще не изменить поведение, так как другие потоки могут выполняться на других ядрах.

Использование yield() для синхронизации — это антипаттерн, ведущий к недетерминированным ошибкам, которые проявляются только под нагрузкой или на определённых конфигурациях железа.

Решение: Для координации потоков используйте механизмы синхронизации из java.util.concurrent или встроенные synchronizedwait()/notify().

Если нужно временно освободить процессор, применяйте Thread.sleep(1) с минимальной задержкой — это более предсказуемый способ, хотя тоже не идеален.

Для реализации эффективного ожидания используйте LockSupport.park() или Condition.await(). Для уступки в вычислительных циклах с интенсивным использованием CPU можно использовать yield() в исключительных случаях, но только после тщательного анализа и с чётким пониманием, что это не влияет на корректность.

В подавляющем большинстве сценариев yield() не нужен, а его использование свидетельствует о попытке "замаскировать" проблемы синхронизации.
public class ThreadYield {
private boolean flag = false;

//Антипаттерн: yield() в цикле ожидания
public void badWait() {
while (!flag) {
Thread.yield(); // Плохо: может никогда не сработать, а если сработает, то не факт
}
// Действие после флага
}

//Правильно: использование Lock или wait/notify
private final Object lock = new Object();

public void goodWait() throws InterruptedException {
synchronized (lock) {
while (!flag) {
lock.wait(); // Блокируется до уведомления
}
}
}

public void setFlagTrue() {
synchronized (lock) {
flag = true;
lock.notifyAll();
}
}
}


Объяснение:
 Спецификация Java не даёт никаких гарантий относительно того, что yield() действительно вызовет переключение контекста.

Разные реализации JVM и операционные системы трактуют его по-разному: на Windows это обычно приводит к немедленному переключению, на Linux может быть проигнорировано.

Полагаться на него для синхронизации — значит внести в код фактор случайности. Правильные механизмы (wait/notify, Lock, Semaphore, CountDownLatch) обеспечивают детерминированное освобождение ресурсов и гарантируют, что поток будет возобновлён только когда условие действительно выполнено.

Даже Thread.sleep(0) часто работает аналогично yield(), но тоже не рекомендуется.


#Java #советы
👍6
Потом удивляемся почему нет рабочих мест для погромистов. Грууусть...
🗿5🤓2
Что выведет код?

public class Task160726 {
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(() -> {
for (int i = 0; i < 10; i++) {
System.out.print("A");
Thread.yield();
}
});
Thread t2 = new Thread(() -> {
for (int i = 0; i < 10; i++) {
System.out.print("B");
Thread.yield();
}
});
t1.start();
t2.start();
t1.join();
t2.join();
}
}


#Tasks
👍3
Что такое ProcessHandle в Java 9? 🤓

Ответ:

ProcessHandle (Java 9)
— это улучшенный API для управления процессами ОС из Java.

Позволяет получить дескриптор текущего процесса (ProcessHandle.current()), получить PID, родительский процесс, дочерние процессы, а также завершить процесс (destroy(), destroyForcibly()).

Можно также подписаться на завершение процесса через onExit(), возвращающее CompletableFuture<ProcessHandle>. Это улучшение по сравнению с устаревшим Runtime.exec() и Process позволяет более удобно управлять внешними процессами.



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

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

Серге́й Константи́нович Годуно́в (17 июля 1929, Москва — 15 июля 2023, Новосибирск) — советский и российский учёный в области математики и механикиакадемик РАН (1994; член-корреспондент АН СССР с 1976). Работы по теории обыкновенных дифференциальных уравненийуравнений в частных производныхвычислительной математикемеханике сплошных средлинейной алгебре.

Гордон Гулд (17 июля 1920, Манхэттен, Нью-Йорк — 16 сентября 2005, Манхэттен, Нью-Йорк) — американский физик, которому широко приписывается изобретение лазера. Гулд первым предложил использовать термин лазер (англ. laser), представляющий собой акроним от английского Light Amplification by Stimulated Emission of Radiation, что в переводе означает усиление света посредством вынужденного излучения.

Жорж Леме́тр (полное имя — Жорж Анри́ Жозе́ф Эдуа́р Леме́тр, фр. Georges Henri Joseph Édouard Lemaître); 17 июля 1894, Шарлеруа, Эно — 20 июня 1966, Лёвен, Брабант) — бельгийский священникастрофизиккосмолог и математик, автор теории Большого взрыва. Один из наиболее влиятельных астрофизиков XX века.


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

1975 — состоялась стыковка космических кораблей «Союз» (СССР) и «Аполлон» (США).


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

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

Атрибуты файлов — BasicFileAttributes

Каждый файл или директория в файловой системе хранит не только данные, но и метаданные — информацию о самом файле. Эти метаданные называются атрибутами файла и включают размер, временные метки (когда файл создан, изменён или к нему обращались), тип файла (обычный файл, директория, символическая ссылка) и права доступа. В Java NIO.2 работа с атрибутами вынесена в отдельную иерархию интерфейсов, корнем которой является BasicFileAttributes.

Метаданные — это данные о данных. В контексте файловой системы это не содержимое файла, а его характеристики: размер, время создания, права доступа, владелец и т.д.

Интерфейс BasicFileAttributes (пакет java.nio.file.attribute) предоставляет базовый набор атрибутов, общий для всех поддерживаемых JVM файловых систем. Для платформоспецифичных атрибутов существуют расширения: PosixFileAttributes (Unix/Linux/macOS) и DosFileAttributes (Windows). Эта архитектура следует принципу ISP (Interface Segregation Principle, принцип разделения интерфейса): общий интерфейс содержит только универсальные атрибуты, а специфичные вынесены в подинтерфейсы.


Что такое атрибуты файла

Атрибуты файла делятся на несколько категорий:
Базовые атрибуты (BasicFileAttributes) — доступны на всех платформах:
Размер файла (size) — число байтов, занимаемых файлом. Для директорий это платформозависимо и обычно равно размеру записей директории, а не сумме размеров файлов внутри.
Время создания (creationTime) — момент первого создания файла. На некоторых файловых системах может не поддерживаться или совпадать с временем модификации.
Время последней модификации (lastModifiedTime) — момент последнего изменения содержимого файла.
Время последнего доступа (lastAccessTime) — момент последнего чтения файла или выполнения stat().
Тип файла — флаги isDirectory(), isRegularFile(), isSymbolicLink(), isOther().

POSIX-атрибуты (PosixFileAttributes) — специфичны для Unix-подобных систем:
Владелец (owner) — пользователь, владеющий файлом.
Группа (group) — группа пользователей, ассоциированная с файлом.
Права доступа (permissions) — набор битов rwx для владельца, группы и остальных.

DOS-атрибуты (DosFileAttributes) — специфичны для Windows:
Read-only — файл доступен только для чтения.
Hidden — скрытый файл.
System — системный файл.
Archive — флаг архивации.

Временные метки в NIO.2 представлены классом FileTime, а не устаревшим java.util.Date или long. FileTime хранит время с точностью до наносекунд и предоставляет удобные методы конвертации.

Наносекунда — единица времени, равная одной миллиардной доле секунды (10^-9 с). Современные файловые системы (ext4, NTFS, APFS) хранят временные метки с наносекундной точностью, поэтому FileTime поддерживает эту гранулярность, в отличие от Date, который ограничен миллисекундами.


Чтение атрибутов: Files.readAttributes()

Files.readAttributes(Path, Class<A>
Этот метод — основной способ чтения атрибутов файла. Он принимает путь, класс интерфейса атрибутов и опции следования по символическим ссылкам. Возвращает объект, реализующий запрошенный интерфейс.
import java.nio.file.*;
import java.nio.file.attribute.*;
import java.io.IOException;

public class AttributesReader {

public void printBasicAttributes(Path path) throws IOException {
// Читаем базовые атрибуты
// LinkOption.NOFOLLOW_LINKS — читаем атрибуты самой ссылки, а не цели
BasicFileAttributes attrs = Files.readAttributes(
path,
BasicFileAttributes.class,
LinkOption.NOFOLLOW_LINKS
);

System.out.println("Файл: " + path.getFileName());
System.out.println("Размер: " + attrs.size() + " байт");
System.out.println("Создан: " + attrs.creationTime());
System.out.println("Изменён: " + attrs.lastModifiedTime());
System.out.println("Доступ: " + attrs.lastAccessTime());
System.out.println("Директория: " + attrs.isDirectory());
System.out.println("Обычный файл: " + attrs.isRegularFile());
System.out.println("Символическая ссылка: " + attrs.isSymbolicLink());
}
}

Метод readAttributes() выполняет один системный вызов stat() (POSIX) или GetFileAttributesEx() (Windows), который возвращает структуру со всеми базовыми метаданными. Это эффективнее, чем вызывать отдельные методы Files.size(), Files.isDirectory(), Files.getLastModifiedTime() — каждый из которых выполняет свой системный вызов.

Системный вызов stat() — это вызов ядра Unix, который возвращает структуру stat с полной информацией о файле: inode, размер, права, временные метки, количество ссылок. Аналог на Windows — GetFileAttributesEx().

Различия между readAttributes() и отдельными методами Files
// Неэффективно: три системных вызова
long size = Files.size(path);
boolean isDir = Files.isDirectory(path);
FileTime modified = Files.getLastModifiedTime(path);

// Эффективно: один системный вызов
BasicFileAttributes attrs = Files.readAttributes(path, BasicFileAttributes.class);
long size = attrs.size();
boolean isDir = attrs.isDirectory();
FileTime modified = attrs.lastModifiedTime();

При интенсивной работе с файловой системой (например, сканирование дерева из миллиона файлов) использование readAttributes() снижает нагрузку на систему в 3–5 раз по сравнению с отдельными вызовами.


#Java #для_новичков #beginner #IO #NIO #Files #BasicFileAttributes
👍4
Методы интерфейса BasicFileAttributes

size()
Возвращает размер файла в байтах как long. Для обычных файлов это точное количество байтов данных. Для директорий значение платформозависимо: на Unix это размер блока данных директории (список записей), а не рекурсивный размер содержимого.
BasicFileAttributes attrs = Files.readAttributes(path, BasicFileAttributes.class);
long fileSize = attrs.size();


creationTime()

Возвращает FileTime — момент создания файла. На файловых системах, не поддерживающих время создания (например, старые версии ext2), может возвращать lastModifiedTime() как fallback. На Windows NTFS это точное время создания записи MFT.

MFT (Master File Table) — это центральная структура данных файловой системы NTFS, содержащая записи обо всех файлах и директориях тома. Каждая запись MFT хранит метаданные файла, включая временные метки.

lastModifiedTime()
Возвращает FileTime — момент последнего изменения содержимого файла. Это время обновляется при записи в файл, усечении (truncate), изменении прав доступа (на некоторых ФС) и других операциях, модифицирующих inode.

lastAccessTime()
Возвращает FileTime — момент последнего обращения к файлу. Важно: на многих Linux-системах по умолчанию atime (access time) обновляется отложенно (relatime) или вообще не обновляется (noatime) для снижения нагрузки на диск. Поэтому это поле может быть неточным или совпадать с lastModifiedTime().

relatime (relative atime) — режим обновления времени доступа в Linux, при котором atime обновляется только если оно старше mtime, или прошло более 24 часов с последнего обновления. Это компромисс между функциональностью и производительностью.

isDirectory(), isRegularFile(), isSymbolicLink(), isOther()


Эти методы определяют тип файловой сущности:
isDirectory() — директория (содержит записи других файлов).
isRegularFile() — обычный файл с данными.
isSymbolicLink() — символическая ссылка.
isOther() — всё остальное: именованные каналы (named pipes), сокеты домена Unix, блочные и символьные устройства.

Именованный канал (named pipe, FIFO) — это механизм межпроцессного взаимодействия в Unix, представленный как файл в файловой системе. Процессы пишут в один конец канала и читают из другого.

Сокет домена Unix (Unix domain socket) — механизм межпроцессного взаимодействия в пределах одной машины, более быстрый, чем TCP-сокеты, так как не использует сетевой стек.
Важно: эти методы не требуют отдельных системных вызовов, так как тип файла уже известен из структуры, возвращённой stat().


fileKey()

Возвращает объект, уникально идентифицирующий файл в файловой системе. На Unix это обычно пара (device_id, inode_number). На Windows — идентификатор файла NTFS. Может вернуть null, если файловая система не поддерживает уникальную идентификацию.
Object key = attrs.fileKey();
if (key != null) {
System.out.println("Уникальный ключ файла: " + key);
}

fileKey() полезен для обнаружения жёстких ссылок (hard links): два разных пути с одинаковым fileKey() указывают на один inode и, следовательно, на одни и те же данные.

Жёсткая ссылка (hard link) — это дополнительное имя для существующего файла в файловой системе. Все жёсткие ссылки на файл имеют один inode и разделяют данные. Удаление одной ссылки уменьшает счётчик ссылок; файл удаляется физически только когда счётчик достигает нуля.

Пример: вывод всех атрибутов для файла
import java.nio.file.*;
import java.nio.file.attribute.*;
import java.io.IOException;
import java.util.concurrent.TimeUnit;

public class FullAttributesReporter {

public void report(Path path) throws IOException {
// Определяем, какой тип атрибутов запрашивать
// Сначала пробуем POSIX (Unix/Linux/macOS)
if (FileSystems.getDefault().supportedFileAttributeViews().contains("posix")) {
reportPosixAttributes(path);
} else if (FileSystems.getDefault().supportedFileAttributeViews().contains("dos")) {
reportDosAttributes(path);
} else {
reportBasicAttributes(path);
}
}

private void reportBasicAttributes(Path path) throws IOException {
BasicFileAttributes attrs = Files.readAttributes(path, BasicFileAttributes.class);

System.out.println("=== Базовые атрибуты ===");
System.out.println("Путь: " + path.toAbsolutePath());
System.out.println("Размер: " + formatSize(attrs.size()));
System.out.println("Создан: " + formatTime(attrs.creationTime()));
System.out.println("Изменён: " + formatTime(attrs.lastModifiedTime()));
System.out.println("Доступ: " + formatTime(attrs.lastAccessTime()));
System.out.println("Тип: " + resolveType(attrs));
System.out.println("Уникальный ключ: " + attrs.fileKey());
}

private void reportPosixAttributes(Path path) throws IOException {
PosixFileAttributes attrs = Files.readAttributes(path, PosixFileAttributes.class);

reportBasicAttributes(path); // POSIX extends Basic

System.out.println("=== POSIX-атрибуты ===");
System.out.println("Владелец: " + attrs.owner().getName());
System.out.println("Группа: " + attrs.group().getName());
System.out.println("Права: " + PosixFilePermissions.toString(attrs.permissions()));
}

private void reportDosAttributes(Path path) throws IOException {
DosFileAttributes attrs = Files.readAttributes(path, DosFileAttributes.class);

reportBasicAttributes(path); // DOS extends Basic

System.out.println("=== DOS-атрибуты ===");
System.out.println("Только чтение: " + attrs.isReadOnly());
System.out.println("Скрытый: " + attrs.isHidden());
System.out.println("Системный: " + attrs.isSystem());
System.out.println("Архивный: " + attrs.isArchive());
}

private String formatSize(long bytes) {
if (bytes < 1024) return bytes + " B";
if (bytes < 1024 * 1024) return String.format("%.2f KB", bytes / 1024.0);
if (bytes < 1024L * 1024 * 1024) return String.format("%.2f MB", bytes / (1024.0 * 1024));
return String.format("%.2f GB", bytes / (1024.0 * 1024 * 1024));
}

private String formatTime(FileTime time) {
// FileTime.toInstant() возвращает Instant — точку на временной шкале
return time.toInstant().toString();
}

private String resolveType(BasicFileAttributes attrs) {
if (attrs.isDirectory()) return "директория";
if (attrs.isRegularFile()) return "обычный файл";
if (attrs.isSymbolicLink()) return "символическая ссылка";
return "другое";
}

public static void main(String[] args) {
try {
new FullAttributesReporter().report(Paths.get("/etc/passwd"));
} catch (IOException e) {
System.err.println("Ошибка чтения атрибутов: " + e.getMessage());
}
}
}

В этом примере FileSystems.getDefault().supportedFileAttributeViews() возвращает множество строк — названий поддерживаемых представлений атрибутов. Проверка "posix" позволяет адаптировать код к текущей ОС без жёсткой привязки к классам.

Представление атрибутов (file attribute view) — это интерфейс, определяющий набор операций чтения/записи для конкретного типа атрибутов. PosixFileAttributeView, DosFileAttributeView, AclFileAttributeView — примеры таких представлений. Каждое представление ассоциировано с именем-строкой ("posix", "dos", "acl").


#Java #для_новичков #beginner #IO #NIO #Files #BasicFileAttributes
👍3
Изменение времени модификации: Files.setLastModifiedTime()

Метод Files.setLastModifiedTime(Path path, FileTime time) устанавливает время последней модификации файла. Это полезно для инструментов, которые копируют файлы и хотят сохранить оригинальные временные метки, или для тестов, которым нужно симулировать старый файл.
import java.nio.file.*;
import java.nio.file.attribute.FileTime;
import java.io.IOException;
import java.time.Instant;

public class ModificationTimeSetter {

public void touchFile(Path path) throws IOException {
// "Touch" — обновить время модификации до текущего момента
FileTime now = FileTime.from(Instant.now());
Files.setLastModifiedTime(path, now);
}

public void setSpecificTime(Path path, Instant instant) throws IOException {
FileTime fileTime = FileTime.from(instant);
Files.setLastModifiedTime(path, fileTime);
}

public void copyTimestamps(Path source, Path target) throws IOException {
// Копируем временные метки с источника на назначение
BasicFileAttributes sourceAttrs = Files.readAttributes(
source, BasicFileAttributes.class
);

Files.setLastModifiedTime(target, sourceAttrs.lastModifiedTime());

// Время создания и доступа устанавливаются через setAttribute
Files.setAttribute(target, "basic:creationTime", sourceAttrs.creationTime());
Files.setAttribute(target, "basic:lastAccessTime", sourceAttrs.lastAccessTime());
}
}


FileTime
— неизменяемый класс, поэтому его экземпляры безопасно передавать между потоками.

Он предоставляет несколько фабричных методов:
FileTime.from(Instant instant) — из объекта Instant (точка на временной шкале UTC).
FileTime.fromMillis(long millis) — из миллисекунд с эпохи Unix (1 января 1970).
FileTime.from(long value, TimeUnit unit) — из произвольной единицы времени.

Эпоха Unix (Unix epoch) — это момент времени 00:00:00 UTC 1 января 1970 года. Время в Unix-системах традиционно отсчитывается от этой точки в секундах или миллисекундах.


Универсальный метод: Files.setAttribute()

Метод Files.setAttribute(Path path, String attribute, Object value, LinkOption... options) позволяет установить любой атрибут по его строковому имени. Это полезно, когда вы работаете с атрибутами динамически или пишете универсальный код, не привязанный к конкретному типу файловой системы.


Синтаксис имени атрибута

Имя атрибута имеет формат viewName:attributeName. Если viewName опущен, используется "basic".
// Установка времени модификации через универсальный метод
Files.setAttribute(path, "basic:lastModifiedTime", FileTime.from(Instant.now()));

// Установка POSIX-прав
Set<PosixFilePermission> perms = PosixFilePermissions.fromString("rwxr-xr-x");
Files.setAttribute(path, "posix:permissions", perms);

// Установка владельца (только Unix с соответствующими правами)
UserPrincipalLookupService lookup = FileSystems.getDefault().getUserPrincipalLookupService();
UserPrincipal newOwner = lookup.lookupPrincipalByName("admin");
Files.setAttribute(path, "posix:owner", newOwner);


UserPrincipal
— это интерфейс в NIO.2, представляющий пользователя или группу в файловой системе. UserPrincipalLookupService — сервис для поиска пользователей по имени.

Метод setAttribute() использует рефлексию и динамическую диспетчеризацию: он парсит строку имени, находит соответствующее FileAttributeView, вызывает его метод записи. Это менее эффективно, чем прямой вызов специализированного метода (например, Files.setPosixFilePermissions()), но предоставляет гибкость при работе с неизвестными заранее типами атрибутов.

Рефлексия (reflection) — это механизм Java, позволяющий программе исследовать и модифицировать структуру и поведение объектов во время выполнения: получать классы, методы, поля, вызывать методы по имени.
Работа с правами доступа POSIX: Files.setPosixFilePermissions()


На Unix-подобных системах права доступа к файлам управляются через POSIX-биты. Класс PosixFilePermissions предоставляет удобные методы для работы с ними.
import java.nio.file.*;
import java.nio.file.attribute.*;
import java.io.IOException;
import java.util.Set;

public class PosixPermissionManager {

public void setStrictPermissions(Path path) throws IOException {
// rw------- — только владелец может читать и писать
Set<PosixFilePermission> perms = PosixFilePermissions.fromString("rw-------");
Files.setPosixFilePermissions(path, perms);
}

public void setExecutable(Path path) throws IOException {
// Добавляем право на выполнение для владельца
Set<PosixFilePermission> perms = Files.getPosixFilePermissions(path);
perms.add(PosixFilePermission.OWNER_EXECUTE);
Files.setPosixFilePermissions(path, perms);
}

public void setSharedReadOnly(Path path) throws IOException {
// r--r--r-- — все могут читать, никто не может писать
Set<PosixFilePermission> perms = PosixFilePermissions.fromString("r--r--r--");
Files.setPosixFilePermissions(path, perms);
}

public void copyPermissions(Path source, Path target) throws IOException {
// Копируем POSIX-права с одного файла на другой
Set<PosixFilePermission> perms = Files.getPosixFilePermissions(source);
Files.setPosixFilePermissions(target, perms);
}

public void createWithPermissions(Path path, String permString) throws IOException {
Set<PosixFilePermission> perms = PosixFilePermissions.fromString(permString);
FileAttribute<Set<PosixFilePermission>> attr = PosixFilePermissions.asFileAttribute(perms);

// createFile с FileAttribute устанавливает права атомарно при создании
Files.createFile(path, attr);
}
}


PosixFilePermission
— это enum, определяющий девять возможных битов прав:
Владелец: OWNER_READ, OWNER_WRITE, OWNER_EXECUTE
Группа: GROUP_READ, GROUP_WRITE, GROUP_EXECUTE
Остальные: OTHERS_READ, OTHERS_WRITE, OTHERS_EXECUTE

Строковое представление "rwxr-xr-x" — это стандартная нотация Unix ls -l, где каждая триада символов описывает права для владельца, группы и остальных. Дефис означает отсутствие права.


#Java #для_новичков #beginner #IO #NIO #Files #BasicFileAttributes
👍4
Пример: установка времени модификации файла в текущий момент
import java.nio.file.*;
import java.nio.file.attribute.FileTime;
import java.io.IOException;
import java.time.Instant;

public class FileToucher {

public void touch(Path path) throws IOException {
if (Files.notExists(path)) {
// Если файла нет — создаём пустой файл
// и время модификации будет равно времени создания (сейчас)
Files.createFile(path);
System.out.println("Файл создан: " + path);
return;
}

// Файл существует — обновляем только время модификации
FileTime now = FileTime.from(Instant.now());
Files.setLastModifiedTime(path, now);

// Проверяем, что установилось
FileTime verify = Files.getLastModifiedTime(path);
if (verify.compareTo(now) >= 0) {
System.out.println("Время модификации обновлено: " + verify);
} else {
throw new IOException("Не удалось обновить время модификации");
}
}

public void touchWithAccessTime(Path path) throws IOException {
// Обновляем и время модификации, и время доступа
FileTime now = FileTime.from(Instant.now());

Files.setAttribute(path, "basic:lastModifiedTime", now);
Files.setAttribute(path, "basic:lastAccessTime", now);

// Проверяем через readAttributes
BasicFileAttributes attrs = Files.readAttributes(path, BasicFileAttributes.class);
System.out.println("mtime: " + attrs.lastModifiedTime());
System.out.println("atime: " + attrs.lastAccessTime());
}

public static void main(String[] args) {
try {
Path file = Paths.get("/tmp/test_touch.txt");
new FileToucher().touch(file);
} catch (IOException e) {
System.err.println("Ошибка: " + e.getMessage());
}
}
}


Команда touch в Unix работает именно так: если файла нет — создаёт пустой, если есть — обновляет временные метки. Важно, что Files.setLastModifiedTime() не изменяет содержимое файла, только метаданные inode.


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

Чтение атрибутов: путь данных

При вызове Files.readAttributes(path, BasicFileAttributes.class):
Объект Path находится в куче (Heap). JVM извлекает его строковое представление и кодирует в массив байтов пути в нативной памяти через JNI.
Выполняется системный вызов stat() (POSIX) или GetFileAttributesEx() (Windows). Ядро ОС читает inode/MFT-запись и возвращает структуру с метаданными в нативную память ядра.
JNI-код JVM копирует поля структуры из нативной памяти в объекты Java. Создаётся экземпляр внутреннего класса, реализующего BasicFileAttributes (например, UnixFileAttributes в OpenJDK). Этот объект создаётся в Young Generation, в области Eden.
Поля объекта атрибутов содержат примитивы (long для размера, long для наносекунд времени) и объекты FileTime. Каждый вызов creationTime(), lastModifiedTime(), lastAccessTime() возвращает новый объект FileTime или кэшированный экземпляр — это зависит от реализации JVM. В OpenJDK FileTime создаётся лениво при первом вызове геттера и кэшируется в поле объекта атрибутов.
Объект BasicFileAttributes — лёгкий: он содержит несколько long-полей и ссылки на FileTime. Если вы читаете атрибуты в цикле (например, при сканировании директории из 100 000 файлов), каждая итерация создаёт новый объект атрибутов в Eden. Эти объекты становятся мусором сразу после обработки файла, если вы не сохраняете ссылку. Minor GC эффективно собирает их, так как они не переживают цикла.
Если вы сохраняете атрибуты в долгоживущую коллекцию (например, строите карту Map<Path, BasicFileAttributes> для всех файлов проекта), объекты атрибутов переходят в Old Generation через Survivor Space. При большом количестве файлов это может существенно увеличить размер Old Generation и вызвать Major GC (Full GC) раньше, чем ожидалось.

Запись атрибутов: путь данных

При Files.setLastModifiedTime(path, fileTime):
Объект FileTime хранит время как long в наносекундах (или секундах + наносекунды). Он находится в куче. JVM извлекает примитивное значение и передаёт его в нативный код.
Выполняется системный вызов utimensat() (современный POSIX, поддерживает наносекунды) или utime() (устаревший, точность до секунд). На Windows — SetFileTime().
Ядро ОС обновляет соответствующие поля в inode/MFT. Это операция только над метаданными; блоки данных файла не затрагиваются.
Никаких новых объектов в куче JVM не создаётся. Метод возвращает void. Объект FileTime, переданный как параметр, остаётся в куче до тех пор, пока на него есть ссылка. Если это локальная переменная — он собирается при следующей Minor GC.


POSIX-права: путь данных

При Files.setPosixFilePermissions(path, permissions):
Множество Set<PosixFilePermission> — это объект в куче, содержащий ссылки на enum-константы. Enum-константы в Java создаются один раз при загрузке класса и хранятся в Metaspace (вместе с классом PosixFilePermission). Само множество — это объект HashSet или EnumSet в куче.

JVM преобразует множество разрешений в целое число — битовую маску POSIX-прав. Эта маска передаётся в нативный код.

Выполняется системный вызов chmod() (POSIX) или SetFileSecurity() (Windows, если используется ACL-эмуляция). Ядро обновляет биты прав в inode.
Объект Set<PosixFilePermission> становится мусором после выхода из метода, если он не сохранён. Minor GC собирает его. Enum-константы остаются в Metaspace навсегда (до выгрузки класса), так как Metaspace не подлежит обычной сборке мусора в полном смысле — классы выгружаются только при выгрузке ClassLoader'а.

ClassLoader — это компонент JVM, отвечающий за динамическую загрузку классов в память. Каждый загруженный класс хранится в Metaspace и связан с ClassLoader'ом. Когда ClassLoader становится недостижимым для GC, все его классы и связанные с ними структуры Metaspace могут быть освобождены.


Оптимизация: массовое чтение атрибутов и давление на GC

При сканировании больших файловых деревьев частая ошибка — хранение всех атрибутов в памяти:
// Проблемный код: храним все атрибуты в памяти
Map<Path, BasicFileAttributes> cache = new HashMap<>();
Files.walk(root).forEach(p -> {
try {
cache.put(p, Files.readAttributes(p, BasicFileAttributes.class));
} catch (IOException e) { /* ignore */ }
});
// При миллионе файлов cache содержит миллион объектов BasicFileAttributes в Old Generation


Каждый объект BasicFileAttributes в OpenJDK занимает примерно 40–56 байт (зависит от архитектуры и JVM). Для миллиона файлов это 40–56 МБ только на объекты атрибутов, плюс накладные расходы HashMap (примерно в 2 раза больше). Это может привести к Major GC или OOM.

Оптимизированный подход — извлекать только нужные поля и не хранить объекты атрибутов:
// Оптимизированный код: извлекаем только размер и mtime, храним примитивы
Map<Path, Long> sizeCache = new HashMap<>();
Files.walk(root).forEach(p -> {
try {
BasicFileAttributes attrs = Files.readAttributes(p, BasicFileAttributes.class);
sizeCache.put(p, attrs.size()); // только long, лёгкий объект
} catch (IOException e) { /* ignore */ }
});
// Объект attrs становится мусором сразу после итерации


Ещё лучше — использовать Files.newDirectoryStream() и читать атрибуты порциями, не загружая всё дерево в память:
// Обход с минимальным потреблением памяти
try (DirectoryStream<Path> stream = Files.newDirectoryStream(dir)) {
for (Path entry : stream) {
BasicFileAttributes attrs = Files.readAttributes(entry, BasicFileAttributes.class);
process(entry, attrs.size(), attrs.lastModifiedTime());
// attrs собирается GC сразу после process()
}
}



#Java #для_новичков #beginner #IO #NIO #Files #BasicFileAttributes
👍3