Loading

数据结构 ArrayList解析 Java8

ArrayList

介绍:一种顺序存储结构,其内部其实就是维护了一个数组,定义了一些常用的方法来对数组进行操作,简化用户使用。
首先我们来上一下整体结构和源码,后面再慢慢聊。
ArrayList的一些简单功能的实现

import java.util.Arrays;

public class ArrayList<T> {
    private T[] elements;
    private int size;
    private int maxSize = 10;
    private final int defaultSize = 10;

    /**
     * 构造一个定长的数组。
     * @param size
     */
    public ArrayList(int size) {
        if (size >= defaultSize){
            maxSize = size + (size >> 1);
            elements = (T[]) new Object[maxSize];
        }else if(size > 0){
            elements = (T[]) new Object[maxSize];
        } else if (size == 0){
            elements = (T[])new Object[]{};
        } else{
            throw new IllegalArgumentException("Illegal Capacity: " + size);
        }
        this.size = size;
    }

    /**
     * 构造一个默认大小的数组
     */
    public ArrayList() {
        elements = (T[]) new Object[defaultSize];
        size = 0;
    }

    /**
     * 以数组arr建立ArrayList
     * @param arr
     */
    public ArrayList(T[] arr){
        if (arr.length >= defaultSize){
            maxSize = arr.length +( arr.length >> 1);
        }
        elements = (T[])new Object[maxSize];
        System.arraycopy(arr, 0, elements, 0, arr.length);
        size = arr.length;
    }

    /**
     * 返回数组大小
     * @return
     */
    public int size(){
        return size;
    }

    /**
     * 返回数组是否为空
     * @return
     */
    public boolean isEmpty(){
        return size == 0;
    }

    /**
     * 找到element对应的第一个数组元素下标
     * @param element
     * @return
     */
    public int indexOf(T element){
        if(element == null){
            for (int i = 0; i < size; i++) {
                if (elements[i] == null){
                    return i;
                }
            }
        }else{
            for (int i = 0; i < size; i++) {
                if (elements[i].equals(element)){
                    return i;
                }
            }
        }
        return -1;
    }

    public int lastIndexOf(T element){
        if(element == null){
            for (int i = size-1; i >= 0; i--) {
                if (elements[i] == null){
                    return i;
                }
            }
        }else{
            for (int i = size-1; i >= 0; i--) {
                if (elements[i].equals(element)){
                    return i;
                }
            }
        }
        return -1;
    }



    /**
     * 判断数组中是否包含element元素
     * @param element
     * @return
     */
    public boolean contains(T element){
        return indexOf(element) >= 0;
    }

    /**
     * 添加元素到数组中
     * @param element
     */
    public void add(T element){
        judgeGrow(++size);
        elements[size-1] = element;
    }

    /**
     * 向指定坐标添加数组
     * @param index
     * @param element
     */
    public void add(int index, T element){
        if (index < 0 || index >= size){
            throw new IndexOutOfBoundsException("IndexOutOfBoundsException:" + index);
        }else{
            judgeGrow(++size);
            System.arraycopy(elements, index, elements,index+1, size-index);
            elements[index] = element;
        }
    }

    /**
     * 返回对应下标的元素
     * @param index
     * @return
     */
    public T get(int index){
        if (index < 0 || index >= size) {throw new IndexOutOfBoundsException("IndexOutOfBoundsException" + index);}
        return elements[index];
    }

    /**
     * 设置对应数组下标下的值,返回原来下标下的位置
     * @param index
     * @param element
     * @return
     */
    public T set(int index, T element){
        if (index < 0 || index >= size) {throw new IndexOutOfBoundsException("IndexOutOfBoundsException" + index);}
        T oldElement = elements[index];
        elements[index] = element;
        return oldElement;
    }

    /**
     * 移除对应下标下的元素
     * @param index
     * @return
     */
    public T remove(int index){
        if (index < 0 || index >= size) {throw new IndexOutOfBoundsException("IndexOutOfBoundsException" + index);}
        T element = elements[index];
        System.arraycopy(elements, index+1, elements,index, size-index-1);
        elements[size-1] = null;
        size--;
        return element;
    }

    /**
     * 移除数组中第一个o
     * @param o
     * @return
     */
    public T remove(T o){
        int index = indexOf(o);
        return index == -1 ? null : remove(index);
    }

    /**
     * 移除数组中最后一个o
     * @param o
     * @return
     */
    public T removeLast(T o){
        int index = lastIndexOf(o);
        return index == -1 ? null : remove(index);
    }

    /**
     *清除数组中所有元素
     */
    public void clear(){
        Arrays.fill(elements, 0, size, null);
        size = 0;
    }

    /**
     * 判断是否需要加长数组长度
     * @param index
     */
    private void judgeGrow(int index){
        if (index < 0){throw new IndexOutOfBoundsException("IndexOutOfBoundsException:"+index);}
        else if (index >= maxSize){
            grow();
        }
    }

    /**
     * 对数组进行增长
     */
    private void grow(){
        maxSize = maxSize + (maxSize >> 1);
        T[] newElements = (T[]) new Object[maxSize];
        System.arraycopy(elements, 0, newElements, 0, elements.length);
        elements = newElements;
    }

    /**
     * 重写toString,方便我一会测试
     * @return
     */
    @Override
    public String toString() {
        return "ArrayList{" +
                "elements=" + Arrays.toString(elements) +
                '}';
    }
}

ArrayList中的成员变量和方法

成员变量

	//ArrayList内部所维护的数组
    private T[] elements;
    //数组当前user所展示出的大小
    private int size;
    //数组实际大小
    private int maxSize = 10;
    //默认大小
    private final int defaultSize = 10;
