[Estructura de datos y algoritmo] Java implementa la expansión dinámica de la matriz (150 líneas de código que imitan ArrayList)

Coloque un análisis del código fuente de ArrayList del portal [código fuente del contenedor Java] al principio del artículo . Es el análisis del código fuente de LinkedList escrito por el autor. Este artículo imita ArrayList e implementa los métodos principales en el contenedor.

1. Estructura básica de ArrayList

public class MyArrayList<E> {
    
    

    private static final int DEFAULT_CAPACITY = 10; // 第一次扩容时的容量

    private static final Object[] EMPTY_ELEMENTDATA = {
    
    }; // 默认初始化数组

    private int size; // 数组实际大小,这里注意一点length是容量

    private int modCount = 0;

    private Object[] elementData; // 核心容器,所有元素都放在这里面。因为要配合泛型,所以是Object[]

    public MyArrayList() {
    
    
    	// 此处就是jdk8相较于jdk7优化的地方,对于不指定大小情况,在初始化时是赋空数组,第一次扩容时再变为10
        this.elementData = EMPTY_ELEMENTDATA; 
    }

    public MyArrayList(int capacity) {
    
    
        this.elementData = new Object[capacity]; // 指定大小初始化,直接按容量初始化
    }
    
    //.......
}    

2. Agregar elementos -add

La ejecución de sumar elementos es muy sencilla, de hecho se trata de asignar un valor al arreglo (arr [i] = valor)

	public boolean add(E e) {
    
    
        ensureCapacity(size + 1); // 确保容量足够
        elementData[size++] = e;
        return true;
    }

3. Asegurar suficiente capacidad-asegurarCapacidad

El criterio de capacidad suficiente es simple, es decir, tamaño + 1> = longitud

	private void ensureCapacity(int minCapacity) {
    
    
        modCount++; // 注:modCount不是add方法中++,因为无论add还是addAll都要走到ensureCapacity

        if (minCapacity <= elementData.length) {
    
    
            return;
        }
        // 若是第一次放元素,那么就进行初始化,初始化的容量是10
        if (elementData == EMPTY_ELEMENTDATA) {
    
     
            minCapacity = 10;
        }
        grow(minCapacity); 
    }

4. Realice expansión dinámica -crecimiento

El tamaño de la matriz se determina cuando se crea, por lo que la llamada expansión no es para agrandar la matriz actual, sino para crear una nueva matriz grande, luego copiar los elementos originales de la matriz y finalmente apuntar el puntero elementData a esta nueva matriz.

	public void grow(int minCapacity) {
    
    
        int oldCapacity = elementData.length;
        int newCapacity = oldCapacity + (oldCapacity >> 1); // 1.5倍扩容
        newCapacity = Math.min(newCapacity, minCapacity);
		
        elementData = Arrays.copyOf(elementData, newCapacity); // 核心!!
    }

5. Incremento por lotes -addAll

Para el caso de agregar varios elementos (colecciones) a la vez, addAll sería una opción más sensata. Como se mencionó anteriormente, al expandir, se creará una nueva matriz y luego se copiará. Si se agregan muchos elementos, pueden ocurrir múltiples operaciones de expansión, lo que inevitablemente desperdiciará más rendimiento. El método addAll solo se expandirá una vez como máximo según el tamaño de la colección que se agregará. Para obtener más análisis, consulte el Resumen de la aplicación de recopilación de [Código fuente del contenedor de Java]: Problemas de seguridad de subprocesos y funcionamiento del iterador

	public boolean addAll(Collection<? extends E> c) {
    
    
        Object[] a = c.toArray();
        ensureCapacity(size + a.length);
        int numNew = a.length;

        System.arraycopy(a, 0, elementData, size, numNew); // 只拷贝一次
        size += numNew;
        return numNew != 0;
    }

6. Eliminar el elemento especificado -remove

La esencia de la eliminación también es una copia de matriz

  1. Encuentre el índice del elemento a eliminar (el primero), nota: dividido en nulo y no vacío
  2. Eliminar por copia
    public void remove(Object o) {
    
    
        modCount++;
        int removeIdx = -1;
        if (o == null) {
    
    
            for (int i = 0; i < size; i++) {
    
    
                if (elementData[i] == null) {
    
    
                    removeIdx = i;
                    break;
                }
            }
        } else {
    
    
            for (int i = 0; i < size; i++) {
    
    
                if (elementData[i].equals(o)) {
    
    
                    removeIdx = i;
                    break;
                }
            }
        }
		
		// 找到索引后通过拷贝进行删除
		// 注:这里用system.arraycopy是因为拷贝前后是同一个数组
        if (removeIdx != -1) {
    
    
            int numMoved = size - removeIdx - 1;
            System.arraycopy(elementData, removeIdx+1, elementData, removeIdx, numMoved);
        }

        elementData[size--] = null;
    }

7. Eliminar por lotes -removeAll

Como se mencionó anteriormente, la esencia de la eliminación es la eliminación, por lo que si quiero eliminar varios elementos, ¿tengo que copiar varias veces? De hecho, se puede eliminar dinámicamente mediante el algoritmo de doble puntero, es decir, un puntero i es responsable de atravesar el elemento original y un puntero j es responsable de la asignación (pausa cuando encuentra el elemento a eliminar).

	/**
     *1.通过双指针进行批量删除,复杂度O(n)
     * 2.将剩余元素置null
     * @param c
     */
    public void removeAll(Collection<?> c) {
    
    
        modCount++;
        Object[] a = c.toArray();
        int i = 0, j = 0;
        for (; i < size; i++) {
    
    
            if (!c.contains(elementData[i])) {
    
    
                elementData[j++] = elementData[i];
            }
        }

        if (j != size) {
    
    
            for (int k = 0; k < size; k++) {
    
    
                elementData[k] = null;
            }
            size = j;
        }
    }

