Un boceto HashMap

Un boceto HashMap

Antes de terminar el simple conocimiento de las colecciones de Java, HashMap no encontraron hablan unas pocas palabras pueden entender, por lo que el conocimiento especializado para solucionar el HashMap.

estructura de almacenamiento HashMap

HashMap tipo Mapa de la estructura de datos es una aplicación tabla hash, el interior no es tan larga una matriz del tipo de entrada. Tipo de entrada a menudo se dice pares.

/**
     * The default initial capacity - MUST be a power of two.
     */
static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16

En la fuente de HashMap, la longitud predeterminada de 16 inicializa la tabla, si se quiere definir la longitud de la mesa también debe enésima potencia de 2.

static class Node<K,V> implements Map.Entry<K,V> {
    final int hash;
    final K key;
    V value;
    Node<K,V> next;
}

En el HashMap, el tipo de entrada de nodo es la clase de implementación, además de los pares de valores clave, así como el valor de hash y un puntero siguiente. No es difícil de dibujar, tabla hash cada posición puede contener una lista de nodos.

Con tal estructura una para acortar la longitud de la tabla hash, por lo que la cantidad limitada de datos, ya sea un mejor rendimiento de las consultas, sino también para reducir el desperdicio de espacio.

Sin embargo, si una gran cantidad de datos, sino también para utilizar la longitud predeterminada de la tabla hash, que dará lugar a una larga lista, rendimiento de las consultas. Afortunadamente HashMap es apoyado por la expansión dinámica.

HashMap en un principio de funcionamiento de la operación

Después de enterarse de la estructura de almacenamiento HashMap, hay dos cuestiones que nos ocupan. En primer lugar, ¿cómo HashMap determinar cuál es la posición en la tabla para insertar elementos, el segundo se inserta en qué posición de la lista para ir.

Primero en responder a la primera pregunta, HashMap hashCode primero obtendrá el objeto, entonces HashCode larga mesa con operación módulo que hacer, es decir, hashCode% larga mesa, el valor resultante es el elemento a ser insertado en el lugar de la tabla.

La respuesta a la segunda pregunta es HashMap utilizará la interpolación para encabezar el nuevo elemento se inserta en la cabeza de la lista.

Aquí es un caso especial, HashMap es nulo como un soporte clave, pero no puede obtener un código hash nula, por lo que se inserta a la fuerza en el nulo para la ubicación de cero.

