Java for Beginner
869 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
С 27.12 по 09.01
Предыдущий пост(с 20.12 по 26.12)

Воскресный мотивационный пост:
Вот и пришёл 2026 год 🎄

Запись встреч/видео:
Tree в Java. Самая сложная коллекция.

OrderHub. Эволюция проекта из монолита к production-ready микросервису
1. Старт серии. Создаём основу для production-системы

Обучающие статьи:
Java:
Глава 8. Дополнительные аспекты коллекций
Потокобезопасные коллекции и типичные ошибки
Практика

Раздел 7. Алгоритмы

Глава 1: Основы анализа алгоритмов
Временная сложность и Big O нотация: язык анализа алгоритмов

Глубокая архитектура и внутреннее устройство RabbitMQ
AMQP 1.0 vs AMQP 0-9-1: эволюция протокола
Введение: Современный стек для production
Паттерны использования и гарантии доставки в RabbitMQ

Полезные статьи и видео:
ПОДКЛЮЧЕНИЕ GPT GO на ГОД!

Моя первая статья на хабре
Field vs Constructor Injection в Java: ошибка объектного дизайна или вопрос синтаксиса?

Как и всегда, задачи можно найти под тегом - #Tasks, вопросы с собеседований - #собеседование
🔥2
Всем привет! 👌

А давайте завтра встретимся? И запишем видео как написать своего AI бота в телеге? ☺️

В нагрузку покажу основные фишки нового обновления Telegram API v9.3. 👏

Кто придет? 🤨
Please open Telegram to view this post
VIEW IN TELEGRAM
👍8🔥1
История IT-технологий сегодня — 11 января


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

Мэтью «Мэтт» Чарльз Мулленвег (англ. Matthew Charles "Matt" Mullenweg; 11 января 1984 года, Хьюстон, Техас, США) — американский программист, предприниматель, менеджер и музыкант; создатель и основной разработчик распространяемой по лицензии GNU GPL системы управления содержимым сайта с открытым исходным кодом WordPress; основатель, владелец и руководитель девелоперской компании Automattic и некоммерческой организации WordPress Foundation, поддерживающей инфраструктуру WordPress; член совета директоров некоммерческого издания Grist; поддерживает ряд филантропических организаций, в частности Архив Интернета, Electronic Frontier Foundation, Фонд свободного программного обеспечения, Long Now Foundation и Innocence Project; участник и докладчик множества международных конференций.


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

1700 — в России вместо византийского календаря введён юлианский календарь. После 31 декабря 7208 года наступило 1 января 1700 года. Начало года перенесёно на 1 января.

1787 — Уильям Гершель открыл Титанию и Оберон — спутники планеты Уран.

2011 — Прекращена общая поддержка операционной системы Windows XP Service Pack 3.

2022 — прекращена основная поддержка Windows Server 2016.



#Biography #Birth_Date #Events #11Января
Please open Telegram to view this post
VIEW IN TELEGRAM
👍1
Напоминаю, что сегодня в 16:00 по МСК встречаемся в Яндекс.Телемост))


Приходите, жду всех!
👍3
Please open Telegram to view this post
VIEW IN TELEGRAM
👍2
Telegram AI Bot. Версия Telegram API 9.3+

В этом видео мы разбираем реальное обновление Telegram Bot API, которое наконец делает стриминг ответов в Telegram нативным, без костылей и бесконечных EditMessageText.

Бота пишем самого простого, просто в целях демонстрации, не более.

Сама демонстрация работы в самом конце)

Показываю и объясняю на живом Java-проекте:
🔹Long Polling (без webhook и ngrok)
🔹Spring Boot + Spring AI
🔹Потоковые ответы от LLM
🔹Новый механизм sendMessageDraft
🔹Автоматическую работу по замене названий в forum topics

⚠️ sendMessageDraft работает только в темах форума
⚠️ В Java SDK метод sendMessageDraft ещё не реализован — показываю, как добавить вручную

Исходный код проекта на GitHub очень ждет Ваших звезд.

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

Смотрите, ставьте лайки, подписывайтесь на каналы!✌️
Please open Telegram to view this post
VIEW IN TELEGRAM
👍5🔥31
История IT-технологий сегодня — 12 января


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

