Java for Beginner
870 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
Что такое boolean в Java? Сколько значений он может принимать? 🤓

Ответ:

boolean
 — примитивный тип, принимающий два значения: true и false.

Размер в памяти явно не специфицирован (зависит от JVM, обычно 1 байт, но в массивах — 1 байт на элемент, а в стеке может быть 4 байта).

Не является числовым типом — не может быть преобразован в int или наоборот (в отличие от C/C++).

Используется в условных выражениях (if, while), логических операторах (&&, ||, !) и для хранения флагов.



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

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

А́лан Мэ́тисон Тью́ринг (англ. Alan Mathison Turing [ˈtjʊərɪŋ]; 23 июня 1912, Вестминстер — 7 июня 1954, Чешир) — британский математиклогик и криптограф, оказавший существенное влияние на развитие информатики. Офицер ордена Британской империи (OBE, 1945), член Лондонского королевского общества (1951). Предложенная им в 1936 году абстрактная вычислительная «Машина Тьюринга», которую можно считать моделью компьютера общего назначения, позволила формализовать понятие алгоритма и до сих пор используется во множестве теоретических и практических исследований. Научные труды А. Тьюринга — общепризнанный вклад в основания информатики (и в частности — теории искусственного интеллекта).


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

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

1991 — выпущена видеоигра Sonic the Hedgehog для платформы Sega Mega Drive (первая игра этой серии).


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

Глава 1. Классический Java I/O (java.io)

BufferedReader – построчное чтение. Путь данных и управление памятью

BufferedReader — декоратор над Reader, добавляющий буферизацию символьного ввода и специализированные методы для построчного чтения. В отличие от базового Reader, который оперирует массивами char[], BufferedReader предоставляет readLine() — метод, возвращающий строку текста, завершающуюся символом перевода строки.

Ключевое архитектурное решение — разделение между транспортом и семантикой. InputStreamReader декодирует байты в символы. BufferedReader накапливает символы в буфере и распознает логические строки, абстрагируя приложение от деталей разделителей строк (\n, \r, \r\n).

Внутреннее устройство
// Упрощенная структура из OpenJDK
public class BufferedReader extends Reader {
// Буфер символов в heap JVM
private char[] cb;

// Количество символов в буфере
private int nChars;

// Текущая позиция чтения
private int nextChar;

// Маркер для mark/reset
private int markedChar = UNMARKED;
private int readAheadLimit = 0;

// Признак пропуска \n после \r
private boolean skipLF = false;

// Конструкторы
public BufferedReader(Reader in) {
this(in, defaultCharBufferSize); // 8192 по умолчанию
}

public BufferedReader(Reader in, int sz) {
super(in);
if (sz <= 0)
throw new IllegalArgumentException("Buffer size <= 0");
cb = new char[sz]; // Выделение в heap
}
}

Поле cb — массив char[] в heap, служащий кольцевым буфером для накопления символов из underlying Reader. Размер по умолчанию 8192 символов, что соответствует 16 KB памяти heap (каждый char — 2 байта).


Механика readLine()

readLine() выполняет последовательный поиск символов перевода строки в буфере, накапливая символы до разделителя во временный StringBuilder, и создает String при нахождении \n или \r\n:
// Упрощенная логика readLine()
public String readLine() throws IOException {
StringBuilder sb = new StringBuilder(defaultExpectedLineLength);

while (true) {
if (nextChar >= nChars) {
fill(); // Заполнение буфера из underlying Reader
if (nextChar >= nChars) {
// Конец потока
return sb.length() > 0 ? sb.toString() : null;
}
}

char c = cb[nextChar++];

if (c == '\n') {
if (skipLF) {
skipLF = false;
continue; // Пропуск \n после \r
}
return sb.toString(); // Строка завершена
}

if (c == '\r') {
skipLF = true;
return sb.toString(); // Строка завершена, \n будет пропущен
}

sb.append(c); // Накопление символов
}
}


Ключевые особенности:
Универсальность разделителей. \n (Unix), \r (старый Mac), \r\n (Windows) распознаются корректно.
Разделитель не включается в результат. Возвращенная String не содержит \n или \r.
Признак конца потока. null возвращается, когда достигнут EOF и StringBuilder пуст.

Путь данных в памяти
[Диск: файл в байтах UTF-8]
-> [Page Cache ОС]
-> [FileInputStream: системный вызов read()]
-> [InputStreamReader: декодирование байт -> char]
-> [BufferedReader.fill(): копирование char[] в cb[8192]]
-> [BufferedReader.readLine(): поиск \n в cb]
-> [StringBuilder: накопление символов строки]
-> [StringBuilder.toString(): создание String в heap]
-> [Возврат String]



Жизненный цикл объектов в heap

При каждом вызове readLine() создаются объекты:
StringBuilder — временный накопитель символов. Создается при каждом вызове readLine(), живет до возврата String. Собирается GC немедленно.
String — результат. Создается через StringBuilder.toString(). В современных JDK (9+) String хранит byte[] вместо char[], с компактным представлением для латиницы (1 байт на символ) и UTF-16 для остального.

Внутренний массив StringBuilderchar[], расширяющийся при необходимости. При превышении емкости создается новый массив большего размера с System.arraycopy.
// Длинная строка превышает начальную емкость StringBuilder
StringBuilder sb = new StringBuilder(80); // Начальный массив char[80]
// Строка длиной 200 символов
// sb расширяется: char[80] -> char[160] -> char[320] -> ...
// Старые массивы становятся мусором

Это создает дополнительное давление на GC для файлов с очень длинными строками.


Роль garbage collector

Сценарий: чтение файла с миллионом строк
public long countLines(String path) throws IOException {
long lineCount = 0;

try (BufferedReader reader = new BufferedReader(
new InputStreamReader(
new FileInputStream(path), StandardCharsets.UTF_8))) {

String line;
while ((line = reader.readLine()) != null) {
lineCount++;
// line — новая String в heap
// StringBuilder и его внутренний char[] — мусор
}

} // reader закрыт, cb освобожден

return lineCount;
}


Память в процессе:

cb[8192] — буфер BufferedReader, живет до закрытия
StringBuilder + внутренний char[] — при каждом вызове readLine()
String — при каждом вызове readLine(), возвращается вызывающему

Если вызывающий код не сохраняет String, она становится мусором после итерации. Для файла с 1 000 000 строк создается 1 000 000 объектов String + 1 000 000 StringBuilder + несколько расширенных char[] для длинных строк. Все они короткоживущие и собираются minor GC в young generation.

Проблема: сохранение всех строк
// Антипаттерн: удержание всех строк в памяти
public List<String> readAllLines(String path) throws IOException {
List<String> lines = new ArrayList<>();

try (BufferedReader reader = new BufferedReader(
new InputStreamReader(new FileInputStream(path), StandardCharsets.UTF_8))) {

String line;
while ((line = reader.readLine()) != null) {
lines.add(line); // Все String удерживаются списком
}
}

return lines; // Возврат: все строки в heap
}

Здесь все String удерживаются списком lines. Для файла 100MB с ~1 000 000 строк это ~100MB в String + оверхед ArrayList. GC не может собрать эти String, пока список достижим. Это приводит к увеличению old generation и потенциальному OutOfMemoryError.

Решение — потоковая обработка без накопления:
// Правильно: потоковая обработка без хранения всех строк
public void processLines(String path, Consumer<String> processor) throws IOException {
try (BufferedReader reader = new BufferedReader(
new InputStreamReader(new FileInputStream(path), StandardCharsets.UTF_8))) {

String line;
while ((line = reader.readLine()) != null) {
processor.accept(line);
// line становится недостижимой после итерации, если processor не сохраняет
}
}
}



#Java #для_новичков #beginner #IO #NIO #BufferedReader
👍4
Проблема: сохранение всех строк
// Антипаттерн: удержание всех строк в памяти
public List<String> readAllLines(String path) throws IOException {
List<String> lines = new ArrayList<>();

try (BufferedReader reader = new BufferedReader(
new InputStreamReader(new FileInputStream(path), StandardCharsets.UTF_8))) {

String line;
while ((line = reader.readLine()) != null) {
lines.add(line); // Все String удерживаются списком
}
}

return lines; // Возврат: все строки в heap
}