//HashMap哈希函数的实现
static final int hash(Object key) {
    int h;
    return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
//

la función de hash HashMap curiosidad y por qué funciona la longitud tabla hash debe ser la enésima potencia de 2 estudiantes pueden consultar la siguiente respuesta.

¿Cuál es el método de hash principio código fuente HashMap JDK es? - Grasa junio respuesta - sabe casi

Así que cuando HashMap encontrará elementos en dos pasos:

  1. Posición del elemento en la tabla hash se calcula
  2. Recorrer la lista para encontrar los elementos

expansión HashMap

Hemos mencionado anteriormente, si la enorme cantidad de datos utilizados en el HashMap predeterminado longitud de la tabla de hash, dirigirá la lista es demasiado larga, el descenso eficiencia de la consulta, incluso degenerar en una lista enlazada de una sola vía. Afortunadamente HashMap soporta a su expansión dinámica, HashMap se basará en la cantidad de datos para ajustar dinámicamente el tamaño de la longitud de la tabla de hash, con el fin de permitir que la eficiencia eficiencia de la búsqueda y el espacio de HashMap puede ser garantizada.

Introducir algunos parámetros importantes:

  • Capacidad: la longitud de la tabla hash, el valor predeterminado es 16. Tenga en cuenta que debe ser garantizado para la enésima potencia de 2
  • Tamaño: el número de pares de clave y valor
  • Umbral: umbral, cuando el tamaño es mayor que el umbral es necesaria para la expansión
  • loadFactor: factor de carga, la relación de la tabla de dispersión se puede utilizar, umbral = (int) (capacidad * loadFactor)

LoadFactor longitud inicial y la tabla hash se define en el HashMap inicialización, loadFactor valor predeterminado 0.75f. Compruebe el tamaño de la condición de expansión del gatillo y el umbral al insertar un nuevo elemento, si el tamaño es mayor que el umbral, se activará la expansión.

/**
     * Initializes or doubles table size.  If null, allocates in
     * accord with initial capacity target held in field threshold.
     * Otherwise, because we are using power-of-two expansion, the
     * elements from each bin must either stay at same index, or move
     * with a power of two offset in the new table.
     *
     * @return the table
     */
final Node<K,V>[] resize() {
    Node<K,V>[] oldTab = table;
    int oldCap = (oldTab == null) ? 0 : oldTab.length;
    int oldThr = threshold;
    int newCap, newThr = 0;
    if (oldCap > 0) {
        if (oldCap >= MAXIMUM_CAPACITY) {
            threshold = Integer.MAX_VALUE;//如果表长已经超过最大值,就会将threshold设置成一个很大的数来阻止HashMap继续扩容
            return oldTab;
        }
        else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&
                 oldCap >= DEFAULT_INITIAL_CAPACITY)//
            newThr = oldThr << 1; // double threshold
    }
    else if (oldThr > 0) // initial capacity was placed in threshold
        newCap = oldThr;
    else {               // zero initial threshold signifies using defaults
        newCap = DEFAULT_INITIAL_CAPACITY;
        newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);
    }
    if (newThr == 0) {
        float ft = (float)newCap * loadFactor;
        newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ?
                  (int)ft : Integer.MAX_VALUE);
    }
    threshold = newThr;
    //下面是将旧表中的数据转移到新表中去,使用了两个嵌套的循环,开销非常大
    @SuppressWarnings({"rawtypes","unchecked"})
    Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap];
    table = newTab;
    if (oldTab != null) {
        for (int j = 0; j < oldCap; ++j) {
            Node<K,V> e;
            if ((e = oldTab[j]) != null) {
                oldTab[j] = null;
                if (e.next == null)
                    newTab[e.hash & (newCap - 1)] = e;
                else if (e instanceof TreeNode)
                    ((TreeNode<K,V>)e).split(this, newTab, j, oldCap);
                else { // preserve order
                    Node<K,V> loHead = null, loTail = null;
                    Node<K,V> hiHead = null, hiTail = null;
                    Node<K,V> next;
                    do {
                        next = e.next;
                        if ((e.hash & oldCap) == 0) {
                            if (loTail == null)
                                loHead = e;
                            else
                                loTail.next = e;
                            loTail = e;
                        }
                        else {
                            if (hiTail == null)
                                hiHead = e;
                            else
                                hiTail.next = e;
                            hiTail = e;
                        }
                    } while ((e = next) != null);
                    if (loTail != null) {
                        loTail.next = null;
                        newTab[j] = loHead;
                    }
                    if (hiTail != null) {
                        hiTail.next = null;
                        newTab[j + oldCap] = hiHead;
                    }
                }
            }
        }
    }
    return newTab;
}

Cálculo capacidad de la matriz

Cuando se crea un HashMap si el enésimo capacidad de potencia de entrada no es 2, HashMap lo convertirá automáticamente a 2 ^ n.

Los árboles se convierten lista de rojo y negro

Inicio del JDK 8. Si la longitud de cadena de más de 8, la lista se convierte en un árbol rojo-negro.

En comparación con el HashTable

  1. HashTable utilizando sincronizado para operar sincrónicamente
  2. HashMap puede insertar la clave es nula entrada
  3. No se puede agregar elementos y expansión iterativa HashMap
  4. Con el tiempo, el orden de los elementos en el HashMap puede variar

ConcurrentHashMap

estructura de almacenamiento

ConcurrentHashMap comparación con HashMap, estructura de almacenamiento es similar, pero la introducción de segmentos de bloqueo ConcurrentHashMap, cada segmento mantiene un bloqueo para una mesa, de modo que diferentes hilos pueden bloquear al mismo tiempo de acceso segmentos diferentes tabla hash. Mejora en gran medida la eficacia del acceso. Los segmentos de bloqueo predeterminado 16.

operación del mismo tamaño

Cada segmento mantiene una variable de recuento para contar el número de pares de valores clave en el segmento.

/**
 * The number of elements. Accessed only either within locks
 * or among other volatile reads that maintain visibility.
 */
transient int count;

