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
История технологии сегодня — 14 июня

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

Андре́й Андре́евич Ма́рков (2 (14) июня 1856, Рязань — 20 июля 1922, Петроград) — русский математик, академик, внёсший большой вклад в теорию вероятностейматематический анализ и теорию чисел. А. А. Марков является первооткрывателем обширного класса стохастических процессов с дискретной и непрерывной временно́й компонентой, названных его именем. Марковские процессы можно описать так: следующее состояние процесса зависит вероятностно только от текущего состояния. В то время, когда эта теория была построена, она считалась абстрактной, однако в настоящее время практические применения данной теории чрезвычайно многочисленны. Теория цепей Маркова выросла в огромную и весьма важную область научных исследований — теорию марковских случайных процессов, которая в свою очередь представляет основу общей теории стохастических процессов (см. также: Неравенство Маркова). А. А. Марков существенно продвинул классические исследования предшественников, касающиеся закона больших чисел и центральной предельной теоремы теории вероятностей, а также распространил их и на цепи Маркова.

Кири́лл Я́ковлевич Кондра́тьев (14 июня 1920, Рыбинск — 1 мая 2006, Санкт-Петербург)советский и российский геофизик. Основные труды относятся к исследованиям в области физики атмосферы, спутниковой метеорологии, атмосферной оптикеактинометрии, проблемам глобальной экологии и глобальным изменениям. Автор первой в мире монографии о спутниковой метеорологии (1963), серии монографий о дистанционном зондировании атмосферы и подстилающей поверхности, проблеме радиационного баланса Земли, фундаментальных основах природноресурсных космических исследований, сравнительном планетоведении. Впервые руководил экологическими исследованиями, проведёнными космонавтами из космоса. Соавтор научного открытия «Явление вертикально-лучевой структуры дневного излучения верхней атмосферы Земли».

Фёдор Васи́льевич То́карев (2 (14) июня 1871, станица МечётинскаяЧеркасский округОбласть Войска ДонскогоРоссийская империя — 7 июня 1968МоскваСССР) — русский и советский конструктор стрелкового оружия. Наиболее известные разработки: пистолет ТТ и винтовка СВТ-38/40.


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

1951 — в Вашингтоне поступил в продажу первый компьютер, созданный в США — UNIVAC I.


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

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

Евгений Викторович Колба́сьев (3 (15) июня 1862, Одесса, Российская империя — 20 ноября 1918, Инкерман, Крым) — русский изобретатель в области военно-морского дела, преподаватель Кронштадтской водолазной школы, капитан 1-го ранга. Автор оригинальной конструкции плавающей мины и нескольких проектов подводных лодок, в том числе диверсионной подводной лодки «Матрос Пётр Кошка», созданной в 1901 году. Колбасьев заметно опередил время, первыми создав секционный метод строительства подводных лодок. В разобранном виде подлодка помещалась в обычном железнодорожном вагоне, а процесс сборки занимал до 6 часов.

Поль Корню́ (фр. Paul Cornu; 15 июня 1881 года, Гло-ля-Феррьер — 6 июня 1944 года, Лизьё) — французский механик-изобретатель и авиатор; пионер вертолётостроения; первый в мире человек, поднявшийся в воздух на вертолёте (3 (13) ноября 1907).

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

1844 — Чарльз Гудьир запатентовал способ вулканизации резины.

1869 — Джон Хайат в Олбани (штат Нью-Йорк) запатентовал целлулоид.

2004 — Обнаружен первый сетевой вирус для мобильных телефонов — Cabir.


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

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

BufferedOutputStream – буферизованная запись. Путь данных от heap к диску

BufferedOutputStream — декоратор над OutputStream, добавляющий внутреннюю буферизацию к любому underlying потоку записи. Он решает зеркальную проблему по отношению к BufferedInputStream: накладные расходы системных вызовов при частых мелких операциях записи. Вместо немедленной отправки каждого байта в ОС, BufferedOutputStream накапливает данные в памяти и сбрасывает их пачками.