Серге́й Па́влович Королёв (30 декабря 1906 [12 января 1907], Житомир, Волынская губерния, Российская империя — 14 января 1966, Москва, СССР)советский учёный, конструктор ракетно-космических систем. Дважды Герой Социалистического Труда. Академик АН СССР (1958). Член-корреспондент Академии артиллерийских наук. Председатель Совета главных конструкторов СССР (1946—1966). Лауреат Ленинской премии. Инженер-полковник.

Дже́ффри Престон Бе́зос (англ. Jeffrey Preston Bezos, фамилия при рождении — Йо́ргенсен (англ. Jorgensen); род. 12 января 1964, Альбукерке, Берналийо, Нью-Мексико, США) американский предприниматель, основатель интернет-компании Amazon, создатель и владелец аэрокосмической компании Blue Origin, владелец издательского дома The Washington Post.


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

2021 — остановлена работа плагина Flash Player.


#Biography #Birth_Date #Events #12Января
Please open Telegram to view this post
VIEW IN TELEGRAM
👍1
Обработка ошибок и мониторинг RabbitMQ

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


Мониторинг: многоуровневый подход

RabbitMQ Management UI: первый рубеж защиты

Management UI — это визуальный интерфейс, но его настоящая ценность в оперативном обнаружении аномалий.

Критические метрики в реальном времени:
Message rates — скорость публикации и потребления сообщений
Queue depths — глубина очередей (backlog)
Consumer count — количество активных потребителей
Connection churn — частота переподключений

Паттерны для наблюдения:
Внезапный рост глубины очереди указывает на отставание потребителей
Падение количества потребителей сигнализирует о проблемах с deployment
Увеличение частоты переподключений говорит о сетевых проблемах

Prometheus метрики: систематический мониторинг

Prometheus предоставляет количественные данные для анализа трендов и настройки алертов. RabbitMQ экспортирует метрики через встроенный плагин, но важнее понимать, что отслеживать:

Базовые метрики для любого кластера:
rabbitmq_queue_messages_ready        # Сообщения, готовые к доставке
rabbitmq_queue_messages_unacked # Сообщения в процессе обработки
rabbitmq_queue_consumers # Количество потребителей
rabbitmq_process_open_fds # Открытые файловые дескрипторы
rabbitmq_erlang_gc_reclaimed_bytes # Память, освобождённая сборщиком мусора


Производственные алерты должны отслеживать:
Глубину очереди, превышающую разумные лимиты
Отсутствие потребителей для критических очередей
Необычные паттерны в скорости обработки сообщений
Потребление памяти, приближающееся к лимитам


Health Checks в Spring Boot Actuator: проверка жизнеспособности

Spring Boot Actuator предоставляет готовые health checks, но в production их нужно расширять:

Три уровня проверок:
Liveness — проверка, что приложение запущено
Readiness — проверка готовности принимать трафик
Startup — мониторинг процесса запуска

Кастомные health checks для RabbitMQ:
// Концептуальный пример расширенной проверки
@Component
public class RabbitMQBusinessHealthIndicator {

public Health checkBusinessReadiness() {
// Проверяем не только соединение, но и:
// 1. Наличие критических очередей
// 2. Наличие активных потребителей
// 3. Разумную глубину очередей
// 4. Скорость обработки сообщений
}
}

Важный принцип: Health checks должны проверять не только доступность RabbitMQ, но и способность вашего приложения эффективно с ним взаимодействовать.



Стратегии обработки ошибок

Классификация ошибок: знай своего врага

Транзиентные ошибки (временные):
Сетевые проблемы
Временная недоступность сервиса
Кратковременные таймауты

Бизнес-ошибки:
Невалидные данные в сообщении
Нарушение бизнес-правил
Конфликты данных

Системные ошибки:
Потеря соединения с базой данных
Недостаток ресурсов
Баги в коде


Retry с экспоненциальной задержкой

Повторные попытки — стандартный подход для транзиентных ошибок, но важно избегать retry storms:

Принципы правильного retry:
Экспоненциальная задержка между попытками
Ограничение максимального количества попыток
Исключение бизнес-ошибок из retry логики
Использование jitter для распределения нагрузки

# Конфигурация Spring Retry
spring:
rabbitmq:
listener:
simple:
retry:
enabled: true
max-attempts: 3
initial-interval: 1s
multiplier: 2
max-interval: 10s



Circuit Breaker: защита от каскадных отказов

Автоматический выключатель предотвращает зацикливание вызовов при постоянных ошибках:

