diff --git a/pom.xml b/pom.xml
index 8c879de44..3f565c3c6 100755
--- a/pom.xml
+++ b/pom.xml
@@ -212,6 +212,7 @@
1.5.0
test
+
diff --git a/src/main/java/net/openhft/chronicle/map/ChronicleMap.java b/src/main/java/net/openhft/chronicle/map/ChronicleMap.java
index 6618dc2ac..f1d42cdbf 100755
--- a/src/main/java/net/openhft/chronicle/map/ChronicleMap.java
+++ b/src/main/java/net/openhft/chronicle/map/ChronicleMap.java
@@ -213,5 +213,36 @@ public interface ChronicleMap extends ConcurrentMap,
* @return the value Class or UnresolvedType if unknown.
*/
Type valueType();
-}
+ /**
+ * @return the amount of free space in the map as a percentage.
+ * When the free space gets low ( around 5% ) the map will automatically expand. The
+ * number of times it can automatically expand is based on the
+ * {@code net.openhft.chronicle.map.ChronicleMapBuilder#maxBloatFactor}. If the map
+ * expands you will see an increase in the available free space. It is not possible to
+ * expand the chronicle map manually.
+ *
+ * see also {@see net.openhft.chronicle.map.ChronicleMap#remainingAutoResizes} as these operations are related.
+ */
+ default short percentageFreeSpace() {
+ throw new UnsupportedOperationException("todo");
+ }
+
+ /**
+ * @return the number of times in the future the map can expand its capacity
+ * ( by expending capacity we mean the number of possible entries that
+ * Can be stored into the map), the map will expand automatically, however, there is an upper limit to the
+ * number of times the map can expand.
+ * This limit is set via the {@code net.openhft.chronicle.map.ChronicleMapBuilder#maxBloatFactor} if
+ * the {@code remainingResizes} drops to zero,
+ * then the map is no longer able to resize, if subsequently, the free space
+ * ( see {@link net.openhft.chronicle.map.ChronicleMap#percentageFreeSpace}) if the map becomes low ( around 5% ),
+ * the map will not be able to take more entries and will fail with an {@code
+ * java.lang.IllegalStateException} for production systems it is recommended you periodically monitor this result and the both the
+ * {@link net.openhft.chronicle.map.ChronicleMap#percentageFreeSpace}.
+ */
+ default int remainingAutoResizes() {
+ throw new UnsupportedOperationException("todo");
+ }
+
+}
\ No newline at end of file
diff --git a/src/main/java/net/openhft/chronicle/map/ChronicleMapBuilder.java b/src/main/java/net/openhft/chronicle/map/ChronicleMapBuilder.java
index 7c3927b25..98b3d004a 100755
--- a/src/main/java/net/openhft/chronicle/map/ChronicleMapBuilder.java
+++ b/src/main/java/net/openhft/chronicle/map/ChronicleMapBuilder.java
@@ -204,7 +204,7 @@ public final class ChronicleMapBuilder implements
private int maxChunksPerEntry = -1;
private int alignment = UNDEFINED_ALIGNMENT_CONFIG;
private long entries = -1L;
- private double maxBloatFactor = 1.0;
+ double maxBloatFactor = 1.0;
private boolean allowSegmentTiering = true;
private double nonTieredSegmentsPercentile = 0.99999;
private boolean aligned64BitMemoryOperationsAtomic = OS.is64Bit();
diff --git a/src/main/java/net/openhft/chronicle/map/VanillaChronicleMap.java b/src/main/java/net/openhft/chronicle/map/VanillaChronicleMap.java
index 78d93b377..d385f8cc6 100755
--- a/src/main/java/net/openhft/chronicle/map/VanillaChronicleMap.java
+++ b/src/main/java/net/openhft/chronicle/map/VanillaChronicleMap.java
@@ -16,6 +16,9 @@
package net.openhft.chronicle.map;
+import impl.CompiledMapIterationContext;
+import impl.CompiledMapQueryContext;
+import net.openhft.chronicle.algo.bitset.ReusableBitSet;
import net.openhft.chronicle.algo.hashing.LongHashFunction;
import net.openhft.chronicle.bytes.Bytes;
import net.openhft.chronicle.bytes.BytesStore;
@@ -34,7 +37,9 @@ import net.openhft.chronicle.hash.serialization.DataAccess;
import net.openhft.chronicle.hash.serialization.SizeMarshaller;
import net.openhft.chronicle.hash.serialization.SizedReader;
import net.openhft.chronicle.hash.serialization.impl.SerializationBuilder;
-import net.openhft.chronicle.map.impl.*;
+import net.openhft.chronicle.map.impl.IterationContext;
+import net.openhft.chronicle.map.impl.NullReturnValue;
+import net.openhft.chronicle.map.impl.QueryContextInterface;
import net.openhft.chronicle.map.impl.ret.InstanceReturnValue;
import net.openhft.chronicle.set.ChronicleSet;
import net.openhft.chronicle.wire.WireIn;
@@ -51,11 +56,14 @@ import java.util.function.Function;
import static net.openhft.chronicle.map.ChronicleMapBuilder.greatestCommonDivisor;
+@SuppressWarnings("JavadocReference")
public class VanillaChronicleMap
extends VanillaChronicleHash, MapSegmentContext,
ExternalMapQueryContext>
implements AbstractChronicleMap {
+ private final double maxBloatFactor;
+
public SizeMarshaller valueSizeMarshaller;
public SizedReader valueReader;
public DataAccess valueDataAccess;
@@ -98,6 +106,7 @@ public class VanillaChronicleMap
valueSizeMarshaller = valueBuilder.sizeMarshaller();
valueReader = valueBuilder.reader();
valueDataAccess = valueBuilder.dataAccess();
+ maxBloatFactor = builder.maxBloatFactor;
constantlySizedEntry = builder.constantlySizedEntries();
@@ -226,6 +235,51 @@ public class VanillaChronicleMap
return valueClass;
}
+ private long tiersUsed() {
+ return globalMutableState().getExtraTiersInUse();
+ }
+
+ private long maxTiers() {
+ return (long) (maxBloatFactor * actualSegments);
+ }
+
+ @Override
+ public int remainingAutoResizes() {
+ return (int) (maxTiers() - tiersUsed());
+ }
+
+ @Override
+ public short percentageFreeSpace() {
+
+ double totalUsed = 0;
+ double totalSize = 0;
+
+ try (IterationContext c = iterationContext()) {
+ for (int segmentIndex = 0; segmentIndex < segments(); segmentIndex++) {
+ c.initSegmentIndex(segmentIndex);
+
+ if (!(c instanceof CompiledMapIterationContext))
+ continue;
+ CompiledMapIterationContext c1 = (CompiledMapIterationContext) c;
+
+ c1.goToFirstTier();
+ ReusableBitSet freeList = c1.freeList();
+ totalUsed += freeList.cardinality();
+ totalSize += freeList.logicalSize();
+
+ while (c1.hasNextTier()) {
+ c1.nextTier();
+ ReusableBitSet freeList0 = c1.freeList();
+ totalUsed += freeList0.cardinality();
+ totalSize += freeList.logicalSize();
+ }
+
+ }
+ }
+
+ return (short) (100 - (int) (100 * totalUsed / totalSize));
+ }
+
@NotNull
@Override
public final Closeable acquireContext(K key, V usingValue) {
diff --git a/src/test/java/net/openhft/chronicle/map/jsr166/map/ChronicleMapTest.java b/src/test/java/net/openhft/chronicle/map/jsr166/map/ChronicleMapTest.java
index 02a71c560..8ce65c4ba 100644
--- a/src/test/java/net/openhft/chronicle/map/jsr166/map/ChronicleMapTest.java
+++ b/src/test/java/net/openhft/chronicle/map/jsr166/map/ChronicleMapTest.java
@@ -19,6 +19,7 @@ package net.openhft.chronicle.map.jsr166.map;
import net.openhft.chronicle.map.ChronicleMap;
import net.openhft.chronicle.map.ChronicleMapBuilder;
import net.openhft.chronicle.map.jsr166.JSR166TestCase;
+import org.junit.Assert;
import org.junit.Test;
import java.io.IOException;
@@ -670,40 +671,37 @@ public class ChronicleMapTest extends JSR166TestCase {
}
}
- /**
- * A deserialized map equals original
- */
- /* @Test public void testSerialization() {
- Map x = map5();
- Map y = serialClone(x);
+ @Test
+ public void testPercentageComplete() {
- assertNotSame(x, y);
- assertEquals(x.size(), y.size());
- assertEquals(x, y);
- assertEquals(y, x);
- }*/
+ try (ChronicleMap map = ChronicleMap
+ .of(Integer.class, Integer.class)
+ .entries(1600)
+ .actualSegments(3)
+ .maxBloatFactor(2)
+ .create()) {
+
+ long remainingAutoResizes = 0;
+ short percentageFreeSpace = 0;
+ try {
+ for (int i = 0; ; i++) {
+
+ map.put(i, 0);
+ remainingAutoResizes = map.remainingAutoResizes();
+ percentageFreeSpace = map.percentageFreeSpace();
+
+ }
+ } catch (IllegalStateException e) {
+ if (e.getMessage().contains("Attempt to allocate")) {
+ Assert.assertEquals(0, (int) remainingAutoResizes);
+ Assert.assertTrue(percentageFreeSpace < 6);
+ return;
+ }
+ Assert.fail();
+ }
- /**
- * TODO : SetValue of an EntrySet entry sets value in the map.
- */
- /*@Test public void testSetValueWriteThrough() {
- // Adapted from a bug report by Eric Zoerner
- ChronicleMap map = newShmIntString(2, 5.0f, 1);
- assertTrue(map.isEmpty());
- for (int i = 0; i < 20; i++)
- map.put(new Integer(i), new Integer(i));
- assertFalse(map.isEmpty());
- Map.Entry entry1 = (Map.Entry) map.entrySet().iterator().next();
- // Unless it happens to be first (in which case remainder of
- // test is skipped), remove a possibly-colliding key from map
- // which, under some implementations, may cause entry1 to be
- // cloned in map
- if (!entry1.getKey().equals(new Integer(16))) {
- map.remove(new Integer(16));
- entry1.setValue("XYZ");
- assertTrue(map.containsValue("XYZ")); // fails if write-through broken
}
- }*/
+ }
}