Java for Beginner
870 subscribers
1.01K photos
275 videos
14 files
1.69K links
Канал от новичков для новичков!
Изучайте Java вместе с нами!
Здесь мы обмениваемся опытом и постоянно изучаем что-то новое!

Наш YouTube канал - https://www.youtube.com/@Java_Beginner-Dev

Наш канал на RUTube - https://rutube.ru/channel/37896292/
Download Telegram
Варианты ответа:
Anonymous Quiz
0%
1
41%
2
12%
null
47%
Исключение
👍2
Что выведет код?

import java.util.concurrent.ConcurrentHashMap;

public class Task060126 {
public static void main(String[] args) {
ConcurrentHashMap<String, Integer> map = new ConcurrentHashMap<>();

Integer result = map.computeIfAbsent("a", k -> map.computeIfAbsent("a", k2 -> 2));

System.out.println(result);
}
}


#Tasks
👍3
Вопрос с собеседований

Почему hashCode() должен быть согласован с equals()? 🤓

Ответ:

Если equals возвращает true, hashCode обязан быть одинаковым.

Иначе HashMap/HashSet не смогут корректно находить элементы.

Нарушение контракта приводит к потерянным объектам и трудноуловимым багам.


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


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

Иоганн Филипп Рейс (нем. Johann Philipp Reis; 7 января 1834, Гельнхаузен, Великое герцогство Гессен — 14 января 1874, Фридрихсдорф, Германия) — немецкий физик и изобретатель, первым в 1860 году сконструировавший электрический телефон, который в его честь сейчас называется телефоном Рейса. Впервые это изобретение было продемонстрировано публике 25 октября 1861 года.


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

1610 — открытие четырёх крупнейших спутников Юпитера Галилео Галилеем.

1954 – Эксперимент Джорджтаун-IBM: Первая публичная демонстрация системы машинного перевода состоялась в Нью-Йорке в головном офисе IBM.


#Biography #Birth_Date #Events #07Января
Please open Telegram to view this post
VIEW IN TELEGRAM
👍4
Паттерны использования и гарантии доставки в RabbitMQ

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


Work Queues: распределение нагрузки между потребителями

Work Queues (очереди задач) используются для распределения трудоемких задач между несколькими worker-процессами. Этот паттерн идеален для обработки фоновых задач, таких как генерация отчетов, обработка изображений или отправка email.

Ключевые характеристики:
Одна очередь, несколько потребителей
Каждое сообщение обрабатывается только одним потребителем
Балансировка нагрузки через настройку prefetch count

Реализация на Java с Spring AMQP

Producer:
@Service
public class TaskProducer {
private final RabbitTemplate rabbitTemplate;

@Value("${app.queues.task-queue}")
private String taskQueue;

public void sendTask(Task task) {
rabbitTemplate.convertAndSend(taskQueue, task, message -> {
// Установка приоритета задачи
message.getMessageProperties().setPriority(task.getPriority());
// Время жизни сообщения
message.getMessageProperties().setExpiration("3600000"); // 1 час
return message;
});
}
}


Consumer с ручными подтверждениями:
@Component
public class TaskWorker {

@RabbitListener(queues = "${app.queues.task-queue}")
public void processTask(Task task, Channel channel,
@Header(AmqpHeaders.DELIVERY_TAG) long deliveryTag) {
try {
// Обработка задачи
boolean success = executeTask(task);

if (success) {
// Подтверждение успешной обработки
channel.basicAck(deliveryTag, false);
log.info("Task {} processed successfully", task.getId());
} else {
// Отказ без повторной очереди (перемещение в DLQ)
channel.basicNack(deliveryTag, false, false);
log.error("Task {} failed, moved to DLQ", task.getId());
}
} catch (Exception e) {
// При ошибке - повторная очередь
channel.basicNack(deliveryTag, false, true);
log.error("Error processing task {}, requeued", task.getId(), e);
}
}

private boolean executeTask(Task task) {
// Логика обработки задачи
return true;
}
}


