From f2957bf7ea227e9521d08e8da3e2f2ed1473cd6e Mon Sep 17 00:00:00 2001 From: priyank-sriv Date: Mon, 15 Jun 2020 01:17:30 +0530 Subject: [PATCH 1/7] circular buffer implementation --- .../circularbuffer/CircularBuffer.java | 73 +++++++++++++++++++ 1 file changed, 73 insertions(+) create mode 100644 data-structures/src/main/java/com/baeldung/circularbuffer/CircularBuffer.java diff --git a/data-structures/src/main/java/com/baeldung/circularbuffer/CircularBuffer.java b/data-structures/src/main/java/com/baeldung/circularbuffer/CircularBuffer.java new file mode 100644 index 0000000000..c36831636f --- /dev/null +++ b/data-structures/src/main/java/com/baeldung/circularbuffer/CircularBuffer.java @@ -0,0 +1,73 @@ +package com.baeldung.circularbuffer; + +public class CircularBuffer { + + private int capacity, writeIndex, readIndex; + private E[] data; + + @SuppressWarnings("unchecked") + public CircularBuffer(int capacity) { + + this.capacity = capacity; + this.data = (E[]) new Object[capacity]; + + this.readIndex = 0; + this.writeIndex = -1; + } + + public boolean offer(E element) { + + if (!isFull()) { + + writeIndex += 1; + int nextWriteIndex = writeIndex % capacity; + data[nextWriteIndex] = element; + + return true; + } + + return false; + } + + public E poll() { + + if (!isEmpty()) { + + int nextReadIndex = readIndex % capacity; + readIndex += 1; + + return data[nextReadIndex]; + } + + return null; + } + + public E peek() { + + if (!isEmpty()) { + + return data[readIndex % capacity]; + } + + return null; + } + + public int capacity() { + return capacity; + } + + public int size() { + + return (writeIndex - readIndex) + 1; + } + + public boolean isEmpty() { + + return writeIndex < readIndex; + } + + public boolean isFull() { + + return size() >= capacity; + } +} From 5263fbf07003875c2dc8b4e1ac2be2b749136b9b Mon Sep 17 00:00:00 2001 From: priyank-sriv Date: Mon, 15 Jun 2020 01:18:22 +0530 Subject: [PATCH 2/7] circular buffer test init --- .../CircularBufferUnitTest.java | 44 +++++++++++++++++++ 1 file changed, 44 insertions(+) create mode 100644 data-structures/src/test/java/com/baeldung/circularbuffer/CircularBufferUnitTest.java diff --git a/data-structures/src/test/java/com/baeldung/circularbuffer/CircularBufferUnitTest.java b/data-structures/src/test/java/com/baeldung/circularbuffer/CircularBufferUnitTest.java new file mode 100644 index 0000000000..34658b9251 --- /dev/null +++ b/data-structures/src/test/java/com/baeldung/circularbuffer/CircularBufferUnitTest.java @@ -0,0 +1,44 @@ +package com.baeldung.circularbuffer; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import org.junit.Test; + +public class CircularBufferUnitTest { + + private final String[] shapes = { "Circle", "Triangle", "Rectangle", "Square", "Rhombus", "Trapezoid", "Pentagon", "Pentagram", "Hexagon", "Hexagram" }; + + @Test + public void givenCircularBuffer_WhenAnElementIsAddedAndRemoved_thenBufferIsEmpty() { + + int capacity = 2; + CircularBuffer buffer = new CircularBuffer<>(capacity); + + assertEquals(capacity, buffer.capacity()); + + buffer.offer("Rectangle"); + + assertEquals(false, buffer.isEmpty()); + assertEquals(1, buffer.size()); + + buffer.poll(); + + assertEquals(true, buffer.isEmpty()); + } + + @Test + public void givenCircularBuffer_WhenElementsAreAddedToCapacity_thenBufferIsFull() { + + int capacity = shapes.length; + CircularBuffer buffer = new CircularBuffer<>(capacity); + + assertEquals(true, buffer.isEmpty()); + + for (String shape : shapes) { + buffer.offer(shape); + } + + assertEquals(true, buffer.isFull()); + } + +} From a4efb737a865c8212ea95940c9fe9aed4719fee9 Mon Sep 17 00:00:00 2001 From: priyank-sriv Date: Mon, 15 Jun 2020 18:44:01 +0530 Subject: [PATCH 3/7] write and read sequence volatile --- .../circularbuffer/CircularBuffer.java | 45 +++++++++---------- 1 file changed, 21 insertions(+), 24 deletions(-) diff --git a/data-structures/src/main/java/com/baeldung/circularbuffer/CircularBuffer.java b/data-structures/src/main/java/com/baeldung/circularbuffer/CircularBuffer.java index c36831636f..a74b480231 100644 --- a/data-structures/src/main/java/com/baeldung/circularbuffer/CircularBuffer.java +++ b/data-structures/src/main/java/com/baeldung/circularbuffer/CircularBuffer.java @@ -1,28 +1,36 @@ package com.baeldung.circularbuffer; public class CircularBuffer { + + private static final int DEFAULT_CAPACITY = 8; - private int capacity, writeIndex, readIndex; - private E[] data; + private final int capacity; + private final E[] data; + private volatile int writeSequence, readSequence; @SuppressWarnings("unchecked") public CircularBuffer(int capacity) { - this.capacity = capacity; + this.capacity = (capacity < 1 ? DEFAULT_CAPACITY : capacity); this.data = (E[]) new Object[capacity]; - this.readIndex = 0; - this.writeIndex = -1; + this.readSequence = 0; + this.writeSequence = -1; } + /** + * Adds a new element to the buffer, if the buffer is not full + * @param element + * @return + */ public boolean offer(E element) { if (!isFull()) { - writeIndex += 1; - int nextWriteIndex = writeIndex % capacity; - data[nextWriteIndex] = element; + int nextWriteSeq = writeSequence + 1; + data[nextWriteSeq % capacity] = element; + writeSequence += 1; return true; } @@ -33,20 +41,9 @@ public class CircularBuffer { if (!isEmpty()) { - int nextReadIndex = readIndex % capacity; - readIndex += 1; - - return data[nextReadIndex]; - } - - return null; - } - - public E peek() { - - if (!isEmpty()) { - - return data[readIndex % capacity]; + E nextValue = data[readSequence % capacity]; + readSequence += 1; + return nextValue; } return null; @@ -58,12 +55,12 @@ public class CircularBuffer { public int size() { - return (writeIndex - readIndex) + 1; + return (writeSequence - readSequence) + 1; } public boolean isEmpty() { - return writeIndex < readIndex; + return writeSequence < readSequence; } public boolean isFull() { From 02e7129ff59c855b835f8eb915563d6b18644cd6 Mon Sep 17 00:00:00 2001 From: priyank-sriv Date: Mon, 15 Jun 2020 18:44:24 +0530 Subject: [PATCH 4/7] simple producer consumer test --- .../CircularBufferUnitTest.java | 3 +- .../ProducerConsumerLiveTest.java | 77 +++++++++++++++++++ 2 files changed, 78 insertions(+), 2 deletions(-) create mode 100644 data-structures/src/test/java/com/baeldung/circularbuffer/ProducerConsumerLiveTest.java diff --git a/data-structures/src/test/java/com/baeldung/circularbuffer/CircularBufferUnitTest.java b/data-structures/src/test/java/com/baeldung/circularbuffer/CircularBufferUnitTest.java index 34658b9251..0809444901 100644 --- a/data-structures/src/test/java/com/baeldung/circularbuffer/CircularBufferUnitTest.java +++ b/data-structures/src/test/java/com/baeldung/circularbuffer/CircularBufferUnitTest.java @@ -2,7 +2,7 @@ package com.baeldung.circularbuffer; import static org.junit.jupiter.api.Assertions.assertEquals; -import org.junit.Test; +import org.junit.jupiter.api.Test; public class CircularBufferUnitTest { @@ -40,5 +40,4 @@ public class CircularBufferUnitTest { assertEquals(true, buffer.isFull()); } - } diff --git a/data-structures/src/test/java/com/baeldung/circularbuffer/ProducerConsumerLiveTest.java b/data-structures/src/test/java/com/baeldung/circularbuffer/ProducerConsumerLiveTest.java new file mode 100644 index 0000000000..06b1c5e4cf --- /dev/null +++ b/data-structures/src/test/java/com/baeldung/circularbuffer/ProducerConsumerLiveTest.java @@ -0,0 +1,77 @@ +package com.baeldung.circularbuffer; + +import static org.junit.Assert.assertArrayEquals; + +import java.util.concurrent.locks.LockSupport; + +import org.junit.jupiter.api.Test; + +public class ProducerConsumerLiveTest { + + private final String[] shapes = { "Circle", "Triangle", "Rectangle", "Square", "Rhombus", "Trapezoid", "Pentagon", "Pentagram", "Hexagon", "Hexagram" }; + + @Test + public void givenACircularBuffer_whenInterleavingProducerConsumer_thenElementsMatch() throws InterruptedException { + CircularBuffer buffer = new CircularBuffer(shapes.length); + String[] consumedShapes = new String[shapes.length]; + + Thread producer = new Thread(new Producer(shapes, buffer)); + Thread consumer = new Thread(new Consumer(consumedShapes, buffer)); + + producer.start(); + consumer.start(); + + producer.join(); + consumer.join(); + + assertArrayEquals(shapes, consumedShapes); + } + + static class Producer implements Runnable { + + private String[] producerShapes; + private CircularBuffer buffer; + + public Producer(String[] producerShapes, CircularBuffer buffer) { + this.producerShapes = producerShapes; + this.buffer = buffer; + } + + @Override + public void run() { + + for (int i = 0; i < producerShapes.length;) { + if (buffer.offer(producerShapes[i])) { + System.out.println("Produced: " + producerShapes[i]); + i++; + LockSupport.parkNanos(5); + } + } + } + } + + static class Consumer implements Runnable { + + private CircularBuffer buffer; + private String[] consumedShapes; + + public Consumer(String[] consumedShapes, CircularBuffer buffer) { + this.consumedShapes = consumedShapes; + this.buffer = buffer; + } + + @Override + public void run() { + + for (int i = 0; i < consumedShapes.length;) { + String shape = buffer.poll(); + if (shape != null) { + consumedShapes[i++] = shape; + + LockSupport.parkNanos(5); + System.out.println("Consumed: " + shape); + } + } + } + } +} From 7479e7b5a04b921e6d0fa12d03b6fe1b0561bff7 Mon Sep 17 00:00:00 2001 From: priyank-sriv Date: Mon, 15 Jun 2020 19:27:30 +0530 Subject: [PATCH 5/7] unit test for empty and full buffer --- .../circularbuffer/CircularBuffer.java | 7 +----- .../CircularBufferUnitTest.java | 25 ++++++++++++++----- 2 files changed, 20 insertions(+), 12 deletions(-) diff --git a/data-structures/src/main/java/com/baeldung/circularbuffer/CircularBuffer.java b/data-structures/src/main/java/com/baeldung/circularbuffer/CircularBuffer.java index a74b480231..6f60621bc8 100644 --- a/data-structures/src/main/java/com/baeldung/circularbuffer/CircularBuffer.java +++ b/data-structures/src/main/java/com/baeldung/circularbuffer/CircularBuffer.java @@ -1,7 +1,7 @@ package com.baeldung.circularbuffer; public class CircularBuffer { - + private static final int DEFAULT_CAPACITY = 8; private final int capacity; @@ -18,11 +18,6 @@ public class CircularBuffer { this.writeSequence = -1; } - /** - * Adds a new element to the buffer, if the buffer is not full - * @param element - * @return - */ public boolean offer(E element) { if (!isFull()) { diff --git a/data-structures/src/test/java/com/baeldung/circularbuffer/CircularBufferUnitTest.java b/data-structures/src/test/java/com/baeldung/circularbuffer/CircularBufferUnitTest.java index 0809444901..801a46b45c 100644 --- a/data-structures/src/test/java/com/baeldung/circularbuffer/CircularBufferUnitTest.java +++ b/data-structures/src/test/java/com/baeldung/circularbuffer/CircularBufferUnitTest.java @@ -1,5 +1,8 @@ package com.baeldung.circularbuffer; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; import static org.junit.jupiter.api.Assertions.assertEquals; import org.junit.jupiter.api.Test; @@ -9,7 +12,7 @@ public class CircularBufferUnitTest { private final String[] shapes = { "Circle", "Triangle", "Rectangle", "Square", "Rhombus", "Trapezoid", "Pentagon", "Pentagram", "Hexagon", "Hexagram" }; @Test - public void givenCircularBuffer_WhenAnElementIsAddedAndRemoved_thenBufferIsEmpty() { + public void givenCircularBuffer_whenAnElementIsAddedAndRemoved_thenBufferIsEmpty() { int capacity = 2; CircularBuffer buffer = new CircularBuffer<>(capacity); @@ -18,26 +21,36 @@ public class CircularBufferUnitTest { buffer.offer("Rectangle"); - assertEquals(false, buffer.isEmpty()); + assertFalse(buffer.isEmpty()); assertEquals(1, buffer.size()); buffer.poll(); - assertEquals(true, buffer.isEmpty()); + assertTrue(buffer.isEmpty()); } @Test - public void givenCircularBuffer_WhenElementsAreAddedToCapacity_thenBufferIsFull() { + public void givenCircularBuffer_whenFilledToCapacity_thenNoMoreElementsCanBeAdded() { int capacity = shapes.length; CircularBuffer buffer = new CircularBuffer<>(capacity); - assertEquals(true, buffer.isEmpty()); + assertTrue(buffer.isEmpty()); for (String shape : shapes) { buffer.offer(shape); } - assertEquals(true, buffer.isFull()); + assertTrue(buffer.isFull()); + assertFalse(buffer.offer("Octagon")); + } + + @Test + public void givenCircularBuffer_whenBufferIsEmpty_thenReturnsNull() { + + CircularBuffer buffer = new CircularBuffer<>(1); + + assertTrue(buffer.isEmpty()); + assertNull(buffer.poll()); } } From 9ba617bbf61f6f3e4aac5c736ad4b6e6e3ca087d Mon Sep 17 00:00:00 2001 From: priyank-sriv Date: Thu, 18 Jun 2020 02:25:41 +0530 Subject: [PATCH 6/7] polishing --- .../circularbuffer/CircularBuffer.java | 16 +++++++++--- .../CircularBufferUnitTest.java | 26 +++++++++++++++---- 2 files changed, 34 insertions(+), 8 deletions(-) diff --git a/data-structures/src/main/java/com/baeldung/circularbuffer/CircularBuffer.java b/data-structures/src/main/java/com/baeldung/circularbuffer/CircularBuffer.java index 6f60621bc8..271fc5f376 100644 --- a/data-structures/src/main/java/com/baeldung/circularbuffer/CircularBuffer.java +++ b/data-structures/src/main/java/com/baeldung/circularbuffer/CircularBuffer.java @@ -11,7 +11,7 @@ public class CircularBuffer { @SuppressWarnings("unchecked") public CircularBuffer(int capacity) { - this.capacity = (capacity < 1 ? DEFAULT_CAPACITY : capacity); + this.capacity = (capacity < 1) ? DEFAULT_CAPACITY : capacity; this.data = (E[]) new Object[capacity]; this.readSequence = 0; @@ -20,7 +20,7 @@ public class CircularBuffer { public boolean offer(E element) { - if (!isFull()) { + if (isNotFull()) { int nextWriteSeq = writeSequence + 1; data[nextWriteSeq % capacity] = element; @@ -34,7 +34,7 @@ public class CircularBuffer { public E poll() { - if (!isEmpty()) { + if (isNotEmpty()) { E nextValue = data[readSequence % capacity]; readSequence += 1; @@ -62,4 +62,14 @@ public class CircularBuffer { return size() >= capacity; } + + private boolean isNotEmpty() { + + return !isEmpty(); + } + + private boolean isNotFull() { + + return !isFull(); + } } diff --git a/data-structures/src/test/java/com/baeldung/circularbuffer/CircularBufferUnitTest.java b/data-structures/src/test/java/com/baeldung/circularbuffer/CircularBufferUnitTest.java index 801a46b45c..4b8e8646f3 100644 --- a/data-structures/src/test/java/com/baeldung/circularbuffer/CircularBufferUnitTest.java +++ b/data-structures/src/test/java/com/baeldung/circularbuffer/CircularBufferUnitTest.java @@ -10,14 +10,30 @@ import org.junit.jupiter.api.Test; public class CircularBufferUnitTest { private final String[] shapes = { "Circle", "Triangle", "Rectangle", "Square", "Rhombus", "Trapezoid", "Pentagon", "Pentagram", "Hexagon", "Hexagram" }; + private final int defaultCapacity = shapes.length; @Test - public void givenCircularBuffer_whenAnElementIsAddedAndRemoved_thenBufferIsEmpty() { + public void givenCircularBuffer_whenAnElementIsEnqueued_thenSizeIsOne() { + CircularBuffer buffer = new CircularBuffer<>(defaultCapacity); - int capacity = 2; - CircularBuffer buffer = new CircularBuffer<>(capacity); + assertTrue(buffer.offer("Square")); + assertEquals(1, buffer.size()); + } - assertEquals(capacity, buffer.capacity()); + @Test + public void givenCircularBuffer_whenAnElementIsDequeued_thenElementMatchesEnqueuedElement() { + CircularBuffer buffer = new CircularBuffer<>(defaultCapacity); + + buffer.offer("Triangle"); + + String shape = buffer.poll(); + assertEquals("Triangle", shape); + } + + @Test + public void givenCircularBuffer_whenAnElementIsEnqueuedAndDeququed_thenBufferIsEmpty() { + + CircularBuffer buffer = new CircularBuffer<>(defaultCapacity); buffer.offer("Rectangle"); @@ -30,7 +46,7 @@ public class CircularBufferUnitTest { } @Test - public void givenCircularBuffer_whenFilledToCapacity_thenNoMoreElementsCanBeAdded() { + public void givenCircularBuffer_whenFilledToCapacity_thenNoMoreElementsCanBeEnqueued() { int capacity = shapes.length; CircularBuffer buffer = new CircularBuffer<>(capacity); From b018d2c6cd955d51fead1007494258a8421dcd98 Mon Sep 17 00:00:00 2001 From: priyank-sriv Date: Sun, 21 Jun 2020 03:06:32 +0530 Subject: [PATCH 7/7] review comments --- .../circularbuffer/CircularBuffer.java | 4 +- .../ProducerConsumerLiveTest.java | 63 ++++++++++--------- 2 files changed, 35 insertions(+), 32 deletions(-) diff --git a/data-structures/src/main/java/com/baeldung/circularbuffer/CircularBuffer.java b/data-structures/src/main/java/com/baeldung/circularbuffer/CircularBuffer.java index 271fc5f376..6b315265b4 100644 --- a/data-structures/src/main/java/com/baeldung/circularbuffer/CircularBuffer.java +++ b/data-structures/src/main/java/com/baeldung/circularbuffer/CircularBuffer.java @@ -25,7 +25,7 @@ public class CircularBuffer { int nextWriteSeq = writeSequence + 1; data[nextWriteSeq % capacity] = element; - writeSequence += 1; + writeSequence++; return true; } @@ -37,7 +37,7 @@ public class CircularBuffer { if (isNotEmpty()) { E nextValue = data[readSequence % capacity]; - readSequence += 1; + readSequence++; return nextValue; } diff --git a/data-structures/src/test/java/com/baeldung/circularbuffer/ProducerConsumerLiveTest.java b/data-structures/src/test/java/com/baeldung/circularbuffer/ProducerConsumerLiveTest.java index 06b1c5e4cf..83ce27043b 100644 --- a/data-structures/src/test/java/com/baeldung/circularbuffer/ProducerConsumerLiveTest.java +++ b/data-structures/src/test/java/com/baeldung/circularbuffer/ProducerConsumerLiveTest.java @@ -2,6 +2,11 @@ package com.baeldung.circularbuffer; import static org.junit.Assert.assertArrayEquals; +import java.util.concurrent.Callable; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.Future; +import java.util.concurrent.TimeUnit; import java.util.concurrent.locks.LockSupport; import org.junit.jupiter.api.Test; @@ -11,38 +16,34 @@ public class ProducerConsumerLiveTest { private final String[] shapes = { "Circle", "Triangle", "Rectangle", "Square", "Rhombus", "Trapezoid", "Pentagon", "Pentagram", "Hexagon", "Hexagram" }; @Test - public void givenACircularBuffer_whenInterleavingProducerConsumer_thenElementsMatch() throws InterruptedException { + public void givenACircularBuffer_whenInterleavingProducerConsumer_thenElementsMatch() throws Exception { CircularBuffer buffer = new CircularBuffer(shapes.length); - String[] consumedShapes = new String[shapes.length]; - Thread producer = new Thread(new Producer(shapes, buffer)); - Thread consumer = new Thread(new Consumer(consumedShapes, buffer)); + ExecutorService executorService = Executors.newFixedThreadPool(2); - producer.start(); - consumer.start(); + executorService.submit(new Producer(buffer, shapes)); + Future consumed = executorService.submit(new Consumer(buffer, shapes.length)); - producer.join(); - consumer.join(); - - assertArrayEquals(shapes, consumedShapes); + String[] shapesConsumed = consumed.get(5L, TimeUnit.SECONDS); + assertArrayEquals(shapes, shapesConsumed); } - static class Producer implements Runnable { + static class Producer implements Runnable { - private String[] producerShapes; - private CircularBuffer buffer; + private CircularBuffer buffer; + private T[] items; - public Producer(String[] producerShapes, CircularBuffer buffer) { - this.producerShapes = producerShapes; + public Producer(CircularBuffer buffer, T[] items) { this.buffer = buffer; + this.items = items; } @Override public void run() { - for (int i = 0; i < producerShapes.length;) { - if (buffer.offer(producerShapes[i])) { - System.out.println("Produced: " + producerShapes[i]); + for (int i = 0; i < items.length;) { + if (buffer.offer(items[i])) { + System.out.println("Produced: " + items[i]); i++; LockSupport.parkNanos(5); } @@ -50,28 +51,30 @@ public class ProducerConsumerLiveTest { } } - static class Consumer implements Runnable { + @SuppressWarnings("unchecked") + static class Consumer implements Callable { - private CircularBuffer buffer; - private String[] consumedShapes; + private CircularBuffer buffer; + private int expectedCount; - public Consumer(String[] consumedShapes, CircularBuffer buffer) { - this.consumedShapes = consumedShapes; + public Consumer(CircularBuffer buffer, int expectedCount) { this.buffer = buffer; + this.expectedCount = expectedCount; } @Override - public void run() { - - for (int i = 0; i < consumedShapes.length;) { - String shape = buffer.poll(); - if (shape != null) { - consumedShapes[i++] = shape; + public T[] call() throws Exception { + T[] items = (T[]) new Object[expectedCount]; + for (int i = 0; i < items.length;) { + T item = buffer.poll(); + if (item != null) { + items[i++] = item; LockSupport.parkNanos(5); - System.out.println("Consumed: " + shape); + System.out.println("Consumed: " + item); } } + return items; } } }