Ключевое отличие от чтения: запись буферизована по умолчанию, потому что ОС сама буферизует записи в page cache. Однако без BufferedOutputStream каждый вызов write(int) выполняет системный вызов, даже если данные попадают только в page cache. BufferedOutputStream устраняет эти избыточные вызовы.

Внутреннее устройство
// Упрощенная структура из OpenJDK
public class BufferedOutputStream extends FilterOutputStream {
// Внутренний буфер в heap JVM
protected byte[] buf;

// Количество байт, фактически записанных в буфер
protected int count;

// Конструкторы
public BufferedOutputStream(OutputStream out) {
this(out, 8192); // Размер по умолчанию
}

public BufferedOutputStream(OutputStream out, int size) {
super(out);
if (size <= 0) {
throw new IllegalArgumentException("Buffer size <= 0");
}
buf = new byte[size]; // Выделение в heap
}
}

Поля buf и count образуют линейный буфер накопления. При вызове write(int) байт помещается в buf[count++]. Когда count достигает buf.length, выполняется flushBuffer() — сброс накопленных данных в underlying поток.


Механика write() и путь данных

Посимвольная запись
public void write(int b) throws IOException {
if (count >= buf.length) {
flushBuffer(); // Буфер полон — сброс
}
buf[count++] = (byte) b; // Запись в heap, системный вызов отсутствует
}


Путь данных для write(int) при неполном буфере:
[Java: bos.write(65)]
-> [BufferedOutputStream: count < buf.length?]
-> [buf[count++] = 65] // Прямая запись в массив heap
-> [Возврат]

Никаких системных вызовов, никакого JNI. Байт записывается в массив в heap напрямую JVM-байткодом. Это на порядки быстрее, чем посимвольная запись через FileOutputStream.


Сброс буфера: flushBuffer()
private void flushBuffer() throws IOException {
if (count > 0) {
// Системный вызов: запись накопленных данных
out.write(buf, 0, count); // out — underlying FileOutputStream
count = 0; // Сброс счетчика
}
}


Путь данных для flushBuffer():
[Java: flushBuffer()]
-> [FileOutputStream.write(buf, 0, count)]
-> [JNI: нативный writeBytes]
-> [Ядро ОС: syscall write(fd, native_buf, count)]
-> [Page Cache: копирование в dirty pages]
-> [Пометка страниц как dirty]
-> [Возврат в userspace]
-> [Возврат в Java]
-> [count = 0]

Данные попадают в page cache ядра, но не немедленно на диск. Ядро асинхронно сбрасывает dirty pages фоновыми потоками.


Роль flush() и принудительный сброс

flush() в BufferedOutputStream выполняет два действия:
Сброс внутреннего буфера. flushBuffer() — запись накопленных данных в underlying поток.
Проксирование flush. out.flush() — сброс буферов underlying потока.
public void flush() throws IOException {
flushBuffer(); // Сброс buf в FileOutputStream
out.flush(); // Проксирование вниз по цепочке
}


Важное различие между flush() и физической записью на диск:
flush() гарантирует, что данные покинули JVM и попали в page cache ОС. Другие процессы могут читать эти данные.
flush() не гарантирует физическую запись на диск. При сбое питания данные в page cache теряются.
FileDescriptor.sync() или FileChannel.force(true) выполняют fsync(), который гарантирует физическую запись.
// Пример: гарантия доставки для критичных данных
try (BufferedOutputStream bos = new BufferedOutputStream(
new FileOutputStream("critical.dat"))) {

bos.write(transactionData);
bos.flush(); // Сброс в page cache

// Для финансовых данных — fsync
bos.out.getFD().sync(); // Физическая запись на диск
}



Путь данных в памяти: детальный разбор

Сценарий: накопление данных в буфере
try (BufferedOutputStream bos = new BufferedOutputStream(
new FileOutputStream("output.bin"), 8192)) {

for (int i = 0; i < 10000; i++) {
bos.write(i); // Накопление в buf, системные вызовы отсутствуют
}
// После цикла: count=10000, buf содержит данные в heap
}


