All Downloads are FREE. Search and download functionalities are using the official Maven repository.

io.opentelemetry.sdk.metrics.internal.state.PooledHashMap Maven / Gradle / Ivy

The newest version!
/*
 * Copyright The OpenTelemetry Authors
 * SPDX-License-Identifier: Apache-2.0
 */

package io.opentelemetry.sdk.metrics.internal.state;

import static java.util.Objects.requireNonNull;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.function.BiConsumer;
import javax.annotation.Nullable;

/**
 * A bucket-based hash map with an internal re-usable map entry objects pool
 *
 * 

The goal of this map is to minimize memory allocation, leading to reduced time spent in * garbage collection. * *

This map avoids allocating a new map entry on each put operation by maintaining a pool of * reusable (mutable) map entries and borrowing a map entry object from the pool to hold the given * key-value of the put operation. The borrowed object is returned to the pool when the map entry * key is removed from the map. * *

This class is internal and is hence not for public use. Its APIs are unstable and can change * at any time. * *

This class is not thread-safe. * * @param The map key type * @param The map value type */ @SuppressWarnings("ForLoopReplaceableByForEach") public final class PooledHashMap implements Map { private static final int DEFAULT_CAPACITY = 16; private static final float LOAD_FACTOR = 0.75f; private ArrayList>[] table; private final ObjectPool> entryPool; private int size; /** * Creates a {@link PooledHashMap} with {@code capacity} buckets. * *

The hashmap contains an array of buckets, each is an array-list of items. The number of * buckets expands over time to avoid having too many items in one bucket, otherwise accessing an * item by key won't be a constant time complexity. * * @param capacity The initial number of buckets to start with */ @SuppressWarnings({"unchecked"}) public PooledHashMap(int capacity) { this.table = (ArrayList>[]) new ArrayList[capacity]; this.entryPool = new ObjectPool<>(Entry::new); this.size = 0; } /** * Creates a new {@link PooledHashMap} with a default amount of buckets (capacity). * * @see PooledHashMap#PooledHashMap(int) */ public PooledHashMap() { this(DEFAULT_CAPACITY); } /** * Add a key, value pair to the map. * *

Internally it uses a MapEntry from a pool of entries, to store this mapping * * @param key key with which the specified value is to be associated * @param value value to be associated with the specified key * @return Null if the was no previous mapping for this key, or the value of the previous mapping * of this key */ @Override @Nullable public V put(K key, V value) { requireNonNull(key, "This map does not support null keys"); requireNonNull(value, "This map does not support null values"); if (size > LOAD_FACTOR * table.length) { rehash(); } int bucket = getBucket(key); ArrayList> entries = table[bucket]; if (entries == null) { entries = new ArrayList<>(); table[bucket] = entries; } else { // Don't optimize to enhanced for-loop since implicit iterator used allocated memory in O(n) for (int i = 0; i < entries.size(); i++) { Entry entry = entries.get(i); if (Objects.equals(entry.key, key)) { V oldValue = entry.value; entry.value = value; return oldValue; } } } Entry entry = entryPool.borrowObject(); entry.key = key; entry.value = value; entries.add(entry); size++; return null; } @SuppressWarnings({"unchecked"}) private void rehash() { ArrayList>[] oldTable = table; table = (ArrayList>[]) new ArrayList[2 * oldTable.length]; // put() to new table below will reset size back to correct number size = 0; for (int i = 0; i < oldTable.length; i++) { ArrayList> bucket = oldTable[i]; if (bucket != null) { for (Entry entry : bucket) { put(requireNonNull(entry.key), requireNonNull(entry.value)); entryPool.returnObject(entry); } bucket.clear(); } } } /** * Retrieves the mapped value for {@code key}. * * @param key the key whose associated value is to be returned * @return The mapped value for {@code key} or null if there is no such mapping */ @Override @Nullable @SuppressWarnings("unchecked") public V get(Object key) { requireNonNull(key, "This map does not support null keys"); int bucket = getBucket((K) key); ArrayList> entries = table[bucket]; if (entries != null) { for (int i = 0; i < entries.size(); i++) { Entry entry = entries.get(i); if (Objects.equals(entry.key, key)) { return entry.value; } } } return null; } /** * Removes the mapping for the given {@code key}. * * @param key key whose mapping is to be removed from the map * @return The value mapped to this key, if the mapping exists, or null otherwise */ @Override @Nullable @SuppressWarnings("unchecked") public V remove(Object key) { requireNonNull(key, "This map does not support null keys"); int bucket = getBucket((K) key); ArrayList> entries = table[bucket]; if (entries != null) { for (int i = 0; i < entries.size(); i++) { Entry entry = entries.get(i); if (Objects.equals(entry.key, key)) { V oldValue = entry.value; entries.remove(i); entryPool.returnObject(entry); size--; return oldValue; } } } return null; } @Override public int size() { return size; } @Override public boolean isEmpty() { return size == 0; } @Override public boolean containsKey(Object key) { requireNonNull(key, "This map does not support null keys"); return get(key) != null; } @Override public boolean containsValue(Object value) { throw new UnsupportedOperationException(); } @Override public void clear() { for (int i = 0; i < table.length; i++) { ArrayList> bucket = table[i]; if (bucket != null) { for (int j = 0; j < bucket.size(); j++) { Entry entry = bucket.get(j); entryPool.returnObject(entry); } bucket.clear(); } } size = 0; } @Override public void forEach(BiConsumer action) { for (int j = 0; j < table.length; j++) { ArrayList> bucket = table[j]; if (bucket != null) { for (int i = 0; i < bucket.size(); i++) { Entry entry = bucket.get(i); action.accept(entry.key, entry.value); } } } } private int getBucket(K key) { return Math.abs(key.hashCode() % table.length); } @Override public Set> entrySet() { throw new UnsupportedOperationException(); } @Override public Collection values() { throw new UnsupportedOperationException(); } @Override public void putAll(Map m) { throw new UnsupportedOperationException(); } @Override public Set keySet() { throw new UnsupportedOperationException(); } private static class Entry { @Nullable K key; @Nullable V value; } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy