[集合]ArrayList源码和时间复杂度

目录

 

前言:

1.ArrayList的复杂度?

2.源码分析

2.1 ArrayList的继承关系

2.2 属性

2.2.1 关于序列化

2.2.2 什么是序列化,为什么要序列化?

2.3 构造函数

为什么elementData.getClass() != Object[].class做这个判断呢?

2.4 添加元素

2.4.1 在末尾增加元素add(E e)

2.4.2 指定位置增加元素add(int index, E element)

2.4.3 增添集合类型addAll(Collection c)

2.4.4 指定位置增添集合类型addAll(int index, Collection c)

2.4.5 对指定位置进行更新set(int index, E element)

2.5 删除元素

2.5.1 根据index删除元素remove(int index)

2.5.2 删除指定元素remove(Object o)

2.5.3 removeAll(Collection c)

2.5.4 clear()

2.6 查找元素

2.6.1 get(int index)

2.7 contains

2.7.1 contains(Object o)

2.8 其他方法

2.8.1 缩衣节食,调整大小trimToSize() 

2.8.2 clone 

2.8.3 toArray()

2.9 序列化相关的writeObject和readObject?

3. 小结


前言:

认真自己照着源码写一遍意思。作为笔记和准备记录。ArrayList比HashMap简单很多读起来。下一篇读LinkedList。

1.ArrayList的复杂度?

如果我们不指定位置直接添加元素时(add(E element)),元素会默认会添加在最后,不会触发底层数组的复制,不考虑底层数组自动扩容的话,时间复杂度为O(1) 

在指定位置添加元素(add(int index, E element)),需要复制底层数组,根据最坏打算时间复杂度是O(n)。

总结:

ArrayList 是线性表(数组)
get() 直接读取第几个下标,复杂度 O(1)
add(E) 添加元素,直接在后面添加,复杂度O(1)
add(index, E) 添加元素,在第几个元素后面插入,后面的元素需要向后移动,复杂度O(n)
remove()删除元素,后面的元素需要逐个移动,复杂度O(n)

2.源码分析

2.1 ArrayList的继承关系

public class ArrayList<E> extends AbstractList<E>
        implements List<E>, RandomAccess, Cloneable, java.io.Serializable
{
//...
}

ArrayList继承了AbstractList。

其中比较需要注意的是随机访问接口。证明能够通过序号就可以访问到元素。

2.2 属性

    private static final long serialVersionUID = 8683452581122892189L;

    /**
     * Default initial capacity.默认容量
     */
    private static final int DEFAULT_CAPACITY = 10;

    /**
     * Shared empty array instance used for empty instances.初始空数组
     */
    private static final Object[] EMPTY_ELEMENTDATA = {};

    /**
     * Shared empty array instance used for default sized empty instances. We
     * distinguish this from EMPTY_ELEMENTDATA to know how much to inflate when
     * first element is added.
     */
    private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};

    /**
     * The array buffer into which the elements of the ArrayList are stored.
     * The capacity of the ArrayList is the length of this array buffer. Any
     * empty ArrayList with elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA
     * will be expanded to DEFAULT_CAPACITY when the first element is added.
       不会被序列化,因为transient这个关键字的原因。因为序列化前会进行把元素write,
        相当于把元素弄到一个合适的数组上面,有元素和大小,这样可以节省空间
     */
    transient Object[] elementData; // non-private to simplify nested class access

    /**
     * The size of the ArrayList (the number of elements it contains).
     * 保存当前数组的长度
     * @serial
     */
    private int size;

    private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
    //继承的元素,标识修改的次数,顾名思义
    protected transient int modCount = 0;

2.2.1 关于序列化

为什么要加上transient 关键字呢?

因为有的属性需要序列化,有的不需要。比如敏感信息不能序列化传输过去,就应该为空值,那么就可以加上这个关键字。

这样的话,就可以这个东西生命周期只在调用者的内存中。

另外有一种情况不起作用,如果是实现了Externalizable接口,哪一个属性被序列化使我们手动去指定的,即使是transient关键字修饰也不起作用。

另外静态变量也不会参与序列化,静态变量在全局区,本来流里面就没有写入静态变量,我打印静态变量当然会去全局区查找,而我们的序列化是写到磁盘上的,所以JVM查找这个静态变量的值,是从全局区查找的,而不是磁盘上。