8. Iterator-Itr

private class Itr<E> {
    
    
        int cursor; // 当前索引
        int expectedModCount = modCount; // 记录迭代前的操作数,若当前modCount与expectedModCount就会抛异常
        int lastRet; // 上一个索引
		
		// 是否还有待迭代元素(即是否已到数组最后一个元素)
        public boolean hasNext() {
    
    
            return cursor != size;
        }
		
		// 返回当前元素,并后移一位
        public E next() {
    
    
            if (modCount != expectedModCount) {
    
    
                throw new RuntimeException("迭代时不允许操作");
            }
            int i = cursor;
            Object[] elementData = MyArrayList.this.elementData;
            cursor++;
            return (E)elementData[lastRet = i];
        }

		// 迭代时删除当前元素
        public void remove() {
    
    
            if (modCount != expectedModCount) {
    
    
                throw new RuntimeException("迭代时不允许操作");
            }

            if (lastRet < 0) {
    
    
                throw new IllegalArgumentException("重复删除");
            }

            MyArrayList.this.remove(lastRet);
            cursor = lastRet;
            lastRet = -1;

            expectedModCount = modCount;
        }
    }

El código completo es el siguiente

public class MyArrayList<E> {
    
    

    private static final int DEFAULT_CAPACITY = 10;

    private static final Object[] EMPTY_ELEMENTDATA = {
    
    };

    private int size;

    private int modCount = 0;

    private Object[] elementData;

    public MyArrayList() {
    
    
        this.elementData = EMPTY_ELEMENTDATA;
    }

    public MyArrayList(int capacity) {
    
    
        this.elementData = new Object[capacity];
    }

    public boolean add(E e) {
    
    
        ensureCapacity(size + 1);
        elementData[size++] = e;
        return true;
    }

    private void ensureCapacity(int minCapacity) {
    
    
        modCount++;

        if (minCapacity <= elementData.length) {
    
    
            return;
        }
        if (elementData == EMPTY_ELEMENTDATA) {
    
    
            minCapacity = 10;
        }
        grow(minCapacity);
    }

    public boolean addAll(Collection<? extends E> c) {
    
    
        Object[] a = c.toArray();
        ensureCapacity(size + a.length);
        int numNew = a.length;

        System.arraycopy(a, 0, elementData, size, numNew);
        size += numNew;
        return numNew != 0;
    }

    public void grow(int minCapacity) {
    
    
        int oldCapacity = elementData.length;
        int newCapacity = oldCapacity + (oldCapacity >> 1);
        newCapacity = Math.min(newCapacity, minCapacity);

        elementData = Arrays.copyOf(elementData, newCapacity);
    }


    /**
     * 1.找到要删除元素索引(第一个),注:分为null与非空
     * 2.通过拷贝进行删除
     * @param o
     */
    public void remove(Object o) {
    
    
        modCount++;
        int removeIdx = -1;
        if (o == null) {
    
    
            for (int i = 0; i < size; i++) {
    
    
                if (elementData[i] == null) {
    
    
                    removeIdx = i;
                    break;
                }
            }
        } else {
    
    
            for (int i = 0; i < size; i++) {
    
    
                if (elementData[i].equals(o)) {
    
    
                    removeIdx = i;
                    break;
                }
            }
        }

        if (removeIdx != -1) {
    
    
            int numMoved = size - removeIdx - 1;
            System.arraycopy(elementData, removeIdx+1, elementData, removeIdx, numMoved);
        }

        elementData[size--] = null;
    }

    /**
     *1.通过双指针进行批量删除,复杂度O(n)
     * 2.将剩余元素置null
     * @param c
     */
    public void removeAll(Collection<?> c) {
    
    
        modCount++;
        Object[] a = c.toArray();
        int i = 0, j = 0;
        for (; i < size; i++) {
    
    
            if (!c.contains(elementData[i])) {
    
    
                elementData[j++] = elementData[i];
            }
        }

        if (j != size) {
    
    
            for (int k = 0; k < size; k++) {
    
    
                elementData[k] = null;
            }
            size = j;
        }

    }

    public Itr<E> iterator() {
    
    
        return new Itr();
    }

    /**
     * 迭代器
     * @param <E>
     */
    private class Itr<E> {
    
    
        int cursor;
        int expectedModCount = modCount;
        int lastRet;

        public boolean hasNext() {
    
    
            return cursor != size;
        }

        public E next() {
    
    
            if (modCount != expectedModCount) {
    
    
                throw new RuntimeException("迭代时不允许操作");
            }
            int i = cursor;
            Object[] elementData = MyArrayList.this.elementData;
            cursor++;
            return (E)elementData[lastRet = i];
        }


        public void remove() {
    
    
            if (modCount != expectedModCount) {
    
    
                throw new RuntimeException("迭代时不允许操作");
            }

            if (lastRet < 0) {
    
    
                throw new IllegalArgumentException("重复删除");
            }

            MyArrayList.this.remove(lastRet);
            cursor = lastRet;
            lastRet = -1;

            expectedModCount = modCount;
        }
    }


    public Object[] toArray() {
    
    
        return Arrays.copyOf(elementData, size);
    }
}

Supongo que te gusta

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