ArrayList

ArrayList

List有序集合,实现了collection,比Collection多了添加方法add和addAll查找方法get,indexOf,set等方法,并且支持index下标操作

package java.util;
import java.util.function.UnaryOperator;
public interface List<E> extends Collection<E> {
    <T> T[] toArray(T[] a);
    boolean addAll(Collection<? extends E> c);
    boolean addAll(int index, Collection<? extends E> c);
    default void replaceAll(UnaryOperator<E> operator) {
        Objects.requireNonNull(operator);
        final ListIterator<E> li = this.listIterator();
        while (li.hasNext()) {
            li.set(operator.apply(li.next()));
        }
    }
    default void sort(Comparator<? super E> c) {
        Object[] a = this.toArray();
        Arrays.sort(a, (Comparator) c);
        ListIterator<E> i = this.listIterator();
        for (Object e : a) {
            i.next();
            i.set((E) e);
        }
    }
    boolean equals(Object o);
    E get(int index);
    E set(int index, E element);
    void add(int index, E element);
    int indexOf(Object o);
    int lastIndexOf(Object o);
    ListIterator<E> listIterator();
    List<E> subList(int fromIndex, int toIndex);
    @Override
    default Spliterator<E> spliterator() {
        return Spliterators.spliterator(this, Spliterator.ORDERED);
    }
}

List和Collection的区别

1.从上边可以看出Collection和List最大的区别就是Collection是无序的,不支持索引操作,而List是有序的,Collection没有顺序的概念.

2.List中Iterator为ListIterator.

3.由a推导List可以进行排序,所以List接口支持使用sort方法.

4.二者的Spliterator操作方式不一样.

ArrayList

此介绍为JDK1.8,最后会去介绍与1.7的区别

ArrayList是list常用的实现类,其继承关系如下:

在这里插入图片描述

  1. 它继承于 AbstractList,实现了 List,RandomAccess,Cloneable, java.io.Serializable 这些接口。
  2. ArrayList 继承了AbstractList,实现了List。提供了相关的添加、删除、修改、遍历等功能。
  3. ArrayList 实现了RandomAccess 接口, RandomAccess 是一个标志接口,表明实现这个这个接口的 List 集合是支持快速随机访问的。在 ArrayList 中,我们即可以通过元素的序号快速获取元素对象,这就是快速随机访问。
  4. ArrayList 实现了Cloneable 接口,即覆盖了函数 clone(),能被克隆。
  5. ArrayList 实现了java.io.Serializable 接口,Serializable是一个标志接口,这意味着ArrayList支持序列化,能通过序列化去传输。
  6. 和 Vector 不同,ArrayList 中的操作不是线程安全的!所以,建议在单线程中才使用 ArrayList,而在多线程中可以选择 Vector 或者 CopyOnWriteArrayList。
ArrayList重要的参数以及构造函数
 /**
 * 默认容量为10
 */
private static final int DEFAULT_CAPACITY = 10;

/**
 * 空集合
 */
private static final Object[] EMPTY_ELEMENTDATA = {};

/**
 * 存放元素的数组
 */
private transient Object[] elementData;

/**
 * ArrayList的大小.
 *
 * @serial
 */
private int size;

/**
 * 使用传入的参数构造一个数组.
 *
 * @param  initialCapacity  the initial capacity of the list
 * @throws IllegalArgumentException if the specified initial capacity
 *         is negative
 */
public ArrayList(int initialCapacity) {
    super();
    if (initialCapacity < 0)
        throw new IllegalArgumentException("Illegal Capacity: "+
                                           initialCapacity);
    this.elementData = new Object[initialCapacity];
}

/**
 * 构造一个空列表,初始化为10 .
 */
public ArrayList() {
    super();
    this.elementData = EMPTY_ELEMENTDATA;
}

/**
 *  使用一个集合来给ArrayList赋初始值。
 */
