美文网首页
ArrayList的优化

ArrayList的优化

作者: code希必地 | 来源:发表于2020-12-30 22:19 被阅读0次

    当向ArrayList中添加和删除元素时都需要进行元素的移动,当添加和删除的是动态数组的头部元素,需要将数组中所有元素进行移动,其最坏情况的复杂度为O(n)。
    那么能不能在添加和删除元素时不进行移动或者少移动元素呢?
    我们可以在动态数组的基础上增加一个int front来记录一下首元素的位置。
    在进行添加和删除,肯定需要修改front,下面先来看下ArrayList的add和remove方法。

    1、ArrayList的add方法

    先来看下之前动态数组的实现

    public void add(int index, E element) {
        checkIndexForAdd(index);
        ensureCapacity(size+1);
        for (int i = size - 1; i >= index; i--) {
            elements[i + 1] = elements[i];
        }
        elements[index] = element;
        size++;
    }
    

    可以看到这里是先将元素向后移动,然后将数据设置到index的位置,过程如下


    image.png

    如果使用了front,在进行元素添加时如何实现不移动元素或者少移动元素呢?下面分三种情况进行分析:

    1.1、当向数组头部添加元素

    即调用add(0,element)。

    image.png
    由图可知,在调用add(0,11)时,我们可以将元素添加到index=0的位置,然后将front减一此时front变成0。
    image.png
    如果再次调用add(0,55),那么此时可以向index=5的位置添加元素。
    image.png
    有上面分析可知,在每次调用add(0,element)时,都会将元素添加到数组中角标=front-1的位置,即elements[front-1]=element。当front-1<0时,将元素添加到(front-1)+elements.length的位置。代码如下:
    if (index == 0) {
        front = index(-1);
        elements[front] = element;
    }
    
    // 修正index
    private int index(int index) {
        index += front;
        if (index < 0)
            index += elements.length;
        else
            index = index % elements.length;
        return index;
    }
    

    1.2、当向数组末尾添加元素

    image.png
    此时size=4,调用add(size,66)方法,会向index=5的位置添加元素,即elements[front+size]=66。
    image.png
    此时size=5,接着调用add(size,77),会向index=0的位置添加元素,即elements[(front+size)%elements.length]=77
    代码如下:
    if (index == size) {
        elements[index(size)] = element;
    } 
    

    1.3、当向数组中其他位置添加元素

    • 如果index大于等于size的一半,则将元素向后移动,然后设置指定位置的元素;如:向index=3,添加元素
      image.png
    • 如果index小于size的一半,则将元素向前移动,并将front减一,然后设置指定位置的元素;
      image.png

    具体代码如下:

    if (index >= size >> 1) { // 向后移动
        for (int i = size - 1; i >= index; i--) {
            elements[index(i + 1)] = elements[index(i)];
        }
        elements[index(index)] = element;
    } else { // 向前移动
        for (int i = 0; i < index; i++) {
        elements[index(i-1)] = elements[index(i)];
          }
          front = index(-1);
          elements[index(index)] = element;
    }
    

    从上面代码可知,在向数组头部添加元素其实也是属于index<size>>1的情况,只不过不需要移动元素;
    在向数组尾部添加元素属于index>=size>>1的情况,只不过不需要移动元素。

    1.4、add方法完整代码如下

    public void add(int index, E element) {
        checkIndexForAdd(index);
        ensureCapacity(size + 1);
        if (index >= size >> 1) { // 向后移动
            for (int i = size - 1; i >= index; i--) {
                elements[index(i + 1)] = elements[index(i)];
            }
        } else { // 向前移动
            for (int i = 0; i < index; i++) {
                elements[index(i-1)] = elements[index(i)];
            }
            front = index(-1);
        }
        elements[index(index)] = element;
        size++;
    }
    
    // 修正index
    private int index(int index) {
        index += front;
        if (index < 0)
            index += elements.length;
        else
            index = index % elements.length;
        return index;
    }
    

    2、ArrayList的remove方法

    2.1、删除数组头部元素

    删除数组头部元素很简单,只需要将头部的数据置成null,然后将front向后移动一位即可。


    image.png

    代码如下

    if (index == 0) {
        elements[front] = null;
        front = index(1);
    }
    

    2.2、删除数组尾部元素

    删除尾部元素和动态数组完全一样,直接将index=size-1位置的元素置成null即可。
    代码如下

    if (index == size - 1) {
        elements[index(size - 1)] = null;
    } 
    

    2.3、删除其他位置的元素

    • 如果index大于等于size的一半,则将元素向前移动,然后设置指定位置的元素;如:向index=3,添加元素
      image.png
    • 如果index小于size的一半,则将元素向后移动,并将front加一
      如删除index=2的元素
      image.png
      代码如下:
    if (index >= size >> 1) { // 向前移动
        for (int i = index; i < size - 1; i++) {
            elements[index(i)] = elements[index(i + 1)];
        }
        elements[index(size - 1)] = null;
    } else { // 向后移动
        for (int i = index; i > 0; i--) {
            elements[index(i)] = elements[index(i - 1)];
        }
        elements[front] = null;
        front = index(1);
    }
    

    2.4、remove的完整代码如下

    public E remove(int index) {
        checkIndex(index);
        E oldE = elements[index(index)];
        if (index >= size >> 1) { // 向前移动
            for (int i = index; i < size - 1; i++) {
                elements[index(i)] = elements[index(i + 1)];
            }
            elements[index(size - 1)] = null;
        } else { // 向后移动
            for (int i = index; i > 0; i--) {
                elements[index(i)] = elements[index(i - 1)];
            }
            elements[front] = null;
            front = index(1);
        }
        size--;
        if (size == 0)
            front = 0;
        trim();
        return oldE;
    }
    

    3、总结

    加入front后的动态数组的增删改查逻辑其实也很简单,在编写逻辑时先认为front不存在,然后按照普通动态数组的逻辑编写代码,最后将牵扯到的index位置的地方,使用我们编写的矫正index的方法对index进行矫正,获取其真实的index即可。
    完整代码如下:

    public class ArrayList2<E> extends AbstractList<E> {
        private E[] elements;
        private static final int DEFAULT_CAPACTITY = 10;
        // 记录数组中首元素的位置,默认为0
        private int front;
    
        public ArrayList2() {
            this(DEFAULT_CAPACTITY);
        }
    
        public ArrayList2(int capacity) {
            if (capacity <= DEFAULT_CAPACTITY)
                capacity = DEFAULT_CAPACTITY;
            elements = (E[]) new Object[capacity];
        }
    
        /**
         * 向指定位置添加元素
         * 
         * @param index
         * @param element
         */
        public void add(int index, E element) {
            checkIndexForAdd(index);
            ensureCapacity(size + 1);
            if (index >= size >> 1) { // 向后移动
                for (int i = size - 1; i >= index; i--) {
                    elements[index(i + 1)] = elements[index(i)];
                }
            } else { // 向前移动
                for (int i = 0; i < index; i++) {
                    elements[index(i - 1)] = elements[index(i)];
                }
                front = index(-1);
            }
            elements[index(index)] = element;
            size++;
        }
    
        // 修正index
        private int index(int index) {
            index += front;
            if (index < 0)
                index += elements.length;
            else
                index = index % elements.length;
            return index;
        }
    
        /**
         * 扩容
         * 
         * @param capactity
         */
        private void ensureCapacity(int capactity) {
            if (capactity >= elements.length) {
                int newCapacity = capactity + (capactity >> 1);
                System.out.println("扩容 oldCapactity:" + elements.length + " newCapacity:" + newCapacity);
                E[] newElements = (E[]) new Object[newCapacity];
                for (int i = 0; i < size; i++) {
                    newElements[i] = elements[index(i)];
                }
                elements = newElements;
                front = 0;
            }
        }
    
        /**
         * 获取指定位置的元素
         * 
         * @param index
         * @return
         */
        public E get(int index) {
            checkIndex(index);
            return elements[index(index)];
        }
    
        /**
         * 设置index位置的元素
         * 
         * @param index
         * @param element
         */
        @Override
        public void set(int index, E element) {
            checkIndex(index);
            elements[index(index)] = element;
        }
    
        /**
         * 删除指定位置的元素
         * 
         * @param index
         * @return
         */
        public E remove(int index) {
            checkIndex(index);
            E oldE = elements[index(index)];
            if (index >= size >> 1) { // 向前移动
                for (int i = index; i < size - 1; i++) {
                    elements[index(i)] = elements[index(i + 1)];
                }
                elements[index(size - 1)] = null;
            } else { // 向后移动
                for (int i = index; i > 0; i--) {
                    elements[index(i)] = elements[index(i - 1)];
                }
                elements[front] = null;
                front = index(1);
            }
            size--;
            if (size == 0)
                front = 0;
            trim();
            return oldE;
        }
    
        /**
         * 缩容:当size==capactity/2时就进行缩容,缩小为容量的一半
         */
        private void trim() {
            int newCapacity = elements.length >> 1;
            if (size <= newCapacity && elements.length > DEFAULT_CAPACTITY) {
                E[] newElement = (E[]) new Object[newCapacity];
                for (int i = 0; i < size; i++) {
                    newElement[i] = elements[index(i)];
                }
                System.out.println(elements.length + "缩容为" + newCapacity);
                front = 0;
                elements = newElement;
            }
        }
    
        /**
         * 删除元素
         * 
         * @param element
         * @return
         */
        public E remove(E element) {
            return remove(indexOf(element));
        }
    
        /**
         * 返回指定元素的位置
         * 
         * @param element
         * @return 返回-1,表示未找到元素
         */
        public int indexOf(E element) {
            for (int i = 0; i < size; i++) {
                if (element == null) {
                    if (elements[index(i)] == null)
                        return i;
                } else {
                    if (element.equals(elements[index(i)]))
                        return i;
                }
            }
            return -1;
        }
    
        /**
         * 清空元素
         */
        public void clear() {
            for (E e : elements)
                e = null;
            size = 0;
            front = 0;
            // 缩容
            if (elements != null && elements.length > DEFAULT_CAPACTITY)
                elements = (E[]) new Object[DEFAULT_CAPACTITY];
        }
    
        @Override
        public String toString() {
            StringBuilder sb = new StringBuilder();
            sb.append("size=" + size + " capactity=" + elements.length + "  front=" + front + "  [");
            for (int i = 0; i < elements.length; i++) {
                sb.append(elements[i]);
                if (i != elements.length - 1)
                    sb.append(",");
            }
            sb.append("]");
            return sb.toString();
        }
    }
    

    相关文章

      网友评论

          本文标题:ArrayList的优化

          本文链接:https://www.haomeiwen.com/subject/voacoktx.html