Конфигурация для равномерного распределения:
@Configuration
public class WorkQueueConfig {

@Bean
public Queue taskQueue() {
return QueueBuilder.durable("tasks.queue")
.withArgument("x-max-priority", 10) // Поддержка приоритетов
.withArgument("x-dead-letter-exchange", "dlx.exchange")
.build();
}

@Bean
public SimpleRabbitListenerContainerFactory workerFactory(
ConnectionFactory connectionFactory) {

SimpleRabbitListenerContainerFactory factory =
new SimpleRabbitListenerContainerFactory();
factory.setConnectionFactory(connectionFactory);

// Критичная настройка для равномерного распределения
factory.setPrefetchCount(1); // По одному сообщению на consumer
factory.setConcurrentConsumers(3); // Три параллельных worker'а
factory.setMaxConcurrentConsumers(10); // Автомасштабирование при нагрузке

// Ручные подтверждения для контроля
factory.setAcknowledgeMode(AcknowledgeMode.MANUAL);

return factory;
}
}



#Java #middle #RabbitMQ
👍4
Publish/Subscribe: широковещательная рассылка

Publish/Subscribe (публикация/подписка) используется, когда одно сообщение должно быть доставлено множеству потребителей. Типичные сценарии: уведомления, аудит-логи, обновления кэша.

Архитектура:
Publisher отправляет сообщение в exchange
Exchange копирует сообщение во все привязанные очереди
Каждый consumer имеет свою собственную очередь

Реализация Fanout Exchange

Конфигурация обменника и очередей:

@Configuration
public class PubSubConfig {

@Bean
public FanoutExchange notificationsExchange() {
return new FanoutExchange("notifications.fanout", true, false);
}

@Bean
public Queue auditQueue() {
return new Queue("notifications.audit.queue", true);
}

@Bean
public Queue cacheQueue() {
return new Queue("notifications.cache.queue", true);
}

@Bean
public Queue analyticsQueue() {
return new Queue("notifications.analytics.queue", true);
}

@Bean
public Binding auditBinding() {
return BindingBuilder.bind(auditQueue())
.to(notificationsExchange());
}

@Bean
public Binding cacheBinding() {
return BindingBuilder.bind(cacheQueue())
.to(notificationsExchange());
}

@Bean
public Binding analyticsBinding() {
return BindingBuilder.bind(analyticsQueue())
.to(notificationsExchange());
}
}


Publisher:
@Service
public class NotificationPublisher {
private final RabbitTemplate rabbitTemplate;

public void publishNotification(Notification notification) {
// Отправка в fanout exchange - все подписчики получат копию
rabbitTemplate.convertAndSend(
"notifications.fanout",
"", // routing key игнорируется для fanout
notification
);
}
}


Consumer для аудита (пример одного из многих):
@Component
public class AuditNotificationConsumer {

@RabbitListener(queues = "notifications.audit.queue")
public void handleNotification(Notification notification) {
// Каждый consumer обрабатывает свою копию сообщения
auditService.logEvent(notification);
}
}



Routing/Topics: селективная подписка

Topic Exchange позволяет выполнять сложную маршрутизацию на основе routing key и patterns. Используется, когда потребители интересуются только определенными типами сообщений.

Pattern syntax:
* (звездочка) заменяет одно слово
# (решетка) заменяет ноль или более слов
Пример: stock.usd.* или stock.#

Реализация Topic Exchange

Конфигурация:
@Configuration
public class TopicRoutingConfig {

@Bean
public TopicExchange stockExchange() {
return new TopicExchange("stock.topic", true, false);
}

@Bean
public Queue usdStockQueue() {
return new Queue("stock.usd.queue", true);
}

@Bean
public Queue eurStockQueue() {
return new Queue("stock.eur.queue", true);
}

@Bean
public Queue allStockQueue() {
return new Queue("stock.all.queue", true);
}

@Bean
public Binding usdBinding() {
// Будет получать: stock.usd.nyse, stock.usd.nasdaq
return BindingBuilder.bind(usdStockQueue())
.to(stockExchange())
.with("stock.usd.*");
}

@Bean
public Binding eurBinding() {
// Будет получать: stock.eur.lse, stock.eur.euronext
return BindingBuilder.bind(eurStockQueue())
.to(stockExchange())
.with("stock.eur.*");
}

@Bean
public Binding allStocksBinding() {
// Будет получать все сообщения о stock
return BindingBuilder.bind(allStockQueue())
.to(stockExchange())
.with("stock.#");
}
}