public ArrayList(Collection<? extends E> c) {
    elementData = c.toArray();
    size = elementData.length;
    // c.toArray might (incorrectly) not return Object[] (see 6260652)
    if (elementData.getClass() != Object[].class)
        elementData = Arrays.copyOf(elementData, size, Object[].class);
}
  1. transient Object[] elementData 是ArrayList真正存放数据的数组,切ArrayList基于数组实现的.
  2. ArrayList支持默认大小构造,和空构造,当空构造的时候存放数据的Object[] elementData是一个空数组{}。
ArrayList中添加元素
    /**
 * 直接在末尾添加元素
 * 
 * 时间复杂度:O(1)
 */
public boolean add(E e) {

    /**
     * 调用此方法确保容量足够容纳新添加的的元素,即确保有size + 1个空间
     */
    ensureCapacityInternal(size + 1);  // Increments modCount!!
    /**
     * 直接将元素添加到数组的末尾,并将size+1
     */
    elementData[size++] = e;
    return true;
}

/**
 * 向指定索引添加元素(此处的索引可以包括size本身哟,即确保使用此方法也可以在末尾添加元素)
 * 
 * 时间复杂度:O(n)     (由于有数组元素的拷贝,即System.arraycopy)
 */
public void add(int index, E element) {
    /**
     * 在ArrayList的源码实现中,有两个范围检查的函数rangeCheckForAdd和rangeCheck
     *
     * 而rangeCheckForAdd是专门用于这个add方法,它要确保index在[0,size]之间,注意此处包括size哟
     */
    rangeCheckForAdd(index);

    /**
     * 确保有size + 1个空间
     */
    ensureCapacityInternal(size + 1);  // Increments modCount!!
    /**
     * 通过System.arraycopy将数组elementData从index及以后的每个元素向后移一位
     */
    System.arraycopy(elementData, index, elementData, index + 1,
            size - index);
    /**
     * 将要插入的新值插入到该索引位置
     */
    elementData[index] = element;
    size++;
}

// 第一个检查范围[0,size]
private void rangeCheckForAdd(int index) {
    if (index < 0 || index > size())
        throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}

其中有一个modCount的属性,表示该实例修改的次数。(所有集合中都有modCount这样一个记录修改次数的属性),每次增改添加都会增加一次该ArrayList修改次数,而上边的add方法是将新元素添加到list尾部。

如果插入元素的数量大于当前数组的最大容量怎么办?

ArrayList扩容
private static int calculateCapacity(Object[] elementData, int minCapacity) {
    if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
   		 //DEFAULT_CAPACITY是10
        return Math.max(DEFAULT_CAPACITY, minCapacity);
    }
    return minCapacity;
}


/**
 * 
 * 如果传入的最小容量比当前最小容量要大,就扩容
 * @param   minCapacity   the desired minimum capacity
 */
public void ensureCapacity(int minCapacity) {
    int minExpand = (elementData != EMPTY_ELEMENTDATA)//获取当前最小容量0 或者是10
        // any size if real element table
        ? 0
        // larger than default for empty table. It's already supposed to be
        // at default size.
        : DEFAULT_CAPACITY;

    if (minCapacity > minExpand) {//有需要就扩容
        ensureExplicitCapacity(minCapacity);
    }
}

private void ensureCapacityInternal(int minCapacity) {//数组为空,设置相应容量
    if (elementData == 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);
}

/**
 *  最大数组大小
 */
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;

/**
 * 
 *1.5倍扩容
 * @param minCapacity the desired minimum capacity
 */
private void grow(int minCapacity) {
    // overflow-conscious code
    int oldCapacity = elementData.length;
	//采用的是右位移运算,也就是原来的一半,所以扩容的是1.5倍.
	//比如10的二进制是1010,右移后变成101就是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);
}

