Новое Распределяем каналы MAX по категориям и территориям. Читать новость
Открытый канал ID 80357283526128

Код в работе

Программирование и разработка: код, архитектура, инструменты и рабочие задачи. Практические разборы для тех, кто создаёт программные продукты.

Каналов в рубрике
2 112
Место этого канала
№ 130
Открыть рубрику →
Россия
Открыть в MAX Отчёт

Публикации за период

По реакциям

Поиск и вся лента — после бесплатной регистрации. Свои даты — по подписке; владелец канала видит их бесплатно. Сортировка по реакциям — по подписке. Зарегистрироваться

Загрузка…

Ограничиваем чтение старых Java-объектов!

Если старый формат уже использует ObjectInputStream, входной поток нельзя считать доверенным. ObjectInputFilter ограничивает размер графа объектов и разрешает только классы, ожидаемые конкретным форматом.

Перечисляем допустимые типы явно:

private static final Set<Class<?>> ALLOWED =
    Set.of(LegacyRecord.class, String.class);{}


В список включаются тип корневой записи и типы её полей, которые действительно сериализуются. Широкие разрешения для всех классов пакета превращают allowlist в формальность.

Сначала отсекаем слишком большие графы:

private static boolean tooLarge(
        ObjectInputFilter.FilterInfo info) {
    return info.depth() > 10
        || info.references() > 500
        || info.streamBytes() > 1_000_000;
}{}


Лимит глубины защищает от чрезмерно вложенных структур, а число ссылок ограничивает количество объектов. Дополнительно можно ограничить общий размер потока через info.streamBytes().

Проверяем тип каждого объекта отдельно:

private static boolean allowed(Class<?> type) {
    return type == null || ALLOWED.contains(type);
}{}


Для служебных проверок фильтр может получить null вместо класса. Такой случай не означает, что неизвестный тип разрешён, поэтому решение о нём остаётся за статусом UNDECIDED.

Сначала отклоняем превышение лимитов и получаем тип:

