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

import java.io.*;
import java.util.Optional;

class Data160626 implements Serializable {
Optional<String> value = Optional.of("secret");
}

public class Task160626 {
public static void main(String[] args) throws Exception {
Data160626 d = new Data160626();
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(baos);
oos.writeObject(d);
oos.close();

ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(baos.toByteArray()));
Data160626 d2 = (Data160626) ois.readObject();
System.out.println(d2.value.get());
}
}


#Tasks
👍3
👍3
Вот это и случилось))) Ютуб канал перегнал по популярности телеграмм-канал.

Что думаете? Помоему закономерно, хотя труда вложенного в телеге гораздо больше....

Хотя если оглянуться то и 70 видео сами себя не записали...

😎
Please open Telegram to view this post
VIEW IN TELEGRAM
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥11🍾2
Что такое OutOfMemoryError? Какие бывают виды? 🤓

Ответ:

OutOfMemoryError выбрасывается, когда JVM не может выделить память для нового объекта.

Виды:
1) Java heap space — кончилась память в куче (heap).
2) Metaspace / PermGen — кончилась память для метаданных классов.
3) GC overhead limit exceeded — GC слишком много работает (более 98% времени) и освобождает мало.
4) Unable to create new native thread — невозможно создать новый поток (мало памяти ОС).
5) Requested array size exceeds VM limit — массив больше допустимого.

Причины и решения разные: увеличение heap, утечки памяти, настройка GC.



#собеседование
Please open Telegram to view this post
VIEW IN TELEGRAM
👍5
Всем доброго утра! 🏃

Смог почти 5 км))

Делитесь вашими достижениями?💪
🔥5
История технологии сегодня — 17 июня

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

Сергей Олимпиевич Максимович (5 [17] июня 1876, Санкт-Петербург — 27 декабря 1941, Ленинград) — русский и советский учёный и изобретатель, один из пионеров в области цветной фотографии и цветной кинематографии. Открыватель эффекта Максимовича — Калье (1907).


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

1970Эдвин Лэнд запатентовал камеру Polaroid.

1988 — Microsoft выпустила операционную систему «MS DOS 4.0».


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

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

Символьные потоки: Reader и Writer. Unicode, кодировки и путь данных в памяти

Байтовые потоки (InputStream/OutputStream) оперируют единицей данных в 8 бит — байтом. Они не интерпретируют содержимое: байт 0x41 может быть буквой 'A' в ASCII, частью многобайтовой последовательности UTF-8, или элементом бинарного протокола.

Символьные потоки (Reader/Writer) оперируют единицей в 16 бит — char Java, который представляет кодовую точку Unicode в диапазоне U+0000..U+FFFF. Это различие коренным образом меняет архитектуру, семантику и путь данных через память JVM.

Unicode — стандарт кодирования символов, охватывающий большинство письменных систем мира. Java приняла Unicode с самого начала: тип char — беззнаковое 16-битное целое, String — неизменяемая последовательность char. Однако Unicode эволюционировал: первоначальная спецификация Unicode 1.0 предполагала 16-битное пространство (65 536 символов), но оказалось недостаточным. Unicode 2.0 ввел дополнительные плоскости за пределами Basic Multilingual Plane (BMP), и для их представления потребовались суррогатные пары — две 16-битные char, кодирующие одну кодовую точку в диапазоне U+10000..U+10FFFF. Это означает, что один "символ" в человеческом понимании может занимать один или два char в Java.


Архитектура Reader и Writer

Reader — абстрактный класс для чтения символов. Его методы зеркалируют InputStream, но работают с char вместо byte:
public abstract class Reader implements Readable, Closeable {
// Чтение одного символа (char как int для EOF-различения)
public int read() throws IOException

// Чтение в массив char[]
public int read(char[] cbuf) throws IOException

// Чтение в массив с офсетом
public abstract int read(char[] cbuf, int off, int len) throws IOException

// Пропуск символов
public long skip(long n) throws IOException

// Готовность к чтению без блокировки
public boolean ready() throws IOException

// Поддержка mark/reset
public boolean markSupported()
public void mark(int readAheadLimit) throws IOException
public void reset() throws IOException

// Закрытие
public abstract void close() throws IOException
}