#Java #middle #RabbitMQ
👍4
Publisher с различными routing keys:
@Service
public class StockPublisher {
private final RabbitTemplate rabbitTemplate;

public void publishUSDPrice(String exchange, BigDecimal price) {
String routingKey = "stock.usd." + exchange.toLowerCase();
StockUpdate update = new StockUpdate("USD", exchange, price);

rabbitTemplate.convertAndSend(
"stock.topic",
routingKey,
update
);
}

public void publishEURPrice(String exchange, BigDecimal price) {
String routingKey = "stock.eur." + exchange.toLowerCase();
StockUpdate update = new StockUpdate("EUR", exchange, price);

rabbitTemplate.convertAndSend(
"stock.topic",
routingKey,
update
);
}
}


RPC over AMQP: синхронные вызовы через асинхронный транспорт

Remote Procedure Call (RPC) поверх AMQP позволяет выполнять синхронные запросы-ответы через ассинхронную систему сообщений. Используется когда нужен немедленный ответ, но нельзя установить прямое соединение.

Механизм работы:
Клиент отправляет запрос с уникальным correlationId
Сервер обрабатывает запрос и отправляет ответ в очередь ответов
Клиент ожидает ответ с matching correlationId

Реализация RPC

Клиентская сторона:
@Service
public class CalculatorRpcClient {
private final RabbitTemplate rabbitTemplate;

public CalculatorRpcClient(RabbitTemplate rabbitTemplate) {
this.rabbitTemplate = rabbitTemplate;

// Настройка reply listener
this.rabbitTemplate.setReplyTimeout(30000);
this.rabbitTemplate.setUseDirectReplyToContainer(false);
}

public BigDecimal calculate(CalculationRequest request) {
// Создание correlation ID
String correlationId = UUID.randomUUID().toString();

// Отправка запроса и ожидание ответа
CalculationResponse response = (CalculationResponse)
rabbitTemplate.convertSendAndReceive(
"rpc.exchange",
"calculator.rpc",
request,
message -> {
message.getMessageProperties()
.setCorrelationId(correlationId);
message.getMessageProperties()
.setReplyTo("amq.rabbitmq.reply-to"); // Временная очередь
return message;
}
);

if (response == null) {
throw new RuntimeException("RPC timeout");
}

return response.getResult();
}
}


Серверная сторона:
@Component
public class CalculatorRpcServer {

@RabbitListener(
bindings = @QueueBinding(
value = @Queue(value = "rpc.calculator.queue", durable = "true"),
exchange = @Exchange(value = "rpc.exchange", type = ExchangeTypes.DIRECT),
key = "calculator.rpc"
)
)
public CalculationResponse calculate(CalculationRequest request,
Message message) {

String correlationId = message.getMessageProperties()
.getCorrelationId();
String replyTo = message.getMessageProperties()
.getReplyTo();

log.debug("Processing RPC request {} from {}",
correlationId, replyTo);

// Выполнение расчета
BigDecimal result = performCalculation(request);

// Возврат результата с сохранением correlationId
CalculationResponse response = new CalculationResponse(result);

// CorrelationId автоматически копируется в ответ
return response;
}

private BigDecimal performCalculation(CalculationRequest request) {
// Логика расчета
return BigDecimal.ZERO;
}
}



#Java #middle #RabbitMQ
👍4
Гарантии доставки

At-most-once: риск потери сообщений

Механизм: Сообщение отправляется без подтверждений, сразу удаляется из очереди.

Использовать когда:

Потеря сообщений допустима
Высокая производительность критична
Система имеет механизмы компенсации

// Producer без подтверждений
@Bean
public RabbitTemplate atMostOnceTemplate(ConnectionFactory cf) {
RabbitTemplate template = new RabbitTemplate(cf);
template.setMandatory(false);

// Отключение publisher confirms
((CachingConnectionFactory) cf)
.setPublisherConfirmType(CachingConnectionFactory.ConfirmType.NONE);

return template;
}

// Consumer с auto-ack
@RabbitListener(queues = "at.most.once.queue")
public void handleAutoAck(Message message) {
// Сообщение уже удалено из очереди при доставке
// При падении здесь - сообщение потеряно
processMessage(message);
}


At-least-once: риск дублирования

Механизм: Гарантирует доставку минимум один раз, но возможны дубликаты.