Память в процессе:
buf[8192] — массив в heap, создан при конструировании
count — примитив в объекте BufferedOutputStream
Данные накапливаются в buf, GC не вовлечен в цикл

Когда count достигает 8192, следующий write() инициирует flushBuffer():
[Java: bos.write(byte_8192)]
-> [count >= 8192? да]
-> [flushBuffer()]
-> [FileOutputStream.write(buf, 0, 8192)]
-> [syscall write: 8192 байт в page cache]
-> [count = 0]
-> [buf[0] = byte_8192]
-> [count = 1]


Сценарий: закрытие потока
} // try-with-resources вызывает close()


close()
в BufferedOutputStream:
public void close() throws IOException {
try (OutputStream os = out) {
flush(); // Неявный flush перед закрытием
} // out.close() вызывается автоматически
}


Путь данных при закрытии:
flush() — сброс остатка буфера в underlying поток
out.flush() — сброс в page cache ОС
out.close() — закрытие файлового дескриптора
Объекты bos, buf, out становятся недостижимыми
GC собирает их при следующей сборке


#Java #для_новичков #beginner #IO #NIO #BufferedOutputStream
👍4
Роль garbage collector

Жизненный цикл буфера при записи
public void writeRecords(String path, List<Record> records) throws IOException {
// buf создается в Eden space
try (BufferedOutputStream bos = new BufferedOutputStream(
new FileOutputStream(path))) {

for (Record record : records) {
byte[] data = record.toBytes(); // Новый массив при каждой итерации!
bos.write(data); // Копирование в buf или flushBuffer()
// data становится мусором
}

bos.flush(); // Явный сброс остатка

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

// buf и все data[] собираются GC
}

В этом примере каждая итерация создает массив data. Эти массивы короткоживущие и нагружают GC. Оптимизация — переиспользование или прямая запись в BufferedOutputStream без промежуточных массивов.

Оптимизация: прямая запись без промежуточных массивов
public void writeRecordsOptimized(String path, List<Record> records) throws IOException {
// Переиспользуемый буфер для сериализации
ByteArrayOutputStream baos = new ByteArrayOutputStream(1024);

try (BufferedOutputStream bos = new BufferedOutputStream(
new FileOutputStream(path))) {

for (Record record : records) {
baos.reset(); // Сброс без создания нового объекта

record.serialize(baos); // Запись в переиспользуемый baos

byte[] data = baos.toByteArray(); // Все еще создается массив
// Лучше: запись напрямую в bos, минуя baos
}
}
}



Идеальное решение — сериализация напрямую в BufferedOutputStream, без промежуточных массивов:
public void writeRecordsDirect(String path, List<Record> records) throws IOException {
try (DataOutputStream dos = new DataOutputStream(
new BufferedOutputStream(new FileOutputStream(path)))) {

for (Record record : records) {
dos.writeUTF(record.getId()); // Прямая запись в buf
dos.writeLong(record.getTimestamp()); // Без промежуточных массивов
dos.writeInt(record.getValue());
// Данные накапливаются в buf BufferedOutputStream
}

} // Автоматический flush() и close()
}

DataOutputStream пишет примитивы напрямую в underlying поток, который буферизован. Никаких временных массивов, минимальная нагрузка на GC.
Проблема: забытый flush() и потеря данных
java
// Антипаттерн: данные могут не попасть в файл
public void dangerousWrite(String path, String data) throws IOException {
BufferedOutputStream bos = new BufferedOutputStream(
new FileOutputStream(path));
bos.write(data.getBytes());
// flush() не вызван!
// close() не вызван!
// При выходе из метода: buf содержит данные, но не сброшен
// GC финализирует объект, close() вызовется через Cleaner, но с задержкой
}


В этом коде:
Данные остаются в buf в heap
Файловый дескриптор открыт, данные не записаны в ОС
Если приложение завершится до финализации — данные потеряны
Даже при финализации задержка непредсказуема

Правильный подход — try-with-resources, который гарантирует close() с неявным flush():
// Правильно: гарантированный flush и close
public void safeWrite(String path, String data) throws IOException {
try (BufferedOutputStream bos = new BufferedOutputStream(
new FileOutputStream(path))) {
bos.write(data.getBytes());
// flush() вызовется неявно в close()
}
}


Практический пример: буферизованное копирование файла
public class BufferedFileCopy {

private static final int BUFFER_SIZE = 8192;

public static void copy(String sourcePath, String destPath) throws IOException {
// Буфер для чтения — внешний, создается в heap
byte[] buffer = new byte[BUFFER_SIZE];

// BufferedInputStream: buf[8192] в heap для накопления чтения
// BufferedOutputStream: buf[8192] в heap для накопления записи
// Итого: ~16KB в heap на время копирования

try (BufferedInputStream bis = new BufferedInputStream(
new FileInputStream(sourcePath), BUFFER_SIZE);
BufferedOutputStream bos = new BufferedOutputStream(
new FileOutputStream(destPath), BUFFER_SIZE)) {

int bytesRead;
// Цикл без аллокаций в heap — GC не вовлечен
while ((bytesRead = bis.read(buffer)) != -1) {
bos.write(buffer, 0, bytesRead);
}

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

} // Автоматический close() для обоих потоков

// buffer, bis.buf, bos.buf — все становятся недостижимым
// GC собирает при следующей minor collection
}

// Альтернатива: без внешнего буфера, используя внутренние буферы оберток
public static void copyOptimized(String sourcePath, String destPath) throws IOException {
try (BufferedInputStream bis = new BufferedInputStream(
new FileInputStream(sourcePath));
BufferedOutputStream bos = new BufferedOutputStream(
new FileOutputStream(destPath))) {

int byteValue;
// Побайтово, но фактически буферизовано
// Внешний буфер не нужен — экономия 8KB в heap
while ((byteValue = bis.read()) != -1) {
bos.write(byteValue);
}
}
}
}


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

import java.io.*;

public class Task150626 {
public static void main(String[] args) throws IOException {

File f = new File("data.txt");

FileOutputStream fos = new FileOutputStream(f);

BufferedOutputStream bos = new BufferedOutputStream(fos, 1024);

bos.write("Java".getBytes());
System.out.println(f.length());
bos.close();
System.out.println(f.length());
}
}


#Tasks
👍4
Варианты ответа:
Anonymous Quiz
17%
0 0
17%
4 4
50%
0 4
17%
4 0
👍2
Что такое StackOverflowError и как его диагностировать? 🤓

Ответ:

StackOverflowError — ошибка (Error), возникающая, когда стек вызовов потоков исчерпал доступную память.

Обычно это результат бесконечной рекурсии (метод вызывает сам себя без условий выхода) или очень глубокой рекурсии (например, обход большого дерева без оптимизации).

Диагностировать можно:
1) посмотреть stack trace — последние вызовы повторяются.
2) увеличить размер стека (флаг -Xss).
3) переписать рекурсию на итерацию или хвостовую рекурсию (Java не оптимизирует её сама).