静态变量是不会被序列化的,即使没有transient关键字修饰。

TODO:需要了解transient底层实现原理

2.2.2 什么是序列化,为什么要序列化?

序列化的本质就是把一种数据按照格式转成另一种数据形式。

序列化本身的目的是二进制序列和程序内部表示结构的转化。

当Java对象通过网络进行传输的时候。因为数据只能够以二进制的形式在网络中进行传输,因此当把对象通过网络发送出去之前需要先序列化成二进制数据,在接收端读到二进制数据之后反序列化成Java对象。

像数组和链表之类的得拍扁hhh,然后反序列化有点像充气。

2.3 构造函数

无参构造函数

    /**
     * Constructs an empty list with an initial capacity of ten.
     */
    public ArrayList() {
        this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
    }

使用默认空数组,elementData中的长度是0,size是0。

但是当进行第一次add的时候,elementDate会使用默认长度10.

有参构造函数(int)

    /**
     * Constructs an empty list with the specified initial capacity.
     *
     * @param  initialCapacity  the initial capacity of the list
     * @throws IllegalArgumentException if the specified initial capacity
     *         is negative
     */
    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);
        }
    }

有参构造函数(入参集合类)

    /**
     * Constructs a list containing the elements of the specified
     * collection, in the order they are returned by the collection's
     * iterator.
     *
     * @param c the collection whose elements are to be placed into this list
     * @throws NullPointerException if the specified collection is null
     */
    public ArrayList(Collection<? extends E> c) {
        //集合類可以直接toArray
        elementData = c.toArray();
        if ((size = elementData.length) != 0) {
            // c.toArray might (incorrectly) not return Object[] (see 6260652)
            if (elementData.getClass() != Object[].class)
                //拷贝元素
                elementData = Arrays.copyOf(elementData, size, Object[].class);
        } else {
            // replace with empty array.
            this.elementData = EMPTY_ELEMENTDATA;
        }
    }

为什么elementData.getClass() != Object[].class做这个判断呢?

大概简单叙述是因为c.toArray();可能会让这个集合转成数组之后,不是Object[]这种类型,有可能重写了toArray这个方法。所以为了让这个Object[] elementData能够成功接收集合中的元素,所以才这么写的,防止出现classcast的异常。

因为 c.toArray(); 功能是将集合中的元素转换成数组,它在Collection中是这样子的,是接口中的方法,那么它就要被重写。

这个涉及到,因为子类重写父类方法的时候,在不修改返回值类型的前提下,子类返回了什么类型,具体得到的是子类的返回值类型,而不会上转成父类的返回值类型。

    public static void main(String[] args) {
        String [] a  = new String[2];
        Object [] b = new Object[2];
        //print :[Ljava.lang.String;
        System.out.println(a.getClass());
        //print :[Ljava.lang.Object;
        System.out.println(b.getClass());
    }

上面代码证明了别的类型的的数组并不是Object[].

这里面会涉及到元素的拷贝。

2.4 添加元素

2.4.1 在末尾增加元素add(E e)

ArrayList提供了add(E e)、add(int index, E element)、addAll(Collection<? extends E> c)、addAll(int index, Collection<? extends E> c)、set(int index, E element)这个五个方法来实现ArrayList增加。

