Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,9 @@

<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
<version>RELEASE</version>
<scope>test</scope>
</dependency>
</dependencies>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
package de.comparus.opensource.longmap;

import java.util.Iterator;
import java.util.NoSuchElementException;

class CollisionAwareLongBucketIterator<V> implements Iterator<LongMapNode<V>> {

private final LongMapNode<V>[] buckets;
private LongMapNode<V> next;
private int currentIndex = 0;

public CollisionAwareLongBucketIterator(LongMapNode<V>[] buckets) {
this.buckets = buckets;
next = getNext();
}

@Override
public boolean hasNext() {
return next != null;
}

@Override
public LongMapNode<V> next() {
if (next == null) {
throw new NoSuchElementException("There is no next element to iterate upon.");
}

LongMapNode<V> current = next;
next = getNext();
return current;
}

private LongMapNode<V> getNext() {
if (next != null && next.getCollision() != null) {
return next.getCollision();
}

while (currentIndex < buckets.length) {
if (buckets[currentIndex] != null) {
return buckets[currentIndex++];
}
currentIndex++;
}
return null;
}
}
164 changes: 156 additions & 8 deletions src/main/java/de/comparus/opensource/longmap/LongMapImpl.java
Original file line number Diff line number Diff line change
@@ -1,43 +1,191 @@
package de.comparus.opensource.longmap;

