[Serie de código fuente de contenedor Java] Análisis de código fuente HashSet

Primero mire el código fuente y mire los comentarios de la clase. La información que podemos obtener es:

  1. La implementación subyacente se basa en HashMap, por lo que no se garantiza que se repita en el orden de inserción u otro orden durante la iteración;
  2. El lento rendimiento de agregar, eliminar, contener, tamaño y otros métodos no aumentará con el aumento de la cantidad de datos. Esto está principalmente relacionado con la estructura de datos de matriz subyacente de HashMap. Independientemente de la cantidad de datos, el conflicto de hash no se considera , La complejidad del tiempo es O (1);
  3. Si el hilo no es seguro, ciérrelo usted mismo si necesita seguridad o use Collections.synchronizedSet;
  4. Durante el proceso de iteración, si se cambia la estructura de datos, fallará rápidamente y se lanzará una ConcurrentModificationException.

1. Estructura

Relación de herencia HashSet, variables de miembros principales, constructor principal:

public class HashSet<E>
    extends AbstractSet<E>
    implements Set<E>, Cloneable, java.io.Serializable{
    
    
    
    // 把 HashMap 组合进来,key 是 Hashset 的 key,value 是下面的 PRESENT
    private transient HashMap<E,Object> map;

    // HashMap 中的 value,所有node中的value相同
    private static final Object PRESENT = new Object();
    
    //---------------------------构造方法---------------------------------------
    // 直接初始化一个HashMap
    public HashSet() {
    
    
        map = new HashMap<>();
    }
    
    // 对 HashMap 的容量进行了计算,在 16 和 给定值大小之间选择最大的值
    public HashSet(Collection<? extends E> c) {
    
    
    	// 选取最优初始容量
        map = new HashMap<>(Math.max((int) (c.size()/.75f) + 1, 16));
        addAll(c);
    }
}

1.1 La realización de Set se basa en la singularidad de las claves basadas en mapas

  • Porque si las claves del Mapa son las mismas, debes elegir si sobrescribir, por lo que no hay clave igual
  • El valor de Map es el mismo en Set (es decir, PRESENTE)

1.2 ¿Cómo combina HashSet HashMap?

Acabo de ver en los comentarios de la clase que la implementación de HashSet se basa en HashMap. En Java, hay dos formas de implementar una implementación innovadora basada en clases básicas:

  • Heredar la clase básica y anular el método de la clase básica, por ejemplo, heredar el HashMap y anular el método add;
  • Combine las clases básicas y reutilice las capacidades de las clases básicas llamando a los métodos de las clases básicas.

HashSet utiliza un HashMap combinado y sus ventajas son las siguientes:

  • La herencia significa que las clases principal y secundaria son lo mismo, y Set and Map originalmente quería expresar dos cosas, por lo que la herencia no es apropiada y las restricciones de sintaxis de Java, la clase secundaria solo puede heredar una clase principal y la expansión posterior es difícil.
  • La combinación es más flexible, puede combinar arbitrariamente las clases básicas existentes y puede extenderse y organizarse sobre la base de los métodos de clase básica, y el nombre del método puede nombrarse arbitrariamente, sin la necesidad de ser coherente con el nombre del método de la clase básica.

Si encuentra problemas similares, nuestro principio es usar la composición tanto como sea posible y usar menos herencia .

1.3 Inicialización de capacidad óptima

En el código anterior: Math.max ((int) (c.size () /. 75f) + 1, 16), que calcula la capacidad del HashMap, y traducido al chino es tomar el máximo de los dos números entre paréntesis ( Valor esperado / 0.75 + 1, valor por defecto 16) A partir del cálculo, podemos ver que el implementador de HashSet tiene muy claro la implementación subyacente de HashMap, que se refleja principalmente en dos aspectos:

  • Comparar el tamaño con 16 significa que si la capacidad inicial de un HashMap dado es menor que 16, se inicializará de acuerdo con el 16 predeterminado del HashMap; si es mayor que 16, se inicializará de acuerdo con el valor dado.
  • La fórmula de cálculo para el valor umbral de la expansión HashMap es: la capacidad del mapa * 0.75f, una vez que se alcanza el umbral, la capacidad se expandirá. Aquí, (int) (c.size () /. 75f) + 1 se usa para representar el valor inicial, entonces Si el valor de tamaño que esperamos es exactamente 1 mayor que el umbral de expansión, no habrá expansión, lo que se ajusta a la fórmula de expansión de HashMap.

Se puede pensar en la fórmula de la plantilla para HashMap para inicializar el valor de tamaño: tome el máximo de los dos entre paréntesis (valor esperado / 0.75 + 1, valor predeterminado 16) , porque aunque se usa el valor esperado, el número de ranuras se puede usar en la matriz Solo representa 0,75 en total, por lo que se requiere / 0,75. +1 es para evitar la situación que apenas llega a 0,75;

2. Análisis de métodos y API

Los otros métodos de HashSet son relativamente simples, es decir, algunos paquetes de la API de mapas

2.1 añadir

agregar simplemente envuelve el put de HashMap

public boolean add(E e) {
    
    
    // 直接使用 HashMap 的 put 方法,进行一些简单的逻辑判断
    // 注:这里所有value都是PRESENT
    return map.put(e, PRESENT)==null;
}

2.2 eliminar

public boolean remove(Object o) {
    
    
    	// 只有当o存在时,才能删除成功
    	// 而在HashMap中,o是key,当key存在时再删除才会返回value
        return map.remove(o)==PRESENT;
}

2.3 iterador

Iterator, devuelve directamente el iterador clave de HashMap, porque la clave de HashMap se compone de Set

public Iterator<E> iterator() {
    
    
      return map.keySet().iterator();
}

Finalmente, vale la pena aprender de la implementación específica de HashSet

  • Análisis y comprensión de combinación o herencia;
  • Realice algunos paquetes de lógica compleja para hacer que la interfaz que escupe sea lo más simple y fácil de usar posible;
  • Al combinar otras api, intente aprender más sobre la api combinada, para que pueda utilizar mejor la api;
  • Fórmula de plantilla de valor de tamaño de inicialización de HashMap: tome el valor máximo de los dos entre paréntesis (valor esperado / 0,75 + 1, valor predeterminado 16)

Supongo que te gusta

Origin blog.csdn.net/weixin_43935927/article/details/108519853
Recomendado
Clasificación