ArrayList

面试的时候又说到这个,没事就整理了一下,有一些地方不是很清楚,但是大概也就是这么些知识点了

1、是什么

​ Array是实现原理就是数组(动态数组),容量能够动态的增长

2、效率如何

​ ArrayList不是线程安全的,所以效率比较高

3、继承哪些类和实现哪些类

​ ArrayList主要继承了AbstractList类,实现了List,RandomAccess,Cloneable,Serializable接口

​ RandomAccess:他就是一个标志接口,支持快速随机访问。在Collections里面找到这个

public static <T>
    int binarySearch(List<? extends Comparable<? super T>> list, T key) {
        if (list instanceof RandomAccess || list.size()<BINARYSEARCH_THRESHOLD)
            return Collections.indexedBinarySearch(list, key);
        else
            return Collections.iteratorBinarySearch(list, key);
    }

我们可以看到,当判断list是否实现RandomAccess来执行不同的方法

看一下这两个方法

private static <T>
    int indexedBinarySearch(List<? extends Comparable<? super T>> list, T key) {
        int low = 0;
        int high = list.size()-1;

        while (low <= high) {
            int mid = (low + high) >>> 1;
            Comparable<? super T> midVal = list.get(mid);
            int cmp = midVal.compareTo(key);

            if (cmp < 0)
                low = mid + 1;
            else if (cmp > 0)
                high = mid - 1;
            else
                return mid; // key found
        }
        return -(low + 1);  // key not found
    }
private static <T>
    int iteratorBinarySearch(List<? extends Comparable<? super T>> list, T key)
    {
        int low = 0;
        int high = list.size()-1;
        ListIterator<? extends Comparable<? super T>> i = list.listIterator();

        while (low <= high) {
            int mid = (low + high) >>> 1;
            Comparable<? super T> midVal = get(i, mid);
            int cmp = midVal.compareTo(key);

            if (cmp < 0)
                low = mid + 1;
            else if (cmp > 0)
                high = mid - 1;
            else
                return mid; // key found
        }
        return -(low + 1);  // key not found
    }

通过查看源代码,发现实现RandomAccess接口的List集合采用一般的for循环遍历,而未实现这接口则采用迭代器。

ArrayList用for循环遍历比iterator迭代器遍历快,LinkedList用iterator迭代器遍历比for循环遍历快

总结:RandomAccess接口这个空架子的存在,是为了能够更好地判断集合是否ArrayList或者LinkedList,从而能够更好选择更优的遍历方式,提高性能!

4、ArrayList的常量和变量

// 序列ID
private static final long serialVersionUID = 8683452581122892189L;

// ArrayList默认的初始容量大小
private static final int DEFAULT_CAPACITY = 10;

// 空对象数组,用于空实例的共享空数组实例
private static final Object[] EMPTY_ELEMENTDATA = {};

// 空对象数组,如果使用默认的构造函数创建,则默认对象内容是该值
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};

// 存放当前数据,不参与序列化
transient Object[] elementData; // non-private to simplify nested class access

// list大小
private int size;

当元素数量超出数组长度的时候,数组就会进行扩容,扩容就是ArrayList存储操作缓慢的原因。尤其到了后面越来越大,扩一次所需的时间就会越来越多

在transient我们可以看到序列化的影子,这也就是为什么要实现Serializable接口

5、ArrayList的构造方法有哪些

/**
     * 使用指定的初始容量构造一个空列表。
     *
     * @param  initialCapacity  列表的初始容量
     * @throws IllegalArgumentException 如果指定初始容量是负的
     */
public ArrayList(int initialCapacity) {
    if (initialCapacity > 0) {
        this.elementData = new Object[initialCapacity];
    } else if (initialCapacity == 0) {
        this.elementData = EMPTY_ELEMENTDATA;
    } else {
        throw new IllegalArgumentException("Illegal Capacity: "+
                                           initialCapacity);
    }
}
/**
     * 构造一个容量为0的空的列表,第一次用add的时候才会确定容量
     */
public ArrayList() {
    this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}