Writer
— абстрактный класс для записи символов:
public abstract class Writer implements Appendable, Closeable, Flushable {
// Запись одного символа
public void write(int c) throws IOException

// Запись массива char[]
public void write(char[] cbuf) throws IOException

// Запись части массива
public abstract void write(char[] cbuf, int off, int len) throws IOException

// Запись строки
public void write(String str) throws IOException

// Запись части строки
public void write(String str, int off, int len) throws IOException

// Добавление (append)
public Writer append(CharSequence csq) throws IOException

// Сброс
public abstract void flush() throws IOException

// Закрытие
public abstract void close() throws IOException
}


Ключевое отличие от байтовых потоков: методы read() и write(int) работают с char (16 бит), а не с byte (8 бит). Возвращаемое значение read()int в диапазоне 0..65535 для валидных символов и -1 для EOF. Это сохраняет контракт различения валидных данных от признака конца потока, но для 16-битных char вместо 8-битных byte.


Кодировки: мост между байтами и символами


Символьные потоки не существуют в вакууме. Файлы, сетевые соединения, базы данных хранят и передают байты. Преобразование между байтами и символами выполняется через кодировку (charset) — таблицу соответствия последовательностей байт кодовым точкам Unicode.

UTF-8: доминирующая кодировка
UTF-8 — переменная длина кодирования Unicode:
U+0000..U+007F: 1 байт (ASCII-совместимость)
U+0080..U+07FF: 2 байта
U+0800..U+FFFF: 3 байта (большинство BMP-символов)
U+10000..U+10FFFF: 4 байта (суррогатные пары)

Преимущества UTF-8: ASCII-совместимость, компактность для латинских текстов, самосинхронизация (можно определить границу символа в произвольной позиции). Недостаток: переменная длина усложняет индексацию по символам — str.charAt(n) — O(1), но str.codePointAt(n) требует проверки суррогатов.


UTF-16: внутреннее представление Java

Java использует UTF-16 для внутреннего представления String и char[]. Каждый char — 16 бит. Символы BMP (U+0000..U+FFFF) представляются одним char. Символы за пределами BMP — суррогатной парой: старший суррогат (U+D800..U+DBFF) + младший суррогат (U+DC00..U+DFFF).
// Пример: эмодзи U+1F600 (😀) — суррогатная пара в Java
String emoji = "😀";
System.out.println(emoji.length()); // 2 — два char
System.out.println(emoji.codePointAt(0)); // 128512 — U+1F600

// Итерация по кодовым точкам, а не char
for (int cp : emoji.codePoints().toArray()) {
System.out.println("Code point: U+" + Integer.toHexString(cp));
}

Это критично для символьных потоков: Reader.read() возвращает int с кодовой точкой, корректно обрабатывая суррогатные пары. read(char[]) заполняет массив char, где суррогатные пары занимают два элемента.


Путь данных в памяти: от байтов файла до char[] в heap

Чтение текстового файла: FileReader
try (FileReader reader = new FileReader("text.txt")) {
int charValue;
while ((charValue = reader.read()) != -1) {
processChar((char) charValue);
}
}


Путь данных при чтении:
[Диск: файл в байтах UTF-8]
-> [Page Cache ОС: байты файла]
-> [FileInputStream (underlying FileReader): чтение байтов]
-> [InputStreamReader: декодирование байт -> char]
-> [CharsetDecoder UTF-8: конечный автомат для мультибайтовых последовательностей]
-> [char[] буфер декодера в heap]
-> [FileReader.read(): возврат char как int]
-> [Java-код: processChar()]


FileReader
— конкретный подкласс InputStreamReader, который автоматически открывает FileInputStream и использует кодировку платформы по умолчанию. Это опасно: кодировка по умолчанию зависит от ОС и locale, что делает поведение непредсказуемым при переносе между системами.
// Антипаттерн: зависимость от кодировки платформы
FileReader reader = new FileReader("text.txt"); // Неявная кодировка!

// Правильно: явное указание кодировки
InputStreamReader reader = new InputStreamReader(
new FileInputStream("text.txt"), StandardCharsets.UTF_8);



Декодирование и память

