replace LinkedHashMap with a more memory efficient implementation

This saves approximately 50MB of heap space.
This commit is contained in:
2017-09-30 17:50:44 +02:00
parent 7e00594382
commit d4fd25dc4c
4 changed files with 188 additions and 13 deletions

View File

@@ -1,4 +1,4 @@
dependencies { dependencies {
compile project(':pdb-utils')
} }

View File

@@ -1,28 +1,27 @@
package org.lucares.pdb.api; package org.lucares.pdb.api;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List; import java.util.List;
import java.util.Map;
import java.util.Objects; import java.util.Objects;
import java.util.Set; import java.util.Set;
import java.util.SortedSet; import java.util.SortedSet;
import java.util.TreeSet; import java.util.TreeSet;
import java.util.function.BiConsumer; import java.util.function.BiConsumer;
import org.lucares.utils.MiniMap;
public class Tags { public class Tags {
public static final Tags EMPTY = new Tags(); public static final Tags EMPTY = new Tags();
private final Map<String, Tag> tags; private final MiniMap<String, Tag> tags;
private int cachedHash = 0; private int cachedHash = 0;
private Tags() { private Tags() {
super(); super();
tags = Collections.emptyMap(); tags = MiniMap.emptyMap();
} }
private Tags(final Map<String, Tag> tags) { private Tags(final MiniMap<String, Tag> tags) {
this.tags = tags; this.tags = tags;
} }
@@ -31,13 +30,13 @@ public class Tags {
} }
public static Tags create(final String key, final String value) { public static Tags create(final String key, final String value) {
final Map<String, Tag> tags = new LinkedHashMap<>(1); final MiniMap<String, Tag> tags = new MiniMap<>();
tags.put(key, new Tag(key, value)); tags.put(key, new Tag(key, value));
return new Tags(tags); return new Tags(tags);
} }
public static Tags create(final String key1, final String value1, final String key2, final String value2) { public static Tags create(final String key1, final String value1, final String key2, final String value2) {
final Map<String, Tag> tags = new LinkedHashMap<>(2); final MiniMap<String, Tag> tags = new MiniMap<>();
tags.put(key1, new Tag(key1, value1)); tags.put(key1, new Tag(key1, value1));
tags.put(key2, new Tag(key2, value2)); tags.put(key2, new Tag(key2, value2));
return new Tags(tags); return new Tags(tags);
@@ -45,7 +44,7 @@ public class Tags {
public static Tags create(final String key1, final String value1, final String key2, final String value2, public static Tags create(final String key1, final String value1, final String key2, final String value2,
final String key3, final String value3) { final String key3, final String value3) {
final Map<String, Tag> tags = new LinkedHashMap<>(3); final MiniMap<String, Tag> tags = new MiniMap<>();
tags.put(key1, new Tag(key1, value1)); tags.put(key1, new Tag(key1, value1));
tags.put(key2, new Tag(key2, value2)); tags.put(key2, new Tag(key2, value2));
tags.put(key3, new Tag(key3, value3)); tags.put(key3, new Tag(key3, value3));
@@ -56,7 +55,7 @@ public class Tags {
Objects.requireNonNull(key, "key must not be null"); Objects.requireNonNull(key, "key must not be null");
Objects.requireNonNull(value, "value must not be null"); Objects.requireNonNull(value, "value must not be null");
final Map<String, Tag> newTags = new LinkedHashMap<>(tags); final MiniMap<String, Tag> newTags = new MiniMap<>(tags);
newTags.put(key, new Tag(key, value)); newTags.put(key, new Tag(key, value));
@@ -85,8 +84,12 @@ public class Tags {
} }
public void forEach(final BiConsumer<String, String> keyValueConsumer) { public void forEach(final BiConsumer<String, String> keyValueConsumer) {
for (final Map.Entry<String, Tag> e : tags.entrySet()) {
keyValueConsumer.accept(e.getKey(), e.getValue().getValue()); Set<String> keys = tags.keySet();
for (String key : keys) {
final Tag value = tags.get(key);
keyValueConsumer.accept(key, value.getValue());
} }
} }

View File

@@ -0,0 +1,146 @@
package org.lucares.utils;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
/**
* A memory efficient map implementation. It doesn't implement {@link Map},
* because this class does not support the full API of {@link Map}.
*/
public class MiniMap<K, V> {
private static final Object[] EMPTY_ARRAY = new Object[0];
private static final MiniMap<?,?> EMPTY_MAP = new MiniMap<>();
private Object[] keys;
private Object[] values;
public MiniMap() {
keys = EMPTY_ARRAY;
values = EMPTY_ARRAY;
}
public MiniMap(final MiniMap<K, V> miniMap){
keys = miniMap.keys.clone();
values = miniMap.values.clone();
}
@SuppressWarnings("unchecked")
public static final <K,V> MiniMap<K,V> emptyMap() {
return (MiniMap<K,V>) EMPTY_MAP;
}
public int size() {
return keys.length;
}
public boolean isEmpty() {
return size() == 0;
}
public boolean containsKey(Object key) {
return get(key) != null;
}
@SuppressWarnings("unchecked")
public V get(Object key) {
for (int i = 0; i < keys.length; i++) {
Object object = keys[i];
if (Objects.equals(key, object)) {
return (V) values[i];
}
}
return null;
}
public V put(K key, V value) {
V oldValue = get(key);
if (oldValue != null) {
for (int i = 0; i < keys.length; i++) {
Object object = keys[i];
if (Objects.equals(key, object)) {
values[i] = value;
break;
}
}
} else {
final Object[] newKeys = new Object[keys.length + 1];
System.arraycopy(keys, 0, newKeys, 0, keys.length);
final Object[] newValues = new Object[values.length + 1];
System.arraycopy(values, 0, newValues, 0, values.length);
newKeys[newKeys.length - 1] = key;
newValues[newValues.length - 1] = value;
keys = newKeys;
values = newValues;
}
return oldValue;
}
public void putAll(Map<? extends K, ? extends V> map) {
for (java.util.Map.Entry<? extends K, ? extends V> e : map.entrySet()) {
put(e.getKey(), e.getValue());
}
}
public void clear() {
keys = EMPTY_ARRAY;
values = EMPTY_ARRAY;
}
@SuppressWarnings("unchecked")
public Set<K> keySet() {
final Set<K> result = new HashSet<>(keys.length);
for (Object k : keys) {
result.add((K) k);
}
return result;
}
@SuppressWarnings("unchecked")
public Set<V> values() {
final Set<V> result = new HashSet<>(values.length);
for (Object v : values) {
result.add((V) v);
}
return result;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + Arrays.hashCode(keys);
result = prime * result + Arrays.hashCode(values);
return result;
}
@SuppressWarnings("rawtypes")
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
MiniMap other = (MiniMap) obj;
if (!Arrays.equals(keys, other.keys))
return false;
if (!Arrays.equals(values, other.values))
return false;
return true;
}
}

View File

@@ -0,0 +1,26 @@
package org.lucares.utils;
import java.util.Map;
import org.testng.Assert;
import org.testng.annotations.Test;
@Test
public class MiniMapTest {
public void testInsertGet()
{
final MiniMap<String, String> map = new MiniMap<>();
String key1 = "key1";
String key2 = "key2";
String value1 = "value1";
String value2 = "value1";
map.put(key1, value1);
map.put(key2, value2);
Assert.assertEquals(map.get(key1), value1);
Assert.assertEquals(map.get(key2), value2);
}
}