Реализация с ручными подтверждениями:
@Component
public class AtLeastOnceConsumer {

@RabbitListener(queues = "orders.queue")
public void processOrder(Order order, Channel channel,
@Header(AmqpHeaders.DELIVERY_TAG) long tag) {

try {
// 1. Обработка заказа
processOrderSafely(order);

// 2. Подтверждение после успешной обработки
channel.basicAck(tag, false);

log.info("Order {} processed and acknowledged", order.getId());

} catch (Exception e) {
// 3. При ошибке - повторная очередь
channel.basicNack(tag, false, true);
log.error("Order processing failed, requeued", e);

// Идемпотентность критична!
// При повторной обработке должна быть сохранена консистентность
}
}

private void processOrderSafely(Order order) {
// Идемпотентная обработка
if (orderRepository.existsById(order.getId())) {
log.warn("Duplicate order {}, skipping", order.getId());
return;
}

orderRepository.save(order);
inventoryService.reserveItems(order);
}
}



#Java #middle #RabbitMQ
👍4
Exactly-once: сложная реализация

Реальность: RabbitMQ не предоставляет exactly-once гарантий на уровне протокола. Достигается комбинацией механизмов.

Паттерн для pseudo-exactly-once:

@Service
@Transactional
public class ExactlyOnceProcessor {

private final RabbitTemplate rabbitTemplate;
private final OrderRepository orderRepository;

public void processWithExactlyOnceSemantics(OrderMessage message) {
// 1. Проверка идемпотентности
if (processedMessageRepository.existsById(message.getId())) {
return; // Уже обработано
}

// 2. Сохранение в outbox паттерн
OrderOutbox outbox = new OrderOutbox();
outbox.setMessageId(message.getId());
outbox.setOrderData(message.getPayload());
outbox.setStatus(OutboxStatus.PROCESSING);

orderRepository.save(outbox);

// 3. Бизнес-логика в транзакции
Order order = createOrder(message);
orderRepository.save(order);

// 4. Обновление статуса в той же транзакции
outbox.setStatus(OutboxStatus.PROCESSED);
orderRepository.save(outbox);

// 5. Отправка подтверждения (после коммита транзакции)
// RabbitTemplate работает после завершения транзакции
rabbitTemplate.convertAndSend(
"order.processed.exchange",
"order.processed",
new OrderProcessedEvent(order.getId())
);

// 6. Идемпотентность на стороне получателя подтверждения
}

@TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT)
public void afterCommit(OrderProcessedEvent event) {
// Только после успешного коммита
rabbitTemplate.convertAndSend(
"order.completed.queue",
event
);
}
}


Механизмы надежности


Publisher Confirms

Механизм: Брокер подтверждает получение сообщения.

Реализация:

@Configuration
public class PublisherConfirmConfig {

@Bean
public CachingConnectionFactory confirmedConnectionFactory() {
CachingConnectionFactory factory = new CachingConnectionFactory();

// Включение publisher confirms
factory.setPublisherConfirmType(CachingConnectionFactory.ConfirmType.CORRELATED);
factory.setPublisherReturns(true);

return factory;
}

@Bean
public RabbitTemplate confirmedRabbitTemplate(
CachingConnectionFactory connectionFactory) {

RabbitTemplate template = new RabbitTemplate(connectionFactory);

// Callback для подтверждений
template.setConfirmCallback((correlationData, ack, cause) -> {
if (ack) {
metrics.increment("publisher.confirms.success");
} else {
metrics.increment("publisher.confirms.failure");
log.error("Message not confirmed: {}", cause);

// Логика повторной отправки
if (correlationData != null) {
retryService.scheduleRetry(correlationData.getId());
}
}
});

// Callback для возвращенных сообщений
template.setReturnsCallback(returned -> {
log.error("Message returned: {}", returned.toString());
returnedMessageService.handleReturned(returned);
});

return template;
}
}



#Java #middle #RabbitMQ
👍4
Consumer Acknowledgements

