replace LinkedHashMap with a more memory efficient implementation
This saves approximately 50MB of heap space.
This commit is contained in:
@@ -1,4 +1,4 @@
|
|||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
|
compile project(':pdb-utils')
|
||||||
}
|
}
|
||||||
@@ -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());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
146
pdb-utils/src/main/java/org/lucares/utils/MiniMap.java
Normal file
146
pdb-utils/src/main/java/org/lucares/utils/MiniMap.java
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
26
pdb-utils/src/test/java/org/lucares/utils/MiniMapTest.java
Normal file
26
pdb-utils/src/test/java/org/lucares/utils/MiniMapTest.java
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user