/**
     * 按照集合迭代器返回的顺序构造一个包含指定集合元素的列表。
     *
     * @param c 要将其元素放入此列表中的集合
     * @throws NullPointerException if the specified collection is null
     */
    public ArrayList(Collection<? extends E> c) {
        //将Collection对象转成Object
        elementData = c.toArray();
       	//这里传进来的时候是要求非空的
        if ((size = elementData.length) != 0) {
            // c.toArray might (incorrectly) not return Object[] (see 6260652)
            if (elementData.getClass() != Object[].class)
                //把collection对象的内容copy到elementdata中,所以要实现Cloneable接口
                elementData = Arrays.copyOf(elementData, size, Object[].class);
        } else {
            // replace with empty array.
            this.elementData = EMPTY_ELEMENTDATA;
        }
    }

6、ArrayList的方法

  • add()

    /**
         * 将指定的元素添加到列表的末尾。
         *
         * @param e 将指定的元素添加到列表的末尾。
         * @return <tt>true</tt> (as specified by {@link Collection#add})
         */
    public boolean add(E e) {
        	//判断添加后的长度是否需要扩容
            ensureCapacityInternal(size + 1);  // Increments modCount!!
            elementData[size++] = e;
            return true;
        }
    
    
    private void ensureCapacityInternal(int minCapacity) {
        	//如果为初始的,就看是不是比默认的10大
            if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
                minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
            }
    
            ensureExplicitCapacity(minCapacity);
        }
    
    private void ensureExplicitCapacity(int minCapacity) {
        	//如果需要扩容modCount自增,这个参数是指当前列表结构被修改的次数
            modCount++;
    
            // overflow-conscious code
            if (minCapacity - elementData.length > 0)
                grow(minCapacity);
        }
    
    /**
         * 增加容量,以确保至少可以保存由最小容量参数指定的元素数量。
         *
         * @param minCapacity 期望的最小容量
         */
    private void grow(int minCapacity) {
            // overflow-conscious code
        	//原来的长度
            int oldCapacity = elementData.length;
        	//新的长度=旧的加上旧的一半,也就是1.5个旧的  
        	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 final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
    private static int hugeCapacity(int minCapacity) {
            if (minCapacity < 0) // overflow
                throw new OutOfMemoryError();
            return (minCapacity > MAX_ARRAY_SIZE) ?
                Integer.MAX_VALUE :
                MAX_ARRAY_SIZE;
        }
    

指定的add()

/**
     * 在列表中的指定位置插入指定的元素。将当前所在位置的元素(如果有的话)和随后的元素向右移动(给它们的下标加1)。
     *
     * @param index 要插入指定元素的索引
     * @param element 要插入的元素
     * @throws IndexOutOfBoundsException {@inheritDoc}
     */
    public void add(int index, E element) {
        rangeCheckForAdd(index);

        ensureCapacityInternal(size + 1);  // Increments modCount!!
        //拷贝数组
        System.arraycopy(elementData, index, elementData, index + 1,
                         size - index);
        elementData[index] = element;
        size++;
    }

/**
     * 范围检查
     */
    private void rangeCheckForAdd(int index) {
        if (index > size || index < 0)
            throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
    }

addAll()

/**
     * 元素返回的顺序,将指定集合中的所有元素追加到列表的末尾指定集合的迭代器。
     * 如果在操作进行中修改了指定的集合,则此操作的行为未定义。(这意味着,如果指定的集合是This List和This,则该调用的行为是未定义的非空的列表)。
     *
     * @param c 包含要添加到此列表的元素的集合
     * @return <tt>true</tt> 如果这个列表因为该调用而改变
     * @throws NullPointerException if the specified collection is null
     */
    public boolean addAll(Collection<? extends E> c) {
        Object[] a = c.toArray();
        int numNew = a.length;
        ensureCapacityInternal(size + numNew);  // Increments modCount
        System.arraycopy(a, 0, elementData, size, numNew);
        size += numNew;
        return numNew != 0;
    }

指定的addAll()

/**
     * 将指定集合中的所有元素插入到此列表中,从指定位置开始。
     * 将当前位于该位置的元素(如果有的话)和随后的元素向右移动(增加它们的下标)。
     * 新元素将按照指定集合的迭代器返回的顺序出现在列表中。
     *
     * @param index 索引,用于插入指定集合中的第一个元素
     * @param c collection containing elements to be added to this list
     * @return <tt>true</tt> if this list changed as a result of the call
     * @throws IndexOutOfBoundsException {@inheritDoc}
     * @throws NullPointerException if the specified collection is null
     */
    public boolean addAll(int index, Collection<? extends E> c) {
        rangeCheckForAdd(index);

        Object[] a = c.toArray();
        int numNew = a.length;
        ensureCapacityInternal(size + numNew);  // Increments modCount

        int numMoved = size - index;
        if (numMoved > 0)
            System.arraycopy(elementData, index, elementData, index + numNew,
                             numMoved);

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

总结:

  • 是否需要扩容
  • 如果要插入指定位置,要判断位置是否合法
  • 扩容:
    • 先判断初始化了没,还没初始了就看看是10大开始加的长度大
    • 初始了就扩容,1.5,不够就放最大值,然后再拷贝

get()

/**
     * 返回列表里特定位置的元素
     *
     * @param  index 要返回的元素的索引
     * @return 位于列表中指定位置的元素
     * @throws IndexOutOfBoundsException {@inheritDoc}
     */   
	public E get(int index) {
        //检查索引是否合法
        rangeCheck(index);

        return elementData(index);
    }

/**
     * 检查给定的索引是否在范围内。
     * 如果不是,则抛出适当的运行时异常。
     * 这个方法不检查索引是否为负:它总是在数组访问之前立即使用
     * 如果索引为负,则会抛出ArrayIndexoutofBoundsException异常。
     */
    private void rangeCheck(int index) {
        if (index >= size)
            throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
    }

	@SuppressWarnings("unchecked")
    E elementData(int index) {
        return (E) elementData[index];
    }

set()

/**
     * 将列表中指定位置的元素替换为指定的元素。
     *
     * @param index 要替换的元素的索引
     * @param element 元素,将存储在指定的位置
     * @return 先前位于指定位置的元素
     * @throws IndexOutOfBoundsException {@inheritDoc}
     */
    public E set(int index, E element) {
        rangeCheck(index);

        E oldValue = elementData(index);
        elementData[index] = element;
        return oldValue;
    }

remove()

    /**
     * 删除列表中指定位置的元素。将所有后续元素向左移动(从它们的下标减去1)。
     *
     * @param index 要删除的元素的索引
     * @return 从列表中删除的元素
     * @throws IndexOutOfBoundsException {@inheritDoc}
     */
    public E remove(int index) {
        rangeCheck(index);

        modCount++;
        E oldValue = elementData(index);

        int numMoved = size - index - 1;
        if (numMoved > 0)
            //数组拷贝
            System.arraycopy(elementData, index+1, elementData, index,
                             numMoved);
        elementData[--size] = null; // clear to let GC do its work

        return oldValue;
    }

删除对象

    /**
     * 如果它存在,从列表中删除第一个出现的指定元素。
     * 如果列表中不包含该元素,则保持不变。更正式的做法是删除索引最低的元素
     *
     * @param o 元素,如果存在,则从列表中删除
     * @return <tt>true</tt> 如果列表中包含指定的元素
     */
    public boolean remove(Object o) {
		//如果要删除的对象为空,进行删除的时候就直接判断哪一个是空
        if (o == null) {
            for (int index = 0; index < size; index++)
                if (elementData[index] == null) {
                    fastRemove(index);
                    return true;
                }
            //如果不为空,再用equal
        } else {
            for (int index = 0; index < size; index++)
                if (o.equals(elementData[index])) {
                    fastRemove(index);
                    return true;
                }
        }
        return false;
    }

    /*
     * Private remove method that skips bounds checking and does not
     * return the value removed.
     * Private remove方法,跳过边界检查,不返回已删除的值。
     */
    private void fastRemove(int index) {
        modCount++;
        //numMoved表示删除index位置的元素后,需要从index后移动多少个元素到前面去
  		//减1的原因,是因为size从1开始算起,index从0开始算起
        int numMoved = size - index - 1;
        if (numMoved > 0)
            //从index+1位置开始被拷贝,拷贝的起始位置是index,长度是numMoved
            System.arraycopy(elementData, index+1, elementData, index,
                             numMoved);
        elementData[--size] = null; //数组最后一个位置赋值null,帮助GC(没有引用则自动回收了)
    }

Iterator:ArrayList的迭代器,这里用的是fail-fast迭代器

/**
     * 以正确的顺序返回列表中元素的迭代器。
     *
     * <p>The returned iterator is <a href="#fail-fast"><i>fail-fast</i></a>.
     *
     * @return an iterator over the elements in this list in proper sequence
     */
    public Iterator<E> iterator() {
        return new Itr();
    }

    /**
     * AbstractList.Itr的优化版本
     */
    private class Itr implements Iterator<E> {
        int cursor;       // 要返回的下一个元素的索引
        int lastRet = -1; // 返回的最后一个元素的索引
        int expectedModCount = modCount;	//列表被修改的次数

        //判断是不是最后一个元素
        public boolean hasNext() {
            //判断该索引的下一个索引是否为数组大小
            return cursor != size;
        }

        @SuppressWarnings("unchecked")
        public E next() {
            checkForComodification();
            int i = cursor;
            if (i >= size)
                //该元素不存在
                throw new NoSuchElementException();
            
            Object[] elementData = ArrayList.this.elementData;
            //再次判断,不一致说明数组被修改过(并发修改异常)
            if (i >= elementData.length)
                throw new ConcurrentModificationException();
            //继续往下
            cursor = i + 1;
            return (E) elementData[lastRet = i];
        }

        //移除当前元素
        public void remove() {
            //数组无元素
            if (lastRet < 0)
                throw new IllegalStateException();
            checkForComodification();

            try {
                //如果在遍历外修改,则expectedModCount不被修改,就会抛出异常
                ArrayList.this.remove(lastRet);
                cursor = lastRet;
                lastRet = -1;
                expectedModCount = modCount;
            } catch (IndexOutOfBoundsException ex) {
                throw new ConcurrentModificationException();
            }
        }

        @Override
        @SuppressWarnings("unchecked")
        public void forEachRemaining(Consumer<? super E> consumer) {
            Objects.requireNonNull(consumer);
            final int size = ArrayList.this.size;
            int i = cursor;
            if (i >= size) {
                return;
            }
            final Object[] elementData = ArrayList.this.elementData;
            if (i >= elementData.length) {
                throw new ConcurrentModificationException();
            }
            while (i != size && modCount == expectedModCount) {
                consumer.accept((E) elementData[i++]);
            }
            // update once at end of iteration to reduce heap write traffic
            cursor = i;
            lastRet = i - 1;
            checkForComodification();
        }

        //判断当前Itr修改的次数和ArrayList是否一致
        final void checkForComodification() {
            if (modCount != expectedModCount)
                //不一致就会抛出异常(并发修改异常)
                throw new ConcurrentModificationException();
        }
    }
    /**
     * AbstractList.Listitr的优化版本
     */
    private class ListItr extends Itr implements ListIterator<E> {
        ListItr(int index) {
            super();
            cursor = index;
        }

        //是否有元素
        public boolean hasPrevious() {
            return cursor != 0;
        }

        public int nextIndex() {
            return cursor;
        }

        public int previousIndex() {
            return cursor - 1;
        }

        @SuppressWarnings("unchecked")
        public E previous() {
            checkForComodification();
            int i = cursor - 1;
            //数组越界
            if (i < 0)
                throw new NoSuchElementException();
            Object[] elementData = ArrayList.this.elementData;
            //并发异常
            if (i >= elementData.length)
                throw new ConcurrentModificationException();
            cursor = i;
            return (E) elementData[lastRet = i];
        }

        public void set(E e) {
            if (lastRet < 0)
                throw new IllegalStateException();
            checkForComodification();

            try {
                ArrayList.this.set(lastRet, e);
            } catch (IndexOutOfBoundsException ex) {
                throw new ConcurrentModificationException();
            }
        }

        public void add(E e) {
            checkForComodification();

            try {
                int i = cursor;
                ArrayList.this.add(i, e);
                cursor = i + 1;
                lastRet = -1;
                expectedModCount = modCount;
            } catch (IndexOutOfBoundsException ex) {
                throw new ConcurrentModificationException();
            }
        }
    }

7、ArrayList序列化机制

我们看到ArrayList实现了Serializable接口,那么证明可以是被序列化的,但是elementData数组又被transient关键字修饰,我们知道被transient修饰的成员属性变量不被序列化,那么我们先看一个例子,ArrayList是否能被序列化成功呢?

public static void main(String[] args) throws IOException, ClassNotFoundException {
        List<Integer> list = new ArrayList<>(Arrays.asList(1, 2, 3));
        ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
        ObjectOutputStream objectOutputStream = new ObjectOutputStream(byteArrayOutputStream);
        objectOutputStream.writeObject(list);

        ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(byteArrayOutputStream.toByteArray());
        ObjectInputStream objectInputStream = new ObjectInputStream(byteArrayInputStream);
        List<Integer> newList = (List<Integer>) objectInputStream.readObject();
        System.out.println(Arrays.toString(newList.toArray()));
    }

结果是序列化和反序列化成功??这是为什么呢?

其实细心的我们在查看源码时发现,ArrayList重写了readObject和writeObject来自定义的序列化和反序列化策略。

什么是自定义序列化和反序列化呢?

在序列化过程中,如果被序列化的类中定义了writeObject 和 readObject 方法,虚拟机会试图调用对象类里的 writeObject 和 readObject 方法,进行用户自定义的序列化和反序列化。

如果没有这样的方法,则默认调用是 ObjectOutputStream 的 defaultWriteObject 方法以及 ObjectInputStream 的 defaultReadObject 方法。

用户自定义的 writeObject 和 readObject 方法可以允许用户控制序列化的过程,比如可以在序列化的过程中动态改变序列化的数值。

    /**
     * 将<tt>ArrayLists/tt>实例的状态保存到流中(也就是说,序列化它)。
     *
     * @serialData 返回<tt>ArrayList</tt>实例的数组的长度被触发(int),
     *				后面跟着它的所有元素(每个元素都有一个<tt>对象</tt>)。
     */
    private void writeObject(java.io.ObjectOutputStream s)
        throws java.io.IOException{
        // 写出元素计数和任何隐藏的东西
        int expectedModCount = modCount;
        s.defaultWriteObject();

        // 写出大小为克隆行为兼容性的容量()
        s.writeInt(size);

        // 按照正确的顺序写出所有元素。
        for (int i=0; i<size; i++) {
            s.writeObject(elementData[i]);
        }

        if (modCount != expectedModCount) {
            throw new ConcurrentModificationException();
        }
    }

    /**
     * 从流中重新构造<tt>ArrayList</tt>实例(也就是说,反序列化它)。
     */
    private void readObject(java.io.ObjectInputStream s)
        throws java.io.IOException, ClassNotFoundException {
        elementData = EMPTY_ELEMENTDATA;

        // Read in size, and any hidden stuff
        s.defaultReadObject();

        // Read in capacity
        s.readInt(); // ignored

        if (size > 0) {
            // be like clone(), allocate array based upon size not capacity
            ensureCapacityInternal(size);

            Object[] a = elementData;
            // Read in all elements in the proper order.
            for (int i=0; i<size; i++) {
                a[i] = s.readObject();
            }
        }
    }

可以看到通过writeObject方法和readObject方法来遍历elementData数组把数组中的元素写入ObjectOutputStream ,ObjectInputStream 中的。那么为什么ArrayList要用这种方式来实现序列化呢?

ArrayList实际上是动态数组,每次在放满以后自动增长设定的长度值,如果数组自动增长长度设为100,而实际只放了一个元素,那就会序列化99个null元素。为了保证在序列化的时候不会将这么多null同时进行序列化,ArrayList把元素数组设置为transient。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值