Capítulo 2 Colección - Notas fuente de Java

1 Análisis de código fuente de ArrayList e ideas de diseño

1.1 Estructura general

ArrayList es una estructura de matriz.

  • Permitir poner valor nulo, se expandirá automáticamente;
  • La complejidad temporal de métodos como size, isEmpty, get, set y add son todos O (1);
  • No es seguro para subprocesos. En el caso de subprocesos múltiples, se recomienda utilizar la clase segura para subprocesos: Collections # synchronizedList;
  • Mejore el bucle for, o use un iterador para iterar, si se cambia el tamaño de la matriz, fallará rápidamente y generará una excepción.

1.2 Inicialización

Tres métodos: inicialización directa sin parámetros, inicialización del tamaño especificado, inicialización de datos iniciales especificados.

// 无参数直接初始化
 public ArrayList() {
        this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
    }
 // 指定大小初始化
 public ArrayList(int initialCapacity) {
        if (initialCapacity > 0) {
            this.elementData = new Object[initialCapacity];
        } else if (initialCapacity == 0) {
            this.elementData = EMPTY_ELEMENTDATA;
        } else {
            throw new IllegalArgumentException("Illegal Capacity: "+
                                               initialCapacity);
        }
    }
 public ArrayList(Collection<? extends E> c) {
        elementData = c.toArray();
        if ((size = elementData.length) != 0) {
            // c.toArray might (incorrectly) not return Object[] (see 6260652)
            if (elementData.getClass() != Object[].class)
                elementData = Arrays.copyOf(elementData, size, Object[].class);
        } else {
            // replace with empty array.
            this.elementData = EMPTY_ELEMENTDATA;
        }
    }
  • Inicialización directa sin parámetros, el tamaño predeterminado es una matriz vacía, no 10. 10 es el valor de la primera expansión.

1.3 Implementación nueva y ampliada

  • 1.5 veces la capacidad original después de la expansión;
  • La expansión máxima es Integer.MAX_VALUE;
  • Al agregar, no hay una verificación estricta del valor, por lo que ArrayList permite valores nulos.
  • Para expandir la esencia , cree una nueva matriz y luego copie la matriz anterior. Consume más rendimiento, por lo tanto, especifique el tamaño de los datos tanto como sea posible para evitar la expansión frecuente.

1.4 Eliminar

Eliminar un elemento indexado índice, los elementos detrás se moverán un bit hacia adelante.

1.5 iterador

int cursor;// 迭代过程中,下一个元素的位置,默认从 0 开始。
int lastRet = -1; // 新增场景:表示上一次迭代过程中,索引的位置;删除场景:为 -1。
int expectedModCount = modCount;// expectedModCount 表示迭代过程中,期望的版本号;modCount 表示数组实际的版本号。
public boolean hasNext() {
            return cursor != size;
        }
 public E next() {
            checkForComodification();
            int i = cursor;
            if (i >= size)
                throw new NoSuchElementException();
            Object[] elementData = ArrayList.this.elementData;
            if (i >= elementData.length)
                throw new ConcurrentModificationException();
            cursor = i + 1;
            return (E) elementData[lastRet = i];
        }
final void checkForComodification() {
            if (modCount != expectedModCount)
                throw new ConcurrentModificationException();
        }
public void remove() {
            if (lastRet < 0)
                throw new IllegalStateException();
            checkForComodification();

            try {
                ArrayList.this.remove(lastRet);
                cursor = lastRet;
                lastRet = -1;
                expectedModCount = modCount;
            } catch (IndexOutOfBoundsException ex) {
                throw new ConcurrentModificationException();
            }
        }

2 Análisis del código fuente de LinkedList

2.1 Estructura general

Inserte la descripción de la imagen aquí
Código de nodo

	private static class Node<E> {
        E item;
        Node<E> next;
        Node<E> prev;

        Node(Node<E> prev, E element, Node<E> next) {
            this.item = element;
            this.next = next;
            this.prev = prev;
        }
    }