InputStreamReader использует CharsetDecoder из java.nio.charset. Декодер поддерживает внутренний буфер байт для неполных мультибайтовых последовательностей (например, первые 2 байта 3-байтового UTF-8 символа в конце блока чтения) и буфер char для выходных символов.
// Упрощенная логика декодирования
ByteBuffer in = ByteBuffer.allocate(8192); // Входные байты из потока
CharBuffer out = CharBuffer.allocate(8192); // Выходные символы

// Чтение байт из FileInputStream
int bytesRead = fileInputStream.read(in.array());
in.position(0).limit(bytesRead);

// Декодирование
CharsetDecoder decoder = StandardCharsets.UTF_8.newDecoder();
CoderResult result = decoder.decode(in, out, false);

// Извлечение символов
char[] chars = new char[out.position()];
out.flip().get(chars);

Буферы ByteBuffer и CharBuffer — объекты в heap. CharsetDecoder — тоже объект в heap. При каждом создании InputStreamReader выделяется память для этих структур. При массовом открытии текстовых файлов без закрытия это создает давление на GC.


#Java #для_новичков #beginner #IO #NIO #Reader #Writer
👍4
Путь данных при записи: от char[] в heap до байтов файла

Запись текстового файла: FileWriter
try (FileWriter writer = new FileWriter("output.txt")) {
writer.write("Hello, 世界! 🌍");
}

Путь данных при записи:
plain
[Java-код: String "Hello, 世界! 🌍"]
-> [String.getBytes() или прямая работа с char[]]
-> [FileWriter (наследник OutputStreamWriter)]
-> [CharsetEncoder UTF-8: кодирование char -> байты]
-> [ByteBuffer буфер кодера в heap]
-> [FileOutputStream (underlying): запись байт]
-> [Page Cache ОС: dirty pages]
-> [Фоновая запись на диск]


Кодирование UTF-8 из char требует:
ASCII (U+0000..U+007F): 1 байт на символ
BMP non-ASCII (U+0080..U+FFFF): 2-3 байта
Суррогатные пары: 4 байта

Строка "Hello, 世界! 🌍" в Java:

'H', 'e', 'l', 'l', 'o', ',', ' ', '!': 8 символов ASCII → 8 байт UTF-8
'世' (U+4E16): 3 байта UTF-8
'界' (U+754C): 3 байта UTF-8
'🌍' (U+1F30D): суррогатная пара, 4 байта UTF-8
Итого: 8 + 3 + 3 + 4 = 18 байт UTF-8 для 11 "символов" в человеческом понимании, 12 char в Java


Роль garbage collector

Жизненный цикл объектов кодирования
public void processTextFiles(List<String> paths) throws IOException {
for (String path : paths) {
// Каждая итерация создает:
// - FileInputStream (heap)
// - InputStreamReader (heap)
// - CharsetDecoder (heap)
// - ByteBuffer/CharBuffer внутренние (heap)
// - char[] для чтения (heap)

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

char[] buffer = new char[4096];
int charsRead;
while ((charsRead = reader.read(buffer)) != -1) {
process(buffer, charsRead);
}

} // close() освобождает ресурсы, объекты становятся недостижимыми

// GC собирает все созданные объекты при следующей minor collection
}
}

При обработке тысяч файлов этот код создает тысячи временных объектов. Они короткоживущие и эффективно собираются minor GC, но при высокой частоте могут вызвать частые паузы young generation.


Оптимизация: переиспользование Reader/Writer

Для потоковой обработки текста из одного источника переиспользование буферов снижает нагрузку на GC:
// Переиспользуемый буфер
private static final ThreadLocal<char[]> CHAR_BUFFER =
ThreadLocal.withInitial(() -> new char[8192]);

public void processStream(InputStream in) throws IOException {
char[] buffer = CHAR_BUFFER.get();

try (InputStreamReader reader = new InputStreamReader(in, StandardCharsets.UTF_8)) {
int charsRead;
while ((charsRead = reader.read(buffer)) != -1) {
process(buffer, charsRead);
}
}
// reader и его декодер собираются GC
// buffer остается в ThreadLocal, не подлежит GC
}


