Skip to content

Commit f1aceea

Browse files
authored
Enhance class & function documentation in CircularBuffer.java (#5582)
1 parent 640d823 commit f1aceea

File tree

2 files changed

+129
-116
lines changed

2 files changed

+129
-116
lines changed

src/main/java/com/thealgorithms/datastructures/buffers/CircularBuffer.java

+75-7
Original file line numberDiff line numberDiff line change
@@ -2,27 +2,62 @@
22

33
import java.util.concurrent.atomic.AtomicInteger;
44

5+
/**
6+
* The {@code CircularBuffer} class implements a generic circular (or ring) buffer.
7+
* A circular buffer is a fixed-size data structure that operates in a FIFO (First In, First Out) manner.
8+
* The buffer allows you to overwrite old data when the buffer is full and efficiently use limited memory.
9+
* When the buffer is full, adding a new item will overwrite the oldest data.
10+
*
11+
* @param <Item> The type of elements stored in the circular buffer.
12+
*/
513
public class CircularBuffer<Item> {
614
private final Item[] buffer;
715
private final CircularPointer putPointer;
816
private final CircularPointer getPointer;
917
private final AtomicInteger size = new AtomicInteger(0);
1018

19+
/**
20+
* Constructor to initialize the circular buffer with a specified size.
21+
*
22+
* @param size The size of the circular buffer.
23+
* @throws IllegalArgumentException if the size is zero or negative.
24+
*/
1125
public CircularBuffer(int size) {
26+
if (size <= 0) {
27+
throw new IllegalArgumentException("Buffer size must be positive");
28+
}
1229
// noinspection unchecked
1330
this.buffer = (Item[]) new Object[size];
1431
this.putPointer = new CircularPointer(0, size);
1532
this.getPointer = new CircularPointer(0, size);
1633
}
1734

35+
/**
36+
* Checks if the circular buffer is empty.
37+
* This method is based on the current size of the buffer.
38+
*
39+
* @return {@code true} if the buffer is empty, {@code false} otherwise.
40+
*/
1841
public boolean isEmpty() {
1942
return size.get() == 0;
2043
}
2144

45+
/**
46+
* Checks if the circular buffer is full.
47+
* The buffer is considered full when its size equals its capacity.
48+
*
49+
* @return {@code true} if the buffer is full, {@code false} otherwise.
50+
*/
2251
public boolean isFull() {
2352
return size.get() == buffer.length;
2453
}
2554

55+
/**
56+
* Retrieves and removes the item at the front of the buffer (FIFO).
57+
* This operation will move the {@code getPointer} forward.
58+
*
59+
* @return The item at the front of the buffer, or {@code null} if the buffer is empty.
60+
*/
2661
public Item get() {
2762
if (isEmpty()) {
2863
return null;
@@ -33,31 +68,64 @@ public Item get() {
3368
return item;
3469
}
3570

71+
/**
72+
* Adds an item to the end of the buffer (FIFO).
73+
* If the buffer is full, this operation will overwrite the oldest data.
74+
*
75+
* @param item The item to be added.
76+
* @throws IllegalArgumentException if the item is null.
77+
* @return {@code true} if the item was successfully added, {@code false} if the buffer was full and the item overwrote existing data.
78+
*/
3679
public boolean put(Item item) {
80+
if (item == null) {
81+
throw new IllegalArgumentException("Null items are not allowed");
82+
}
83+
84+
boolean wasEmpty = isEmpty();
3785
if (isFull()) {
38-
return false;
86+
getPointer.getAndIncrement(); // Move get pointer to discard oldest item
87+
} else {
88+
size.incrementAndGet();
3989
}
4090

4191
buffer[putPointer.getAndIncrement()] = item;
42-
size.incrementAndGet();
43-
return true;
92+
return wasEmpty;
4493
}
4594

95+
/**
96+
* The {@code CircularPointer} class is a helper class used to track the current index (pointer)
97+
* in the circular buffer.
98+
* The max value represents the capacity of the buffer.
99+
* The `CircularPointer` class ensures that the pointer automatically wraps around to 0
100+
* when it reaches the maximum index.
101+
* This is achieved in the `getAndIncrement` method, where the pointer
102+
* is incremented and then taken modulo the maximum value (`max`).
103+
* This operation ensures that the pointer always stays within the bounds of the buffer.
104+
*/
46105
private static class CircularPointer {
47106
private int pointer;
48107
private final int max;
49108

109+
/**
110+
* Constructor to initialize the circular pointer.
111+
*
112+
* @param pointer The initial position of the pointer.
113+
* @param max The maximum size (capacity) of the circular buffer.
114+
*/
50115
CircularPointer(int pointer, int max) {
51116
this.pointer = pointer;
52117
this.max = max;
53118
}
54119

120+
/**
121+
* Increments the pointer by 1 and wraps it around to 0 if it reaches the maximum value.
122+
* This ensures the pointer always stays within the buffer's bounds.
123+
*
124+
* @return The current pointer value before incrementing.
125+
*/
55126
public int getAndIncrement() {
56-
if (pointer == max) {
57-
pointer = 0;
58-
}
59127
int tmp = pointer;
60-
pointer++;
128+
pointer = (pointer + 1) % max;
61129
return tmp;
62130
}
63131
}
Original file line numberDiff line numberDiff line change
@@ -1,143 +1,88 @@
11
package com.thealgorithms.datastructures.buffers;
22

33
import static org.junit.jupiter.api.Assertions.assertEquals;
4-
import static org.junit.jupiter.api.Assertions.assertFalse;
54
import static org.junit.jupiter.api.Assertions.assertNull;
5+
import static org.junit.jupiter.api.Assertions.assertThrows;
66
import static org.junit.jupiter.api.Assertions.assertTrue;
77

8-
import java.util.ArrayList;
9-
import java.util.Comparator;
10-
import java.util.List;
11-
import java.util.concurrent.CountDownLatch;
12-
import java.util.concurrent.ExecutorService;
13-
import java.util.concurrent.Executors;
14-
import java.util.concurrent.ThreadLocalRandom;
15-
import java.util.concurrent.TimeUnit;
16-
import java.util.concurrent.atomic.AtomicIntegerArray;
17-
import org.junit.jupiter.api.BeforeEach;
18-
import org.junit.jupiter.api.RepeatedTest;
198
import org.junit.jupiter.api.Test;
209

2110
class CircularBufferTest {
22-
private static final int BUFFER_SIZE = 10;
23-
private CircularBuffer<Integer> buffer;
24-
25-
@BeforeEach
26-
void setUp() {
27-
buffer = new CircularBuffer<>(BUFFER_SIZE);
28-
}
2911

3012
@Test
31-
void isEmpty() {
13+
void testInitialization() {
14+
CircularBuffer<Integer> buffer = new CircularBuffer<>(5);
3215
assertTrue(buffer.isEmpty());
33-
buffer.put(generateInt());
34-
assertFalse(buffer.isEmpty());
16+
assertEquals(Boolean.FALSE, buffer.isFull());
3517
}
3618

3719
@Test
38-
void isFull() {
39-
assertFalse(buffer.isFull());
40-
buffer.put(generateInt());
41-
assertFalse(buffer.isFull());
20+
void testPutAndGet() {
21+
CircularBuffer<String> buffer = new CircularBuffer<>(3);
4222

43-
for (int i = 1; i < BUFFER_SIZE; i++) {
44-
buffer.put(generateInt());
45-
}
23+
assertTrue(buffer.put("A"));
24+
assertEquals(Boolean.FALSE, buffer.isEmpty());
25+
assertEquals(Boolean.FALSE, buffer.isFull());
26+
27+
buffer.put("B");
28+
buffer.put("C");
4629
assertTrue(buffer.isFull());
30+
31+
assertEquals("A", buffer.get());
32+
assertEquals("B", buffer.get());
33+
assertEquals("C", buffer.get());
34+
assertTrue(buffer.isEmpty());
4735
}
4836

4937
@Test
50-
void get() {
51-
assertNull(buffer.get());
52-
for (int i = 0; i < 100; i++) {
53-
buffer.put(i);
54-
}
55-
for (int i = 0; i < BUFFER_SIZE; i++) {
56-
assertEquals(i, buffer.get());
57-
}
38+
void testOverwrite() {
39+
CircularBuffer<Integer> buffer = new CircularBuffer<>(3);
40+
41+
buffer.put(1);
42+
buffer.put(2);
43+
buffer.put(3);
44+
assertEquals(Boolean.FALSE, buffer.put(4)); // This should overwrite 1
45+
46+
assertEquals(2, buffer.get());
47+
assertEquals(3, buffer.get());
48+
assertEquals(4, buffer.get());
5849
assertNull(buffer.get());
5950
}
6051

6152
@Test
62-
void put() {
63-
for (int i = 0; i < BUFFER_SIZE; i++) {
64-
assertTrue(buffer.put(generateInt()));
65-
}
66-
assertFalse(buffer.put(generateInt()));
53+
void testEmptyBuffer() {
54+
CircularBuffer<Double> buffer = new CircularBuffer<>(2);
55+
assertNull(buffer.get());
6756
}
6857

69-
@RepeatedTest(1000)
70-
void concurrentTest() throws InterruptedException {
71-
final int numberOfThreadsForProducers = 3;
72-
final int numberOfThreadsForConsumers = 2;
73-
final int numberOfItems = 300;
74-
final CountDownLatch producerCountDownLatch = new CountDownLatch(numberOfItems);
75-
final CountDownLatch consumerCountDownLatch = new CountDownLatch(numberOfItems);
76-
final AtomicIntegerArray resultAtomicArray = new AtomicIntegerArray(numberOfItems);
77-
78-
// We are running 2 ExecutorService simultaneously 1 - producer, 2 - consumer
79-
// Run producer threads to populate buffer.
80-
ExecutorService putExecutors = Executors.newFixedThreadPool(numberOfThreadsForProducers);
81-
putExecutors.execute(() -> {
82-
while (producerCountDownLatch.getCount() > 0) {
83-
int count = (int) producerCountDownLatch.getCount();
84-
boolean put = buffer.put(count);
85-
while (!put) {
86-
put = buffer.put(count);
87-
}
88-
producerCountDownLatch.countDown();
89-
}
90-
});
91-
92-
// Run consumer threads to retrieve the data from buffer.
93-
ExecutorService getExecutors = Executors.newFixedThreadPool(numberOfThreadsForConsumers);
94-
getExecutors.execute(() -> {
95-
while (consumerCountDownLatch.getCount() > 0) {
96-
int count = (int) consumerCountDownLatch.getCount();
97-
Integer item = buffer.get();
98-
while (item == null) {
99-
item = buffer.get();
100-
}
101-
resultAtomicArray.set(count - 1, item);
102-
consumerCountDownLatch.countDown();
103-
}
104-
});
105-
106-
producerCountDownLatch.await();
107-
consumerCountDownLatch.await();
108-
putExecutors.shutdown();
109-
getExecutors.shutdown();
110-
shutDownExecutorSafely(putExecutors);
111-
shutDownExecutorSafely(getExecutors);
112-
113-
List<Integer> resultArray = getSortedListFrom(resultAtomicArray);
114-
for (int i = 0; i < numberOfItems; i++) {
115-
int expectedItem = i + 1;
116-
assertEquals(expectedItem, resultArray.get(i));
117-
}
58+
@Test
59+
void testFullBuffer() {
60+
CircularBuffer<Character> buffer = new CircularBuffer<>(2);
61+
buffer.put('A');
62+
buffer.put('B');
63+
assertTrue(buffer.isFull());
64+
assertEquals(Boolean.FALSE, buffer.put('C')); // This should overwrite 'A'
65+
assertEquals('B', buffer.get());
66+
assertEquals('C', buffer.get());
11867
}
11968

120-
private int generateInt() {
121-
return ThreadLocalRandom.current().nextInt(0, 100);
122-
}
69+
@Test
70+
void testIllegalArguments() {
71+
assertThrows(IllegalArgumentException.class, () -> new CircularBuffer<>(0));
72+
assertThrows(IllegalArgumentException.class, () -> new CircularBuffer<>(-1));
12373

124-
private void shutDownExecutorSafely(ExecutorService executorService) {
125-
try {
126-
if (!executorService.awaitTermination(1_000, TimeUnit.MILLISECONDS)) {
127-
executorService.shutdownNow();
128-
}
129-
} catch (InterruptedException e) {
130-
executorService.shutdownNow();
131-
}
74+
CircularBuffer<String> buffer = new CircularBuffer<>(1);
75+
assertThrows(IllegalArgumentException.class, () -> buffer.put(null));
13276
}
13377

134-
public List<Integer> getSortedListFrom(AtomicIntegerArray atomicArray) {
135-
int length = atomicArray.length();
136-
ArrayList<Integer> result = new ArrayList<>(length);
137-
for (int i = 0; i < length; i++) {
138-
result.add(atomicArray.get(i));
78+
@Test
79+
void testLargeBuffer() {
80+
CircularBuffer<Integer> buffer = new CircularBuffer<>(1000);
81+
for (int i = 0; i < 1000; i++) {
82+
buffer.put(i);
13983
}
140-
result.sort(Comparator.comparingInt(o -> o));
141-
return result;
84+
assertTrue(buffer.isFull());
85+
buffer.put(1000); // This should overwrite 0
86+
assertEquals(1, buffer.get());
14287
}
14388
}

0 commit comments

Comments
 (0)