ArrayList的优化

当向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>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 extends AbstractList {
    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的优化)