Проблема: большие строки и heap
// Антипаттерн: чтение всего файла в одну строку
String content = new String(Files.readAllBytes("huge.txt"), StandardCharsets.UTF_8);


Files.readAllBytes
возвращает byte[] размером с файл. new String(...) создает char[] размером с количество символов (для ASCII — тот же размер, для UTF-8 с мультибайтом — меньше). Для файла 100MB это 100MB в byte[] + ~100MB в char[] = 200MB heap на один вызов. Если файл содержит преимущественно ASCII, byte[] и char[] примерно равны. Если содержит много 3-байтовых UTF-8 символов, char[] будет в 3 раза меньше byte[].


BufferedReader и BufferedWriter: буферизация символьных потоков

Аналогично байтовым потокам, символьные потоки имеют буферизованные обертки:
try (BufferedReader br = new BufferedReader(
new InputStreamReader(
new FileInputStream("text.txt"), StandardCharsets.UTF_8), 8192)) {

String line;
while ((line = br.readLine()) != null) {
processLine(line);
}
}

BufferedReader содержит char[] cb в heap — буфер символов. readLine() накапливает символы до \n или \r\n, создавая новую String при каждом вызове. Эти строки — долгоживущие объекты, которые GC не собирает до потери ссылки.
java
// Путь данных readLine():
// 1. Чтение блока символов в cb[8192] из underlying Reader
// 2. Поиск \n в cb
// 3. new String(cb, start, end - start) — создание String в heap
// 4. Возврат String
// 5. Следующий вызов: повторение

Каждая String — отдельный объект в heap с собственным char[] (или byte[] в современных JDK с компакт-строками). При чтении файла 100MB по строкам в 100 символов создается ~1 000 000 объектов String. Это создает массивное давление на GC.


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

import java.io.*;

public class Task170626 {
public static void main(String[] args) throws IOException {
StringReader sr = new StringReader("ABCDEFG");
BufferedReader br = new BufferedReader(sr);

System.out.print((char) br.read());
br.mark(2);
System.out.print((char) br.read());
System.out.print((char) br.read());
br.reset();
System.out.print((char) br.read());
System.out.print((char) br.read());
}
}


#Tasks
👍2
Варианты ответа:
Anonymous Quiz
40%
ABCBC
20%
ABCDE
20%
ABCAB
20%
ABABC
👍3
Что такое soft и weak ссылки в контексте коллекций? 🤓

Ответ:

SoftReference — объект будет удалён только при нехватке памяти (для кэшей). 
WeakReference — объект будет удалён при ближайшей сборке, если нет сильных ссылок (WeakHashMap). 
WeakHashMap автоматически удаляет записи, когда ключ становится слабодостижимым. 
SoftReference используется в реализациях кэшей, например, Guava Cache с softValues().

Разница:
Weak — идеальны для карт метаданных (удаляются агрессивно),
Soft — для кэшей (удаляются только под давлением памяти).

Оба помогают избежать утечек памяти.



#собеседование
Please open Telegram to view this post
VIEW IN TELEGRAM
👍4
Открытый урок: как на самом деле работают транзакции в Postgres | MVCC, уровни изоляции и блокировки

Прошу прощения за звук, Егора почти не слышно... 🤷‍♀️

В этом открытом уроке разбираю, как на самом деле работают транзакции в PostgreSQL и почему одного @Transactional недостаточно для защиты от проблем конкурентного доступа.

Поговорим не только о теории, но и о том, что реально происходит внутри PostgreSQL и Spring-приложений.

В уроке:
• Что такое MVCC и зачем PostgreSQL хранит несколько версий строк
• Почему читатели не блокируют писателей
• Dirty Read, Non-repeatable Read, Phantom Read, Lost Update и Write Skew
• READ COMMITTED, REPEATABLE READ и SERIALIZABLE в PostgreSQL
• Как работают снимки данных (snapshots)
• Строковые, табличные и индексные блокировки
• FOR UPDATE и FOR SHARE
• Почему SELECT может не видеть строку, а INSERT уже конфликтует по UNIQUE индексу
• Optimistic Locking (@Version) в Hibernate
• Pessimistic Locking (PESSIMISTIC_WRITE, PESSIMISTIC_FORCE_INCREMENT)
• Как выбирать стратегию блокировок для реальных задач
• Практические примеры на Spring Boot + PostgreSQL