这里简单聊一下这三个size
size是ArrayList对user所展示出来的大小,用户对其数组进行加入/删除元素,都会引发size的变化
maxsize 这里我们默认为10,和defaultSize是一样大小,表示当前数组的真实长度
每当size == maxSize时,我们将自动对数组进行扩容处理
defaultSize当我们未指定数组大小时,数组的默认大小。

部分方法:

    /**
     * 找到element对应的第一个数组元素下标
     * @param element
     * @return
     */
    public int indexOf(T element){
        if(element == null){
            for (int i = 0; i < size; i++) {
                if (elements[i] == null){
                    return i;
                }
            }
        }else{
            for (int i = 0; i < size; i++) {
                if (elements[i].equals(element)){
                    return i;
                }
            }
        }
        return -1;
    }
    /**
     * 判断数组中是否包含element元素
     * @param element
     * @return
     */
    public boolean contains(T element){
        return indexOf(element) >= 0;
    }
这边时模仿Java原生ArrayList中的这两个方法去写的
最开始写contains时想的是直接写个循环,查到了就直接返回true
循环正常结束就返回false
但等到我去看官方的源码是发现是直接调用indexof
根据其返回值进行判断是否含有这个值
这种方法减少了编写时的代码量,不愧是源码。
	//计算容量
	//对elementData进行判空,如果是,将更改后大小与默认大小10比较返回最大值,不为空直接返回更改后大小
    private static int calculateCapacity(Object[] elementData, int minCapacity) {
        if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
            return Math.max(DEFAULT_CAPACITY, minCapacity);
        }
        return minCapacity;
    }
	//确保内部容量函数
    private void ensureCapacityInternal(int minCapacity) {
        ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
    }
	//确保显式容量
    private void ensureExplicitCapacity(int minCapacity) {
    	//操作数
        modCount++;
        // overflow-conscious code
        //如果大于当前数组长度,就对数组长度进行扩容处理
        if (minCapacity - elementData.length > 0)
            grow(minCapacity);
    }
    
    //增加容量函数
    /**
     * Increases the capacity to ensure that it can hold at least the
     * number of elements specified by the minimum capacity argument.
     *
     * @param minCapacity the desired minimum capacity
     */
    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);
    }
    private static int hugeCapacity(int minCapacity) {
        if (minCapacity < 0) // overflow
            throw new OutOfMemoryError();
        return (minCapacity > MAX_ARRAY_SIZE) ?
            Integer.MAX_VALUE :
            MAX_ARRAY_SIZE;
    }
    public boolean add(E e) {
        ensureCapacityInternal(size + 1);  // Increments modCount!!
        elementData[size++] = e;
        return true;
    }
在看ArraysList添加元素的源码中我们经常会看到ensureCapacityInternal()这个函数
翻译过来也就是确保内部容量函数
这里面调用了计算容量函数,当数组为空时,会默认返回10
这是我在写ArrayList中所没有考虑到的东西
试想我生成了一个长度为0的ArrayList的数组,然后为其添加元素
那么它自然会超出最大长度0,那么它就会调用grow,但0加上0的二分之一还是0
数组不会产生增长。

再来看一下它的grow函数
minCapacity代表最小范围
当增长完成后,会对最小返回进行比较,小于就直接用最小增长范围
之后会与MAX_ARRAY_SIZE = Integer.MAX_VALUE-8进行比较(减法比较)
	这边感觉有必要提一下这个减法,倘若我们单纯用大于小于号进行比较,那么当minCapacity超出Integer.MAX_VALUE时会		  变成负数,那么得到的答案就是minCapacity不大于MAX_ARRAY_SIZE,但实际并不是这样
	而我们用减法比较可以解决这个问题
倘若大于,那我们就去拿Integer.MAX_VALUE,如果发现数据溢出了,那么就抛出异常。
最后根据我们拿到的数据去copy数组(为什么是Array.copy 而不是System.copyarr,不明白,有了解的可以评论说一下)

不愧是源码,考虑太周到了
    /**
     * 移除对应下标下的元素
     * @param index
     * @return
     */
    public T remove(int index){
        if (index < 0 || index >= size) {throw new IndexOutOfBoundsException("IndexOutOfBoundsException" + index);}
        T element = elements[index];
        System.arraycopy(elements, index+1, elements,index, size-index-1);
        elements[size-1] = null;
        size--;
        return element;
    }

    /**
     * 移除数组中第一个o
     * @param o
     * @return
     */
    public T remove(T o){
        int index = indexOf(o);
        return index == -1 ? null : remove(index);
    }

    /**
     * 移除数组中最后一个o
     * @param o
     * @return
     */
    public T removeLast(T o){
        int index = lastIndexOf(o);
        return index == -1 ? null : remove(index);
    }
remove传入删除对象,我们之间调用之前写过的indexOf获取下标,然后调用remove(index)

基本上大致功能就这些了,跟源码比起来,写的还是存在一些BUG,如果还有其他问题可以在评论中给出

请注意,此实现不同步。如果多个线程同时访问一个ArrayList实例,并且至少有一个线程在结构上修改了列表,则必须在外部进
行同步。 (结构修改是添加或删除一个或多个元素,或显式调整后备数组大小的任何操作;仅设置元素的值不是结构修改。)这通
常通过同步一些自然封装的对象来完成列表。如果不存在这样的对象,则应使用Collections.synchronizedList方法“包装”
该列表。这最好在创建时完成,以防止对列表的意外不同步访问:
     List list = Collections.synchronizedList(new ArrayList(...));

从源码里面扒出来的一段话,大致意思就是ArrayList线程不安全,想要使其安全可以使用上面那个语句。

刚建的博客,拿以前的文章做个测试

posted @ 2023-06-17 00:26  最爱吃兽奶01  阅读(84)  评论(0)    收藏  举报