2.2 Agregar, eliminar

Se puede agregar desde el principio hasta el final, algunos se pueden eliminar desde el principio

2.3 Consulta de nodo

Si el índice está en la primera mitad, comience la búsqueda desde el principio; de lo contrario, comience la búsqueda desde atrás, lo que mejora la eficiencia. tamaño >> 1 significa desplazamiento a la derecha en 1 bit, que es equivalente a 2.

Node<E> node(int index) {
        // assert isElementIndex(index);

        if (index < (size >> 1)) {
            Node<E> x = first;
            for (int i = 0; i < index; i++)
                x = x.next;
            return x;
        } else {
            Node<E> x = last;
            for (int i = size - 1; i > index; i--)
                x = x.prev;
            return x;
        }
    }

2.4 iterador

Implemente la interfaz ListIterator y admita la iteración bidireccional, es decir, puede iterar desde el principio o el final.

3 Lista de preguntas de la entrevista

3.1 Hablar sobre la comprensión de ArrayList y LinkedList

Puede responder primero a la arquitectura general y luego romper un cierto detalle. Por ejemplo, la parte inferior de ArrayList es una matriz, y su API es encapsular la matriz inferior, etc.

3.2 Problemas de expansión

1. ArrayList se construye sin un constructor de parámetros. Ahora agregue un valor. En este momento, ¿cuál es el tamaño de la matriz y cuál es el tamaño máximo disponible antes de la próxima expansión?
En este momento, el tamaño de la matriz es 1, y el tamaño máximo disponible antes de la próxima expansión es 10. El valor predeterminado para la primera expansión es 10.
2. Si agrego nuevos valores a la lista continuamente, cuando aumento a la 11, el tamaño de la matriz es Cuanto?
oldCapacity + (oldCapacity >> 1), la próxima expansión es 1.5 veces. Eso es de 10 a 15.
3. Después de que se inicializa la matriz y se agrega un valor, si uso el método addAll y agrego 15 valores a la vez, ¿cuál es el tamaño de la matriz final?
Después de la inicialización, agregue un valor. En este momento, la expansión de capacidad predeterminada es 10, y luego agregue 15 valores a la vez. La expansión de capacidad es 1.5 veces, es decir, 15, todavía no se puede satisfacer. Hay una estrategia en este momento:

	private void grow(int minCapacity) {
        // overflow-conscious code
        int oldCapacity = elementData.length;
        int newCapacity = oldCapacity + (oldCapacity >> 1);
        if (newCapacity - minCapacity < 0)
            newCapacity = minCapacity; // 直接扩容到我们需要的大小
        if (newCapacity - MAX_ARRAY_SIZE > 0)
            newCapacity = hugeCapacity(minCapacity);
        // minCapacity is usually close to size, so this is a win:
        elementData = Arrays.copyOf(elementData, newCapacity);
    }

Expansión directa a 16.
4. Ahora tengo una gran matriz para copiar, el tamaño de la matriz original es de 5k, ¿cómo puedo copiarlo rápidamente?
5k es un valor relativamente grande, y la capacidad especificada es 5k cuando se inicializa la matriz. Si la inicialización no especifica un tamaño, se ampliará con frecuencia, con una gran cantidad de copias, lo que afectará el rendimiento.
5. ¿Por qué la expansión de capacidad consume rendimiento?
La capa inferior usa System.arraycopy para copiar la matriz original a la nueva matriz, por lo que consume rendimiento.
6. ¿Hay algo que valga la pena aprender del proceso de expansión del código fuente?

  • Expansión automática, los usuarios no necesitan preocuparse por los cambios en la estructura de datos subyacente. La expansión de la capacidad se basa en un crecimiento de 1.5 veces. El crecimiento en la etapa inicial es relativamente lento, y el crecimiento en la etapa posterior es relativamente grande. El tamaño de los datos utilizados para la mayoría del trabajo no es grande. Existe una utilización para ahorrar rendimiento.
  • Durante el proceso de expansión, preste atención al desbordamiento de la matriz, que no es menor que 0 ni mayor que el valor máximo de Integer.

