Реализации Deque: ArrayDeque и LinkedList
ArrayDeque
Описание: ArrayDeque — эффективная реализация Deque на основе кругового массива (circular array), который resizable. Она оптимизирована для операций на концах и рекомендуется как стандартная Deque в Java.
Особенности:
FIFO/LIFO: Поддерживает оба.
Уникальность: Нет.
Null: Разрешен.
Big O: O(1) amortized для addFirst/addLast, removeFirst/removeLast, peek (постоянное время). Contains — O(n).
Внутренняя работа: Массив с head и tail индексами. При добавлении/удалении индексы циклически сдвигаются. При заполнении массив удваивается (resizing O(n) rarely).
Нюансы:
Память: Эффективнее LinkedList (нет ссылок на узлы).
Initial capacity: Конструктор с int для начального размера (default 16).
Thread-safety: Нет — используйте для single-thread.
Когда использовать: Для большинства Deque-задач (быстрее LinkedList для концов).
Ограничение: Не реализует List, нет доступа по индексу.
Пример кода для ArrayDeque:
LinkedList
Описание: LinkedList — двусвязный список, который реализует Deque (и Queue, List). Как Deque, она позволяет операции на обоих концах.
Особенности:
FIFO/LIFO: Поддерживает оба.
Уникальность: Нет.
Null: Разрешен.
Big O: O(1) для addFirst/addLast, removeFirst/removeLast, peek (ссылки на first/last узлы). Contains — O(n).
Внутренняя работа: Узлы с prev/next ссылками. Добавление — создание узла и обновление ссылок first/last. Удаление — сдвиг ссылок.
Нюансы:
Память: Выше, чем ArrayDeque (каждый узел — объект с ссылками).
Универсальность: Реализует List, так что доступ по индексу (но O(n)).
Thread-safety: Нет.
Когда использовать: Для Deque с дополнительными List-функциями или частых вставок в середину (но для концов ArrayDeque быстрее).
Ограничение: Медленнее ArrayDeque для больших размеров из-за overhead узлов.
Пример кода для LinkedList как Deque (аналогичен ArrayDeque):
Полезные советы для новичков
ArrayDeque по умолчанию: Для большинства Deque-задач — эффективнее.
LinkedList для универсальности: Если нужно List API (get(index)), используйте её.
FIFO vs LIFO: Выбирайте методы (First/Last) по нуждам.
Null: Избегайте, чтобы не путаться.
Итераторы: descendingIterator() для обратного перебора — полезно для LIFO.
#Java #для_новичков #beginner #Collections #Deque #ArrayDeque #LinkedList
ArrayDeque
Описание: ArrayDeque — эффективная реализация Deque на основе кругового массива (circular array), который resizable. Она оптимизирована для операций на концах и рекомендуется как стандартная Deque в Java.
Особенности:
FIFO/LIFO: Поддерживает оба.
Уникальность: Нет.
Null: Разрешен.
Big O: O(1) amortized для addFirst/addLast, removeFirst/removeLast, peek (постоянное время). Contains — O(n).
Внутренняя работа: Массив с head и tail индексами. При добавлении/удалении индексы циклически сдвигаются. При заполнении массив удваивается (resizing O(n) rarely).
Нюансы:
Память: Эффективнее LinkedList (нет ссылок на узлы).
Initial capacity: Конструктор с int для начального размера (default 16).
Thread-safety: Нет — используйте для single-thread.
Когда использовать: Для большинства Deque-задач (быстрее LinkedList для концов).
Ограничение: Не реализует List, нет доступа по индексу.
Пример кода для ArrayDeque:
import java.util.ArrayDeque;
import java.util.Deque;
public class Main {
public static void main(String[] args) {
Deque<String> deque = new ArrayDeque<>();
// FIFO: Очередь
deque.offerLast("Элемент 1"); // Добавление в конец
deque.offerLast("Элемент 2");
System.out.println(deque.pollFirst()); // Элемент 1 (извлечение из начала)
System.out.println(deque.peekFirst()); // Элемент 2 (просмотр)
// LIFO: Стек
deque.offerFirst("Элемент 3"); // Добавление в начало
deque.offerFirst("Элемент 4");
System.out.println(deque.pollFirst()); // Элемент 4 (LIFO)
// Обратный итератор
for (String elem : deque.descendingIterator()) {
System.out.println(elem); // С конца к началу
}
}
}
Вывод: Показывает FIFO и LIFO, операции O(1).
LinkedList
Описание: LinkedList — двусвязный список, который реализует Deque (и Queue, List). Как Deque, она позволяет операции на обоих концах.
Особенности:
FIFO/LIFO: Поддерживает оба.
Уникальность: Нет.
Null: Разрешен.
Big O: O(1) для addFirst/addLast, removeFirst/removeLast, peek (ссылки на first/last узлы). Contains — O(n).
Внутренняя работа: Узлы с prev/next ссылками. Добавление — создание узла и обновление ссылок first/last. Удаление — сдвиг ссылок.
Нюансы:
Память: Выше, чем ArrayDeque (каждый узел — объект с ссылками).
Универсальность: Реализует List, так что доступ по индексу (но O(n)).
Thread-safety: Нет.
Когда использовать: Для Deque с дополнительными List-функциями или частых вставок в середину (но для концов ArrayDeque быстрее).
Ограничение: Медленнее ArrayDeque для больших размеров из-за overhead узлов.
Пример кода для LinkedList как Deque (аналогичен ArrayDeque):
import java.util.LinkedList;
import java.util.Deque;
public class Main {
public static void main(String[] args) {
Deque<String> deque = new LinkedList<>();
deque.addLast("Элемент 1");
deque.addLast("Элемент 2");
System.out.println(deque.removeFirst()); // Элемент 1
deque.addFirst("Элемент 3");
System.out.println(deque.removeFirst()); // Элемент 3 (LIFO)
}
}
Вывод: То же, что и ArrayDeque, но с List-возможностями.
Полезные советы для новичков
ArrayDeque по умолчанию: Для большинства Deque-задач — эффективнее.
LinkedList для универсальности: Если нужно List API (get(index)), используйте её.
FIFO vs LIFO: Выбирайте методы (First/Last) по нуждам.
Null: Избегайте, чтобы не путаться.
Итераторы: descendingIterator() для обратного перебора — полезно для LIFO.
#Java #для_новичков #beginner #Collections #Deque #ArrayDeque #LinkedList
👍2
Реактивное программирование
Горячие и холодные Publisher’ы в реактивном программировании
Publisher — это источник данных в Reactive Streams, который "толкает" элементы подписчикам (Subscriber). Но не все Publisher’ы одинаковы по поведению при множественных подписках. Это зависит от того, генерирует ли он данные независимо от подписчиков (горячий) или заново для каждого (холодный).
Холодный Publisher (Cold Publisher): Данные генерируются лениво — только при подписке, и для каждого подписчика отдельно. Это как видео по запросу: каждый зритель получает свою копию потока. Плюс: свежие данные каждый раз. Минус: если источник дорогой (запрос в БД, вычисления), повторяется зря.
Горячий Publisher (Hot Publisher): Данные генерируются независимо от подписчиков — поток "вещает" непрерывно. Подписчики "подключаются" к существующему потоку и получают данные с момента подписки. Это как живой эфир: все слушают одно и то же, но опоздавшие пропускают начало. Плюс: экономия ресурсов (один источник). Минус: данные могут быть "старыми" или пропущенными.
В Project Reactor большинство конструкторов — холодные (just, fromIterable, range), но есть горячие (interval, push). Поведение можно менять операторами (share, cache).
Примеры холодных Publisher’ов: ленивость и независимость
Холодные — default в Reactor: подписка запускает генерацию заново.
Пример с Mono (одиночный элемент, но принцип тот же):
С Flux:
Примеры горячих Publisher’ов: общий поток и вещание
Горячие — генерируют данные один раз, подписчики "присоединяются".
Пример с Flux.push (горячий по дизайну):
Другой горячий:
#Java #middle #Reactor #WebFlux #Mono #Flux
Горячие и холодные Publisher’ы в реактивном программировании
Publisher — это источник данных в Reactive Streams, который "толкает" элементы подписчикам (Subscriber). Но не все Publisher’ы одинаковы по поведению при множественных подписках. Это зависит от того, генерирует ли он данные независимо от подписчиков (горячий) или заново для каждого (холодный).
Холодный Publisher (Cold Publisher): Данные генерируются лениво — только при подписке, и для каждого подписчика отдельно. Это как видео по запросу: каждый зритель получает свою копию потока. Плюс: свежие данные каждый раз. Минус: если источник дорогой (запрос в БД, вычисления), повторяется зря.
Горячий Publisher (Hot Publisher): Данные генерируются независимо от подписчиков — поток "вещает" непрерывно. Подписчики "подключаются" к существующему потоку и получают данные с момента подписки. Это как живой эфир: все слушают одно и то же, но опоздавшие пропускают начало. Плюс: экономия ресурсов (один источник). Минус: данные могут быть "старыми" или пропущенными.
В Project Reactor большинство конструкторов — холодные (just, fromIterable, range), но есть горячие (interval, push). Поведение можно менять операторами (share, cache).
Примеры холодных Publisher’ов: ленивость и независимость
Холодные — default в Reactor: подписка запускает генерацию заново.
Пример с Mono (одиночный элемент, но принцип тот же):
Mono<String> coldMono = Mono.fromCallable(() -> {
System.out.println("Генерация данных...");
return "Данные";
});
coldMono.subscribe(System.out::println); // Вывод: "Генерация данных..." и "Данные"
coldMono.subscribe(System.out::println); // Снова: "Генерация данных..." и "Данные"
Каждый subscribe() вызывает fromCallable заново — данные свежие, но если это запрос в API, будет два вызова.С Flux:
Flux<Integer> coldFlux = Flux.range(1, 3).doOnSubscribe(sub -> System.out.println("Новая подписка!"));
coldFlux.subscribe(val -> System.out.println("Подписчик 1: " + val));
coldFlux.subscribe(val -> System.out.println("Подписчик 2: " + val));
// Вывод: "Новая подписка!" + 1,2,3 для первого; "Новая подписка!" + 1,2,3 для второго
Каждый подписчик получает полный поток независимо. Полезно для idempotent операций (без side-effects), как чтение статичных данных.
Асинхронный пример: coldFlux = Flux.interval(Duration.ofSeconds(1)).take(3); // Каждый subscribe() запускает свой таймер.Примеры горячих Publisher’ов: общий поток и вещание
Горячие — генерируют данные один раз, подписчики "присоединяются".
Пример с Flux.push (горячий по дизайну):
ConnectableFlux<Integer> hotFlux = Flux.push(sink -> {
// Симулируем внешний источник
new Thread(() -> {
for (int i = 1; i <= 3; i++) {
try { Thread.sleep(1000); } catch (InterruptedException e) {}
sink.next(i);
}
sink.complete();
}).start();
});
hotFlux.subscribe(val -> System.out.println("Подписчик 1: " + val));
Thread.sleep(1500); // Ждём, чтобы пропустить начало
hotFlux.subscribe(val -> System.out.println("Подписчик 2: " + val));
hotFlux.connect(); // Запуск горячего
// Вывод примерно: Подписчик 1: 1 (1с), 2 (2с), 3 (3с); Подписчик 2: 2 (присоединился после 1), 3
Второй пропустил 1 — поток общий. Connect() — триггер для ConnectableFlux (обёртка для горячих).Другой горячий:
Flux.interval(Duration.ofSeconds(1)) — бесконечный таймер, вещает независимо.
Оператор share(): Делает холодный горячим.
Flux<Integer> shared = coldFlux.share();
shared.subscribe(...); // Запускает
shared.subscribe(...); // Присоединяется к существующему
#Java #middle #Reactor #WebFlux #Mono #Flux
👍2
Переключение типов: операторы для контроля
Из холодного в горячий: share() (для Flux), cache() (кэширует элементы для повторов), publish() (ConnectableFlux с backpressure).
Пример cache:
coldMono.cache() — первый subscribe генерирует, последующие — из кэша.
Из горячего в холодный: Редко нужно, но replay() на ConnectableFlux кэширует историю для новых подписчиков.
Сценарии:
Холодный: Запросы к БД (каждый клиент — свежие данные).
Горячий: Мониторинг (один сенсор — всем подписчикам), стриминг событий (Kafka topic).
Практические советы и подводные камни
Диагностика: doOnSubscribe(() -> log("Subscribe")) — увидите, сколько раз запускается.
Камень: Холодный с side-effects (мутации) — непредсказуемо при множественных подписках; используйте defer() для ленивости.
Камень: Горячий бесконечный без take() — утечки; добавьте refCount() на publish() для авто-отписки при 0 подписчиках.
Совет: В WebFlux — Flux из БД (R2DBC, пост 17) холодный по умолчанию; share() для кэширования результатов.
Тестирование: StepVerifier с .publish() для симуляции горячих.
#Java #middle #Reactor #WebFlux #Mono #Flux
Из холодного в горячий: share() (для Flux), cache() (кэширует элементы для повторов), publish() (ConnectableFlux с backpressure).
Пример cache:
coldMono.cache() — первый subscribe генерирует, последующие — из кэша.
Из горячего в холодный: Редко нужно, но replay() на ConnectableFlux кэширует историю для новых подписчиков.
Сценарии:
Холодный: Запросы к БД (каждый клиент — свежие данные).
Горячий: Мониторинг (один сенсор — всем подписчикам), стриминг событий (Kafka topic).
Практические советы и подводные камни
Диагностика: doOnSubscribe(() -> log("Subscribe")) — увидите, сколько раз запускается.
Камень: Холодный с side-effects (мутации) — непредсказуемо при множественных подписках; используйте defer() для ленивости.
Камень: Горячий бесконечный без take() — утечки; добавьте refCount() на publish() для авто-отписки при 0 подписчиках.
Совет: В WebFlux — Flux из БД (R2DBC, пост 17) холодный по умолчанию; share() для кэширования результатов.
Тестирование: StepVerifier с .publish() для симуляции горячих.
#Java #middle #Reactor #WebFlux #Mono #Flux
👍2
Раздел 6. Коллекции в Java
Глава 4. Queue и Deque
Практика:
В «Библиотеке» добавить очередь читателей (Queue<String>) для книги. Когда книга возвращается — выдать её первому из очереди
Откройте проект «Библиотека»
Запустите IntelliJ IDEA и откройте существующий проект LibraryProject. Убедитесь, что классы Book и Library существуют: Book с полями title, author, year (и геттерами), Library с массивом книг, счетчиком bookCount, методом addBook и Set для авторов.
Импортируйте необходимые пакеты:
В файлах, где будете использовать Queue, убедитесь, что импортированы java.util.Queue и java.util.LinkedList (или ArrayDeque — выберите реализацию для FIFO). IDE подскажет, когда вы начнете писать код — используйте Ctrl+Enter для автодобавления импорта.
Выберите реализацию Queue:
Для этого задания используйте LinkedList<String> как Queue — она проста для FIFO и позволяет операции на концах. Альтернатива — ArrayDeque для большей эффективности, если хотите поэкспериментировать.
Обновление класса Book для очереди читателей
Поскольку очередь читателей относится к конкретной книге (кто ждет эту книгу), добавим её в класс Book.
Добавьте поле для Queue<String>:
Откройте файл Book.java.
Объявите приватное поле readerQueue типа Queue<String>, инициализированное как new LinkedList<> (или new ArrayDeque<>).
Это множество будет хранить имена читателей в порядке очереди (FIFO: первый добавленный — первый получит книгу).
Добавьте методы для работы с очередью
Создайте публичный метод addToQueue(String readerName), который:
Добавляет имя читателя в конец очереди с помощью offer (readerQueue.offer(readerName)).
Можно вывести сообщение, например, "Читатель [readerName] добавлен в очередь за книгой [title]".
Создайте публичный метод returnBook(), который моделирует возврат книги:
Проверяет, пуста ли очередь (readerQueue.isEmpty()).
Если пуста — выводит сообщение "Книга свободна, очередь пуста".
Если не пуста — извлекает первого читателя с помощью poll (String nextReader = readerQueue.poll();).
Выводит сообщение "Книга выдана следующему читателю: [nextReader]".
(Опционально: Если книга была занята, здесь можно обновить статус, но для простоты пока опустим).
Обновите конструктор Book:
В конструкторе Book убедитесь, что readerQueue инициализируется (если не сделали при объявлении поля).
Интеграция с классом Library
Теперь обновим Library, чтобы при добавлении книги учитывать очередь, но поскольку очередь в Book, Library будет работать с объектами Book.
Обновите метод addBook(Book book):
В методе addBook оставьте добавление в массив книг, но добавьте проверку: Если очередь читателей в книге не пуста (book.getReaderQueue().isEmpty() == false), выведите сообщение "Книга добавлена, но имеет очередь читателей".
(Это опционально, но поможет связать с практикой).
Добавьте метод для возврата книги
Создайте публичный метод returnBookByTitle(String title), который:
Ищет книгу в массиве по title (перебор с equals).
Если найдена — вызывает returnBook() на объекте Book.
Если не найдена — выводит сообщение "Книга не найдена".
Обновление класса Main для тестирования
Теперь протестируем новую функциональность в Main.
Создайте объекты и добавьте книги:
В методе main создайте объект Library.
Создайте объект Book (например, с title "Война и мир", author "Толстой", year 1869).
Вызовите addBook на Library.
Добавьте читателей в очередь:
Через объект Book вызовите addToQueue несколько раз с разными именами читателей (например, "Иван", "Мария", "Петр").
Вызовите returnBook() на Book — первый читатель ("Иван") должен получить книгу, остальные остаются в очереди.
Протестируйте возврат:
Вызовите returnBook() еще раз — следующий читатель ("Мария") получит.
Продолжите, пока очередь не опустеет — последний вызов вернет null или сообщение о пустоте.
#Java #для_новичков #beginner #Collections #Deque #ArrayDeque #LinkedList
Глава 4. Queue и Deque
Практика:
В «Библиотеке» добавить очередь читателей (Queue<String>) для книги. Когда книга возвращается — выдать её первому из очереди
Откройте проект «Библиотека»
Запустите IntelliJ IDEA и откройте существующий проект LibraryProject. Убедитесь, что классы Book и Library существуют: Book с полями title, author, year (и геттерами), Library с массивом книг, счетчиком bookCount, методом addBook и Set для авторов.
Импортируйте необходимые пакеты:
В файлах, где будете использовать Queue, убедитесь, что импортированы java.util.Queue и java.util.LinkedList (или ArrayDeque — выберите реализацию для FIFO). IDE подскажет, когда вы начнете писать код — используйте Ctrl+Enter для автодобавления импорта.
Выберите реализацию Queue:
Для этого задания используйте LinkedList<String> как Queue — она проста для FIFO и позволяет операции на концах. Альтернатива — ArrayDeque для большей эффективности, если хотите поэкспериментировать.
Обновление класса Book для очереди читателей
Поскольку очередь читателей относится к конкретной книге (кто ждет эту книгу), добавим её в класс Book.
Добавьте поле для Queue<String>:
Откройте файл Book.java.
Объявите приватное поле readerQueue типа Queue<String>, инициализированное как new LinkedList<> (или new ArrayDeque<>).
Это множество будет хранить имена читателей в порядке очереди (FIFO: первый добавленный — первый получит книгу).
Добавьте методы для работы с очередью
Создайте публичный метод addToQueue(String readerName), который:
Добавляет имя читателя в конец очереди с помощью offer (readerQueue.offer(readerName)).
Можно вывести сообщение, например, "Читатель [readerName] добавлен в очередь за книгой [title]".
Создайте публичный метод returnBook(), который моделирует возврат книги:
Проверяет, пуста ли очередь (readerQueue.isEmpty()).
Если пуста — выводит сообщение "Книга свободна, очередь пуста".
Если не пуста — извлекает первого читателя с помощью poll (String nextReader = readerQueue.poll();).
Выводит сообщение "Книга выдана следующему читателю: [nextReader]".
(Опционально: Если книга была занята, здесь можно обновить статус, но для простоты пока опустим).
Обновите конструктор Book:
В конструкторе Book убедитесь, что readerQueue инициализируется (если не сделали при объявлении поля).
Интеграция с классом Library
Теперь обновим Library, чтобы при добавлении книги учитывать очередь, но поскольку очередь в Book, Library будет работать с объектами Book.
Обновите метод addBook(Book book):
В методе addBook оставьте добавление в массив книг, но добавьте проверку: Если очередь читателей в книге не пуста (book.getReaderQueue().isEmpty() == false), выведите сообщение "Книга добавлена, но имеет очередь читателей".
(Это опционально, но поможет связать с практикой).
Добавьте метод для возврата книги
Создайте публичный метод returnBookByTitle(String title), который:
Ищет книгу в массиве по title (перебор с equals).
Если найдена — вызывает returnBook() на объекте Book.
Если не найдена — выводит сообщение "Книга не найдена".
Обновление класса Main для тестирования
Теперь протестируем новую функциональность в Main.
Создайте объекты и добавьте книги:
В методе main создайте объект Library.
Создайте объект Book (например, с title "Война и мир", author "Толстой", year 1869).
Вызовите addBook на Library.
Добавьте читателей в очередь:
Через объект Book вызовите addToQueue несколько раз с разными именами читателей (например, "Иван", "Мария", "Петр").
Вызовите returnBook() на Book — первый читатель ("Иван") должен получить книгу, остальные остаются в очереди.
Протестируйте возврат:
Вызовите returnBook() еще раз — следующий читатель ("Мария") получит.
Продолжите, пока очередь не опустеет — последний вызов вернет null или сообщение о пустоте.
#Java #для_новичков #beginner #Collections #Deque #ArrayDeque #LinkedList
👍2
Тестирование и отладка
После реализации протестируйте, чтобы убедиться в правильной работе очереди.
Запустите проект:
Правой кнопкой на Main.java → Run 'Main.main()'.
В консоли увидите сообщения о добавлении читателей и выдаче книги по порядку (FIFO: первый добавленный — первый получает).
Проверьте FIFO:
Убедитесь, что читатели выдаются в порядке добавления (offer в конец, poll из начала).
Попробуйте добавить null как читателя — проверьте поведение (LinkedList позволит).
Отладка:
Установите breakpoint в методе returnBook перед poll и после — шагайте (F8) и смотрите размер очереди (readerQueue.size()).
Если ошибки: NullPointerException (если очередь не инициализирована или книга не найдена) — добавьте проверки if (readerQueue != null && !readerQueue.isEmpty()).
ArrayIndexOutOfBoundsException в массиве книг — расширьте массив или используйте динамический список (позже заменим на List).
Эксперименты:
Измените реализацию Queue на ArrayDeque — проверьте, работает ли аналогично (да, но эффективнее по памяти).
Добавьте метод isQueueEmpty() в Book для проверки пустоты — используйте в Library для статистики.
Полезные советы для новичков
Инициализация Queue: Всегда инициализируйте в конструкторе Book (readerQueue = new LinkedList<>();), чтобы избежать NullPointerException.
Проверка возвращаемого: Poll возвращает null при пустой — используйте if (nextReader != null) для сообщений.
Расширение: Подумайте о статусе книги (boolean isAvailable) — при возврате устанавливайте true, при выдаче — false.
Массив книг: Пока используем массив, но заметьте ограничения — в следующих уроках заменим на List<Book>.
Thread-safety: Если проект вырастет, подумайте о ConcurrentLinkedQueue для многопоточности.
#Java #для_новичков #beginner #Collections #Deque #ArrayDeque #LinkedList
После реализации протестируйте, чтобы убедиться в правильной работе очереди.
Запустите проект:
Правой кнопкой на Main.java → Run 'Main.main()'.
В консоли увидите сообщения о добавлении читателей и выдаче книги по порядку (FIFO: первый добавленный — первый получает).
Проверьте FIFO:
Убедитесь, что читатели выдаются в порядке добавления (offer в конец, poll из начала).
Попробуйте добавить null как читателя — проверьте поведение (LinkedList позволит).
Отладка:
Установите breakpoint в методе returnBook перед poll и после — шагайте (F8) и смотрите размер очереди (readerQueue.size()).
Если ошибки: NullPointerException (если очередь не инициализирована или книга не найдена) — добавьте проверки if (readerQueue != null && !readerQueue.isEmpty()).
ArrayIndexOutOfBoundsException в массиве книг — расширьте массив или используйте динамический список (позже заменим на List).
Эксперименты:
Измените реализацию Queue на ArrayDeque — проверьте, работает ли аналогично (да, но эффективнее по памяти).
Добавьте метод isQueueEmpty() в Book для проверки пустоты — используйте в Library для статистики.
Полезные советы для новичков
Инициализация Queue: Всегда инициализируйте в конструкторе Book (readerQueue = new LinkedList<>();), чтобы избежать NullPointerException.
Проверка возвращаемого: Poll возвращает null при пустой — используйте if (nextReader != null) для сообщений.
Расширение: Подумайте о статусе книги (boolean isAvailable) — при возврате устанавливайте true, при выдаче — false.
Массив книг: Пока используем массив, но заметьте ограничения — в следующих уроках заменим на List<Book>.
Thread-safety: Если проект вырастет, подумайте о ConcurrentLinkedQueue для многопоточности.
#Java #для_новичков #beginner #Collections #Deque #ArrayDeque #LinkedList
👍2
Что такое gRPC и зачем он нужен
Представьте, что ваши программы — это люди в огромном городе. Им нужно обмениваться информацией: один спрашивает адрес, другой — погоду, третий — заказывает еду. Если они общаются письмами (медленно и объемно), то город встанет в пробке. А если по телефону — быстро, четко и без лишних слов?
Вот gRPC (высокопроизводительный фреймворк для удаленного вызова процедур) — это как суперсовременная телефонная сеть для программ. Он позволяет одной программе вызывать функции в другой, как будто они работают на одном компьютере, но через интернет или сеть.
Зачем изучать gRPC в 2025 году? Потому что мир софта ушел от простых сайтов к сложным системам: миллиарды устройств в интернете вещей, тысячи микросервисов в компаниях вроде Netflix или Uber. Обычные веб-API (как REST) здесь тормозят — gRPC ускоряет всё в 7–10 раз, экономит трафик и упрощает разработку.
Что такое RPC: Простое объяснение концепции удаленного вызова процедур
Удаленный вызов процедур (RPC — Remote Procedure Call) — это идея, где программа А "звонит" программе Б и говорит: "Выполни эту функцию и верни результат". Всё выглядит как обычный вызов локальной функции: передаешь данные, ждешь ответ — и готово. Никаких сложностей с сетью на уровне кода.
Пример из жизни: Вы в приложении нажимаете "Получить погоду" — ваше мобильное app вызывает функцию на сервере метеослужбы. Сервер считает, возвращает данные. В gRPC это пишется так же просто:
Без RPC пришлось бы вручную формировать запросы, парсить ответы — утомительно. gRPC автоматизирует всё: генерирует готовый код, управляет соединением и ошибками. В итоге — код чище, быстрее и надежнее.
Разница gRPC с REST: Почему RPC побеждает в скорости и гибкости
REST (Representational State Transfer — архитектурный стиль для веб-API) — популярный подход, где API строится вокруг эндпоинтов для получения данных. Использует HTTP-методы (GET, POST), текст в формате JSON. Просто читать человеку, но медленно для машин.
Вот ключевые различия:
Формат данных: REST — текст (JSON, большой и медленный), gRPC — бинарный (компактный, в 3–10 раз меньше). JSON "Привет, мир!" — 15 байт, в gRPC — 5 байт.
Скорость: gRPC на HTTP/2 (мультиплексирование — много запросов по одному каналу) в 7 раз быстрее при приеме данных, в 10 раз — при отправке. Идеально для мобильного интернета.
Гибкость общения: REST — только "запрос-ответ". gRPC поддерживает стриминг (поток сообщений): сервер шлет обновления в реальном времени (как чат), клиент загружает файлы порциями, или оба общаются одновременно.
Языки и платформы: REST — универсален, но код пишется вручную. gRPC генерирует код для 10+ языков (Python, Go, Java) из одного описания — никаких несоответствий.
Отладка: REST читаем в браузере, gRPC — бинарный, но есть инструменты вроде gRPCurl (аналог curl).
REST хорош для публичных API (сайты, простые apps). gRPC — для внутренних систем, где важна производительность. В 2025 году компании комбинируют: REST снаружи, gRPC внутри.
#Java #middle #gRPC
Представьте, что ваши программы — это люди в огромном городе. Им нужно обмениваться информацией: один спрашивает адрес, другой — погоду, третий — заказывает еду. Если они общаются письмами (медленно и объемно), то город встанет в пробке. А если по телефону — быстро, четко и без лишних слов?
Вот gRPC (высокопроизводительный фреймворк для удаленного вызова процедур) — это как суперсовременная телефонная сеть для программ. Он позволяет одной программе вызывать функции в другой, как будто они работают на одном компьютере, но через интернет или сеть.
Зачем изучать gRPC в 2025 году? Потому что мир софта ушел от простых сайтов к сложным системам: миллиарды устройств в интернете вещей, тысячи микросервисов в компаниях вроде Netflix или Uber. Обычные веб-API (как REST) здесь тормозят — gRPC ускоряет всё в 7–10 раз, экономит трафик и упрощает разработку.
Что такое RPC: Простое объяснение концепции удаленного вызова процедур
Удаленный вызов процедур (RPC — Remote Procedure Call) — это идея, где программа А "звонит" программе Б и говорит: "Выполни эту функцию и верни результат". Всё выглядит как обычный вызов локальной функции: передаешь данные, ждешь ответ — и готово. Никаких сложностей с сетью на уровне кода.
Пример из жизни: Вы в приложении нажимаете "Получить погоду" — ваше мобильное app вызывает функцию на сервере метеослужбы. Сервер считает, возвращает данные. В gRPC это пишется так же просто:
Клиент (ваше app): ответ = сервер.ПолучитьПогоду(город="Москва")
Сервер: выполняет расчет и отдает результат.
Без RPC пришлось бы вручную формировать запросы, парсить ответы — утомительно. gRPC автоматизирует всё: генерирует готовый код, управляет соединением и ошибками. В итоге — код чище, быстрее и надежнее.
Разница gRPC с REST: Почему RPC побеждает в скорости и гибкости
REST (Representational State Transfer — архитектурный стиль для веб-API) — популярный подход, где API строится вокруг эндпоинтов для получения данных. Использует HTTP-методы (GET, POST), текст в формате JSON. Просто читать человеку, но медленно для машин.
Вот ключевые различия:
Формат данных: REST — текст (JSON, большой и медленный), gRPC — бинарный (компактный, в 3–10 раз меньше). JSON "Привет, мир!" — 15 байт, в gRPC — 5 байт.
Скорость: gRPC на HTTP/2 (мультиплексирование — много запросов по одному каналу) в 7 раз быстрее при приеме данных, в 10 раз — при отправке. Идеально для мобильного интернета.
Гибкость общения: REST — только "запрос-ответ". gRPC поддерживает стриминг (поток сообщений): сервер шлет обновления в реальном времени (как чат), клиент загружает файлы порциями, или оба общаются одновременно.
Языки и платформы: REST — универсален, но код пишется вручную. gRPC генерирует код для 10+ языков (Python, Go, Java) из одного описания — никаких несоответствий.
Отладка: REST читаем в браузере, gRPC — бинарный, но есть инструменты вроде gRPCurl (аналог curl).
REST хорош для публичных API (сайты, простые apps). gRPC — для внутренних систем, где важна производительность. В 2025 году компании комбинируют: REST снаружи, gRPC внутри.
#Java #middle #gRPC
👍2👎1
Почему Google создал gRPC: От внутренних нужд к мировому стандарту
Google — гигант с миллионами микросервисов (маленькие программы, работающие вместе). С 2001 года они использовали внутренний фреймворк Stubby для их связи. Но Stubby был закрытым, и партнерам (Android, YouTube API) приходилось писать свои библиотеки.
В 2015 году Google открыл gRPC: эволюцию Stubby на HTTP/2 и Protocol Buffers (protobuf — бинарный формат для данных).
Цели:
Объединить сервисы в дата-центрах и на устройствах.
Поддержка стриминга для реал-тайма.
Автоматическая генерация кода — один .proto-файл для всех языков.
К 2025 году gRPC — проект CNCF (Cloud Native Computing Foundation), используется в Google Cloud, Netflix (стриминг видео), Uber (поездки в реальном времени), Cisco. За 10 лет обработал триллионы вызовов — доказанная надежность.
Где применяется gRPC: От микросервисов до умных устройств
gRPC — король сценариев с высокой нагрузкой:
Микросервисы: Тысячи маленьких сервисов в Kubernetes (оркестратор контейнеров). Netflix использует для рекомендаций фильмов — миллиарды запросов в секунду без задержек. Внутренняя связь: сервис оплаты "звонит" сервису доставки.
Интернет вещей (IoT): Миллиарды устройств (умные лампочки, датчики). gRPC соединяет их с облаком: низкий трафик, стриминг данных (температура в реальном времени). Пример: умный дом от Google Nest.
Внутренние API: В компаниях — связь backend'ов. Uber: расчет маршрутов между сервисами. Банки: обработка транзакций. Не для клиентов (там REST), а внутри — для скорости.
В 2025: gRPC в AI (TensorFlow), играх (реал-тайм мультиплеер), авто (Tesla — связь машин с облаком).
#Java #middle #gRPC
Google — гигант с миллионами микросервисов (маленькие программы, работающие вместе). С 2001 года они использовали внутренний фреймворк Stubby для их связи. Но Stubby был закрытым, и партнерам (Android, YouTube API) приходилось писать свои библиотеки.
В 2015 году Google открыл gRPC: эволюцию Stubby на HTTP/2 и Protocol Buffers (protobuf — бинарный формат для данных).
Цели:
Объединить сервисы в дата-центрах и на устройствах.
Поддержка стриминга для реал-тайма.
Автоматическая генерация кода — один .proto-файл для всех языков.
К 2025 году gRPC — проект CNCF (Cloud Native Computing Foundation), используется в Google Cloud, Netflix (стриминг видео), Uber (поездки в реальном времени), Cisco. За 10 лет обработал триллионы вызовов — доказанная надежность.
Где применяется gRPC: От микросервисов до умных устройств
gRPC — король сценариев с высокой нагрузкой:
Микросервисы: Тысячи маленьких сервисов в Kubernetes (оркестратор контейнеров). Netflix использует для рекомендаций фильмов — миллиарды запросов в секунду без задержек. Внутренняя связь: сервис оплаты "звонит" сервису доставки.
Интернет вещей (IoT): Миллиарды устройств (умные лампочки, датчики). gRPC соединяет их с облаком: низкий трафик, стриминг данных (температура в реальном времени). Пример: умный дом от Google Nest.
Внутренние API: В компаниях — связь backend'ов. Uber: расчет маршрутов между сервисами. Банки: обработка транзакций. Не для клиентов (там REST), а внутри — для скорости.
В 2025: gRPC в AI (TensorFlow), играх (реал-тайм мультиплеер), авто (Tesla — связь машин с облаком).
#Java #middle #gRPC
👍3
Раздел 6. Коллекции в Java
Глава 5. Map — отображения (словари)
Интерфейс Map<K, V> — это часть Java Collections Framework (JCF) из пакета java.util, который представляет структуру для хранения ассоциативных данных. В отличие от других коллекций, таких как List или Set, которые хранят отдельные элементы, Map хранит пары, где каждый ключ (K) связан с значением (V). Это позволяет быстро находить значение по ключу, как в словаре или телефонной книге.
Основные понятия:
Ключ (Key): Уникальный идентификатор для доступа к значению. Ключи не могут дублироваться — если добавить пару с существующим ключом, значение перезапишется.
Значение (Value): Данные, ассоциированные с ключом. Значения могут дублироваться.
Пара (Entry): Единица хранения в Map — комбинация ключа и значения.
Map моделирует математическое отображение (mapping), где каждый ключ maps to ровно одно значение. Это делает Map идеальным для сценариев, где нужна ассоциация, например, ID пользователя — профиль, слово — перевод.
Отличия от Collection:
Map не расширяет Collection<E> — это отдельная ветвь JCF.
Collection — последовательность элементов, Map — ассоциативный массив.
В Map нет индексации (нет get(int index)), доступ только по ключу.
Размер Map — количество пар, не элементов.
Generics в Map: <K, V> обеспечивает типобезопасность: ключи одного типа (например, Integer), значения другого (String). Без generics (raw Map) — устарело и небезопасно.
Хранение пар «ключ–значение»: Особенности
Map хранит данные в форме пар, где ключ — уникальный, а значение — связанное с ним. Это позволяет эффективно решать задачи поиска и ассоциации.
Уникальность ключей:
Ключи всегда уникальны: Map не позволяет дубликаты ключей. Если ключ уже существует, значение обновляется.
Уникальность определяется методами equals() и hashCode() (в hash-based реализациях) или compareTo() (в sorted).
Нюанс: Для custom ключей обязательно переопределите equals() и hashCode() — иначе уникальность по ссылке, не по значению.
Дубликаты значений:
Значения могут повторяться: Несколько ключей могут ссылаться на одно значение.
Нюанс: Если значение — mutable объект, изменения в одном месте отразятся везде (по ссылке).
Null в Map:
Ключи: Большинство реализаций позволяют один null-ключ (HashMap, LinkedHashMap), но TreeMap — нет (NullPointerException).
Значения: Null разрешен всегда.
Порядок в Map:
В общем случае нет (зависит от реализации): Не полагайтесь на порядок пар.
Нюанс: Map не упорядочен, как List, но некоторые реализации добавляют порядок.
Размер и емкость:
Размер (size()) — количество пар.
Емкость: В hash-based — initial capacity и load factor (например, 0.75 — при заполнении >75% ресайз).
Производительность:
В среднем O(1) для доступа по ключу в hash-based, O(log n) в tree-based.
Нюанс: Зависит от качества hashCode() — плохие хэши приводят к деградации до O(n).
Полезные советы для новичков
Выбор типов K/V: Ключи — immutable (String, Integer), чтобы избежать изменений, влияющих на хэш.
Custom ключи: Переопределяйте equals/hashCode (IDE поможет: Generate → equals() and hashCode()).
Map vs другие коллекции: Используйте Map для ассоциаций, Set для уникальных элементов, List для последовательностей.
#Java #для_новичков #beginner #Map
Глава 5. Map — отображения (словари)
Интерфейс Map<K, V> — это часть Java Collections Framework (JCF) из пакета java.util, который представляет структуру для хранения ассоциативных данных. В отличие от других коллекций, таких как List или Set, которые хранят отдельные элементы, Map хранит пары, где каждый ключ (K) связан с значением (V). Это позволяет быстро находить значение по ключу, как в словаре или телефонной книге.
Основные понятия:
Ключ (Key): Уникальный идентификатор для доступа к значению. Ключи не могут дублироваться — если добавить пару с существующим ключом, значение перезапишется.
Значение (Value): Данные, ассоциированные с ключом. Значения могут дублироваться.
Пара (Entry): Единица хранения в Map — комбинация ключа и значения.
Map моделирует математическое отображение (mapping), где каждый ключ maps to ровно одно значение. Это делает Map идеальным для сценариев, где нужна ассоциация, например, ID пользователя — профиль, слово — перевод.
Отличия от Collection:
Map не расширяет Collection<E> — это отдельная ветвь JCF.
Collection — последовательность элементов, Map — ассоциативный массив.
В Map нет индексации (нет get(int index)), доступ только по ключу.
Размер Map — количество пар, не элементов.
Generics в Map: <K, V> обеспечивает типобезопасность: ключи одного типа (например, Integer), значения другого (String). Без generics (raw Map) — устарело и небезопасно.
Хранение пар «ключ–значение»: Особенности
Map хранит данные в форме пар, где ключ — уникальный, а значение — связанное с ним. Это позволяет эффективно решать задачи поиска и ассоциации.
Уникальность ключей:
Ключи всегда уникальны: Map не позволяет дубликаты ключей. Если ключ уже существует, значение обновляется.
Уникальность определяется методами equals() и hashCode() (в hash-based реализациях) или compareTo() (в sorted).
Нюанс: Для custom ключей обязательно переопределите equals() и hashCode() — иначе уникальность по ссылке, не по значению.
Дубликаты значений:
Значения могут повторяться: Несколько ключей могут ссылаться на одно значение.
Нюанс: Если значение — mutable объект, изменения в одном месте отразятся везде (по ссылке).
Null в Map:
Ключи: Большинство реализаций позволяют один null-ключ (HashMap, LinkedHashMap), но TreeMap — нет (NullPointerException).
Значения: Null разрешен всегда.
Порядок в Map:
В общем случае нет (зависит от реализации): Не полагайтесь на порядок пар.
Нюанс: Map не упорядочен, как List, но некоторые реализации добавляют порядок.
Размер и емкость:
Размер (size()) — количество пар.
Емкость: В hash-based — initial capacity и load factor (например, 0.75 — при заполнении >75% ресайз).
Производительность:
В среднем O(1) для доступа по ключу в hash-based, O(log n) в tree-based.
Нюанс: Зависит от качества hashCode() — плохие хэши приводят к деградации до O(n).
Полезные советы для новичков
Выбор типов K/V: Ключи — immutable (String, Integer), чтобы избежать изменений, влияющих на хэш.
Custom ключи: Переопределяйте equals/hashCode (IDE поможет: Generate → equals() and hashCode()).
Map vs другие коллекции: Используйте Map для ассоциаций, Set для уникальных элементов, List для последовательностей.
#Java #для_новичков #beginner #Map
👍4
Архитектура gRPC: как всё работает под капотом
gRPC — это современный фреймворк удалённого вызова процедур (RPC, Remote Procedure Call), разработанный Google. Он позволяет приложениям общаться друг с другом как будто они вызывают локальные функции, хотя на самом деле взаимодействие идёт по сети. Чтобы понять, почему gRPC так эффективен, нужно разобрать его архитектуру и то, что происходит «под капотом».
1. Концепция gRPC: RPC-модель нового поколения
RPC (Remote Procedure Call) — это подход, при котором одна программа может вызвать функцию, которая физически исполняется на другом сервере.
В классической модели RPC разработчик просто вызывает метод, а инфраструктура берёт на себя всё — упаковку данных, передачу по сети и распаковку на другой стороне.
gRPC реализует эту идею, но в современном, высокопроизводительном виде — поверх HTTP/2 и с использованием Protocol Buffers для сериализации.
2. Основные участники архитектуры
Клиент (Client)
Это программа, которая инициирует вызов удалённого метода. Она не знает деталей того, как сервер устроен внутри.
Клиент работает с client stub — это локальный объект, который выглядит как обычный класс с методами, но при вызове каждого метода на самом деле выполняется сетевое обращение к серверу.
Сервер (Server)
Это приложение, которое реализует интерфейс, описанный в .proto файле. Сервер принимает запросы от клиентов, обрабатывает их и отправляет ответы.
Client Stub и Server Stub
Что такое Stub
Stub — это «заглушка», или точнее — сгенерированный код, который связывает ваш код с gRPC-инфраструктурой.
Client Stub (клиентская заглушка) — это класс, который содержит методы, соответствующие сервисам, определённым в .proto.
Когда вы вызываете метод stub.buyCar(request), gRPC автоматически:
Сериализует объект request в бинарный формат.
Отправляет его по сети через HTTP/2.
Получает ответ, десериализует и возвращает его как обычный объект Java.
Server Stub — это абстрактный класс, который вы расширяете, чтобы реализовать свою бизнес-логику. Он автоматически принимает входящие вызовы, десериализует данные и вызывает ваш метод.
Пример:
После генерации protoc создаёт классы:
Реализация сервера:
Клиент:
#Java #middle #gRPC
gRPC — это современный фреймворк удалённого вызова процедур (RPC, Remote Procedure Call), разработанный Google. Он позволяет приложениям общаться друг с другом как будто они вызывают локальные функции, хотя на самом деле взаимодействие идёт по сети. Чтобы понять, почему gRPC так эффективен, нужно разобрать его архитектуру и то, что происходит «под капотом».
1. Концепция gRPC: RPC-модель нового поколения
RPC (Remote Procedure Call) — это подход, при котором одна программа может вызвать функцию, которая физически исполняется на другом сервере.
В классической модели RPC разработчик просто вызывает метод, а инфраструктура берёт на себя всё — упаковку данных, передачу по сети и распаковку на другой стороне.
gRPC реализует эту идею, но в современном, высокопроизводительном виде — поверх HTTP/2 и с использованием Protocol Buffers для сериализации.
2. Основные участники архитектуры
Клиент (Client)
Это программа, которая инициирует вызов удалённого метода. Она не знает деталей того, как сервер устроен внутри.
Клиент работает с client stub — это локальный объект, который выглядит как обычный класс с методами, но при вызове каждого метода на самом деле выполняется сетевое обращение к серверу.
Сервер (Server)
Это приложение, которое реализует интерфейс, описанный в .proto файле. Сервер принимает запросы от клиентов, обрабатывает их и отправляет ответы.
Client Stub и Server Stub
Что такое Stub
Stub — это «заглушка», или точнее — сгенерированный код, который связывает ваш код с gRPC-инфраструктурой.
Client Stub (клиентская заглушка) — это класс, который содержит методы, соответствующие сервисам, определённым в .proto.
Когда вы вызываете метод stub.buyCar(request), gRPC автоматически:
Сериализует объект request в бинарный формат.
Отправляет его по сети через HTTP/2.
Получает ответ, десериализует и возвращает его как обычный объект Java.
Server Stub — это абстрактный класс, который вы расширяете, чтобы реализовать свою бизнес-логику. Он автоматически принимает входящие вызовы, десериализует данные и вызывает ваш метод.
Пример:
// .proto файл
syntax = "proto3";
service CarService {
rpc BuyCar (CarRequest) returns (CarResponse);
}
message CarRequest {
string model = 1;
int32 budget = 2;
}
message CarResponse {
string status = 1;
}
После генерации protoc создаёт классы:
CarServiceGrpc.CarServiceImplBase — Server Stub
CarServiceGrpc.CarServiceBlockingStub и CarServiceGrpc.CarServiceFutureStub — Client Stubs
Реализация сервера:
public class CarServiceImpl extends CarServiceGrpc.CarServiceImplBase {
@Override
public void buyCar(CarRequest request, StreamObserver<CarResponse> responseObserver) {
String result = "You bought: " + request.getModel();
CarResponse response = CarResponse.newBuilder()
.setStatus(result)
.build();
responseObserver.onNext(response);
responseObserver.onCompleted();
}
}Клиент:
ManagedChannel channel = ManagedChannelBuilder.forAddress("localhost", 8080)
.usePlaintext()
.build();
CarServiceGrpc.CarServiceBlockingStub stub = CarServiceGrpc.newBlockingStub(channel);
CarRequest request = CarRequest.newBuilder()
.setModel("Tesla Model 3")
.setBudget(50000)
.build();
CarResponse response = stub.buyCar(request);
System.out.println(response.getStatus());#Java #middle #gRPC
👍2
3. Роль Protocol Buffers (protobuf)
Protocol Buffers — это бинарный формат сериализации данных, разработанный Google. Он выполняет две функции:
Описание структуры данных (через .proto файл).
Это аналог схемы JSON или XML, но строгий и типизированный.
Сериализация и десериализация (преобразование объектов в компактную бинарную форму и обратно).
Пример .proto файла не только описывает сообщения, но и определяет сервис (то есть API интерфейс).
Почему protobuf — ключевой элемент:
Он компактен: бинарный формат в несколько раз меньше JSON.
Он типобезопасен: при компиляции проверяются типы.
Он быстр: сериализация и десериализация работают на уровне байтов, без парсинга текста.
4. Как происходит сериализация и десериализация
Сериализация — это процесс превращения объекта в поток байтов для передачи по сети.
Десериализация — обратный процесс.
В gRPC:
Клиент вызывает метод stub.method(request).
request сериализуется с помощью Protocol Buffers в бинарный поток.
Поток отправляется через HTTP/2.
Сервер принимает поток, десериализует его обратно в объект CarRequest.
После обработки сервер сериализует ответ (CarResponse) и отправляет обратно.
Важно: gRPC сам управляет сериализацией. Вам не нужно ничего кодировать вручную — всё делает сгенерированный stub.
5. Что делает protoc и зачем нужны плагины
protoc — это компилятор Protocol Buffers. Он принимает .proto файл и генерирует исходный код для нужного языка.
Например:
gRPC добавляет ещё один плагин — --grpc-java_out, который генерирует код для stub'ов.
Таким образом, protoc создаёт:
Классы-сообщения (CarRequest, CarResponse)
gRPC классы (CarServiceGrpc, Stub и ImplBase)
Для каждого языка есть свой плагин:
--grpc-java_out для Java
--grpc-python_out для Python
--grpc-go_out для Go
и т. д.
Это и есть причина, почему gRPC мультиплатформенный — интерфейс описывается один раз в .proto, а код для всех языков генерируется автоматически.
6. Почему gRPC быстрее REST
gRPC построен поверх HTTP/2, а REST — чаще всего поверх HTTP/1.1. Разница принципиальна.
Ключевые причины производительности:
HTTP/2 поддерживает мультиплексирование — можно отправлять несколько запросов в одном соединении без блокировки.
Сжатие заголовков (HPACK) уменьшает накладные расходы.
Бинарная сериализация (protobuf) — меньше данных, быстрее парсинг.
Постоянное соединение — нет затрат на открытие/закрытие TCP для каждого запроса.
Streaming — можно передавать поток данных, а не ждать полного ответа (например, поток логов или большого файла).
7. Суммарно: что происходит при вызове метода в gRPC
Пошагово:
Клиент вызывает метод stub.someMethod(request).
Stub сериализует объект через protobuf.
Сериализованные данные упаковываются в HTTP/2 фрейм и отправляются на сервер.
Сервер принимает фрейм, десериализует данные.
Вызвается метод реализации (ImplBase).
Сервер формирует ответ, сериализует через protobuf.
Ответ отправляется обратно по тому же соединению.
Клиент получает и десериализует ответ.
Для разработчика — это выглядит как обычный вызов функции.
Под капотом же происходит оптимизированное сетевое взаимодействие с минимальными потерями.
#Java #middle #gRPC
Protocol Buffers — это бинарный формат сериализации данных, разработанный Google. Он выполняет две функции:
Описание структуры данных (через .proto файл).
Это аналог схемы JSON или XML, но строгий и типизированный.
Сериализация и десериализация (преобразование объектов в компактную бинарную форму и обратно).
Пример .proto файла не только описывает сообщения, но и определяет сервис (то есть API интерфейс).
Почему protobuf — ключевой элемент:
Он компактен: бинарный формат в несколько раз меньше JSON.
Он типобезопасен: при компиляции проверяются типы.
Он быстр: сериализация и десериализация работают на уровне байтов, без парсинга текста.
4. Как происходит сериализация и десериализация
Сериализация — это процесс превращения объекта в поток байтов для передачи по сети.
Десериализация — обратный процесс.
В gRPC:
Клиент вызывает метод stub.method(request).
request сериализуется с помощью Protocol Buffers в бинарный поток.
Поток отправляется через HTTP/2.
Сервер принимает поток, десериализует его обратно в объект CarRequest.
После обработки сервер сериализует ответ (CarResponse) и отправляет обратно.
Важно: gRPC сам управляет сериализацией. Вам не нужно ничего кодировать вручную — всё делает сгенерированный stub.
5. Что делает protoc и зачем нужны плагины
protoc — это компилятор Protocol Buffers. Он принимает .proto файл и генерирует исходный код для нужного языка.
Например:
protoc --java_out=./build/generated proto/car.proto
gRPC добавляет ещё один плагин — --grpc-java_out, который генерирует код для stub'ов.
protoc --plugin=protoc-gen-grpc-java=path/to/protoc-gen-grpc-java \
--grpc-java_out=./build/generated \
--java_out=./build/generated \
proto/car.proto
Таким образом, protoc создаёт:
Классы-сообщения (CarRequest, CarResponse)
gRPC классы (CarServiceGrpc, Stub и ImplBase)
Для каждого языка есть свой плагин:
--grpc-java_out для Java
--grpc-python_out для Python
--grpc-go_out для Go
и т. д.
Это и есть причина, почему gRPC мультиплатформенный — интерфейс описывается один раз в .proto, а код для всех языков генерируется автоматически.
6. Почему gRPC быстрее REST
gRPC построен поверх HTTP/2, а REST — чаще всего поверх HTTP/1.1. Разница принципиальна.
Ключевые причины производительности:
HTTP/2 поддерживает мультиплексирование — можно отправлять несколько запросов в одном соединении без блокировки.
Сжатие заголовков (HPACK) уменьшает накладные расходы.
Бинарная сериализация (protobuf) — меньше данных, быстрее парсинг.
Постоянное соединение — нет затрат на открытие/закрытие TCP для каждого запроса.
Streaming — можно передавать поток данных, а не ждать полного ответа (например, поток логов или большого файла).
7. Суммарно: что происходит при вызове метода в gRPC
Пошагово:
Клиент вызывает метод stub.someMethod(request).
Stub сериализует объект через protobuf.
Сериализованные данные упаковываются в HTTP/2 фрейм и отправляются на сервер.
Сервер принимает фрейм, десериализует данные.
Вызвается метод реализации (ImplBase).
Сервер формирует ответ, сериализует через protobuf.
Ответ отправляется обратно по тому же соединению.
Клиент получает и десериализует ответ.
Для разработчика — это выглядит как обычный вызов функции.
Под капотом же происходит оптимизированное сетевое взаимодействие с минимальными потерями.
#Java #middle #gRPC
👍2
Раздел 6. Коллекции в Java
Глава 5. Map — отображения (словари)
Реализации: HashMap, LinkedHashMap, TreeMap и остальные
JCF предоставляет богатый набор реализаций Map, каждая оптимизирована под конкретные нужды.
Все они реализуют Map<K, V>, но различаются по:
Хранению: Хэш-таблица (HashMap), связный список + хэш (LinkedHashMap), красно-черное дерево (TreeMap).
Порядку: Нет (HashMap), вставки (LinkedHashMap), сортировка по ключам (TreeMap).
Производительности: O(1) для хэш, O(log n) для дерево.
Дополнительно: Специальные для enum, слабых ссылок и т.д.
1. HashMap<K, V>: Основная реализация
Описание: HashMap — сердце Map в Java, основанная на хэш-таблице. Хранит пары в массиве "бакетов" (buckets), где каждый бакет — список узлов (node) с ключом, значением и хэш-кодом.
Внутренняя структура:
Массив Node<K,V>[] table (initial capacity 16, load factor 0.75).
При put: Вычисляет hash = key.hashCode() ^ (hash >>> 16) для равномерности.
Индекс бакета: hash & (table.length - 1).
Коллизия: LinkedList или Tree (с Java 8, если > 8 узлов — дерево для O(log n)).
Ресайз: При >75% заполнения — удваивает размер, перехэширует все элементы.
Big O:
put/get/remove/containsKey: O(1) средний, O(n) worst (плохие хэши).
Итерация: O(capacity).
Особенности:
Порядок: Нет (iteration order не гарантирован).
Null: Один null-ключ, несколько null-значений.
Thread-safe: Нет (ConcurrentModificationException при параллельном доступе).
Initial capacity/load factor: new HashMap<>(16, 0.75f) для оптимизации.
Нюансы и ловушки:
hashCode/equals: Критичны! Плохой hashCode — деградация до O(n). Изменение ключа после put — потеря элемента.
Ресайз: O(n) время, но редко.
Java 8+: Tree nodes для коллизий (>8 узлов).
Remove: Если null-ключ — специальная обработка.
Итерация: entrySet(), keySet(), values() — O(capacity), не size().
Пример кода:
2. LinkedHashMap<K, V>: HashMap с порядком
Описание: Расширение HashMap с двусвязным списком для сохранения порядка вставки (insertion-order) или доступа (access-order для LRU-кэша).
Внутренняя структура: HashMap + Entry с prev/next ссылками. Два режима: INSERTION_ORDER (default) или ACCESS_ORDER (constructor flag).
Big O: O(1) для put/get/remove, как HashMap.
Особенности:
Порядок: Вставки (по умолчанию) или доступа (get/put обновляет позицию).
Null: Да.
Thread-safe: Нет.
Нюансы:
LRU кэш: new LinkedHashMap<>(16, 0.75f, true) — access-order, removeEldestEntry для eviction.
Итерация: В порядке вставки/доступа.
Ресайз: Как HashMap, но сохраняет порядок.
Пример:
#Java #для_новичков #beginner #Map #HashMap #LinkedHashMap #TreeMap #Hashtable #IdentityHashMap #EnumMap #WeakHashMap
Глава 5. Map — отображения (словари)
Реализации: HashMap, LinkedHashMap, TreeMap и остальные
JCF предоставляет богатый набор реализаций Map, каждая оптимизирована под конкретные нужды.
Все они реализуют Map<K, V>, но различаются по:
Хранению: Хэш-таблица (HashMap), связный список + хэш (LinkedHashMap), красно-черное дерево (TreeMap).
Порядку: Нет (HashMap), вставки (LinkedHashMap), сортировка по ключам (TreeMap).
Производительности: O(1) для хэш, O(log n) для дерево.
Дополнительно: Специальные для enum, слабых ссылок и т.д.
1. HashMap<K, V>: Основная реализация
Описание: HashMap — сердце Map в Java, основанная на хэш-таблице. Хранит пары в массиве "бакетов" (buckets), где каждый бакет — список узлов (node) с ключом, значением и хэш-кодом.
Внутренняя структура:
Массив Node<K,V>[] table (initial capacity 16, load factor 0.75).
При put: Вычисляет hash = key.hashCode() ^ (hash >>> 16) для равномерности.
Индекс бакета: hash & (table.length - 1).
Коллизия: LinkedList или Tree (с Java 8, если > 8 узлов — дерево для O(log n)).
Ресайз: При >75% заполнения — удваивает размер, перехэширует все элементы.
Big O:
put/get/remove/containsKey: O(1) средний, O(n) worst (плохие хэши).
Итерация: O(capacity).
Особенности:
Порядок: Нет (iteration order не гарантирован).
Null: Один null-ключ, несколько null-значений.
Thread-safe: Нет (ConcurrentModificationException при параллельном доступе).
Initial capacity/load factor: new HashMap<>(16, 0.75f) для оптимизации.
Нюансы и ловушки:
hashCode/equals: Критичны! Плохой hashCode — деградация до O(n). Изменение ключа после put — потеря элемента.
Ресайз: O(n) время, но редко.
Java 8+: Tree nodes для коллизий (>8 узлов).
Remove: Если null-ключ — специальная обработка.
Итерация: entrySet(), keySet(), values() — O(capacity), не size().
Пример кода:
javaimport java.util.HashMap;
import java.util.Map;
public class Main {
public static void main(String[] args) {
Map<String, Integer> ages = new HashMap<>();
ages.put("Алексей", 35);
ages.put("Мария", 28);
ages.put("Алексей", 36); // Перезапись
System.out.println(ages.get("Алексей")); // 36
ages.put(null, 0); // Null-ключ
System.out.println(ages.size()); // 3
// Итерация
for (Map.Entry<String, Integer> entry : ages.entrySet()) {
System.out.println(entry.getKey() + ": " + entry.getValue());
}
}
}
Когда использовать: 99% случаев — быстрый поиск/кэш (userId -> User).
2. LinkedHashMap<K, V>: HashMap с порядком
Описание: Расширение HashMap с двусвязным списком для сохранения порядка вставки (insertion-order) или доступа (access-order для LRU-кэша).
Внутренняя структура: HashMap + Entry с prev/next ссылками. Два режима: INSERTION_ORDER (default) или ACCESS_ORDER (constructor flag).
Big O: O(1) для put/get/remove, как HashMap.
Особенности:
Порядок: Вставки (по умолчанию) или доступа (get/put обновляет позицию).
Null: Да.
Thread-safe: Нет.
Нюансы:
LRU кэш: new LinkedHashMap<>(16, 0.75f, true) — access-order, removeEldestEntry для eviction.
Итерация: В порядке вставки/доступа.
Ресайз: Как HashMap, но сохраняет порядок.
Пример:
javaimport java.util.LinkedHashMap;
import java.util.Map;
public class Main {
public static void main(String[] args) {
Map<String, Integer> map = new LinkedHashMap<>();
map.put("A", 1);
map.put("B", 2);
map.put("A", 3); // Обновляет, но порядок сохраняется
for (String key : map.keySet()) {
System.out.println(key); // A, B — порядок вставки
}
// Access-order
Map<String, Integer> lru = new LinkedHashMap<>(16, 0.75f, true);
lru.put("A", 1);
lru.get("A"); // "A" перемещается в конец
}
}
Когда использовать: Кэш с порядком (LRU), когда нужен predictable iteration.
#Java #для_новичков #beginner #Map #HashMap #LinkedHashMap #TreeMap #Hashtable #IdentityHashMap #EnumMap #WeakHashMap
👍1
3. TreeMap<K, V>: Отсортированная Map
Описание: Реализация SortedMap<K, V> на красно-черном дереве (red-black tree). Ключи всегда отсортированы.
Внутренняя структура: Дерево узлов с left/right/child ссылками, цветом для баланса.
Big O: O(log n) для put/get/remove/containsKey.
Особенности:
Порядок: Сортировка по ключам (Comparable или Comparator).
Null-ключ: Нет (NPE).
Null-значение: Да.
Thread-safe: Нет.
Нюансы:
Comparator: new TreeMap<>(comparator) для custom сортировки.
Дополнительные методы: firstKey(), lastKey(), headMap(K to), tailMap(K from), subMap.
Custom ключи: Comparable<K> или Comparator.
Баланс: Автоматический, O(log n) гарантировано.
Пример:
Остальные реализации: Hashtable, IdentityHashMap, EnumMap, WeakHashMap
Hashtable<K, V> (устаревшая)
Описание: Первая Map в Java (1.0), synchronized версия HashMap.
Особенности: Thread-safe (synchronized методы), нет null, порядок нет.
Big O: O(1).
Нюансы: Медленнее HashMap (locks на каждый метод), legacy — используйте ConcurrentHashMap.
Когда: Только legacy код.
IdentityHashMap<K, V>
Описание: HashMap с == вместо equals/hashCode (по ссылке).
Особенности: Для объектов, где важна идентичность (graph algorithms).
Big O: O(1).
Нюансы: Load factor 0.5, double hash для коллизий.
Когда: Редко, для identity сравнения.
EnumMap<K extends Enum<K>, V>
Описание: Оптимизированная Map для enum ключей (массив вместо хэш).
Особенности: O(1), порядок enum, нет null-ключа.
Нюансы: Ключи — enum, values any.
Когда: State machines, enum configs.
WeakHashMap<K, V>
Описание: HashMap с weak keys (GC может удалить entry, если ключ недостижим).
Особенности: Для кэшей, где память критична.
Big O: O(1).
Нюансы: Values strong, cleanup не мгновенный.
Когда: Canonicalizing mappings (interning).
Полезные советы для новичков
HashMap 95% случаев: Начните с неё.
LinkedHashMap для кэша: removeEldestEntry для LRU.
TreeMap для сортировки: Comparator для reverse.
Custom ключи: IDE генерирует equals/hashCode.
Initial capacity: new HashMap<>(expectedSize) для избежания ресайза.
Ошибки: NPE в TreeMap с null-ключом; ClassCast в TreeMap без Comparable.
#Java #для_новичков #beginner #Map #HashMap #LinkedHashMap #TreeMap #Hashtable #IdentityHashMap #EnumMap #WeakHashMap
Описание: Реализация SortedMap<K, V> на красно-черном дереве (red-black tree). Ключи всегда отсортированы.
Внутренняя структура: Дерево узлов с left/right/child ссылками, цветом для баланса.
Big O: O(log n) для put/get/remove/containsKey.
Особенности:
Порядок: Сортировка по ключам (Comparable или Comparator).
Null-ключ: Нет (NPE).
Null-значение: Да.
Thread-safe: Нет.
Нюансы:
Comparator: new TreeMap<>(comparator) для custom сортировки.
Дополнительные методы: firstKey(), lastKey(), headMap(K to), tailMap(K from), subMap.
Custom ключи: Comparable<K> или Comparator.
Баланс: Автоматический, O(log n) гарантировано.
Пример:
javaimport java.util.TreeMap;
import java.util.Map;
public class Main {
public static void main(String[] args) {
Map<Integer, String> map = new TreeMap<>();
map.put(3, "Три");
map.put(1, "Один");
map.put(2, "Два");
for (Integer key : map.keySet()) {
System.out.println(key + ": " + map.get(key)); // 1: Один, 2: Два, 3: Три
}
System.out.println(map.firstKey()); // 1
}
}
Когда использовать: Отсортированные ключи (диапазонные запросы, алфавитный словарь).
Остальные реализации: Hashtable, IdentityHashMap, EnumMap, WeakHashMap
Hashtable<K, V> (устаревшая)
Описание: Первая Map в Java (1.0), synchronized версия HashMap.
Особенности: Thread-safe (synchronized методы), нет null, порядок нет.
Big O: O(1).
Нюансы: Медленнее HashMap (locks на каждый метод), legacy — используйте ConcurrentHashMap.
Когда: Только legacy код.
IdentityHashMap<K, V>
Описание: HashMap с == вместо equals/hashCode (по ссылке).
Особенности: Для объектов, где важна идентичность (graph algorithms).
Big O: O(1).
Нюансы: Load factor 0.5, double hash для коллизий.
Когда: Редко, для identity сравнения.
EnumMap<K extends Enum<K>, V>
Описание: Оптимизированная Map для enum ключей (массив вместо хэш).
Особенности: O(1), порядок enum, нет null-ключа.
Нюансы: Ключи — enum, values any.
Когда: State machines, enum configs.
WeakHashMap<K, V>
Описание: HashMap с weak keys (GC может удалить entry, если ключ недостижим).
Особенности: Для кэшей, где память критична.
Big O: O(1).
Нюансы: Values strong, cleanup не мгновенный.
Когда: Canonicalizing mappings (interning).
Полезные советы для новичков
HashMap 95% случаев: Начните с неё.
LinkedHashMap для кэша: removeEldestEntry для LRU.
TreeMap для сортировки: Comparator для reverse.
Custom ключи: IDE генерирует equals/hashCode.
Initial capacity: new HashMap<>(expectedSize) для избежания ресайза.
Ошибки: NPE в TreeMap с null-ключом; ClassCast в TreeMap без Comparable.
#Java #для_новичков #beginner #Map #HashMap #LinkedHashMap #TreeMap #Hashtable #IdentityHashMap #EnumMap #WeakHashMap
👍2