Типы подтверждений:
public class AcknowledgementExamples {

// 1. Автоматическое подтверждение (ненадежно)
@RabbitListener(queues = "auto.ack.queue")
public void autoAck(Message message) {
// Подтверждение происходит автоматически при возврате метода
}

// 2. Ручное подтверждение (рекомендуется)
@RabbitListener(queues = "manual.ack.queue")
public void manualAck(Order order, Channel channel,
@Header(AmqpHeaders.DELIVERY_TAG) long tag) {

try {
process(order);
channel.basicAck(tag, false); // Положительное подтверждение
} catch (BusinessException e) {
// Не requeue для бизнес-ошибок
channel.basicNack(tag, false, false);
} catch (TechnicalException e) {
// Requeue для технических ошибок
channel.basicNack(tag, false, true);
}
}

// 3. Подтверждение с транзакцией
@Transactional
@RabbitListener(queues = "transactional.queue")
public void transactionalAck(Payment payment, Channel channel,
@Header(AmqpHeaders.DELIVERY_TAG) long tag) {

// Сохранение в БД
paymentRepository.save(payment);

// Подтверждение произойдет только после коммита транзакции
// При откате транзакции - сообщение вернется в очередь
}
}


Transactional Channels

Использование транзакций:

@Service
public class TransactionalMessageService {

private final RabbitTemplate rabbitTemplate;

@Transactional
public void processInTransaction(Order order) {
// 1. Сохранение в базу данных
orderRepository.save(order);

// 2. Отправка сообщения в той же транзакции
rabbitTemplate.execute(channel -> {
// Начало транзакции RabbitMQ
channel.txSelect();

try {
// Отправка сообщения
channel.basicPublish(
"orders.exchange",
"order.created",
null,
serialize(order)
);

// Коммит транзакции RabbitMQ
channel.txCommit();

return null;
} catch (Exception e) {
// Откат транзакции RabbitMQ
channel.txRollback();
throw new RuntimeException("Transaction failed", e);
}
});

// 3. Если произойдет исключение здесь,
// откатятся и БД и сообщение RabbitMQ
inventoryService.updateStock(order);
}
}



#Java #middle #RabbitMQ
👍4
Dead Letter Exchanges: обработка проблемных сообщений

Dead Letter Exchange (DLX) — специальный exchange, куда перенаправляются сообщения, которые не могут быть обработаны.

Типичные сценарии:
Сообщение отбраковано (nack без requeue)
Превышено максимальное количество попыток обработки
Истек TTL сообщения
Очередь заполнена (при overflow поведении)

Реализация DLX

Конфигурация основной очереди с DLX:
@Configuration
public class DeadLetterConfig {

// Основной DLX
@Bean
public DirectExchange dlxExchange() {
return new DirectExchange("dlx.exchange", true, false);
}

// Очередь для мертвых писем
@Bean
public Queue dlQueue() {
return QueueBuilder.durable("dead.letter.queue")
.withArgument("x-max-length", 10000) // Ограничение размера
.withArgument("x-message-ttl", 86400000) // 24 часа хранения
.build();
}

@Bean
public Binding dlBinding() {
return BindingBuilder.bind(dlQueue())
.to(dlxExchange())
.with("#"); // Все routing keys
}

// Рабочая очередь с настройкой DLX
@Bean
public Queue orderProcessingQueue() {
return QueueBuilder.durable("orders.processing.queue")
.withArgument("x-dead-letter-exchange", "dlx.exchange")
.withArgument("x-dead-letter-routing-key", "orders.failed")
.withArgument("x-max-retries", 3) // Кастомный аргумент
.build();
}
}


Обработчик проблемных сообщений:

@Component
public class DeadLetterProcessor {

@RabbitListener(queues = "dead.letter.queue")
public void handleDeadLetter(Message failedMessage,
Channel channel,
@Header(AmqpHeaders.DELIVERY_TAG) long tag,
@Header(AmqpHeaders.RECEIVED_ROUTING_KEY) String routingKey,
@Header(AmqpHeaders.RECEIVED_EXCHANGE) String exchange,
@Header("x-death") List<Map<String, Object>> deaths) {

// Анализ причины попадания в DLQ
String reason = analyzeFailureReason(deaths);

log.error("Dead letter received: routingKey={}, reason={}, deaths={}",
routingKey, reason, deaths);

// Логика обработки в зависимости от причины
if (isRecoverable(failedMessage, deaths)) {
handleRecoverableMessage(failedMessage);
} else {
handlePermanentFailure(failedMessage);
}

// Подтверждение обработки DLQ
channel.basicAck(tag, false);
}

private String analyzeFailureReason(List<Map<String, Object>> deaths) {
if (deaths != null && !deaths.isEmpty()) {
Map<String, Object> lastDeath = deaths.get(0);
return (String) lastDeath.get("reason");
}
return "unknown";
}

private void handleRecoverableMessage(Message message) {
// Пример: повторная отправка после задержки
String originalQueue = extractOriginalQueue(message);
long delay = calculateRetryDelay(message);

retryService.scheduleRetry(message, originalQueue, delay);
}

private void handlePermanentFailure(Message message) {
// Архивирование неудачных сообщений
archiveService.archiveFailedMessage(message);

// Уведомление команды поддержки
alertService.notifySupportTeam(message);
}
}