Этот урок является частью моего подхода к менторингу Java Backend-разработчиков, где основной акцент делается не на запоминании аннотаций, а на понимании механизмов, которые стоят за ними.

Ссылка на Youtube
Ссылка на Рутьюб

Смотрите, ставьте лайки, подписывайтесь на каналы!✌️

Жду ваших реакций и оценок
🙂
Please open Telegram to view this post
VIEW IN TELEGRAM
👍7
История технологии сегодня — 18 июня

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

Вита́лий Ио́сифович Гольда́нский (18 июня 1923, Витебск — 14 января 2001, Москва) — советский и российский учёный, физик-ядерщик и физикохимик, общественный деятель, народный депутат СССР. В 19701973 Гольданский показал неприменимость классического закона Аррениуса для скоростей химических реакций при низких температурах: им был открыт квантовый предел скорости реакций, протекающих за счёт туннелирования даже вблизи абсолютного нуля. Он также известен как основоположник химической физики позитрона и позитрония, показал возможность полимеризации под действием ударных волн, что было признано как научное открытие.


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

1959 — Запуск ракеты-носителя «Восток-Л» с советской автоматической станцией «Луна-2А».


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

Тема: flatMap на стриме может заменить filter + map, если внутри есть Optional.

Проблема: При работе со стримами, содержащими Optional, распространенной практикой является фильтрация существующих значений с последующим извлечением: stream.filter(Optional::isPresent).map(Optional::get).

Такой код громоздок, содержит неявный вызов get(), который ассоциируется с плохим тоном, и создает промежуточную операцию. Кроме того, это нарушает принцип "не используйте Optional как контейнер, который нужно разворачивать вручную". Вместо этого можно применить flatMap, который умеет сворачивать Optional в стрим из одного элемента или пустой стрим.

Решение: Используйте flatMap(Optional::stream) (доступно с Java 9).

Для Java 8 используйте flatMap(o -> o.map(Stream::of).orElseGet(Stream::empty)). Этот подход объединяет фильтрацию и преобразование в одну операцию, делая код лаконичнее и выразительнее. Optional.stream() возвращает стрим из одного элемента, если значение присутствует, или пустой стрим, если отсутствует.

Это естественно интегрируется с flatMap, который разворачивает каждый Optional в ноль или один элемент итогового стрима.
import java.util.*;
import java.util.stream.*;

public class OptionalFlatMap {

public static void main(String[] args) {
List<Optional<String>> list = Arrays.asList(
Optional.of("Java"),
Optional.empty(),
Optional.of("Kotlin"),
Optional.empty()
);

//Антипаттерн: filter + map
List<String> bad = list.stream()
.filter(Optional::isPresent)
.map(Optional::get)
.collect(Collectors.toList());
System.out.println("Filter+map: " + bad);

//Решение: flatMap с Optional.stream (Java 9+)
List<String> good = list.stream()
.flatMap(Optional::stream) // Преобразует каждый Optional в стрим
.collect(Collectors.toList());
System.out.println("flatMap: " + good); // [Java, Kotlin]

// Для Java 8: альтернатива
List<String> java8 = list.stream()
.flatMap(o -> o.map(Stream::of).orElseGet(Stream::empty))
.collect(Collectors.toList());
System.out.println("Java 8 flatMap: " + java8);

// Работает и с другими Optional-подобными типами
List<OptionalInt> optionalInts = Arrays.asList(
OptionalInt.of(10),
OptionalInt.empty(),
OptionalInt.of(20)
);

// flatMap для OptionalInt требует преобразования в IntStream
// Не так прямо, но идея та же.
}
}


Объяснение:
 flatMap принимает функцию, возвращающую стрим, и объединяет все такие стримы в один. Optional.stream() (Java 9+) преобразует Optional<T> в Stream<T> из одного элемента или пустой стрим.

Это делает код более декларативным: вы говорите "для каждого Optional преврати его в поток значений (ноль или одно), и объедини их".