Здесь все String удерживаются списком lines. Для файла 100MB с ~1 000 000 строк это ~100MB в String + оверхед ArrayList. GC не может собрать эти String, пока список достижим. Это приводит к увеличению old generation и потенциальному OutOfMemoryError.

Решение — потоковая обработка без накопления:
// Правильно: потоковая обработка без хранения всех строк
public void processLines(String path, Consumer<String> processor) throws IOException {
try (BufferedReader reader = new BufferedReader(
new InputStreamReader(new FileInputStream(path), StandardCharsets.UTF_8))) {

String line;
while ((line = reader.readLine()) != null) {
processor.accept(line);
// line становится недостижимой после итерации, если processor не сохраняет
}
}
}



Практический пример: чтение файла построчно с подсчетом
import java.io.*;
import java.nio.charset.StandardCharsets;

public class LineCounter {

public LineStats analyzeFile(String path) throws IOException {
long lineCount = 0;
long totalChars = 0;
long maxLineLength = 0;
String longestLine = null;

// BufferedReader с underlying InputStreamReader и FileInputStream
// cb[8192] создается в heap при конструировании
try (BufferedReader reader = new BufferedReader(
new InputStreamReader(
new FileInputStream(path), StandardCharsets.UTF_8))) {

String line;
while ((line = reader.readLine()) != null) {
lineCount++;
long lineLength = line.length();
totalChars += lineLength;

if (lineLength > maxLineLength) {
maxLineLength = lineLength;
longestLine = line; // Сохранение ссылки — String остается в heap!
}
}

} // close() освобождает cb, но longestLine удерживается

return new LineStats(lineCount, totalChars, maxLineLength, longestLine);
}

public record LineStats(long lineCount, long totalChars,
long maxLineLength, String longestLine) {}
}

Анализ памяти:
cb[8192] — освобожден при закрытии, GC собирает
Все String из readLine() — собраны GC, кроме longestLine
longestLine — удерживается возвращаемым LineStats, живет до потери ссылки

Оптимизация: избежание хранения самой длинной строки
// Если нужна только длина, не хранить строку
public LineStats analyzeFileOptimized(String path) throws IOException {
long lineCount = 0;
long totalChars = 0;
long maxLineLength = 0;

try (BufferedReader reader = new BufferedReader(
new InputStreamReader(new FileInputStream(path), StandardCharsets.UTF_8))) {

String line;
while ((line = reader.readLine()) != null) {
lineCount++;
long lineLength = line.length();
totalChars += lineLength;
maxLineLength = Math.max(maxLineLength, lineLength);
// line не сохраняется — собирается GC
}
}

return new LineStats(lineCount, totalChars, maxLineLength, null);
}

Здесь ни одна String не удерживается. Все собираются minor GC, память остается минимальной даже для гигабайтных файлов.


Проблема: очень длинные строки

Если файл содержит строку, превышающую размер cb (8192 символов), readLine() выполняет несколько итераций fill():
// Строка длиной 100 000 символов без \n
// readLine() выполняет ~13 вызовов fill(), накапливая в StringBuilder

StringBuilder sb = new StringBuilder(80); // Начальный массив char[80]
// При достижении 80: расширение до 160, копирование
// При достижении 160: расширение до 320, копирование
// ...
// Итоговый массив char[131072] для 100000 символов


Расширение StringBuilder создает промежуточные массивы char[], которые становятся мусором.

Для предотвращения можно указать начальную емкость:
// Если известна ожидаемая длина строки
StringBuilder sb = new StringBuilder(expectedLineLength);

Однако readLine() не позволяет контролировать StringBuilder — он создается внутри метода с фиксированной начальной емкостью.


Альтернатива: Files.lines() и Stream API

Java 8 предоставляет Files.lines() — потоковый API для построчного чтения:
// Потоковое чтение без явного BufferedReader
public long countLinesStream(String path) throws IOException {
try (Stream<String> lines = Files.lines(Path.of(path), StandardCharsets.UTF_8)) {
return lines.count();
}
}