Три состояния Circuit Breaker:
Closed — запросы проходят нормально
Open — запросы сразу отклоняются
Half-Open — пробные запросы для проверки восстановления

Использование с RabbitMQ: Circuit breaker следует применять для операций, которые могут вызывать каскадные отказы, например, при вызове внешних сервисов из обработчиков сообщений.

#Java #middle #RabbitMQ
👍2
Dead Letter Queues: изоляция проблемных сообщений

DLQ — это не мусорка, а система диагностики:

Что отправлять в DLQ:
Сообщения, превысившие лимит попыток обработки
Сообщения с истёкшим TTL
Сообщения, отброшенные из-за переполнения очереди

Архитектура обработки DLQ:
Анализ — изучение причин попадания в DLQ
Классификация — разделение на исправимые и неисправимые ошибки
Восстановление — повторная обработка после исправления
Архивация — сохранение неисправимых сообщений для анализа


Логирование и трассировка

Correlation IDs: сквозная идентификация запросов

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

Реализация в Spring Boot:
// Фильтр для HTTP запросов
@Component
public class CorrelationIdFilter implements Filter {

public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) {
// Извлекаем или генерируем correlation ID
// Помещаем в MDC для логирования
// Добавляем в заголовки RabbitMQ сообщений
}
}

// MessagePostProcessor для RabbitMQ
@Component
public class CorrelationIdMessagePostProcessor implements MessagePostProcessor {

public Message postProcessMessage(Message message) {
// Добавляем correlation ID из MDC в заголовки сообщения
return message;
}
}

Важно: Correlation ID должен передаваться через все асинхронные границы — HTTP запросы, сообщения RabbitMQ, вызовы внешних API.



Структурированное логирование: от текста к данным

Структурированные логи (JSON, Logstash) позволяют автоматически анализировать и агрегировать данные.

Ключевые поля для каждого log entry:
timestamp
level
logger
message
correlationId
traceId/spanId (для трассировки)
Дополнительный контекст (queue, messageId, userId)

Конфигурация Logback для JSON:
<appender name="JSON" class="ch.qos.logback.core.ConsoleAppender">
<encoder class="net.logstash.logback.encoder.LogstashEncoder">
<customFields>{"application":"${APP_NAME}"}</customFields>
</encoder>
</appender>


Паттерны логирования для RabbitMQ:
При публикации сообщения: логируем messageId, routingKey, размер сообщения
При получении сообщения: логируем deliveryTag, очередь, consumer
При обработке: логируем длительность, результат, ошибки
При подтверждении: логируем успешность, время обработки


Распределённая трассировка с OpenTelemetry

Трассировка показывает путь запроса через все микросервисы, включая асинхронные взаимодействия через RabbitMQ.

Интеграция RabbitMQ с OpenTelemetry:
Инъекция trace context в заголовки сообщений
Создание spans для операций публикации и потребления
Связывание spans через очередь сообщений

Концептуальный подход:
HTTP Request → [Span A] → RabbitMQ Publish → [Span B]

(сообщение в очереди)

RabbitMQ Consume → [Span C] → DB Call → [Span D]

Важно: Даже при асинхронной коммуникации через RabbitMQ можно сохранить контекст трассировки, передавая traceId и spanId в заголовках сообщений.



Практические рекомендации

Уровни мониторинга

Инфраструктурный уровень: доступность RabbitMQ, использование ресурсов
Уровень приложения: health checks, метрики Spring Boot
Бизнес-уровень: скорость обработки заказов, количество ошибок

Стратегия алертинга

Приоритеты алертов:
P0: Полная недоступность RabbitMQ или критичных очередей
P1: Быстрый рост глубины очереди (> 1000 сообщений/минуту)
P2: Отсутствие потребителей для критичных очередей
P3: Ухудшение производительности (> 95 перцентиль latency)

Паттерны для production

Всегда используйте Publisher Confirms для гарантированной доставки
Реализуйте идемпотентность на стороне потребителя
Настройте разумные TTL для сообщений
Мониторьте не только RabbitMQ, но и своё приложение
Тестируйте сценарии отказа в staging среде


#Java #middle #RabbitMQ
👍2
Что выведет код?