Al realizar el tamaño, es necesario recorrer todos los segmentos a continuación, añadir el recuento.

ConcurrentHashMap tamaño en la ejecución de las operaciones para tratar de no bloqueado, abierto si dos resultados consecutivos obtenidos en la misma operación, se puede pensar que este resultado es correcto.

intentos RETRIES_BEFORE_LOCK para uso definido, el valor 2, vuelve a intentar el valor inicial de -1, y por lo tanto intentos a 3.

Si el número de intentos más de tres veces, necesitamos bloqueo para cada segmento.


/**
 * Number of unsynchronized retries in size and containsValue
 * methods before resorting to locking. This is used to avoid
 * unbounded retries if tables undergo continuous modification
 * which would make it impossible to obtain an accurate result.
 */
static final int RETRIES_BEFORE_LOCK = 2;

public int size() {
    // Try a few times to get accurate count. On failure due to
    // continuous async changes in table, resort to locking.
    final Segment<K,V>[] segments = this.segments;
    int size;
    boolean overflow; // true if size overflows 32 bits
    long sum;         // sum of modCounts
    long last = 0L;   // previous sum
    int retries = -1; // first iteration isn't retry
    try {
        for (;;) {
            // 超过尝试次数,则对每个 Segment 加锁
            if (retries++ == RETRIES_BEFORE_LOCK) {
                for (int j = 0; j < segments.length; ++j)
                    ensureSegment(j).lock(); // force creation
            }
            sum = 0L;
            size = 0;
            overflow = false;
            for (int j = 0; j < segments.length; ++j) {
                Segment<K,V> seg = segmentAt(segments, j);
                if (seg != null) {
                    sum += seg.modCount;
                    int c = seg.count;
                    if (c < 0 || (size += c) < 0)
                        overflow = true;
                }
            }
            // 连续两次得到的结果一致,则认为这个结果是正确的
            if (sum == last)
                break;
            last = sum;
        }
    } finally {
        if (retries > RETRIES_BEFORE_LOCK) {
            for (int j = 0; j < segments.length; ++j)
                segmentAt(segments, j).unlock();
        }
    }
    return overflow ? Integer.MAX_VALUE : size;
}

Los cambios JDK 8

JDK 1.7 segmentado mecanismo de bloqueo utilizado para implementar operación de actualización concurrente, clase principal Segmento, hereda la cerradura de peso ReentrantLock, igual al número de segmento concurrente.

JDK 1.8 utiliza un operaciones CAS para apoyar un mayor grado de concurrencia, utilizan la incorporada en el bloqueo sincronizado cuando la operación CAS falla.

Cuando se convierte en un árbol rojo-negro y darse cuenta de JDK 1.8 también la lista es demasiado larga.

ashanap Laidaked ः

estructura de almacenamiento

LinkedHashMap heredado de HashMap, HashMap, por tanto, encontrar rápidamente las mismas características.

Además, en el interior LinkedHashMap también mantiene se registró inserta secuencialmente un datos lista doblemente enlazada o usado (LRU) orden menos recientemente.

accessOrder que determina el orden de grabación, por defecto es falsa orden de inserción, ficha.

/**
 * The iteration ordering method for this linked hash map: {@code true}
 * for access-order, {@code false} for insertion-order.
 *
 * @serial
 */
final boolean accessOrder;

LinkedHashMap utilizar los dos métodos siguientes para mantener la lista

  • afterNodeInsertion
  • afterNodeAccess

afterNodeInsertion

void afterNodeInsertion(boolean evict) { // possibly remove eldest
    LinkedHashMap.Entry<K,V> first;
    if (evict && (first = head) != null && removeEldestEntry(first)) {
        K key = first.key;
        removeNode(hash(key), key, null, false, true);
    }
}

Después de poner otras operaciones que se realizan, cuando el método de devolución verdadera removeEldestEntry () elimina el último nodo, es decir, nodo de cabecera de la lista primero.

evict sólo cuando el mapa se construyó en false, aquí es cierto.

protected boolean removeEldestEntry(Map.Entry<K,V> eldest) {
    return false;
}

