[Data structure & algorithm] Java implements dynamic expansion of array (150 lines of code imitating ArrayList)

Put a portal [Java container source code] ArrayList source code analysis at the beginning of the article . It is the analysis of the source code of LinkedList written by the author. This article imitates ArrayList and implements the core methods in the container.

1. ArrayList basic structure

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. Add elements -add

The execution of adding elements is very simple, in fact it is to assign a value to the array (arr[i] = value)

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

3. Ensure enough capacity-ensureCapacity

The criterion for sufficient capacity is simple, that is size+1 >= length

	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. Perform dynamic expansion -grow

The size of the array is determined when it is created, so the so-called expansion is not to make the current array larger, but to create a new large array, then copy the original array elements, and finally point the elementData pointer to this new array.

	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. Batch increase -addAll

For the case of adding multiple elements (collections) at once, addAll would be a more sensible choice. As mentioned above, when expanding, a new array will be created and then copied. If a lot of elements are added, multiple expansion operations may occur, which will inevitably waste more performance. The addAll method will only expand once at most according to the size of the collection to be added. For more analysis, please refer to [Java Container Source Code] Collection Application Summary: Iterator & Batch Operation & Thread Safety Issues

	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. Delete the specified element -remove

The essence of deletion is also an array copy

  1. Find the index of the element to be deleted (the first one), note: divided into null and non-empty
  2. Delete by copy
    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. Batch delete -removeAll

As mentioned above, the essence of deletion is deletion, so if I want to delete multiple elements, do I have to copy multiple times? In fact, it can be dynamically deleted by the double pointer algorithm, that is, a pointer i is responsible for traversing the original element, and a pointer j is responsible for assignment (pause when it encounters the element to be deleted).

	/**
     *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;
        }
    }

The complete code is as follows

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);
    }
}

Guess you like

Origin blog.csdn.net/weixin_43935927/article/details/108713111
Recommended