3.3 Problemas de borrado

1. Hay una ArrayList, los datos son 2, 3, 3, 3, 4 y hay tres 3 en el medio. Ahora uso para (int i = 0; i <list.size (); i ++), quiero cambiar el valor a 3 Elementos eliminados, ¿puedo eliminarlos limpiamente? ¿Cuál es el resultado de la eliminación final y por qué? El código de eliminación es el siguiente:

List<String> list = new ArrayList<String>() {{
            add("2");
            add("3");
            add("3");
            add("3");
            add("4");
        }};
        for (int i = 0; i < list.size(); i++) {
            if (list.get(i).equals("3")) {
                list.remove(i);
            }
        }

No puedo Inserte la descripción de la imagen aquí
2. Aún la matriz ArrayList anterior, ¿podemos eliminar mejorando el bucle for?
No, informará un error. Debido a que el proceso de bucle mejorado para realmente llama al método next () del iterador. Cuando llama al método list # remove () para eliminar, el valor de modCount será +1, pero el valor de esperadoModCount en el iterador no ha cambiado. , Causando la próxima vez que el iterador ejecute el método next (), expectModCount! = ModCount informará un error ConcurrentModificationException.
Consulte: ¿Por qué Alibaba prohíbe la operación de eliminar / agregar elementos en el bucle foreach
3. La matriz anterior, si se puede eliminar utilizando el método Iterator.remove () al eliminar, ¿por qué?
Sí, porque el método Iterator.remove () asignará el último modCount al esperadoModCount durante la ejecución, de modo que durante el próximo ciclo, tanto modCount como esperadoModCount serán iguales.
4. ¿Las tres preguntas anteriores son las mismas para LinkedList?
Sí, aunque la estructura subyacente de LinkedList es una lista doblemente vinculada, pero para los tres problemas anteriores, los resultados son consistentes con ArrayList.

3.4 Problemas de contraste

1. ¿Cuál es la diferencia entre ArrayList y LinkedList?
La lógica subyacente de los dos es diferente. ArrayList es una matriz y LinkedList es una lista doblemente vinculada. Las API implementadas en la parte inferior también son diferentes, bla, bla, etc.
2. ¿Cuáles son los diferentes escenarios de aplicación de ArrayList y LinkedList?
ArrayList es adecuado para el escenario de búsqueda rápida sin adición y eliminación frecuentes, mientras que LinkedList es adecuado para el escenario de adición y eliminación frecuentes y pocas consultas.
3. ¿ArrayList y LinkedList tienen la capacidad máxima? La
primera tiene el número entero más grande, la segunda es teóricamente inalámbrica, pero el tamaño real es int, por lo que solo hay números enteros.
4. ¿Cómo manejan ArrayList y LinkedList los valores nulos?
Ambos ejecutan la adición y eliminación de valores nulos, pero ArrayList elimina los valores nulos desde el principio.
5. ¿Son seguros los subprocesos ArrayList y LinedList, por qué?
No En un entorno de subprocesos múltiples, las operaciones como la adición y eliminación no están sincronizadas.
6. ¿Cómo resolver el problema de la seguridad del hilo?
Hay listas seguras para subprocesos en Collections, Collections.synchronizedList () o CopyOnWriteArrayList.

3.5 Otros tipos de temas

1. ¿Describe la lista doblemente vinculada?
Ligeramente
2. Describa la adición y eliminación de listas doblemente vinculadas
.

4 Análisis de código fuente de HashMap

97 artículos originales publicados · elogiados 3 · 10,000+ vistas

Supongo que te gusta

Origin blog.csdn.net/qq_39530821/article/details/105347974
Recomendado
Clasificación