#Java #middle #RabbitMQ
👍4
Продвинутая обработка с задержкой повторных попыток:
@Configuration
public class DelayedRetryConfig {

// Exchange для отложенных повторных попыток
@Bean
public CustomExchange delayedExchange() {
Map<String, Object> args = new HashMap<>();
args.put("x-delayed-type", "direct");

return new CustomExchange(
"delayed.retry.exchange",
"x-delayed-message",
true,
false,
args
);
}

// Очередь для отложенных повторных попыток
@Bean
public Queue delayedRetryQueue() {
return new Queue("delayed.retry.queue", true);
}

@Bean
public Binding delayedBinding() {
return BindingBuilder.bind(delayedRetryQueue())
.to(delayedExchange())
.with("retry.key")
.noargs();
}

// Сервис для отложенных повторных попыток
@Service
public class DelayedRetryService {
private final RabbitTemplate rabbitTemplate;

public void scheduleRetry(Message message, int attempt) {
long delay = calculateExponentialBackoff(attempt);

rabbitTemplate.convertAndSend(
"delayed.retry.exchange",
"retry.key",
message,
m -> {
// Установка задержки
m.getMessageProperties()
.setHeader("x-delay", delay);

// Сохранение номера попытки
m.getMessageProperties()
.setHeader("retry-attempt", attempt);

return m;
}
);
}

private long calculateExponentialBackoff(int attempt) {
return (long) Math.pow(2, attempt) * 1000; // Экспоненциальная задержка
}
}
}



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

Выбор паттерна

Work Queues — для фоновой обработки задач с балансировкой нагрузки
Publish/Subscribe — для широковещательных уведомлений
Routing/Topics — для сложной маршрутизации сообщений
RPC — для синхронных запросов в асинхронной среде

Гарантии доставки

At-most-once — только для non-critical данных
At-least-once — стандартный выбор для большинства систем
Exactly-once — достигается через идемпотентность и транзакции

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

public class Task070126 {
public static void main(String[] args) {
new Child070126();
}
}

class Parent070126 {
Parent070126() {
print();
}

void print() {
System.out.println("Parent");
}
}

class Child070126 extends Parent070126 {
private String value = "Hello";

Child070126() {
System.out.println("Child constructor");
}

@Override
void print() {
System.out.println(value.length());
}
}


#Tasks
👍2
Как HashMap превращает список в дерево? 🤓

Ответ:

При превышении порога коллизий (обычно 8) бакет преобразуется в красно-чёрное дерево.

Это снижает сложность операций с O(n) до O(log n), защищая от деградации производительности и атак на хеш-функцию.


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


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

Сти́вен Уи́льям Хо́кинг (англ. Stephen William Hawking; 8 января 1942, Оксфорд — 14 марта 2018, Кембридж) — британский физик-теоретик, космолог, астрофизик и писатель.

Алекса́ндр Льво́вич Минц (27 декабря 1894 [8 января 1895], Ростов-на-Дону — 29 декабря 1974, Москва)советский радиофизик, инженер и организатор науки. Разработчик систем связи и радиолокации; один из создателей РЛС дальнего обнаружения и советского синхрофазотрона в Дубне.


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

1851 — французский физик Жан Бернар Леон Фуко доказал, что Земля вращается вокруг своей оси.


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

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

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


Пример задачи: поиск информации

Рассмотрим простую задачу поиска телефонного номера по имени в списке контактов. На первый взгляд тривиальная задача раскрывает фундаментальные различия в подходах к проектированию алгоритмов.