Такой подход устраняет необходимость явной проверки isPresent и вызова get, что уменьшает вероятность ошибок. Кроме того, он естественно сочетается с другими операциями стрима, например, с map после flatMap. Для Java 8 можно использовать аналогичную конструкцию с map и orElseGet.


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

import java.util.*;

public class Task180626 {
public static void main(String[] args) {
List<Optional<String>> list = Arrays.asList(
Optional.of("a"),
null,
Optional.of("b")
);
list.stream()
.flatMap(Optional::stream)
.forEach(System.out::print);
}
}


#Tasks
👍3
Варианты ответа:
Anonymous Quiz
60%
ab
0%
a
7%
b
33%
Исключение NullPointerException
👍4
Что такое Memory Leak в Java? Как найти? 🤓

Ответ:

Утечка памяти — ситуация, когда объекты больше не нужны приложению, но GC не может их удалить, потому что на них всё ещё есть достижимые ссылки.

В Java это не утечка в классическом смысле, а "удерживание ссылок". Причины: статические коллекции, не закрытые ресурсы, слушатели/обработчики, неправильное кэширование, потоки, ThreadLocal без remove().

Поиск: профайлеры (VisualVM, JProfiler) смотрит heap dump, определяют самые большие объекты и пути к корням GC (GC roots).

Также помогают детекторы утечек (Eclipse Memory Analyzer).



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

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

Блез Паска́ль (фр. Blaise Pascal [blɛz pasˈkal]; 19 июня 1623, родной дом Блеза Паскаля[вд], Клермон-Ферран, Королевство Франция — 19 августа 1662, Париж, Королевство Франция) — французский математикмеханикфизиклитераторфилософ и теолог. Классик французской литературы, один из основателей математического анализатеории вероятностей и проективной геометрии, создатель первых образцов счётной техники, автор основного закона гидростатики.

Оге Нильс Бор (дат. Aage Niels Bohr; 19 июня 1922, Копенгаген, Дания — 8 сентября 2009, там же) — датский учёный, физик-ядерщик. Член Датской королевской академии наук (1955), ряда других академий мира. Лауреат Нобелевской премии по физике (1975), как и его отец Нильс Бор.


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

1999 — вышла первая версия (1.0 Beta) онлайн-шутера Counter-Strike.


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

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

Правильная работа с текстовыми файлами: InputStreamReader и OutputStreamWriter

Классы FileReader и FileWriter — удобные обертки, но содержат фундаментальный архитектурный дефект: они используют кодировку платформы по умолчанию, определяемую системным свойством file.encoding. Это свойство зависит от операционной системы, locale пользователя и параметров запуска JVM.
// Антипаттерн: неявная кодировка платформы
try (FileReader reader = new FileReader("text.txt")) {
// На Windows может использоваться windows-1251
// На Linux — UTF-8
// На macOS — UTF-8
// Результат непредсказуем при переносе между системами
}


Эта неопределенность делает FileReader/FileWriter непригодными для переносимого кода. Файл, записанный на одной системе, может быть искажен при чтении на другой. Современная практика рекомендует явное указание кодировки UTF-8 для всех текстовых операций.


InputStreamReader: декодирование байтов в символы

InputStreamReader — мост от байтовых потоков к символьным. Он читает байты из InputStream и декодирует их в символы char с использованием указанной кодировки.
public InputStreamReader(InputStream in, Charset cs)

Конструктор принимает InputStream — источник сырых байтов, и Charset — правило декодирования. StandardCharsets.UTF_8 — предопределенная константа, исключающая риск неподдерживаемой кодировки.

Путь данных через память JVM
try (InputStreamReader reader = new InputStreamReader(
new FileInputStream("text.txt"), StandardCharsets.UTF_8)) {

int charValue;
while ((charValue = reader.read()) != -1) {
System.out.print((char) charValue);
}
}


Путь данных детально:
[Файл на диске: байты UTF-8]
-> [Page Cache ОС: байты файла]
-> [FileInputStream.read(byte[]) — системный вызов]
-> [Нативный буфер userspace]
-> [JNI: копирование в byte[] внутри InputStreamReader]
-> [InputStreamReader: накопление байт во внутреннем ByteBuffer]
-> [CharsetDecoder UTF-8: конечный автомат декодирования]
-> [CharBuffer: накопление декодированных символов]
-> [read(): возврат char как int]
-> [System.out.print(char): кодирование в байты платформы]