public class Task120126 {
public static void main(String[] args) {
Integer a = 127;
Integer b = 127;
Integer c = 128;
Integer d = 128;
int e = 128;

System.out.println(a == b);
System.out.println(c == d);
System.out.println(c == e);
System.out.println(c.equals(e));
}
}


#Tasks
👍1
Хотел уточнить - на последнем виде про бота нет ни одного лайка... Все так плохо? Может удалить это видео?
Anonymous Poll
4%
Да, все плохо. Лучше удали
67%
Не смотрел...
30%
Вроде все хорошо, щас поставлю лайк)))
👍1
Почему лямбды не всегда лучше обычных методов? 🤓

Ответ:

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

Их используют для простых операций, а не как замену полноценным методам.


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


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

Никого не нашел, поэтому вот:

Ракеш Ша́рма (хинди राकेश शर्मा; род. 13 января 1949, Патиала, Пенджаб, Индия) — первый индийский космонавт и 138-й человек в мире, совершивший полёт в космос. Герой Советского Союза (1984).


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

Не нашел(


#Biography #Birth_Date #Events #13Января
Please open Telegram to view this post
VIEW IN TELEGRAM
👍1🔥1
Раздел 7. Алгоритмы

Глава 1: Основы анализа алгоритмов

Пространственная сложность и компромиссы

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

Пространственная сложность измеряется в тех же асимптотических обозначениях (Big O, Ω, Θ), что и временная, но фокусируется на потреблении памяти. Она включает три основные составляющие:

Входные данные: обязательная память

Любой алгоритм должен хранить входные данные, над которыми он работает. В асимптотическом анализе память под входные данные обычно считается необходимой и включается в общую оценку.
public class InputMemoryExample {
// Метод суммирует элементы массива
// Память: O(n) для хранения входного массива
public static int sumArray(int[] array) {
int sum = 0;
for (int value : array) {
sum += value;
}
return sum;
}

// Метод создает копию массива
// Память: O(n) для входных данных + O(n) для копии = O(n)
public static int[] copyAndModify(int[] array) {
int[] copy = new int[array.length]; // Дополнительная память O(n)
for (int i = 0; i < array.length; i++) {
copy[i] = array[i] * 2;
}
return copy;
}
}


Для алгоритмов, которые модифицируют входные данные in-place (на месте), дополнительная память может быть минимальной. Однако часто такие алгоритмы требуют, чтобы входные данные можно было изменять, что не всегда допустимо в реальных системах.


Вспомогательные структуры данных

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

public class AuxiliaryStructures {
// Алгоритм поиска двух чисел с заданной суммой
// Версия 1: Без дополнительной памяти, но O(n²) времени
public static int[] findPairNaive(int[] array, int targetSum) {
for (int i = 0; i < array.length; i++) {
for (int j = i + 1; j < array.length; j++) {
if (array[i] + array[j] == targetSum) {
return new int[]{array[i], array[j]};
}
}
}
return null; // Память: O(1) вспомогательной
}

// Версия 2: С хеш-таблицей, O(n) времени, но O(n) памяти
public static int[] findPairOptimized(int[] array, int targetSum) {
Set<Integer> seen = new HashSet<>(); // Вспомогательная структура O(n)
for (int num : array) {
int complement = targetSum - num;
if (seen.contains(complement)) {
return new int[]{complement, num};
}
seen.add(num);
}
return null;
}
}


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


#Java #для_новичков #beginner #algorithm #bigO
👍2
Стек вызовов

При использовании рекурсии каждый рекурсивный вызов размещает в стеке вызовов новый фрейм, содержащий параметры функции, локальные переменные и адрес возврата. Глубина рекурсии напрямую влияет на потребление памяти.

public class RecursionMemory {
// Рекурсивное вычисление факториала
// Пространственная сложность: O(n) из-за глубины рекурсии
public static long factorialRecursive(int n) {
if (n <= 1) return 1;
return n * factorialRecursive(n - 1); // Каждый вызов добавляет фрейм в стек
}

// Итеративное вычисление факториала
// Пространственная сложность: O(1)
public static long factorialIterative(int n) {
long result = 1;
for (int i = 2; i <= n; i++) {
result *= i;
}
return result;
}

// Рекурсивный обход дерева в глубину (DFS)
// Пространственная сложность: O(h), где h - высота дерева
public static void dfs(TreeNode node) {
if (node == null) return;

// Обработка узла
System.out.println(node.value);

// Рекурсивный обход детей
dfs(node.left);
dfs(node.right);

// В худшем случае (вырожденное дерево) h = n, сложность O(n)
// В сбалансированном дереве h = log n, сложность O(log n)
}
}


Для рекурсивных алгоритмов критически важно оценивать максимальную глубину рекурсии. Переполнение стека (StackOverflowError) происходит, когда глубина рекурсии превышает ограничение стека вызовов JVM (обычно несколько тысяч фреймов).


Компромисс «Время vs Память»

Компромисс время-память (time-memory trade-off) — фундаментальный принцип проектирования алгоритмов, утверждающий, что можно уменьшить время выполнения за счет увеличения потребления памяти, и наоборот. Этот компромисс проявляется в различных аспектах разработки программного обеспечения.

Хеш-таблица vs Отсортированный список

Рассмотрим две принципиально разные структуры данных для реализации словаря (map) и проанализируем их характеристики.

Хеш-таблица (HashMap в Java):
public class HashTableAnalysis {
// HashMap обеспечивает в среднем O(1) для операций put, get, remove
// Но требует значительной дополнительной памяти
public static void hashTableDemo() {
Map<String, Integer> hashMap = new HashMap<>(1000);

// Вставка: вычисление хеша O(1) + обработка коллизий
hashMap.put("key1", 100);
hashMap.put("key2", 200);

// Поиск: вычисление хеша O(1) + доступ к bucket
Integer value = hashMap.get("key1");

// Память: массив buckets (capacity) + узлы для пар ключ-значение
// Коэффициент загрузки (load factor) по умолчанию 0.75
// При capacity=16 и 12 элементах массив будет расширен
}
}


Характеристики хеш-таблицы:

Время доступа: O(1) в среднем, O(n) в худшем случае (при плохой хеш-функции)
Память: O(n) для хранения элементов + O(capacity) для массива buckets
Особенности: требует хорошей хеш-функции, не сохраняет порядок элементов

#Java #для_новичков #beginner #algorithm #bigO
👍2
Отсортированный список (используя TreeMap или ручную сортировку):
public class SortedListAnalysis {
// TreeMap обеспечивает O(log n) для операций
// Но требует меньше дополнительной памяти
public static void sortedMapDemo() {
// TreeMap основан на красно-черном дереве
Map<String, Integer> treeMap = new TreeMap<>();

// Вставка: O(log n) для поиска позиции + O(1) для вставки (с балансировкой)
treeMap.put("key1", 100);
treeMap.put("key2", 200);

// Поиск: O(log n) для обхода дерева
Integer value = treeMap.get("key1");

// Память: только узлы дерева, без массива buckets
// Каждый узел содержит ссылки на детей, цвет, ключ и значение
}

// Альтернатива: отсортированный ArrayList с бинарным поиском
public static class SortedArrayList<K extends Comparable<K>, V> {
private List<Entry<K, V>> list = new ArrayList<>();

// Вставка с поддержанием порядка: O(n) для поиска позиции + O(n) для сдвига
public void put(K key, V value) {
int index = Collections.binarySearch(
list.stream().map(e -> e.key).collect(Collectors.toList()),
key
);

if (index >= 0) {
list.set(index, new Entry<>(key, value));
} else {
int insertionPoint = -index - 1;
list.add(insertionPoint, new Entry<>(key, value));
}
}

// Поиск: O(log n) бинарным поиском
public V get(K key) {
int index = Collections.binarySearch(
list.stream().map(e -> e.key).collect(Collectors.toList()),
key
);
return index >= 0 ? list.get(index).value : null;
}
}
}


Характеристики отсортированных структур:
Время доступа: O(log n) для деревьев, O(n) для вставки в список
Память: O(n) только для элементов, без значительных накладных расходов
Особенности: сохраняют порядок элементов, позволяют диапазонные запросы

Количественное сравнение

Рассмотрим практический сценарий: необходимо хранить 1 миллион пар ключ-значение в памяти.

Хеш-таблица (HashMap):
Память: ~48 байт на entry (в 64-битной JVM с Compressed OOPs)
Общая память: 1,000,000 × 48 байт ≈ 48 MB + массив buckets (16 MB) ≈ 64 MB
Время поиска: ~100 наносекунд в среднем

TreeMap (красно-черное дерево):
Память: ~56 байт на узел (дополнительные ссылки и цвет)
Общая память: 1,000,000 × 56 байт ≈ 56 MB
Время поиска: log₂(1,000,000) ≈ 20 сравнений × 50 нс ≈ 1000 нс

Отсортированный ArrayList с бинарным поиском:
Память: ~32 байт на entry (только ключ и значение)
Общая память: 1,000,000 × 32 байт ≈ 32 MB
Время поиска: log₂(1,000,000) ≈ 20 сравнений × 10 нс ≈ 200 нс
Но время вставки: O(n) ≈ 500,000 сдвигов в среднем

Выбор зависит от паттерна доступа:
Частые вставки и удаления: HashMap или TreeMap
Частые поиски, редкие модификации: отсортированный ArrayList
Ограниченная память: отсортированный ArrayList


#Java #для_новичков #beginner #algorithm #bigO
👍4
Требование предсказуемого времени доступа: TreeMap (гарантирует O(log n))

Амортизированная сложность: анализ средней стоимости операций

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

Динамический массив (ArrayList): классический пример

Реализация ArrayList в Java демонстрирует ключевые принципы амортизированного анализа.

Рассмотрим внутреннее устройство и стратегию роста:

// Упрощенная реализация ArrayList для демонстрации принципов
public class AmortizedArrayList<E> {
private static final int DEFAULT_CAPACITY = 10;
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;

private Object[] elementData;
private int size;

public AmortizedArrayList() {
this.elementData = new Object[DEFAULT_CAPACITY];
this.size = 0;
}

// Амортизированная сложность добавления: O(1)
public boolean add(E element) {
ensureCapacityInternal(size + 1); // Может вызвать дорогое расширение
elementData[size++] = element;
return true;
}

private void ensureCapacityInternal(int minCapacity) {
if (minCapacity - elementData.length > 0) {
// Требуется расширение массива
grow(minCapacity);
}
}

private void grow(int minCapacity) {
int oldCapacity = elementData.length;

// Стандартная стратегия роста: увеличение на 50%
int newCapacity = oldCapacity + (oldCapacity >> 1);

if (newCapacity - minCapacity < 0) {
newCapacity = minCapacity;
}

if (newCapacity - MAX_ARRAY_SIZE > 0) {
newCapacity = hugeCapacity(minCapacity);
}

// Самая дорогая операция: копирование всех элементов
elementData = Arrays.copyOf(elementData, newCapacity);

// В этот момент выполняется O(n) операций
// Но эта стоимость распределяется (амортизируется) по предыдущим дешевым операциям
}

// Метод получения элемента: всегда O(1)
@SuppressWarnings("unchecked")
public E get(int index) {
if (index >= size) {
throw new IndexOutOfBoundsException();
}
return (E) elementData[index];
}
}


Анализ амортизированной стоимости

Для анализа амортизированной сложности операции add() рассмотрим последовательность из n операций добавления.

Метод агрегирования (учетных издержек):

Каждая обычная операция add() стоит 1 единицу (запись в массив)

При расширении массива с capacity k до capacity 1.5k:
Копирование k элементов: k единиц
Эти k единиц "распределяются" по предыдущим k/2 операциям
Амортизированная стоимость на операцию: (стоимость всех операций) / n

Рассмотрим рост массива по мере добавления элементов:
Начальная capacity: 10
Расширения при: 10 → 15 → 22 → 33 → 49 → 73 → 109 → ...

Последовательность стоимостей для 100 операций add():
90 операций стоят 1 единицу
10 операций расширения стоят: 10 + 15 + 22 + 33 + 49 + 73 + 109 = 311 единиц
Общая стоимость: 90 + 311 = 401 единиц
Амортизированная стоимость на операцию: 401 / 100 ≈ 4.01

Таким образом, хотя некоторые операции очень дороги (O(n)), их средняя стоимость в длинной последовательности остается константной O(1).

Метод потенциала: формальный анализ

Более формальный подход — метод потенциала. Определим потенциал Φ как величину, пропорциональную разнице между capacity и size:
Φ = 2 × (capacity - size)
При каждой операции add():
Фактическая стоимость: 1 (обычная запись) или k+1 (при расширении)
Изменение потенциала: ΔΦ
Амортизированная стоимость: фактическая стоимость + ΔΦ

Можно доказать, что амортизированная стоимость каждой операции ограничена константой, что формально доказывает O(1) амортизированную сложность.


#Java #для_новичков #beginner #algorithm #bigO
👍2
Практические следствия амортизированного анализа

Если известно примерное количество элементов, можно задать начальную capacity, избежав нескольких расширений:
// Плохо: множественные расширения при добавлении 1000 элементов
List<Integer> list = new ArrayList<>(); // capacity=10
for (int i = 0; i < 1000; i++) {
list.add(i); // Расширения при 10...
}

// Хорошо: одно расширение или вообще без расширений
List<Integer> optimizedList = new ArrayList<>(1000); // capacity=1000
for (int i = 0; i < 1000; i++) {
optimizedList.add(i); // Без расширений
}



Амортизация в других структурах: Принцип амортизации применяется в:

Динамических таблицах хеширования: расширение buckets
Стеках с мультипопом: операция multipop
Деревьях со сбалансированным слиянием: операции union-find


Реальные компромиссы в современных системах

Кэширование — это практическое применение компромисса время-память. Различные стратегии кэширования представляют разные точки на спектре этого компромисса.
public class CacheTradeoff {
// LRU (Least Recently Used) кэш
// Использует больше памяти для поддержания порядка доступа
public static class LRUCache<K, V> {
private final int capacity;
private final Map<K, V> map;
private final Deque<K> accessOrder;

public LRUCache(int capacity) {
this.capacity = capacity;
this.map = new HashMap<>(capacity);
this.accessOrder = new LinkedList<>();
}

public V get(K key) {
V value = map.get(key);
if (value != null) {
// Обновляем порядок: O(n) операция
accessOrder.remove(key);
accessOrder.addFirst(key);
}
return value;
}

public void put(K key, V value) {
if (map.size() >= capacity) {
// Удаляем наименее использованный
K lruKey = accessOrder.removeLast();
map.remove(lruKey);
}
map.put(key, value);
accessOrder.addFirst(key);
}
}

// Простой кэш с фиксированным размером
// Использует меньше памяти, но может иметь хуже hit ratio
public static class SimpleCache<K, V> {
private final Map<K, V> map;
private final int maxSize;

public SimpleCache(int maxSize) {
this.maxSize = maxSize;
this.map = new HashMap<>(maxSize);
}

public V get(K key) {
return map.get(key); // O(1), но нет обновления порядка
}

public void put(K key, V value) {
if (map.size() >= maxSize) {
// Удаляем случайный элемент (на практике сложнее)
Iterator<K> it = map.keySet().iterator();
if (it.hasNext()) {
map.remove(it.next());
}
}
map.put(key, value);
}
}
}


Сжатие данных: время на распаковку vs экономия памяти


Сжатие данных — это еще одна форма компромисса, где экономия памяти достигается за счет времени на сжатие и распаковку.
public class CompressionTradeoff {
// Быстрое сжатие (LZ4), но меньшая степень сжатия
public byte[] compressFast(byte[] data) {
// LZ4: высокая скорость, средняя степень сжатия
// Подходит для компрессии "на лету"
return lz4Compress(data);
}

// Медленное сжатие (Zstandard с высоким уровнем), но лучшее сжатие
public byte[] compressHigh(byte[] data) {
// Zstandard уровень 19: медленно, но максимальное сжатие
// Подходит для архивов, где сжатие выполняется один раз
return zstdCompress(data, 19);
}

// Выбор стратегии в зависимости от контекста
public byte[] compressAdaptive(byte[] data, boolean prioritizeSpeed) {
if (prioritizeSpeed) {
return compressFast(data); // Для сетевой передачи
} else {
return compressHigh(data); // Для долговременного хранения
}
}
}


#Java #для_новичков #beginner #algorithm #bigO
👍3
Что выведет код?

import java.util.TreeSet;

public class Task130126 {
static class Item130126 implements Comparable<Item130126> {
int value;
Item130126(int value) { this.value = value; }

public int compareTo(Item130126 other) {
return Integer.compare(this.value, other.value);
}
}
public static void main(String[] args) {
TreeSet<Item130126> set = new TreeSet<>();
Item130126 item1 = new Item130126(1);
Item130126 item2 = new Item130126(2);
set.add(item1);
set.add(item2);
item1.value = 3;
System.out.println(set.contains(item1));
System.out.println(set.contains(item2));
set.remove(item1);
System.out.println(set.size());
}
}


#Tasks
👍1