Подход 1: Полный перебор (линейный поиск)
Это наивный подход, основанный на последовательном сравнении искомого значения с каждым элементом коллекции.

Реализация на Java выглядит следующим образом:
public class LinearSearch {
public static Contact findContact(List<Contact> contacts, String name) {
for (Contact contact : contacts) {
if (contact.getName().equals(name)) {
return contact;
}
}
return null; // Контакт не найден
}
}


Асимптотическая сложность этого алгоритма — O(n), где n — количество контактов. Это означает, что в худшем случае нам потребуется проверить все элементы списка. При поиске в списке из 10 контактов мы выполним до 10 сравнений, в списке из 1 000 000 контактов — до миллиона сравнений.

Подход 2: Индексный поиск (использование хеш-таблиц)
Индексирование — это техника предварительной обработки данных для ускорения последующих операций поиска. Хеш-таблица преобразует ключ (в нашем случае имя контакта) в индекс массива с помощью хеш-функции.

public class IndexedSearch {
private Map<String, Contact> contactIndex;

public IndexedSearch(List<Contact> contacts) {
// Предварительная обработка: построение индекса
contactIndex = new HashMap<>();
for (Contact contact : contacts) {
contactIndex.put(contact.getName(), contact);
}
}

public Contact findContact(String name) {
// Поиск по индексу за постоянное время
return contactIndex.get(name);
}
}


Асимптотическая сложность поиска по хеш-таблице — O(1) в среднем случае. Хеш-функция вычисляет позицию элемента, и мы получаем прямой доступ к нему. Однако этот подход требует дополнительной памяти для хранения индекса и времени на его предварительное построение.

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

public class CachedSearch {
private List<Contact> contacts;
private Map<String, Contact> cache;
private int cacheHits = 0;
private int cacheMisses = 0;

public CachedSearch(List<Contact> contacts) {
this.contacts = contacts;
this.cache = new HashMap<>();
}

public Contact findContact(String name) {
// Проверка кэша
Contact cached = cache.get(name);
if (cached != null) {
cacheHits++;
return cached;
}

// Кэш-промах: выполняем линейный поиск
cacheMisses++;
for (Contact contact : contacts) {
if (contact.getName().equals(name)) {
// Сохраняем результат в кэш для будущих запросов
cache.put(name, contact);
return contact;
}
}
return null;
}

public double getCacheHitRatio() {
int total = cacheHits + cacheMisses;
return total > 0 ? (double) cacheHits / total : 0.0;
}
}


Этот подход демонстрирует классический компромисс между временем и памятью. При частых повторных запросах к одним и тем же данным эффективность поиска стремится к O(1), но мы тратим дополнительную память на хранение кэша.


#Java #для_новичков #beginner #algorithm
👍4🔥1
Корректный vs Подходящий: фундаментальное различие

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

Подходящий алгоритм учитывает контекст применения:
Объем и структура входных данных
Частоту выполнения операции
Ограничения по времени отклика
Доступные вычислительные ресурсы
Требования к потребляемой памяти

Рассмотрим влияние выбора алгоритма на различные аспекты системы:


Скорость работы пользовательского интерфейса

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

Для поиска в небольшом списке контактов (десятки элементов) линейный поиск может быть вполне приемлем. Однако при работе с большими наборами данных (тысячи элементов) даже асимптотически эффективный алгоритм O(n) становится проблемой. Представьте поле автодополнения, которое должно отфильтровать результаты при каждом нажатии клавиши. Здесь необходим алгоритм с сублинейной сложностью, например, использование префиксного дерева (trie) для мгновенного поиска.

Время пакетной обработки больших данных

В системах обработки данных, где операции выполняются над миллионами или миллиардами записей, разница в асимптотической сложности становится определяющей. Алгоритм O(n²) для миллиарда элементов потребует порядка 10¹⁸ операций, что на современных процессорах займет десятки лет.

Рассмотрим задачу дедупликации записей. Наивный алгоритм попарного сравнения всех элементов имеет сложность O(n²). Алгоритм с предварительной сортировкой (O(n log n)) и последующим линейным проходом (O(n)) сокращает время выполнения на несколько порядков для больших наборов данных.