Внутреннее устройство InputStreamReader из OpenJDK:
// Упрощенная структура
public class InputStreamReader extends Reader {
private final StreamDecoder sd; // Делегат декодирования

// StreamDecoder содержит:
// - ByteBuffer: входные байты из потока
// - CharBuffer: выходные символы
// - CharsetDecoder: конечный автомат кодировки
}

StreamDecoder — внутренний класс, выполняющий реальную работу. Он управляет ByteBuffer для входных байт и CharBuffer для выходных символов. Буферы создаются при конструировании и живут до закрытия потока.


Механика декодирования UTF-8

UTF-8 — кодировка переменной длины. Декодер должен обрабатывать мультибайтовые последовательности, разбитые между блоками чтения:
// Сценарий: 3-байтовый символ '世' (U+4E16, E4 B8 96) разбит между чтениями

// Первое чтение: получены байты [..., E4, B8] — неполная последовательность
// StreamDecoder сохраняет E4 B8 во внутреннем ByteBuffer, возвращает 0 символов

// Второе чтение: получен байт [96, ...] — завершение последовательности
// StreamDecoder комбинирует E4 B8 96, декодирует в U+4E16, возвращает '世'

Это требует внутреннего состояния в StreamDecoder: неполные байтовые последовательности сохраняются между вызовами read(). Буфер для этих остатков — часть ByteBuffer в heap.


OutputStreamWriter: кодирование символов в байты

OutputStreamWriter — зеркальный мост: принимает символы char или String, кодирует их в байты согласно указанной кодировке, и записывает в OutputStream.
public OutputStreamWriter(OutputStream out, Charset cs)


Путь данных через память JVM
try (OutputStreamWriter writer = new OutputStreamWriter(
new FileOutputStream("output.txt"), StandardCharsets.UTF_8)) {

writer.write("Hello, 世界! 🌍");
}


Путь данных детально:
[Java-код: String "Hello, 世界! 🌍"]
-> [String.toCharArray() или прямой доступ к внутреннему char[]]
-> [OutputStreamWriter.write(char[], off, len)]
-> [StreamEncoder: кодирование char -> байты]
-> [CharsetEncoder UTF-8: конечный автомат]
-> [ByteBuffer: накопление закодированных байт]
-> [FileOutputStream.write(byte[]): системный вызов]
-> [Нативный буфер userspace]
-> [Ядро ОС: syscall write]
-> [Page Cache: dirty pages]
-> [Фоновая запись на диск]

StreamEncoder — внутренний аналог StreamDecoder. Он управляет CharBuffer для входных символов и ByteBuffer для выходных байт. При вызове write(String) символы сначала копируются в CharBuffer, затем кодер преобразует их в байты UTF-8.


Кодирование и размер выходных данных

Строка "Hello, 世界! 🌍" содержит:
8 символов ASCII: 8 байт UTF-8
'世' (U+4E16): 3 байта UTF-8
'界' (U+754C): 3 байта UTF-8
'🌍' (U+1F30D): суррогатная пара, 4 байта UTF-8

Итого: 18 байт для 11 кодовых точек, 12 char в Java
StreamEncoder должен обрабатывать суррогатные пары корректно: старший суррогат без младшего не является валидным символом и должен либо ждать дополнительных данных, либо генерировать замену (replacement character U+FFFD).


#Java #для_новичков #beginner #IO #NIO #InputStreamReader #OutputStreamWriter
👍3
Практический пример: чтение UTF-8 и вывод в консоль
import java.io.*;
import java.nio.charset.StandardCharsets;