可见当初始化的list是一个空ArrayList的时候,会直接扩容到DEFAULT_CAPACITY,该值大小是一个默认值10。
而当添加进ArrayList中的元素超过了数组能存放的最大值就会进行扩容。
底层采用array.copyOf 和System.arraycopy来实现数组的复制.
arraycopy()需要目标数组,将原数组拷贝到你自己定义的数组里,而且可以选择拷贝的起点和长度以及放入新数组中的位置
copyOf()是系统自动在内部新建一个数组,并返回该数组
Java是无法自动分配空间的,底层是c和c++实现。
通过arraycopy这个native方法实现数组的复制,扩容机制采用每次乘以原来的1.5倍进行扩容。

get/set

/**
 * default权限的get方法,获取指定索引的元素值
 * 
 * 时间复杂度:O(1)
 */
E elementData(int index) {
    return (E) elementData[index];
}

/**
 * 提供给用户的get方法,获取指定索引的元素值
 * 
 * 时间复杂度:O(1)
 */
public E get(int index) {
    //范围检查
    rangeCheck(index);

    return elementData(index);
}

/**
 * 修改指定索引处的值
 */
public E set(int index, E element) {
    //范围检查
    rangeCheck(index);
    
    //保存该索引处原来的值,用于后面返回
    E oldValue = elementData(index);
    //修改
    elementData[index] = element;
    return oldValue;
}

//第二个检查范围,如果传入负数,则会抛出数组下标越界异常
private void rangeCheck(int index) {
    if (index >= size)
        throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}
查询

根据元素和下标

/**
 * 返回第一次出现这个元素的索引,找不到就返回-1
 * lastIndexOf方法,查询最后一个同理
 */
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;
}
删除
/**
 *根据索引来删除,并返回所删除的值
 */
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;

    return oldValue;
}

/**
 * 使用fastremove来删除值时不返回删除的值。
 */
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;
}
ArrayList如何实现序列化安全
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()方法中可以看到,先用默认写方法,然后将size写出,最后遍历写出elementData,因为该变量是transient修饰的,所有进行手动写出,这样它也会被序列化了。
其中有一个关键的modCount, 该变量是记录list修改的次数的,当写入完之后如果发现修改次数和开始序列化前不一致就会抛出异常,序列化失败。这样就保证了序列化过程中是未经修改的数据,保证了序列化安全。(java集合中都是这样实现)

下面说一下1.7和1.8中的区别

1.7:

package java.util;

public class ArrayList<E> extends AbstractList<E>
        implements List<E>, RandomAccess, Cloneable, java.io.Serializable
{
    private static final long serialVersionUID = 8683452581122892189L;
 
    private transient Object[] elementData;
 
    private int size;
 
    public ArrayList(int initialCapacity) {
        super();
        if (initialCapacity < 0)
            throw new IllegalArgumentException("Illegal Capacity: "+
                                               initialCapacity);
        this.elementData = new Object[initialCapacity];
    }
 
    public ArrayList() {
        this(10);
    }

}

ArrayList list =new ArrayList();
创建数组的时候,就调用了this(10);即创建了一个长度为10的Object[]数组elementData
就好比是单例模式中的饿汉式,不管用不用,我先创建,消耗内存

而在1.8中

private void ensureCapacityInternal(int minCapacity) {//数组为空,设置相应容量
    if (elementData == EMPTY_ELEMENTDATA) {
		//DEFAULT_CAPACITY = 10
        minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
    }
    ensureExplicitCapacity(minCapacity);
}

ArrayList list =new ArrayList();
调用默认构造函数的时候,底层Object[] elementData初始化为{},此时并没有创建长度为10的数组
当第一次去add时,通过下面的方法才创建长度为10的数组,并将元素添加到数组中。
好比单例模式中的懒汉式,只有在真正使用的时候才回去创建,节省内存

参考:

https://binhao.blog.csdn.net/article/details/117048324

https://blog.csdn.net/qq_34805255/article/details/99712430?utm_medium=distribute.pc_relevant.none-task-blog-2%7Edefault%7EBlogCommendFromMachineLearnPai2%7Edefault-12.control&depth_1-utm_source=distribute.pc_relevant.none-task-blog-2%7Edefault%7EBlogCommendFromMachineLearnPai2%7Edefault-12.control

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值