public class LongMapImpl<V> implements LongMap<V> {
import java.lang.reflect.Array;
import java.util.Arrays;
import java.util.Iterator;

public class LongMapImpl<V> implements LongMap<V>, Iterable<LongMapNode<V>> {
private static final int ARRAY_MAX_SIZE = Integer.MAX_VALUE - 8;
private static final int DEFAULT_CAPACITY = 8;
private static final float DEFAULT_LOAD_FACTOR = 0.75f;
private static final int RESIZE_FACTOR = 2;
final float loadFactor;
private int bucketCount;
private LongMapNode<V>[] buckets;

public LongMapImpl() {
this(DEFAULT_CAPACITY, DEFAULT_LOAD_FACTOR);
}

@SuppressWarnings("unchecked")
public LongMapImpl(int capacity, float loadFactor) {
this.loadFactor = loadFactor;
this.buckets = new LongMapNode[capacity];
}

public V put(long key, V value) {
return null;
if (bucketCount == ARRAY_MAX_SIZE) {
throw new IllegalStateException(String.format(
"Instance of Map is capped. It can only store %s mappings at max.", ARRAY_MAX_SIZE
));
}

if (isThresholdExceeded(key)) {
resize();
}

int bucketIndex = getIndex(key);
LongMapNode<V> currentBucket = buckets[bucketIndex];

if (currentBucket == null) {
buckets[bucketIndex] = new LongMapNode<>(key, value);
bucketCount++;
return null;
}

V storedValue = currentBucket.put(key, value);

if (storedValue == null) {
bucketCount++;
}
return storedValue;
}

public V get(long key) {
return null;
int index = getIndex(key);
return buckets[index] == null
? null
: buckets[index].get(key);
}

public V remove(long key) {
int index = getIndex(key);
LongMapNode<V> currentBucket = buckets[index];

if (currentBucket != null) {
if (currentBucket.getKey() == key) {
V result = currentBucket.getValue();
buckets[index] = currentBucket.getCollision();
bucketCount--;
return result;
} else {
V removedValue = currentBucket.remove(key);

if (removedValue != null) {
bucketCount--;
}

return removedValue;
}
}
return null;
}

public boolean isEmpty() {
return false;
return bucketCount == 0;
}

public boolean containsKey(long key) {
return false;
return get(key) != null;
}

public boolean containsValue(V value) {
for (LongMapNode<V> bucket : this) {
if (bucket.getValue() == value || (bucket.getValue() != null && bucket.getValue().equals(value))) {
return true;
}
}
return false;
}

public long[] keys() {
return null;
if (bucketCount == 0) {
return null;
}
long[] keys = new long[bucketCount];
int keyIndex = bucketCount;
for (LongMapNode<V> bucket : this) {
keys[--keyIndex] = bucket.getKey();
}
return keys;
}

@SuppressWarnings("unchecked")
public V[] values() {
return null;
if (bucketCount == 0) {
return null;
}
V[] values = (V[]) Array.newInstance(this.iterator().next().getValue().getClass(), bucketCount);
int valueIndex = bucketCount;
for (LongMapNode<V> bucket : this) {
values[--valueIndex] = bucket.getValue();
}
return values;
}

public long size() {
return 0;
return bucketCount;
}

public void clear() {
Arrays.fill(buckets, null);
bucketCount = 0;
}

@Override
public Iterator<LongMapNode<V>> iterator() {
return new CollisionAwareLongBucketIterator<>(buckets);
}

@SuppressWarnings("unchecked")
private void resize() {
if (buckets.length == ARRAY_MAX_SIZE) {
return;
}

LongMapNode<V>[] newBuckets = new LongMapNode[getNewSize()];
for (LongMapNode<V> currentBucket : buckets) {
while (currentBucket != null) {
int newIndex = getIndex(currentBucket.getKey(), newBuckets);
LongMapNode<V> collision = currentBucket.getCollision();
currentBucket.setCollision(null);

LongMapNode<V> rehashedBucket = newBuckets[newIndex];

if (rehashedBucket == null) {
newBuckets[newIndex] = currentBucket;
} else {
rehashedBucket.collide(currentBucket);
}
currentBucket = collision;
}
}
this.buckets = newBuckets;
}

private int getIndex(long key) {
return getIndex(key, buckets);
}

private int getNewSize() {
if (buckets.length == 0) {
return DEFAULT_CAPACITY;
}
return ARRAY_MAX_SIZE / RESIZE_FACTOR > buckets.length
? buckets.length * RESIZE_FACTOR
: ARRAY_MAX_SIZE;
}

private boolean isThresholdExceeded(long key) {
return !containsKey(key)
&& (buckets.length == 0 || (int) (buckets.length * loadFactor) <= bucketCount);
}

private int getIndex(long key, LongMapNode<V>[] storage) {
return (Long.hashCode(key) & 0x7FFFFFFF) % storage.length;
}

public int getCapacity() {
return buckets.length;
}
}
92 changes: 92 additions & 0 deletions src/main/java/de/comparus/opensource/longmap/LongMapNode.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
package de.comparus.opensource.longmap;

import java.util.Objects;

class LongMapNode<T> {

private final long key;
private T value;
private LongMapNode<T> collision;

public LongMapNode(long key, T value) {
this.key = key;
this.value = value;
}

public final long getKey() {
return key;
}

public final T getValue() {
return value;
}

public final void setValue(T newValue) {
this.value = newValue;
}

@Override
public final String toString() {
return key + "=" + value;
}

@Override
public final int hashCode() {
return Objects.hash(key, value);
}

public LongMapNode<T> getCollision() {
return collision;
}

public void setCollision(LongMapNode<T> collision) {
this.collision = collision;
}

public T remove(long key) {
if (collision == null) {
return null;
}

if (collision.getKey() == key) {
T removed = collision.getValue();
collision = collision.getCollision();
return removed;
} else {
return collision.remove(key);
}
}

public T get(long key) {
if (this.key == key) {
return this.value;
}
if (collision != null) {
return collision.get(key);
}
return null;
}

public T put(long key, T value) {
if (this.getKey() == key) {
T result = this.getValue();
this.setValue(value);
return result;
}

if (collision != null) {
return collision.put(key, value);
}

collision = new LongMapNode<>(key, value);
return null;
}

public void collide(LongMapNode<T> anotherBucket) {
if (collision != null) {
collision.collide(anotherBucket);
} else {
collision = anotherBucket;
}
}
}
Loading