removeEldestEntry () por defecto es falso, si usted necesita para que sea verdadera, la necesidad de ampliar LinkedHashMap la cobertura y la aplicación de este método, que caché LRU es particularmente útil para lograr, mediante la eliminación de nodos utilizados menos recientemente, con el fin de asegurar suficiente espacio de almacenamiento intermedio, y los datos en caché de datos es caliente.

afterNodeAccess

void afterNodeAccess(Node<K,V> e) { // move node to last
    LinkedHashMap.Entry<K,V> last;
    if (accessOrder && (last = tail) != e) {
        LinkedHashMap.Entry<K,V> p =
            (LinkedHashMap.Entry<K,V>)e, b = p.before, a = p.after;
        p.after = null;
        if (b == null)
            head = a;
        else
            b.after = a;
        if (a != null)
            a.before = b;
        else
            last = b;
        if (last == null)
            head = p;
        else {
            p.before = last;
            last.after = p;
        }
        tail = p;
        ++modCount;
    }
}

Si accessOrder es cierto, el paso a la lista de la cola, la cola de la lista para asegurarse de que después de un nodo de acceso nodo será una visita reciente nodo, a continuación, la lista es la cabecera de los nodos menos recientemente utilizada.

caché LRU

La herencia LinkedHashMap puede implementar rápidamente una caché LRU, el siguiente es un ejemplo de un LRU:

class LRUCache<K, V> extends LinkedHashMap<K, V> {
    private static final int MAX_ENTRIES = 3;

    //重写removeEldestEntry方法使元素数量大于3时将最近最久未使用的元素移除
    @Override
    protected boolean removeEldestEntry(Map.Entry eldest) {
        return size() > MAX_ENTRIES;
    }

    LRUCache() {
        super(MAX_ENTRIES, 0.75f, true);
    }
}
public static void main(String[] args) {
    LRUCache<Integer, String> cache = new LRUCache<>();
    cache.put(1, "a");
    cache.put(2, "b");
    cache.put(3, "c");
    cache.get(1);//访问1使(1,"a")重新置于链表尾部
    cache.put(4, "d");//加入4使数量大于3而将最近最少使用的2移除
    System.out.println(cache.keySet());
}
[3, 1, 4]

WeakHashMap

estructura de almacenamiento

WeakHashMap de entrada heredado de WeakReference, se recicla cuando la próxima recolección de basura WeakReference objetos asociados.

WeakHashMap utilizado principalmente para implementar la caché, la memoria caché para hacer referencia al objeto mediante WeakHashMap, esta parte de la recuperación almacenada en caché por JVM.

ConcurrerntCache

Tomcat en ConcurrentCache WeakHashMap utiliza para implementar la característica de almacenamiento en caché.

ConcurrentCache adoptó una caché generacional:

  • objetos de uso frecuente en el Eden, eden aplicación ConcurrentHashMap uso, sin temor a ser recuperado (Jardín del Edén);
  • objetos en el largo plazo no se usa comúnmente, el uso a largo plazo WeakHashMap lograr estos objetos antiguos son basura recogida.
  • Cuando se llama método get (), que comenzará a adquirir eden Distrito, si no encontrar las palabras y luego de obtener a largo plazo, cuando se obtienen de largo plazo para poner objetos en eden con el fin de garantizar nodo de acceso a menudo no se recicla fácilmente.
  • Cuando se llama método put (), si no se utiliza con frecuencia el tamaño de eden sobre el tamaño, por lo que serán todos los objetos se colocan en el largo plazo eden el uso de recuperación de la máquina virtual de parte del objeto.
public final class ConcurrentCache<K, V> {

    private final int size;

    private final Map<K, V> eden;

    private final Map<K, V> longterm;

    public ConcurrentCache(int size) {
        this.size = size;
        this.eden = new ConcurrentHashMap<>(size);
        this.longterm = new WeakHashMap<>(size);
    }

    public V get(K k) {
        V v = this.eden.get(k);
        if (v == null) {
            v = this.longterm.get(k);
            if (v != null)
                this.eden.put(k, v);
        }
        return v;
    }

    public void put(K k, V v) {
        if (this.eden.size() >= size) {
            this.longterm.putAll(this.eden);
            this.eden.clear();
        }
        this.eden.put(k, v);
    }
}

Supongo que te gusta

Origin www.cnblogs.com/supermaskv/p/12488888.html
Recomendado
Clasificación