// Неэффективный алгоритм дедупликации O(n²)
public List<Record> deduplicateNaive(List<Record> records) {
List<Record> unique = new ArrayList<>();
for (Record r1 : records) {
boolean isDuplicate = false;
for (Record r2 : unique) {
if (r1.equals(r2)) {
isDuplicate = true;
break;
}
}
if (!isDuplicate) {
unique.add(r1);
}
}
return unique;
}

// Эффективный алгоритм дедупликации O(n log n)
public List<Record> deduplicateEfficient(List<Record> records) {
if (records.isEmpty()) return Collections.emptyList();

// Сортировка позволяет находить дубликаты за один проход
List<Record> sorted = new ArrayList<>(records);
Collections.sort(sorted);

List<Record> unique = new ArrayList<>();
Record previous = sorted.get(0);
unique.add(previous);

for (int i = 1; i < sorted.size(); i++) {
Record current = sorted.get(i);
if (!current.equals(previous)) {
unique.add(current);
previous = current;
}
}
return unique;
}



#Java #для_новичков #beginner #algorithm
👍3
Стоимость инфраструктуры

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

Рассмотрим пример обработки запросов в веб-приложении.

Алгоритм с временной сложностью O(n) для обработки одного запроса при увеличении нагрузки в 10 раз потребует в 10 раз больше вычислительных ресурсов. Алгоритм с оптимизированной сложностью O(log n) при таком же росте нагрузки увеличит потребление ресурсов лишь на постоянную величину.

Важным аспектом является также потребление памяти. Алгоритмы, работающие in-place (без дополнительной памяти), предпочтительнее для обработки больших данных. Однако иногда расход памяти оправдан для достижения лучшего времени выполнения. Этот компромисс известен как trade-off между временем и памятью.

Масштабируемость системы

Масштабируемость — это способность системы справляться с ростом нагрузки. Алгоритмы с неоптимальной асимптотической сложностью становятся узким местом при горизонтальном масштабировании.

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

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

Рассмотрим алгоритм согласованного хеширования (consistent hashing), используемый в распределенных кэшах и базах данных. Вместо перераспределения всех данных при изменении количества узлов кластера, этот алгоритм перемещает только O(1/n) данных, где n — количество узлов. Это обеспечивает предсказуемую производительность при масштабировании системы.


Практические рекомендации по выбору алгоритма

Профилирование перед оптимизацией: Используйте инструменты профилирования (такие как JProfiler, YourKit, Async Profiler) для идентификации реальных узких мест. Преждевременная оптимизация часто приводит к усложнению кода без значительного выигрыша в производительности.

Учет распределения данных: Эффективность алгоритмов может зависеть от характеристик данных. Хеш-таблицы обеспечивают среднее время O(1), но при плохом распределении хеш-функции могут деградировать до O(n). Деревья поиска гарантируют O(log n), но имеют большую константу.

Анализ частоты операций: Оптимизируйте операции, которые выполняются чаще всего. Если чтение происходит в 100 раз чаще, чем запись, имеет смысл использовать более сложные структуры данных для ускорения чтения, даже в ущерб производительности записи.

Учет аппаратных особенностей: Современные процессоры имеют многоуровневые кэши. Алгоритмы, обладающие локальностью ссылок (обращение к соседним элементам памяти), работают значительно быстрее из-за уменьшения промахов кэша.

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


#Java #для_новичков #beginner #algorithm
👍4
OrderHub. Эволюция проекта из монолита к production-ready микросервису

1. Старт серии. Создаём основу для production-системы.

Я начинаю большой практический курс, в котором мы с нуля спроектируем и разработаем полноценную, отказоустойчивую микросервисную систему на современном Java-стеке.

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

Сегодня:
🔹 Полный анонс курса: зачем это всё нужно и что вы получите в итоге.
🔹 Постановка бизнес-задачи и проектирование доменной модели.
🔹 Инициализация Spring Boot 3 проекта с правильной структурой пакетов.
🔹 Создание основных сущностей (Order, OrderItem) и REST API для работы с ними.
🔹 Закладываем фундамент для будущего масштабирования.

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

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

Смотрите, ставьте лайки, подписывайтесь на каналы!✌️
Please open Telegram to view this post
VIEW IN TELEGRAM
👍8🔥1