一般来说,添加元素可以添加到末尾,不涉及到元素的移动,但是如果制定了index的话,就会涉及到了元素的移动。

    /**
     * Appends the specified element to the end of this list.
     *
     * @param e element to be appended to this list
     * @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;
    }

设想一下,元素添加,肯定要判断一下数组大小是否够,需不需要扩容。

size是作为当前数组的大小,所以可以直接对size进行操作。

ensureCapacityInternal

    //
    private void ensureCapacityInternal(int minCapacity) {
//是这个地方,如果构造函数是空的数组,那么给与一下容量。
//毕竟你如果不用也没必要分配空间,这个思想可以去思考借鉴
        if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
            minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
        }

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


    //如果比最大的大小都大的话,那么就进行huge 超大容量
    //但是好像少没少8个大小真的有那么大关系吗 
    private static int hugeCapacity(int minCapacity) {
        //这个地方为啥是minCapacity<0? 没有想太明白 
        if (minCapacity < 0) // overflow
            throw new OutOfMemoryError();
        return (minCapacity > MAX_ARRAY_SIZE) ?
            Integer.MAX_VALUE :
            MAX_ARRAY_SIZE;
    }

在某些情况下>>1可以理解为除以2,所以如果是扩容,数组长度为原来的1.5倍。

 

总之:

1.判断长度+1之后是否会触发扩容(扩容1.5倍),如果是刚刚初始化的,会分配默认为10的空间。

2.如果扩容 修改次数modCount标识自增1(证明数组空间变化的次数)

3.将新元素添加到位于size++的位置上。

4.返回添加成功的布尔值。

2.4.2 指定位置增加元素add(int index, E element)

 

    /**
     * Inserts the specified element at the specified position in this
     * list. Shifts the element currently at that position (if any) and
     * any subsequent elements to the right (adds one to their indices).
     *
     * @param index index at which the specified element is to be inserted
     * @param element element to be inserted
     * @throws IndexOutOfBoundsException {@inheritDoc}
     */
    public void add(int index, E element) {
        //校验index的合理性
        rangeCheckForAdd(index);

        //和上面的add方法一样,进行判断是否需要扩容之类的
        ensureCapacityInternal(size + 1);  // Increments modCount!!

        //为什么进行数组拷贝呢?是因为插入了index制定了位置之后,别的都需要进行移位
        //对源数组进行复制处理(位移),从index + 1到size - index
        System.arraycopy(elementData, index, elementData, index + 1,
                         size - index);
        elementData[index] = element;
        size++;
    }

其中:

public static native void arraycopy(Object src,  int  srcPos,
                                    Object dest, int destPos,
                                    int length);

相当于原数组进行移位,那么吧index空出来,移动的元素是siez - index这个长度大小的。从index 移动到destPos

rangeCheckForAdd这个方法其实就是做一个check,并不重要。

    /**
     * A version of rangeCheck used by add and addAll.
     */
    private void rangeCheckForAdd(int index) {

        //判断index是否合法
        if (index > size || index < 0)
            throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
    }
 

    /**
     * Constructs an IndexOutOfBoundsException detail message.
     * Of the many possible refactorings of the error handling code,
     * this "outlining" performs best with both server and client VMs.
     */
    private String outOfBoundsMsg(int index) {
        return "Index: "+index+", Size: "+size;
    }

2.4.3 增添集合类型addAll(Collection<? extends E> c)

    /**
     * Appends all of the elements in the specified collection to the end of
     * this list, in the order that they are returned by the
     * specified collection's Iterator.  The behavior of this operation is
     * undefined if the specified collection is modified while the operation
     * is in progress.  (This implies that the behavior of this call is
     * undefined if the specified collection is this list, and this
     * list is nonempty.)
     *
     * @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 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的方法

1. 先把它转成Object[] 2. 是否需要扩容 3. 转换后的数组复制到列表尾部

2.4.4 指定位置增添集合类型addAll(int index, Collection<? extends E> c)

    /**
     * Inserts all of the elements in the specified collection into this
     * list, starting at the specified position.  Shifts the element
     * currently at that position (if any) and any subsequent elements to
     * the right (increases their indices).  The new elements will appear
     * in the list in the order that they are returned by the
     * specified collection's iterator.
     *
     * @param index index at which to insert the first element from the
     *              specified collection
     * @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;
    }

首先了解这个方法的含义,在指定的位置增加集合全部元素是什么意思呢?

就是把新的要插入集合中的元素,都留出空位来,然后插入进去。

2.4.5 对指定位置进行更新set(int index, E element)

    /**
     * Replaces the element at the specified position in this list with
     * the specified element.
     *
     * @param index index of the element to replace
     * @param element element to be stored at the specified position
     * @return the element previously at the specified position
     * @throws IndexOutOfBoundsException {@inheritDoc}
     */
    public E set(int index, E element) {
        rangeCheck(index);

        //获取插入位置的当前元素
        E oldValue = elementData(index);
        //将新的元素替换当前插入位置的元素
        elementData[index] = element;
        //返回老的值
        return oldValue;
    }

set()是更新,更新指定下标位置的值。

2.5 删除元素