Также может возникать при большом количестве вложенных вызовов (например, парсинг глубокого JSON).



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

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

Алекса́ндр Алекса́ндрович Фри́дман (4 (16) июня 1888[3], Санкт-Петербург — 16 сентября 1925, Ленинград) — русский и советский математикгидромеханикфизик-теоретик и геофизик. Основоположник современной физической космологии, автор исторически первой нестационарной модели Вселенной (Вселенная Фридмана), один из создателей современной динамической метеорологии. Основные работы посвящены проблемам динамической метеорологии (теории атмосферных вихрей и порывистости ветра, теории разрывов непрерывности в атмосфере, атмосферной турбулентности), гидродинамике сжимаемой жидкости, физике атмосферы и релятивистской космологии. В июле 1925 года с научными целями совершил полёт на аэростате вместе с пилотом Павлом Федосеенко, достигнув рекордной по тому времени для СССР высоты 7400 м. Фридман одним из первых освоил математический аппарат теории гравитации Эйнштейна и начал читать в университете курс тензорного исчисления как вводную часть к курсу общей теории относительности. В 1923 году вышла в свет его книга «Мир как пространство и время» (переиздана в 1965 году), познакомившая широкую публику с новой физикой.

Юлиус Плю́ккер (нем. Julius Plücker; 16 июня или 16 июля 1801 года, Эльберфельд — 22 мая 1868 года, Бонн) — немецкий математик и физик, работавший в области аналитической геометрии, был пионером в области исследования катодных лучей, что впоследствии привело к открытию электрона. Также занимался исследованиями кривых Ламе.


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

