From 8726b951504dac555169b06ced65842cf347fcee Mon Sep 17 00:00:00 2001 From: SanriaArgos Date: Fri, 10 Apr 2026 13:15:32 +0300 Subject: [PATCH] limiter: solution --- .../lectures/lesson7/limiter/RateLimiter.java | 25 +++++++++++++-- .../lectures/lesson7/RateLimiterTest.java | 31 +++++++++++++++++++ 2 files changed, 54 insertions(+), 2 deletions(-) create mode 100644 src/test/java/hse/java/lectures/lesson7/RateLimiterTest.java diff --git a/src/main/java/hse/java/lectures/lesson7/limiter/RateLimiter.java b/src/main/java/hse/java/lectures/lesson7/limiter/RateLimiter.java index 08df1b13..51eabd96 100644 --- a/src/main/java/hse/java/lectures/lesson7/limiter/RateLimiter.java +++ b/src/main/java/hse/java/lectures/lesson7/limiter/RateLimiter.java @@ -12,15 +12,36 @@ public class RateLimiter { * (скользящее окно 1 секунда или 1 минута) * @param maxRequests максимум успешных {@link #check()} за окно (должно быть > 0) */ + + long[] times; + long window; + int maxRequests, head, size; + public RateLimiter(ChronoUnit unit, int maxRequests) { - throw new UnsupportedOperationException("Not implemented"); + if (maxRequests <= 0) throw new IllegalArgumentException(); + if (unit != ChronoUnit.SECONDS && unit != ChronoUnit.MINUTES) throw new IllegalArgumentException(); + this.maxRequests = maxRequests; + times = new long[maxRequests]; + window = unit == ChronoUnit.MINUTES ? 60000L : 1000L; } /** * Регистрирует попытку и возвращает, разрешена ли она в пределах лимита. */ public boolean check() { - throw new UnsupportedOperationException("Not implemented"); + long now = System.currentTimeMillis(); + while (size > 0 && now - times[head] >= window) { + if (++head == maxRequests) head = 0; + size -= 1; + } + + if (size == maxRequests) return false; + + int position = head + size; + if (position >= maxRequests) position -= maxRequests; + times[position] = now; + size += 1; + return true; } } diff --git a/src/test/java/hse/java/lectures/lesson7/RateLimiterTest.java b/src/test/java/hse/java/lectures/lesson7/RateLimiterTest.java new file mode 100644 index 00000000..e25c9f5f --- /dev/null +++ b/src/test/java/hse/java/lectures/lesson7/RateLimiterTest.java @@ -0,0 +1,31 @@ +package hse.java.lectures.lesson7.limiter; + +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.Test; +import java.time.temporal.ChronoUnit; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +@Tag("limiter") +public class RateLimiterTest { + @Test + void badArgs() { + assertThrows(IllegalArgumentException.class, () -> new RateLimiter(null, 1)); + assertThrows(IllegalArgumentException.class, () -> new RateLimiter(ChronoUnit.HOURS, 1)); + assertThrows(IllegalArgumentException.class, () -> new RateLimiter(ChronoUnit.SECONDS, 0)); + assertThrows(IllegalArgumentException.class, () -> new RateLimiter(ChronoUnit.MINUTES, -1)); + } + + @Test + void floatingSecond() throws Exception { + RateLimiter limiter = new RateLimiter(ChronoUnit.SECONDS, 2); + assertTrue(limiter.check()); + Thread.sleep(700); + assertTrue(limiter.check()); + Thread.sleep(400); + assertTrue(limiter.check()); + assertFalse(limiter.check()); + } + +} \ No newline at end of file