ArrayList提供了外界remove(int index)、remove(Object o)、removeAll(Collection<?> c)、clear()四个方法进行元素的删除。

2.5.1 根据index删除元素remove(int index)

    /**
     * Removes the element at the specified position in this list.
     * Shifts any subsequent elements to the left (subtracts one from their
     * indices).
     *
     * @param index the index of the element to be removed
     * @return the element that was removed from the list
     * @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;
    }

最后的

elementData[--size] = null; // clear to let GC do its work

原因是因为都向前移动了元素之后,会留一个元素,那么把这个置为空。

2.5.2 删除指定元素remove(Object o)

    /**
     * Removes the first occurrence of the specified element from this list,
     * if it is present.  If the list does not contain the element, it is
     * unchanged.  More formally, removes the element with the lowest index
     * <tt>i</tt> such that
     * <tt>(o==null&nbsp;?&nbsp;get(i)==null&nbsp;:&nbsp;o.equals(get(i)))</tt>
     * (if such an element exists).  Returns <tt>true</tt> if this list
     * contained the specified element (or equivalently, if this list
     * changed as a result of the call).
     *
     * @param o element to be removed from this list, if present
     * @return <tt>true</tt> if this list contained the specified element
     */
    public boolean remove(Object o) {
        if (o == null) {
            for (int index = 0; index < size; index++)
                if (elementData[index] == null) {
                    fastRemove(index);
                    return true;
                }
        } 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 void fastRemove(int index) {
        modCount++;
        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
    }

这里面的fastRemove 根据index来进行删除操作,其实也就是先进行比较,然后做跟上面index删除一样的操作。

2.5.3 removeAll(Collection<?> c)

    public boolean removeAll(Collection<?> c) {
        Objects.requireNonNull(c);
        return batchRemove(c, false);
    }

   
    //这里面入参是false batch是批量的意思
    private boolean batchRemove(Collection<?> c, boolean complement) {
        final Object[] elementData = this.elementData;
        int r = 0, w = 0;
        boolean modified = false;
        try {
            //把不包含的元素都扔到一个新的数组里面
            for (; r < size; r++)
                if (c.contains(elementData[r]) == complement)
                    elementData[w++] = elementData[r];
        } finally {
            // Preserve behavioral compatibility with AbstractCollection,
            // even if c.contains() throws.
            //这边说明 为了确保没有出错,如果r!=size 那么就有可能是出错了,因为需要保证异常抛//出前的内容满足期望,把如果出错后面的元素弄到后面拼接上面去
            if (r != size) {
                System.arraycopy(elementData, r,
                                 elementData, w,
                                 size - r);
                w += size - r;
            }
            if (w != size) {
                // clear to let GC do its work
                for (int i = w; i < size; i++)
                    elementData[i] = null;
                modCount += size - w;
                size = w;
                modified = true;
            }
        }
        return modified;
    }

这里面比较有意思的是,使用到了Objects的判断方法。

Objects里面很多中工具方法。

 final Object[] elementData = this.elementData;

这个修改的是一个对象吗?

答案是是的。

public class Test {

    public String[] a = {"1","2","3"};

    public void handle(){
        final String[] b = a;
        b[0] = "2";
        for (int i = 0; i < a.length; i++) {
            System.out.println(a[i]);
        }
    }

    public static void main(String[] args) {
        Test t = new Test();
        t.handle();
    }
}

输出结果是:

2
2
3

2.5.4 clear()

    /**
     * Removes all of the elements from this list.  The list will
     * be empty after this call returns.
     */
    public void clear() {
        modCount++;

        // clear to let GC do its work
        for (int i = 0; i < size; i++)
            elementData[i] = null;

        size = 0;
    }

删除这个list的所有元素,

将当前数组大小设置为0,但是不减少数组容量。

2.6 查找元素

2.6.1 get(int index)

        public E get(int index) {
            rangeCheck(index);
            checkForComodification();
            return ArrayList.this.elementData(offset + index);
        }

        private void checkForComodification() {
            if (ArrayList.this.modCount != this.modCount)
                throw new ConcurrentModificationException();
        }
    /**
     * Returns the element at the specified position in this list.
     *
     * @param  index index of the element to return
     * @return the element at the specified position in this list
     * @throws IndexOutOfBoundsException {@inheritDoc}
     */
    public E get(int index) {
        rangeCheck(index);

        return elementData(index);
    }

2.7 contains

contains方法会遍历ArrayList。

2.7.1 contains(Object o)

    /**
     * Returns <tt>true</tt> if this list contains the specified element.
     * More formally, returns <tt>true</tt> if and only if this list contains
     * at least one element <tt>e</tt> such that
     * <tt>(o==null&nbsp;?&nbsp;e==null&nbsp;:&nbsp;o.equals(e))</tt>.
     *
     * @param o element whose presence in this list is to be tested
     * @return <tt>true</tt> if this list contains the specified element
     */
    public boolean contains(Object o) {
        //大于等于0则存在
        return indexOf(o) >= 0;
    }

    /**
     * Returns the index of the first occurrence of the specified element
     * in this list, or -1 if this list does not contain the element.
     * More formally, returns the lowest index <tt>i</tt> such that
     * <tt>(o==null&nbsp;?&nbsp;get(i)==null&nbsp;:&nbsp;o.equals(get(i)))</tt>,
     * or -1 if there is no such index.
     */
    public int indexOf(Object o) {
        if (o == null) {
            for (int i = 0; i < size; i++)
                if (elementData[i]==null)
                    return i;
        } else {
            for (int i = 0; i < size; i++)
                if (o.equals(elementData[i]))
                    //返回下标   
                    return i;
        }
        return -1;
    }

注意ArrayList判断null和元素相等是不同的。

2.8 其他方法

2.8.1 缩衣节食,调整大小trimToSize() 

    /**
     * Trims the capacity of this <tt>ArrayList</tt> instance to be the
     * list's current size.  An application can use this operation to minimize
     * the storage of an <tt>ArrayList</tt> instance.
     */
    public void trimToSize() {
        modCount++;
        if (size < elementData.length) {
            elementData = (size == 0)
              ? EMPTY_ELEMENTDATA
              : Arrays.copyOf(elementData, size);
        }
    }

如果当前ArrayList的实际长度小于列表的长度,将列表超过size后的空余的空间(包括null值)去除,调用Arrays.cppyof方法拷贝elementData,长度为size;把当前长度大小的拷贝。

Arrays.copyOf(elementData, size) 应该是会返回一个新的数组。

2.8.2 clone 

    /**
     * Returns a shallow copy of this <tt>ArrayList</tt> instance.  (The
     * elements themselves are not copied.)
     *
     * @return a clone of this <tt>ArrayList</tt> instance
     */
    public Object clone() {
        try {
            ArrayList<?> v = (ArrayList<?>) super.clone();
            v.elementData = Arrays.copyOf(elementData, size);
            v.modCount = 0;
            return v;
        } catch (CloneNotSupportedException e) {
            // this shouldn't happen, since we are Cloneable
            throw new InternalError(e);
        }
    }

将元素都拷贝到v中。使用的弗雷德克隆方法。

2.8.3 toArray()

    /**
     * Returns an array containing all of the elements in this list
     * in proper sequence (from first to last element).
     *
     * <p>The returned array will be "safe" in that no references to it are
     * maintained by this list.  (In other words, this method must allocate
     * a new array).  The caller is thus free to modify the returned array.
     *
     * <p>This method acts as bridge between array-based and collection-based
     * APIs.
     *
     * @return an array containing all of the elements in this list in
     *         proper sequence
     */
    public Object[] toArray() {
        return Arrays.copyOf(elementData, size);
    }

2.9 序列化相关的writeObject和readObject?

    /**
     * Save the state of the <tt>ArrayList</tt> instance to a stream (that
     * is, serialize it).
     *
     * @serialData The length of the array backing the <tt>ArrayList</tt>
     *             instance is emitted (int), followed by all of its elements
     *             (each an <tt>Object</tt>) in the proper order.
     */
    private void writeObject(java.io.ObjectOutputStream s)
        throws java.io.IOException{
        // Write out element count, and any hidden stuff
        int expectedModCount = modCount;
        s.defaultWriteObject();

        // Write out size as capacity for behavioural compatibility with clone()
        s.writeInt(size);

        // Write out all elements in the proper order.
        for (int i=0; i<size; i++) {
            s.writeObject(elementData[i]);
        }

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

    /**
     * Reconstitute the <tt>ArrayList</tt> instance from a stream (that is,
     * deserialize it).
     */
    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
            int capacity = calculateCapacity(elementData, size);
            SharedSecrets.getJavaOISAccess().checkArray(s, Object[].class, 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中,一样是先读容量,再读数据

实现这两个方法是因为,

transient Object[] elementData;

这个属性是不会参与序列化的,因为有很多没有用到的null空间,这样会占用内存。

如果你重写了writeObject 和readObjec 这个两个方法,在实际序列化的时候,会利用反射最终调用到你重写的writeObject和readObject 来序列化。

3. 小结

ArrayList是线程不安全的。

ArrayList扩容是1.5倍扩容。

删除元素不会缩小数组空间,可以使用trimToSize()来缩小空间。

最好指定大小初始化,可以防止扩容带来的内存消耗。

 

 

 

 

 

 

感谢:

关于序列化:https://zhuanlan.zhihu.com/p/69440847

ArrayList:https://www.jianshu.com/p/4a403049a4a2

  • 2
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: ArrayList和LinkedList的时间复杂度如下: 1. ArrayList时间复杂度: - 插入和删除操作的时间复杂度为O(n),因为需要移动其他元素。 - 随机访问的时间复杂度为O(1),因为可以通过索引直接访问元素。 2. LinkedList的时间复杂度: - 插入和删除操作的时间复杂度为O(1),因为只需要修改相邻节点的指针。 - 随机访问的时间复杂度为O(n),因为需要遍历链表找到对应的节点。 因此,如果需要频繁进行随机访问操作,建议使用ArrayList;如果需要频繁进行插入和删除操作,建议使用LinkedList。 ### 回答2: 在Java世界中,ArrayList和LinkedList是两种非常常见的数据结构,它们都可以用于存储和操作一系列对象。不过,它们的实现方式有所不同,因此它们的时间复杂度也有所不同。 ArrayList是一种基于数组实现的数据结构,它的特点是可以支持随机访问和快速读取操作。这意味着,如果你需要经常读取数据或者随机访问数据,就应该选择使用ArrayListArrayList底层的实现方式是创建一个数组,并使用该数组来存储数据。在插入元素时,如果数组已经被分配满了,就会重新分配一个更大的数组。这就导致了,当元素数量增加时,插入或删除一个元素可能会导致整个数组的重新分配。因此,在需要经常进行插入或删除操作的场合,ArrayList的效率就会变得较低。下面是ArrayList时间复杂度: - 随机访问O(1) - 插入/删除O(n) 相比之下,LinkedList采用的是链表的数据结构,它适合于需要经常进行插入或删除操作的场合。LinkedList的底层实现是一个双向链表,每个节点都存储一个元素,还包含指向前一个和后一个节点的指针。在插入或删除一个元素时,只需要改变该元素前后节点的指针即可,而不需要像ArrayList一样重新分配整个数组。但是,由于LinkedList不支持随机访问,因此需要遍历链表来查找某个元素,这就导致了它的效率比ArrayList低。下面是LinkedList的时间复杂度: - 随机访问O(n) - 插入/删除O(1) 总之,ArrayList和LinkedList各有优势,你需要根据具体场合选择合适的数据结构。 ### 回答3: ArrayList和LinkedList都是Java中的常用集合类。两者都可以存储元素,并提供添加、删除、遍历等操作。然而,两者之间的时间复杂度存在差异。 ArrayList是基于数组实现的列表,它的内部实现使用了动态数组,当数组元素不足时会自动扩容。因此,ArrayList支持随机访问,可以在O(1)的时间复杂度内访问任意元素。在插入或删除元素的过程中,需要移动其他元素,因此这些操作的时间复杂度为O(n)。 LinkedList是基于链表实现的列表,它的每个节点包含指向前一个和后一个节点的指针。因此,LinkedList不支持随机访问,只能通过遍历链表来访问元素。在插入或删除元素的过程中,只需要修改相邻节点的指针,因此这些操作的时间复杂度为O(1)。 综上所述,ArrayList适用于随机访问和频繁的读取操作,而LinkedList适用于插入和删除操作频繁的情况。在使用时需要根据具体的需求进行选择。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值