Внутри Files.lines() создает BufferedReader, но инкапсулирует его в Stream. Преимущество — ленивое выполнение и встроенные методы обработки. Недостаток — Stream требует закрытия, иначе файловый дескриптор остается открытым.


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

import java.io.*;

public class Task230626 {
public static void main(String[] args) throws IOException {
BufferedReader br = new BufferedReader(new StringReader("Hello"));
br.reset();
System.out.println("Done");
}
}


#Tasks
👍2
Что такое текстовые блоки (Text Blocks)? 🤓

Ответ:

Текстовые блоки (Java 13 как preview, финал в Java 15)
— это многострочные строки, упрощающие написание JSON, XML, SQL и HTML.

Обрамляются тремя двойными кавычками """. Сохраняют отступы относительно позиции закрывающих кавычек.

Пример: String json = """ {"name": "Alice"} """;.

Не требуют экранирования кавычек и переводов строк. Убирают общий пробельный отступ (indentation stripping).

Удобны для встраиваемых шаблонов. Поддерживают интерполяцию через String.formatted().



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

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

Виктор Франц Гесс (нем. Victor Franz Hess; 24 июня 1883, замок Вальдштейн, Штирия, Австро-Венгрия — 17 декабря 1964, Нью-Йорк, США) — австрийский и американский физикнобелевский лауреат 1936 года (совместно с Карлом Андерсоном), награждённый за открытие космических лучей.

Ма́ртин Лью́ис Перл (англ. Martin Lewis Perl; 24 июня 1927, Нью-Йорк — 30 сентября 2014, Пало-Алто) — американский физикпрофессор, общественный деятель, лауреат премии Вольфа (1982) за открытие ряда элементарных частиц, в том числе кварков, и Нобелевской премии по физике 1995 года «за открытие тау-лептона» (вторую половину премии получил Фредерик Райнес «за экспериментальное обнаружение нейтрино»).


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

1963 — в столице Великобритании Лондоне, в студии Би-Би-Си, впервые продемонстрирован бытовой видеомагнитофон.


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

Тема: Collectors.toMap при дублирующихся ключах выбрасывает IllegalStateException.

Проблема: Статический метод Collectors.toMap(Function keyMapper, Function valueMapper) создает коллектор, который собирает элементы стрима в HashMap.

Если два элемента стрима отображаются на один и тот же ключ, коллектор выбрасывает IllegalStateException с сообщением "duplicate key". Это поведение часто оказывается неожиданным для разработчиков, которые полагаются на уникальность ключей в данных, но из-за ошибок в данных или бизнес-логике дубликаты возникают. В результате приложение падает в рантайме, и диагностика может быть затруднена, особенно в больших стримах. Даже если вероятность дубликатов мала, надежность кода требует явного определения стратегии разрешения конфликтов.

Решение: Используйте перегруженную версию Collectors.toMap(keyMapper, valueMapper, mergeFunction), где mergeFunction — это BinaryOperator, определяющий, какое значение сохранить при конфликте.
Наиболее частые стратегии: оставить первое значение ((oldValue, newValue) -> oldValue), взять последнее ((old, new) -> new), объединить значения (например, конкатенировать строки или суммировать числа). Для сложной агрегации используйте Collectors.groupingBy в связке с downstream-коллектором.

Также существует Collectors.toConcurrentMap для потокобезопасных карт.
import java.util.*;
import java.util.stream.*;

public class ToMapDuplicate {

public static void main(String[] args) {
List<String> items = Arrays.asList("apple", "banana", "apple", "cherry");

//Антипаттерн: выбросит IllegalStateException
try {
Map<String, Integer> bad = items.stream()
.collect(Collectors.toMap(s -> s, String::length));
} catch (IllegalStateException e) {
System.err.println("Duplicate key: " + e.getMessage());
}

//Решение 1: оставить первое значение
Map<String, Integer> first = items.stream()
.collect(Collectors.toMap(
s -> s,
String::length,
(oldVal, newVal) -> oldVal
));
System.out.println("First: " + first); // {banana=6, apple=5, cherry=6}

//Решение 2: взять последнее значение (часто для перезаписи)
Map<String, Integer> last = items.stream()
.collect(Collectors.toMap(
s -> s,
String::length,
(old, newVal) -> newVal
));
System.out.println("Last: " + last);

//Решение 3: объединить значения (например, список длин)
Map<String, List<Integer>> grouped = items.stream()
.collect(Collectors.toMap(
s -> s,
s -> new ArrayList<>(List.of(s.length())),
(list1, list2) -> { list1.addAll(list2); return list1; }
));
System.out.println("Grouped: " + grouped);

//Альтернатива: groupingBy для агрегации
Map<String, Long> countByKey = items.stream()
.collect(Collectors.groupingBy(
s -> s,
Collectors.counting()
));
System.out.println("Counts: " + countByKey);
}
}


Объяснение:
 Базовая версия toMap вызывает Map.merge(key, value, throwingMerger()), где throwingMerger просто бросает IllegalStateException. Это сделано для того, чтобы предотвратить случайную потерю данных без явного указания стратегии.

Указывая mergeFunction, вы берете на себя ответственность за разрешение конфликтов. Выбор стратегии зависит от бизнес-правил: для уникальных идентификаторов лучше перезаписывать последним значением; для агрегации — использовать groupingBy с downstream; для подсчета частоты — groupingBy с counting().


#Java #советы
👍5
Что выведет код?

import java.util.*;
import java.util.stream.*;

public class Task240626 {
static class Person240626 {
int id;
String name;
Person240626(int id, String name) {
this.id = id;
this.name = name;
}
}

public static void main(String[] args) {
List<Person240626> people = Arrays.asList(
new Person240626(1, "Alice"),
new Person240626(2, "Bob"),
new Person240626(1, "Charlie")
);

try {
Map<Integer, String> map = people.stream()
.collect(
Collectors.toMap(p -> p.id, p -> p.name)
);

System.out.println("Success");

} catch (Exception e) {
System.out.println(e.getClass().getSimpleName());
}
}
}


#Tasks
👍2
Что такое switch-выражения (Switch Expressions)? 🤓

Ответ:

Начиная с Java 12 (preview) и финал в Java 14, switch может быть выражением, возвращающим значение.

Используется -> вместо : и yield для возврата значения из блока.

Пример: 
String result = switch (day) { case MON -> "Work"; case SAT, SUN -> "Weekend"; default -> throw ...; };.

Также поддерживается блок: case MON -> { yield "Work"; }. Это делает код лаконичнее, устраняет fall-through (прорыв) и позволяет использовать switch в функциональном стиле.

Важно, что для выражения должны быть покрыты все возможные значения (или default).



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

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

Шамиль Гимбатович Алиев (род. 25 июня 1943, Тануси, Хунзахский район) — доктор технических наук, профессор. Заслуженный деятель наук Дагестанской АССР. Разработчик ракетного оружия и космических технологий. Известен как один из авторов монографий «Низкочастотное излучение развитых кавитационных течений», «Фундаментальные технические комплексы. Теория аналитического проектирования» и др. Разработчик торпед, установленных на атомной подводной лодке «Курск». Знаток проблем устойчивости вихревой пелены за крылом конечного размаха. Со второй половины 1980-х годов на крупнейших выставках оружия (на Международной Морской выставке в Сиднее в 2004 году и др.) позиционируется как «идеолог и конструктор надводного и подводного оружия аэрокосмического комплекса России» и «разработчик торпедного оружия России».

Алексе́й Алексе́евич Абрико́сов (25 июня 1928, Москва — 29 марта 2017, Пало-Алто, США) — советскийроссийский и американский физик-теоретик. Лауреат Нобелевской премии по физике(2003, совместно с Виталием Гинзбургом и Энтони Леггетом). В 2003 году, совместно с В. Л. Гинзбургом и Э. Леггетом, получил Нобелевскую премию по физике за «основополагающие работы по теории сверхпроводников и сверхтекучих жидкостей».

Йоханнес Ханс Даниель Йе́нсен (нем. Johannes Hans Daniel Jensen; 25 июня 1907, Гамбург — 11 февраля 1973, Гейдельберг) — немецкий учёный, физик-ядерщик, лауреат Нобелевской премии по физике в 1963 году. Половина премии, совместно с Марией Гёпперт-Майер, «за открытия, касающиеся оболочечной структуры ядра». Вторую половину премии получил Юджин Вигнер.

Герман Юлиус Оберт (нем. Hermann Julius Oberth, 25 июня 1894 — 28 декабря 1989) — немецкий учёный и инженер в области космонавтики и ракетостроения, один из основоположников современной ракетной техники.


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

1966 — запускается первый советский метеоспутник «Космос-122».

1974 — запуск советской орбитальной космической станции «Салют-3».

1998 — официально вышла операционная система «Windows 98».


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

Глава 1. Классический Java I/O (java.io)

BufferedWriter – запись строк. Буферизация и путь данных в памяти JVM

BufferedWriter — декоратор над Writer, добавляющий внутреннюю буферизацию символьного вывода. Он решает проблему, зеркальную BufferedReader: накладные расходы системных вызовов при частых мелких операциях записи. Вместо немедленной отправки каждого символа или строки в underlying Writer, BufferedWriter накапливает данные в массиве char[] в heap и сбрасывает их пачками.

Ключевое отличие от BufferedOutputStream: BufferedWriter работает с символами char (16 бит), а не байтами. Это означает, что его внутренний буфер хранит char[], а преобразование в байты происходит позже — в underlying OutputStreamWriter, который кодирует символы в UTF-8 или другую кодировку.


Внутреннее устройство
// Упрощенная структура из OpenJDK
public class BufferedWriter extends Writer {
// Внутренний буфер символов в heap JVM
private char[] cb;

// Текущая позиция записи в буфере
private int nChars;

// Ссылка на underlying Writer
private Writer out;

// Разделитель строки для newLine()
private String lineSeparator;

// Конструкторы
public BufferedWriter(Writer out) {
this(out, defaultCharBufferSize); // 8192 по умолчанию
}

public BufferedWriter(Writer out, int sz) {
super(out);
if (sz <= 0) {
throw new IllegalArgumentException("Buffer size <= 0");
}
this.out = out;
cb = new char[sz]; // Выделение в heap: 8192 * 2 = 16384 байт
nChars = 0;
lineSeparator = System.lineSeparator(); // Платформенный разделитель
}
}


Поле cb — массив char[] размером 8192 по умолчанию, что занимает 16 KB в heap. lineSeparator инициализируется значением System.lineSeparator(), которое равно \n на Unix/Linux/macOS и \r\n на Windows.
Механика write(String) и путь данных


Накопление в буфере
public void write(String s, int off, int len) throws IOException {
// Если строка не влезает в оставшийся буфер — сброс
if (len >= cb.length - nChars) {
flushBuffer(); // Сброс накопленного в underlying Writer
}

if (len >= cb.length) {
// Строка больше буфера — запись напрямую, минуя буфер
writeDirect(s, off, len);
} else {
// Копирование строки в буфер
s.getChars(off, off + len, cb, nChars); // Быстрое копирование из String
nChars += len;
}
}


Путь данных при write("Hello"):
[Java: bw.write("Hello")]
-> [BufferedWriter: len=5 < cb.length - nChars?]
-> [s.getChars(0, 5, cb, nChars)] // Копирование char[] из String в buf
-> [System.arraycopy: копирование 5 char в cb]
-> [nChars += 5]
-> [Возврат]

Никаких системных вызовов, никакого кодирования. Пять символов копируются в cb через System.arraycopy — нативный метод, оптимизированный JVM.


Сброс буфера: flushBuffer()
private void flushBuffer() throws IOException {
if (nChars > 0) {
out.write(cb, 0, nChars); // Запись в underlying Writer
nChars = 0; // Сброс позиции
}
}


Путь данных при заполнении буфера:
[BufferedWriter: flushBuffer()]
-> [OutputStreamWriter.write(cb, 0, nChars)]
-> [StreamEncoder: кодирование char[] -> байты UTF-8]
-> [ByteBuffer: накопление закодированных байт]
-> [FileOutputStream.write(byte[]): системный вызов]
-> [Нативный буфер userspace]
-> [Ядро ОС: syscall write]
-> [Page Cache: dirty pages]

Ключевой момент: BufferedWriter не знает о кодировке. Он накапливает char[], а кодирование в байты происходит в OutputStreamWriter. Это означает, что буфер BufferedWriter содержит символы, а буфер StreamEncoder — байты. Два уровня буферизации создают двойное копирование при сбросе.


Метод newLine(): платформенная независимость
public void newLine() throws IOException {
write(lineSeparator); // Запись \n или \r\n из поля lineSeparator
}


newLine()
записывает разделитель строки, определенный при конструировании. Это позволяет генерировать файлы с правильными разделителями для целевой платформы, независимо от платформы выполнения.
// На Unix: запись \n
// На Windows: запись \r\n
// Результат одинаков для кода, различается для файла



#Java #для_новичков #beginner #IO #NIO #BufferedWriter
👍4
Путь данных в памяти: детальный разбор

Сценарий: запись списка строк
public void writeLines(String path, List<String> lines) throws IOException {
// cb[8192] создается в heap при конструировании BufferedWriter
try (BufferedWriter writer = new BufferedWriter(
new OutputStreamWriter(
new FileOutputStream(path), StandardCharsets.UTF_8))) {

for (String line : lines) {
writer.write(line); // Копирование в cb
writer.newLine(); // Копирование \n или \r\n в cb
// Системный вызов отсутствует, пока cb не заполнен
}

// Явный flush для гарантии сброса
writer.flush();

} // close() с неявным flush()
}

Память в процессе:
cb[8192] — буфер BufferedWriter, 16 KB heap
ByteBuffer внутри StreamEncoder — буфер для кодирования, ~24 KB heap
String из lines — удерживаются списком, не создаются заново
Никаких новых аллокаций в цикле, кроме возможных расширений ByteBuffer


Сценарий: буфер заполнен

// Допустим, cb заполнен на 8188 символов
// Запись строки "Hello" (5 символов + 1 \n = 6)

writer.write("Hello"); // 8188 + 5 = 8193 > 8192 — сброс!
writer.newLine(); // \n записывается в пустой cb


[write("Hello")]
-> [len=5 >= cb.length - nChars? 5 >= 4? да]
-> [flushBuffer()]
-> [out.write(cb, 0, 8188)] // Сброс 8188 символов в OutputStreamWriter
-> [StreamEncoder: кодирование 8188 char в байты UTF-8]
-> [FileOutputStream: системный вызов]
-> [nChars = 0]
-> [s.getChars(0, 5, cb, 0)] // "Hello" в cb[0..4]
-> [nChars = 5]

[newLine()]
-> [write("\n")] // или "\r\n"
-> [s.getChars(0, 1, cb, 5)] // \n в cb[5]
-> [nChars = 6]



Роль garbage collector

Жизненный цикл буферов
public void processAndWrite(String inputPath, String outputPath) throws IOException {
// Все объекты создаются в heap
try (BufferedReader reader = new BufferedReader(
new InputStreamReader(new FileInputStream(inputPath), StandardCharsets.UTF_8));
BufferedWriter writer = new BufferedWriter(
new OutputStreamWriter(new FileOutputStream(outputPath), StandardCharsets.UTF_8))) {

String line;
while ((line = reader.readLine()) != null) {
String processed = processLine(line); // Новая String в heap
writer.write(processed); // Копирование в cb
writer.newLine();
// processed становится мусором, если не сохранена
}

} // Все потоки закрыты, все буферы освобождены

// reader.cb, writer.cb, ByteBuffer'ы StreamEncoder — недостижимы
// GC собирает все при следующей minor collection
}


Анализ памяти:

reader.cb[8192] — 16 KB, освобожден при закрытии
writer.cb[8192] — 16 KB, освобожден при закрытии
ByteBuffer декодера — ~16 KB, освобожден
ByteBuffer кодера — ~24 KB, освобожден
line — собирается GC каждую итерацию
processed — собирается GC, если не сохранена
Общий пик памяти: ~72 KB на буферы + строки. Для больших файлов это незначительно.


Проблема: очень длинные строки

// Строка длиной 100 000 символов
String hugeLine = generateHugeLine();

// write() обнаруживает, что len >= cb.length
// Прямая запись, минуя буфер
writer.write(hugeLine);

При превышении размера буфера BufferedWriter не копирует строку в cb, а передает её напрямую в underlying Writer. Это экономит память (не дублирует строку в буфере), но лишает преимущества буферизации — системный вызов выполняется немедленно.

Практический пример: запись списка строк в файл

import java.io.*;
import java.nio.charset.StandardCharsets;
import java.util.List;

public class LineWriter {

public void writeLines(String path, List<String> lines) throws IOException {
// Путь данных:
// String -> char[] -> cb[BufferedWriter] -> ByteBuffer[StreamEncoder] -> FileOutputStream -> page cache -> диск

try (BufferedWriter writer = new BufferedWriter(
new OutputStreamWriter(
new FileOutputStream(path), StandardCharsets.UTF_8))) {

for (String line : lines) {
writer.write(line); // Копирование в cb
writer.newLine(); // Платформенный разделитель
}

// Явный flush перед закрытием
writer.flush();

} // close() с неявным flush()

// Все буферы в heap освобождены, GC собирает
}

// Альтернатива: запись с заголовком и подвалом
public void writeReport(String path, String header,
List<String> data, String footer) throws IOException {
try (BufferedWriter writer = new BufferedWriter(
new OutputStreamWriter(
new FileOutputStream(path), StandardCharsets.UTF_8))) {

writer.write(header);
writer.newLine();
writer.newLine(); // Пустая строка

for (String line : data) {
writer.write(line);
writer.newLine();
}

writer.newLine(); // Пустая строка перед подвалом
writer.write(footer);
writer.newLine();

}
}
}


Сравнение с небуферизованной записью
// Антипаттерн: запись без буферизации
public void writeUnbuffered(String path, List<String> lines) throws IOException {
try (OutputStreamWriter writer = new OutputStreamWriter(
new FileOutputStream(path), StandardCharsets.UTF_8)) {

for (String line : lines) {
writer.write(line); // Каждый символ = системный вызов!
writer.write('\n');
}
}
}


OutputStreamWriter
без BufferedWriter кодирует и записывает каждую порцию данных немедленно. Для строки "Hello" это означает: кодирование 5 символов в 5 байт UTF-8, системный вызов write(fd, buf, 5). Для 1 000 000 строк — 2 000 000 системных вызовов.

С BufferedWriter: накопление в cb, сброс при заполнении. Для средней строки 80 символов + 1 \n = 81 символ. Буфер 8192 вмещает ~100 строк. Системных вызовов: 1 000 000 / 100 = 10 000. Ускорение в 200 раз.


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

import java.io.*;

public class Task250626 {
public static void main(String[] args) throws IOException {
File file = new File("test.txt");
try (BufferedWriter bw = new BufferedWriter(new FileWriter(file))) {
bw.write(65);
}
try (BufferedReader br = new BufferedReader(new FileReader(file))) {
System.out.println(br.readLine());
}
file.delete();
}
}


#Tasks
👍2
Варианты ответа:
Anonymous Quiz
45%
65
27%
A
18%
Пустая строка
9%
null
👍3
Что такое record (Record-классы)? 🤓

Ответ:

Records
(Java 14 preview, финал в Java 16) — это неизменяемые (immutable) классы-носители данных.

Объявляются как record Point(int x, int y) {}.

Компилятор автоматически генерирует: конструктор, геттеры (с теми же именами), equals(), hashCode(), toString().

Можно добавить компактный конструктор для валидации. Records не могут наследоваться, но могут реализовывать интерфейсы.

Идеальны для DTO, value objects, кортежей. Упрощают создание классов-данных, уменьшая шаблонный код.



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