private static ObjectInputFilter.Status filter(
        ObjectInputFilter.FilterInfo info) {
    if (tooLarge(info))
        return ObjectInputFilter.Status.REJECTED;
    Class<?> type = info.serialClass();{}


Затем разрешаем только перечисленные классы:

    if (type == null)
        return ObjectInputFilter.Status.UNDECIDED;
    return allowed(type)
        ? ObjectInputFilter.Status.ALLOWED
        : ObjectInputFilter.Status.REJECTED;{}


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

Подключаем фильтр к потоку до десериализации:

input.setObjectInputFilter(MyFilters::filter);{}


Здесь MyFilters.filter объединяет проверки лимитов и типов из предыдущих шагов. Фильтр важно подключить до чтения, иначе объекты уже могут быть созданы.

После настройки получаем ожидаемый корневой объект:

LegacyRecord record = LegacyRecord.class
    .cast(input.readObject());{}


👉 Java Ready | практика
Просмотры: 252
Перейти в пост Открыть в MAX Сообщить о неточности
Знаете, почему после записи ByteBuffer данные сразу не читаются?

У буфера есть position и limit. После put позиция указывает на место следующей записи, поэтому сначала переключаем его из режима записи в режим чтения:
buffer.put(payload);
buffer.flip();

flip переносит текущую position в limit и возвращает position в начало. Теперь чтение ограничено только записанными байтами, а не всей ёмкостью буфера.

Читаем данные, пока между position и limit остаются байты:
while (buffer.hasRemaining()) {
    process(buffer.get());
}

После чтения clear подготавливает буфер к новой записи. Он сбрасывает position и limit, но не стирает старые байты, поэтому счётчики состояния нельзя путать с очисткой памяти.

Если часть данных ещё не обработана, compact сохраняет остаток в начале и оставляет место для новых байтов:
buffer.compact();
buffer.put(nextChunk);

Такой цикл удобен при чтении неполных сетевых сообщений. Остаток переносится вперёд, а новые данные дописываются после него.
Просмотры: 419
Перейти в пост Открыть в MAX Сообщить о неточности
Чем scheduleAtFixedRate отличается от scheduleWithFixedDelay?

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

При фиксированной частоте следующий запуск привязан к расписанию, а не к окончанию предыдущего:
scheduler.scheduleAtFixedRate(
    this::refresh, 0, 30, TimeUnit.SECONDS);

Если обновление занимает 8 секунд, следующий запуск всё равно планируется через 30 секунд от предыдущего старта. Один и тот же периодический task не выполняется параллельно сам с собой, но при долгой операции новый запуск может начаться почти сразу после её завершения.

Фиксированная задержка отсчитывается после завершения операции:
scheduler.scheduleWithFixedDelay(
    this::refresh, 0, 30, TimeUnit.SECONDS);

При работе в 8 секунд между завершением одного запуска и началом следующего пройдёт 30 секунд.

Есть ещё важный нюанс. Если периодический запуск завершится с исключением, следующие вызовы подавляются:
Runnable safeRefresh = () -> {
    try { refresh(); }
    catch (RuntimeException error) { logger.error("Refresh failed", error); }
};

Сохраняй ScheduledFuture, если задачу нужно остановить до остановки всего executor:
ScheduledFuture<?> job = scheduler.scheduleWithFixedDelay(
    safeRefresh, 0, 30, TimeUnit.SECONDS);
job.cancel(false);

Выбирай fixed rate для привязки к расписанию, а fixed delay для паузы после каждой работы.
Просмотры: 449
Перейти в пост Открыть в MAX Сообщить о неточности
Сохраняем пачку событий в БД одной транзакцией!

Нужно записать несколько событий и не оставить таблицу в частично обновлённом состоянии. Используем PreparedStatement batch и фиксируем транзакцию только после успешной вставки.

В этой задаче:

• открываем соединение с отключённым auto-commit
• добавляем записи через addBatch
• выполняем batch и подтверждаем или откатываем транзакцию

Для больших импортов пачку разделяем на ограниченные части.

👉 Java Ready | задача
Просмотры: 388
Перейти в пост Открыть в MAX Сообщить о неточности
Добавляем место вызова в сообщения об ошибках на C++!

Когда ошибка обнаруживается глубоко внутри общей функции, по одному тексту лога бывает трудно найти её источник. В C++20 std::source_location позволяет получить файл и строку вызова без ручной передачи этих параметров.

Подключаем нужные заголовки:

include <iostream>
include <source_location>
include <string_view>{}


Добавляем source_location последним параметром со значением по умолчанию:

void logError(std::string_view message,
    std::source_location where =
        std::source_location::current()) {{}


current() вычисляется в месте вызова функции. Поэтому в лог попадёт файл и строка кода, откуда вызвали logError, а не строка внутри самой функции.

Формируем компактную запись с координатами:

std::cerr << where.file_name()
          << ":" << where.line()
          << " " << message << "\n";
}{}


Такой формат легко искать в CI-логах и локальном выводе. При необходимости к нему добавляется имя функции через where.function_name().

Вызываем логгер:

logError("config file is missing");{}


Компилятор подставит место этого вызова автоматически.

📣 C++ Ready | практика
Просмотры: 349
Перейти в пост Открыть в MAX Сообщить о неточности
🔥 Мастхев подборка для каждого айтишника

Держите подборку авторских IT-каналов по всем направлениям: курсы, гайды, статьи, практики, примеры кода, шпаргалки и прохождение собесов.

▪️ Python: @python
▪️ Frontend: @frontend
▪️ Java: @java
👨‍💻 Go: @golang
▪️ ИБ & Хакинг: @hacking
▪️ C/C++: @cplusplus
▪️ C & GameDev: @gamedev
▪️ Data Science: @datascience
▪️ DevOps: @devops
▪️ Linux: @linux
▪️ IT Курсы: @courses
▪️ Вайбкодинг: @vibecoding
▪️ Весь айти: @itnews

⏱️ Успей подписаться пока не удалили!
Просмотры: 308
Перейти в пост Открыть в MAX Сообщить о неточности
Объединяем отсортированные логи на C++!

Несколько файлов уже содержат записи в порядке времени, но для анализа нужен один общий поток. Соберём его через min-heap, читая из каждого файла только текущую запись.

В этой задаче:
• связываем запись с её файлом
• помещаем в очередь первые записи
• после извлечения подгружаем следующую из того же файла

Так объём дополнительной памяти зависит от числа файлов, а не от размера логов.
Просмотры: 233
Перейти в пост Открыть в MAX Сообщить о неточности
Группируем метрики по минутным окнам на C++!

Представим, что сервис записывает latency каждого запроса. Для графика не всегда нужны все отдельные измерения, поэтому соберём число событий и среднее значение для каждой минуты.

Храним у каждого измерения время и значение, а в сводке накапливаем сумму и количество:

struct Sample { std::chrono::system_clock::time_point at; double value; };
struct Stats { double total{}; std::size_t count{}; };{}


Ключом сделаем начало минуты. floor округляет время вниз, поэтому измерения в 10:14:02 и 10:14:59 попадут в одно окно:

using Minute = std::chrono::time_point<
    std::chrono::system_clock, std::chrono::minutes>;
std::map<Minute, Stats> buckets;{}


Для каждого измерения вычисляем ключ и обновляем аккумулятор:

auto minute = std::chrono::floor<std::chrono::minutes>(sample.at);
auto& stats = buckets[minute];
stats.total += sample.value;
++stats.count;{}


Здесь хранится сумма, а не готовое среднее. Так обновление занимает постоянное время и не требует пересчитывать все предыдущие значения окна.

После чтения входных данных проходим по окнам в хронологическом порядке и считаем среднее:

for (const auto& [minute, stats] : buckets) {
    const double average = stats.total / stats.count;
    writeRow(minute, stats.count, average);
}{}


На выходе каждая строка содержит начало минуты, количество запросов и среднюю задержку.

📣 C++ Ready | практика
Просмотры: 152
Перейти в пост Открыть в MAX Сообщить о неточности
ВКонтакте Телеграм Одноклассники
Избранное в datool.ru

Следите за каналом в личном кабинете

Зарегистрируйтесь в datool.ru, чтобы добавить канал «Код в работе» в избранное и отслеживать его показатели.

Зарегистрироваться
Для владельцев каналов

Подтвердите свой канал

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

Войти или зарегистрироваться

Сообщить о неточности

Опишите, что нужно исправить. Если есть источник, добавьте ссылку.

Вы покидаете datool.ru

Ссылка ведёт на внешний сайт. datool.ru не отвечает за его содержимое — проверьте адрес, прежде чем вводить пароли или платёжные данные.

Переход через 10 с

Перейти