1911 — основана корпорация IBM.

1963 — стартовал космический корабль «Восток-6» с Валентиной Терешковой, первой в мире женщиной-космонавтом.


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

Тема: Optional не предназначен для полей класса и для параметров методов.

Проблема: Optional был создан как ограниченный механизм для возвращаемых значений методов, чтобы явно указать возможность отсутствия результата.

Использование Optional в полях класса нарушает сериализацию: Optional не реализует Serializable, и многие фреймворки (Hibernate, Jackson, сериализация Java) работают с ним некорректно или требуют дополнительной настройки. Кроме того, поле-Optional создает избыточность — само поле уже может быть null, а его обертка добавляет еще один уровень. Использование Optional в параметрах методов делает API громоздким: вызывающий код вынужден создавать Optional.of() или Optional.empty(), что усложняет чтение и не дает реальных преимуществ.

Решение: Используйте Optional только для возвращаемых типов методов, которые могут не возвращать значение. Для полей класса используйте null-допустимые типы с аннотациями (@Nullable@NotNull) или библиотеки вроде Lombok. Для параметров методов используйте перегрузки методов или явную проверку на null. Если параметр может быть опциональным, лучше применить паттерн Builder или передавать отдельный флаг.
import java.util.Optional;
import java.io.Serializable;

public class OptionalUsage {

//Антипаттерн: Optional как поле класса
static class BadEntity implements Serializable {
private Optional<String> name = Optional.empty(); // Не сериализуется!
private Optional<Integer> age; // Hibernate не поймет
// getter/setter
}

//Правильно: просто nullable поле
static class GoodEntity implements Serializable {
private String name; // null = отсутствует
private Integer age; // null = отсутствует
}

//Антипаттерн: Optional в параметре метода
public static void badMethod(Optional<String> param) {
// Вызывающему нужно писать badMethod(Optional.of("text"))
if (param.isPresent()) { /* ... */ }
}

//Решение 1: перегрузка метода
public static void goodMethod() {
// без параметра
}

public static void goodMethod(String param) {
// с параметром (может быть null)
}

//Решение 2: проверка на null
public static void betterMethod(String param) {
if (param != null) { /* ... */ }
}

//Единственное правильное место: возвращаемое значение
public static Optional<String> findUserById(long id) {
// поиск в БД
return id > 0 ? Optional.of("User") : Optional.empty();
}

public static void main(String[] args) {
// Использование Optional из возврата
findUserById(10).ifPresent(System.out::println);

// Перегрузка вместо Optional параметра
goodMethod(); // без параметра
goodMethod("text"); // с параметром
}
}


Объяснение:
 Создатели Java (в частности, Брайан Гетц) неоднократно подчеркивали, что Optional предназначен исключительно для возвращаемых значений. Он не должен использоваться в полях, параметрах методов, коллекциях и как ключ в Map. Причина: Optional не сериализуем (есть обходные пути, но они хрупкие), и его использование в полях вводит в заблуждение — поле уже может быть null, а обертка не добавляет безопасности. Для параметров методов Optional создает лишний объект и не улучшает читаемость. Вместо этого используйте явную проверку на null, аннотации @Nullable или @NotNull, а для возврата — Optional.

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

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