public class Utf8FileProcessor {

public void readAndPrint(String path) throws IOException {
// InputStreamReader: байты из файла -> символы UTF-8
try (InputStreamReader reader = new InputStreamReader(
new FileInputStream(path), StandardCharsets.UTF_8)) {

char[] buffer = new char[1024];
int charsRead;

// Буферизованное чтение символов
while ((charsRead = reader.read(buffer)) != -1) {
// Создание String из char[] — новый объект в heap
String chunk = new String(buffer, 0, charsRead);
System.out.print(chunk);
}
}
// reader закрыт, StreamDecoder и его буферы освобождены
// buffer и chunk — недостижимы, GC собирает при следующей minor collection
}

// Альтернатива: BufferedReader для построчного чтения
public void readLines(String path) throws IOException {
try (BufferedReader reader = new BufferedReader(
new InputStreamReader(
new FileInputStream(path), StandardCharsets.UTF_8))) {

String line;
int lineNumber = 0;
while ((line = reader.readLine()) != null) {
lineNumber++;
System.out.printf("%d: %s%n", lineNumber, line);
// Каждая line — новая String в heap
}
}
// Все String из readLine() недостижимы, если не сохранены
}
}


Путь данных с BufferedReader


BufferedReader добавляет собственный char[] cb в heap — буфер символов размером 8192 по умолчанию. readLine() накапливает символы в этот буфер до \n, затем создает String:
[BufferedReader.readLine()]
-> [Заполнение cb[8192] из underlying InputStreamReader]
-> [InputStreamReader: декодирование байт -> символы]
-> [Поиск \n или \r\n в cb]
-> [new String(cb, start, length): создание String в heap]
-> [String конструктор: копирование char[] (или byte[] в современных JDK)]
-> [Возврат String]

Каждая строка — отдельный объект String в heap. При чтении файла 100MB с ~1 000 000 строк создается 1 000 000 объектов String. Это создает значительное давление на GC, хотя строки короткоживущие и эффективно собираются minor GC.

Роль garbage collector


Жизненный цикл объектов кодирования
public void processManyFiles(List<String> paths) throws IOException {
for (String path : paths) {
// Каждая итерация создает:
// - FileInputStream (heap)
// - InputStreamReader (heap)
// - StreamDecoder (heap)
// - ByteBuffer, CharBuffer внутренние (heap)
// - CharsetDecoder (heap)
// - char[] buffer (heap)
// - String chunk (heap, при каждом read)

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

char[] buffer = new char[4096];
int charsRead;
while ((charsRead = reader.read(buffer)) != -1) {
process(new String(buffer, 0, charsRead));
}
}
// Все объекты становятся недостижимыми
// GC собирает их при следующей minor collection
}
}

При обработке тысяч файлов этот код создает тысячи временных объектов. Они короткоживущие и собираются minor GC, но при высокой частоте могут вызвать частые паузы young generation.

Оптимизация: переиспользование буферов
// Переиспользуемый буфер для чтения
private static final ThreadLocal<char[]> CHAR_BUFFER =
ThreadLocal.withInitial(() -> new char[8192]);

public void processFileOptimized(String path) throws IOException {
char[] buffer = CHAR_BUFFER.get();

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

int charsRead;
while ((charsRead = reader.read(buffer)) != -1) {
// String всё еще создается, но buffer переиспользуется
process(new String(buffer, 0, charsRead));
}
}
}

Здесь char[] buffer переиспользуется между вызовами, устраняя одну аллокацию на файл. Однако new String(...) при каждом чтении всё еще создает объекты. Для полного устранения аллокаций требуется обработка in-place в char[] без создания String.


Запись с явной кодировкой
public void writeUtf8File(String path, List<String> lines) throws IOException {
// OutputStreamWriter: символы -> байты UTF-8
try (OutputStreamWriter writer = new OutputStreamWriter(
new FileOutputStream(path), StandardCharsets.UTF_8)) {

for (String line : lines) {
writer.write(line); // Кодирование char[] -> байты UTF-8
writer.write(System.lineSeparator());
}

writer.flush(); // Сброс внутренних буферов в FileOutputStream

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

// StreamEncoder и его буферы освобождены
}



Буферизация записи

Для эффективной записи OutputStreamWriter оборачивается в BufferedWriter:
try (BufferedWriter writer = new BufferedWriter(
new OutputStreamWriter(
new FileOutputStream(path), StandardCharsets.UTF_8))) {

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

BufferedWriter содержит char[] cb в heap — буфер символов. Запись накапливается в этом буфере и сбрасывается в OutputStreamWriter при заполнении или вызове flush(). Это устраняет частые вызовы кодера и underlying потока.


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