This media is not supported in your browser
VIEW IN TELEGRAM
🌐 Путешествие URL: что происходит, когда вы вводите адрес сайта
Каждый раз, когда вы набираете https://example.com в браузере — за кулисами запускается целая цепочка событий 👇
1️⃣ Разбор адреса
Браузер делит URL на части:
▪️ https — протокол
▪️ example.com — домен
▪️ /page — путь к ресурсу
2️⃣ Поиск IP
Если IP не сохранён в кеше, браузер спрашивает DNS-сервер: «Где живёт example.com?»
3️⃣ Установление соединения
Создаётся TCP-соединение с сервером по IP и порту (80 для HTTP, 443 для HTTPS).
4️⃣ Запрос ресурса
Браузер отправляет HTTP-запрос: GET /page HTTP/1.1
5️⃣ Ответ сервера
Сервер возвращает HTML, CSS, JS и статус-код (например, 200 OK или 404 Not Found).
6️⃣ Рендеринг страницы
Браузер обрабатывает HTML, применяет стили и выполняет JavaScript.
7️⃣ Шифрование
Если сайт работает по HTTPS, соединение шифруется через SSL/TLS.
8️⃣ Кеширование
Браузер сохраняет ресурсы, чтобы при следующем визите всё грузилось быстрее.
🐸 Библиотека джависта
#CoreJava
Каждый раз, когда вы набираете https://example.com в браузере — за кулисами запускается целая цепочка событий 👇
Браузер делит URL на части:
▪️ https — протокол
▪️ example.com — домен
▪️ /page — путь к ресурсу
Если IP не сохранён в кеше, браузер спрашивает DNS-сервер: «Где живёт example.com?»
Создаётся TCP-соединение с сервером по IP и порту (80 для HTTP, 443 для HTTPS).
Браузер отправляет HTTP-запрос: GET /page HTTP/1.1
Сервер возвращает HTML, CSS, JS и статус-код (например, 200 OK или 404 Not Found).
Браузер обрабатывает HTML, применяет стили и выполняет JavaScript.
Если сайт работает по HTTPS, соединение шифруется через SSL/TLS.
Браузер сохраняет ресурсы, чтобы при следующем визите всё грузилось быстрее.
#CoreJava
Please open Telegram to view this post
VIEW IN TELEGRAM
❤6👍4🔥4
⚡️ Просто о сложном: как JIT делает код быстрее
Вы когда-нибудь задумывались, как JIT умеет так ловко оптимизировать код в реальном времени?
Сегодня разберём, какие именно суперспособности JIT даёт вашему коду.
🔹 Инлайнинг методов
Когда JIT обнаруживает, что какой-то метод выполняется слишком часто, он может инлайнить его. Это значит, что весь код метода не будет вызываться отдельно, а его инструкции будут вставляться прямо в место вызова.
Пример:
Стало:
🔹 Удаление мертвого кода
JIT всегда анализирует, выполняется ли тот или иной участок кода. Если метод или условие никогда не будет вызвано (например, условие, которое всегда ложно), JIT его удаляет.
🔹 Оптимизация циклов
Циклы — частая конструкция, и JIT всегда старается оптимизировать их выполнение. Например, если цикл выполняется много раз, JIT может переработать его для уменьшения количества операций.
🔹 Параллельная компиляция
Если ранее компиляция шла поэтапно, сейчас JIT может компилировать несколько частей программы одновременно. Это не только ускоряет работу компилятора, но и уменьшает задержки при запуске программы.
🔹 Использование final переменных
JIT предполагает, что final переменные не изменяются после инициализации. Это даёт компилятору возможность предсказать их значения и оптимизировать операции с ними.
🔹 Удаление лишних проверок
JIT понимает, какие проверки в коде избыточны. Например, если переменная была уже проверена на null, и это значение не изменяется, JIT может просто игнорировать повторные проверки.
🔹 Классификация горячих и холодных методов
JIT следит за тем, какие методы выполняются часто (горячие) и какие — нет (холодные). Он решает компилировать только те методы, которые чаще всего используются. Если горячий метод вдруг перестаёт быть горячим, JIT может освободить его от компиляции, освобождая ресурсы.
🔹 Слияние и удаление дубликатов
Если программа выполняет одинаковые операции несколько раз, JIT сливает их в одну или избавляется от дублирующихся вычислений.
Пример:
JIT может понять, что a + b одинаково для обеих операций, и просто использовать результат из первого вычисления во втором.
💬 Пишите в комменты, какие инструменты ещё разобрать.
🐸 Библиотека джависта
#CoreJava
Вы когда-нибудь задумывались, как JIT умеет так ловко оптимизировать код в реальном времени?
Сегодня разберём, какие именно суперспособности JIT даёт вашему коду.
🔹 Инлайнинг методов
Когда JIT обнаруживает, что какой-то метод выполняется слишком часто, он может инлайнить его. Это значит, что весь код метода не будет вызываться отдельно, а его инструкции будут вставляться прямо в место вызова.
Пример:
int sum(int a, int b) {
return a + b;
}
int result = sum(5, 10);
Стало:
int result = 5 + 10; // JIT инлайнит код метода sum
🔹 Удаление мертвого кода
JIT всегда анализирует, выполняется ли тот или иной участок кода. Если метод или условие никогда не будет вызвано (например, условие, которое всегда ложно), JIT его удаляет.
🔹 Оптимизация циклов
Циклы — частая конструкция, и JIT всегда старается оптимизировать их выполнение. Например, если цикл выполняется много раз, JIT может переработать его для уменьшения количества операций.
🔹 Параллельная компиляция
Если ранее компиляция шла поэтапно, сейчас JIT может компилировать несколько частей программы одновременно. Это не только ускоряет работу компилятора, но и уменьшает задержки при запуске программы.
🔹 Использование final переменных
JIT предполагает, что final переменные не изменяются после инициализации. Это даёт компилятору возможность предсказать их значения и оптимизировать операции с ними.
🔹 Удаление лишних проверок
JIT понимает, какие проверки в коде избыточны. Например, если переменная была уже проверена на null, и это значение не изменяется, JIT может просто игнорировать повторные проверки.
🔹 Классификация горячих и холодных методов
JIT следит за тем, какие методы выполняются часто (горячие) и какие — нет (холодные). Он решает компилировать только те методы, которые чаще всего используются. Если горячий метод вдруг перестаёт быть горячим, JIT может освободить его от компиляции, освобождая ресурсы.
🔹 Слияние и удаление дубликатов
Если программа выполняет одинаковые операции несколько раз, JIT сливает их в одну или избавляется от дублирующихся вычислений.
Пример:
int a = 5;
int b = 5;
int c = a + b;
int d = a + b; // Дублирование
JIT может понять, что a + b одинаково для обеих операций, и просто использовать результат из первого вычисления во втором.
#CoreJava
Please open Telegram to view this post
VIEW IN TELEGRAM
👍5❤4🔥2
Многие недооценивают, насколько ArrayList может быть узким местом в производительности, особенно при работе с большими объёмами данных. Разберём, как оптимизировать его использование 👇
⚙️ Указывайте ёмкость при создании
По умолчанию:
List<String> list = new ArrayList<>();
ArrayList начинает с небольшой ёмкости (10 элементов) и каждый раз увеличивается в 1.5 раза, когда не хватает места. Это вызывает множество копирований массива, что замедляет работу при миллионах элементов.
List<String> list = new ArrayList<>(1_000_000);
Если вы знаете (или можете оценить) количество элементов заранее — выделите память сразу. Это уменьшает количество realocations и экономит до 30–40% времени при массовых вставках.
🧠 Очищайте, а не пересоздавайте
Многие делают так:
list = new ArrayList<>();
Это создаёт новый объект и выбрасывает старый в GC. При частых операциях — GC начинает тормозить систему.
list.clear();
Если список используется повторно, очистка быстрее и не требует новой аллокации памяти.
⚡️ Не используйте remove() в цикле
Удаление элементов в цикле вручную — частая и дорогая ошибка.
Плохо:
for (String s : list) {
if (s.startsWith("A")) list.remove(s);
}
❌ Такой код приведёт к ConcurrentModificationException.
Даже если использовать Iterator, это безопасно, но медленно — каждый вызов remove() сдвигает все последующие элементы (O(n) на удаление).
list.removeIf(s -> s.startsWith("A"));
Метод removeIf оптимизирован под внутренние операции и работает быстрее при массовом удалении.
* Кстати, у нас есть курс по Алгоритмам и структурам данных со скидкой.
Ставь → 🔥, если интересно почитать про внутреннюю реализацию стандартных методов коллекций.
#CoreJava
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥49👍9❤3
ConcurrentHashMap — это потокобезопасная реализация хэш-таблицы из пакета java.util.concurrent. В отличие от обычного HashMap, он допускает одновременные операции чтения и записи без глобальной блокировки всей таблицы.
📦 Базовая структура
Внутри ConcurrentHashMap хранит данные в массиве Node<K,V>[] table, где каждый элемент массива — это цепочка (связанный список или дерево).
Главная особенность:
— Вся таблица не блокируется целиком.
— Блокируется только нужный бакет при изменении.
— Для мелких структур используется synchronized на уровне ноды, для больших — tree bin locks (аналог TreeMap).
🔍 Как устроено хранение
— При вставке ключ хэшируется, чтобы равномерно распределить данные по бакетам.
— Каждая ячейка может содержать:
• Node — обычная запись (ключ/значение/ссылка).
• TreeBin — сбалансированное дерево при переполнении бакета (>8 элементов).
— Чтения (get) работают без блокировок, просто читают volatile-ссылки.
⚡️ Операции вставки и удаления
— put(K key, V value):
1. Высчитывается индекс бакета.
2. Если ячейка пустая, создаётся новая нода через CAS (Compare-And-Swap).
3. Если нет, блокируется только этот бакет (synchronized (f)), выполняется вставка.
— remove(Object key):
1. Определяется бакет.
2. Захватывается локальная блокировка на уровне бакета.
3. Узел удаляется, при необходимости структура перестраивается.
Сложность операций — O(1) в среднем случае, но с учётом блокировок.
🌊 Итераторы
Итераторы в ConcurrentHashMap — weakly consistent:
— Не выбрасывают ConcurrentModificationException.
— Видят часть изменений, сделанных другими потоками (в отличие от CopyOnWriteArrayList).
— Итерация не требует блокировок и не мешает параллельным вставкам или удалению.
📊 Производительность
— get() → практически O(1), без блокировок.
— put() / remove() → O(1) в среднем, но с локальной синхронизацией.
— Итерация → O(n), стабильная, но может не отражать все изменения.
⚖️ Важные нюансы
— С 8-й Java ConcurrentHashMap отказался от сегментов (Segment[]), теперь всё управляется атомарными операциями CAS и локальными синхронизациями.
— Не допускает null ключей и значений (в отличие от HashMap).
— Внутри используется LongAdder для счётчиков, чтобы избежать ложного sharing-а.
🧮 Когда использовать
— Часто читаемые и обновляемые словари (например, кэш, статистика, счётчики).
— Реализация пулов подключений, очередей задач, метрик и хранилищ состояний.
— Когда важно масштабирование под многоядерные системы без глобальных блокировок.
❗️ Не использовать, если:
— Обновления редки → Collections.synchronizedMap проще и дешевле.
— Нужен строгий порядок → лучше ConcurrentSkipListMap.
🔗 Документация: JavaDoc (Java 17)
Ставьте 🔥, если хотите разбор ConcurrentSkipListMap или LinkedBlockingQueue.
#CoreJava
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥18👍2👏1
В Java 8 появился CompletableFuture — это реализация паттерна Promise, которая позволяет строить декларативные цепочки асинхронных операций.
По сути, это обёртка над Future, которая может быть завершена вручную (отсюда "Completable") и предоставляет богатый API для композиции.
🔹 Зачем он нужен
Классический Future не позволяет:
— Комбинировать несколько асинхронных операций.
— Обрабатывать результат без блокировки.
— Реагировать на ошибки внутри цепочки.
CompletableFuture решает эти проблемы, предоставляя fluent API для композиции асинхронных вычислений.
🔹 Базовый пример
javaCompletableFuture<String> future = CompletableFuture
.supplyAsync(() -> fetchUserFromDB(userId))
.thenApply(user -> user.getEmail())
.thenApply(String::toUpperCase)
.exceptionally(ex -> "default@example.com");
future.thenAccept(System.out::println); // не блокирует
Здесь каждый этап выполняется асинхронно. Если где-то произошла ошибка, сработает exceptionally().
🔹 Ключевые методы
▪️ supplyAsync() / runAsync() — запустить задачу асинхронно.
▪️ thenApply() — трансформировать результат.
▪️ thenAccept() — обработать результат (void).
▪️ thenCompose() — развернуть вложенный CompletableFuture.
▪️ thenCombine() — объединить результаты двух независимых future.
▪️ exceptionally() / handle() — обработка ошибок.
▪️ allOf() / anyOf() — дождаться завершения нескольких задач.
🔹 Пулы потоков
По умолчанию CompletableFuture использует ForkJoinPool.commonPool(). Для задач с блокирующими операциями (IO, БД) лучше передать свой Executor. Иначе можно заблокировать общий пул и замедлить всё приложение.
🔹 Подводные камни
— Отсутствие отмены
CompletableFuture.cancel() не останавливает выполнение задачи, а только меняет статус. Реальная отмена требует проверки Thread.interrupted() внутри задачи.
— Проглатывание исключений
Если не добавить exceptionally() или handle(), исключение останется внутри future до вызова get() или join().
— Цепочки могут выполняться синхронно
Методы без суффикса Async (например, thenApply) могут выполниться в том же потоке, где завершился предыдущий этап. Если нужна гарантия асинхронности, используйте thenApplyAsync().
— Для композиции нескольких асинхронных операций (API-вызовы, запросы в БД).
— Когда нужны неблокирующие обработчики результатов.
— В реактивных архитектурах (хотя там лучше Project Reactor или RxJava).
— Для CPU-bound задач с высокой конкуренцией (лучше использовать параллельные стримы или явное управление потоками).
— Когда важна отмена выполняющейся задачи.
#CoreJava
Please open Telegram to view this post
VIEW IN TELEGRAM
👍6🔥3❤1