集合-ArrayList源码解析

首先我们对Jdk中集合类的命名做一个简单的梳理。ArrayList即Array+List表示这是一个集合,它的数据结构使用的数组。同理LinkedList表示这是其数据结构是链表;HashMap表示其基于hash算法实现的(本质是数组+链表+红黑树);TreeMap其数据结构是红黑树;LinkedHashMap底层是HashMap,同时进一步做了封装,对外展示是链表结构。说了这么多,我想说的是Jdk中的命名基本都是见名知意的,这些都是值得我么学习。好了,不扯了,进入正题。

1.ArrayList特征:

List有索引、有序、元素可重复。查询快,新增删除慢。

为啥这么说,通过了解它的存储结构就会明白。

2.实现接口:

List、RandomAccess、Cloneable、Serializable

实现RandomAccess便于集合元素的快速访问即查询。

实现Cloneable便于集合的拷贝,本质是数组的复制,属于浅拷贝。

实现Serializable便于集合的序列化

3.成员变量:

初始容量10、最大容量Integer.MAX_VALUE – 8(和jvm相关),元素存储位置Object[]、集合大小size。

数据结构:Object[]

//数组初始化大小
private static final int DEFAULT_CAPACITY = 10;

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

//存储元素的对象数组
transient Object[] elementData;

//数组中元素的个数
private int size;

4.构造方法:

可指定初始容量、或者直接传递一个集合实现类来构造ArrayList

public ArrayList(int initialCapacity) {}

public ArrayList() {}

public ArrayList(Collection<? extends E> c) {}

5.元素查询:

    public E get(int index) {
        //检查索引是否越界
        rangeCheck(index);

        //从数组中取值
        return elementData(index);
    }

1.通过索引获取元素时,首先检查索引是否越界。

2.从数组中取值。查询非常简单,我们知道数组在堆内存开辟的是一块连续的空间,通过索引能够很快计算出元素的存储位置,这也是为什么查询快的原因。

6.元素新增:

    public boolean add(E e) {
        //插入前的操作,主要是看数组是否初始化,是否需要扩容
        ensureCapacityInternal(size + 1);  // Increments modCount!!
        //数值复制,数值元素个数size增加
        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++;

        // overflow-conscious code
        //size+1大于数组长度时扩容
        if (minCapacity - elementData.length > 0)
            grow(minCapacity);
    }
    private void grow(int minCapacity) {
        // overflow-conscious code
        //数组长度变为原来的1.5倍
        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);
    }

1.添加元素之前,先做数组的初始化工作,数组长度默认为10。

2.当size+1大于数组长度时,进行扩容

3.扩容时,新数组的长度变为原来的1.5倍。并进行数组元素的复制,这个过程非常耗性能,所以建议构造数组时指定容量。

4.元素存储到数组指定索引处。

7.元素删除:

    public E remove(int index) {
        //索引越界检查
        rangeCheck(index);

        //数组结构变更次数增加1,对于新增、删除操作会影响数组的结构
        modCount++;
        E oldValue = elementData(index);

        //数组元素位置调整,删除元素后,index后的所有元素往前移一个位置
        //numMoved 需要移动的元素个数
        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;
    }

8.元素修改:

    public E set(int index, E element) {
        //检查索引是否越界
        rangeCheck(index);

        //元素替换,新值替换旧值
        E oldValue = elementData(index);
        elementData[index] = element;
        return oldValue;
    }

9.线程安全问题:

    public boolean add(E e) {
        ensureCapacityInternal(size + 1);  // Increments modCount!!
        //位置1,多线程存在风险
        elementData[size++] = e;
        return true;
    }

ArrayList是线程不安全的。当多线程访问公共变量arrayList进行add(e)方法时,当线程1在位置1出完成了复制操作,但是size还未增加1,此时线程2进来添加元素时会覆盖掉线程1的值。即 elementData[size++] = e并非一个原子操作,会有多线程安全问题。

10.深拷贝、浅拷贝的概念:

Java中对象的复制通常通过赋值来实现,但是这种方式通过任何一方的引用操作都会改变对象。

浅拷贝:对象实现Cloneable接口,重写clone()方法,可以实现对象的复制,得到一个新的对象。对于一个对象中的属性既包含基本类型又包含引用类型,clone方法完成的拷贝是浅拷贝,因为引用类型的对象拷贝的是引用。

深拷贝:就是让对象中的任何一个属性,包括基本数据类型和引用数据类型都是不相同的,这个拷贝得到的对象就是一个深拷贝的对象。这种方法需要各个引用类型都实现Cloneable接口,重写clone方法。最后在拷贝对象clone方法中,逐一调用其属性对象的clone()方法。

以List<Student>为例,直接调用ArrayList的clone方法属于浅拷贝,即ArrayList对象是新对象,但是里面的Student元素是相同的,新老集合指向相同的Student引用。

代码示例:其中Student对象是Person对象中的一个属性。

public class AnnotationController{
    public static void main(String[] args) throws CloneNotSupportedException {
        //浅拷贝 拷贝对象实现Cloneable接口,拷贝对象的成员变量都是基础数据类型
        shallowCloneWithBaseDataType();

        //浅拷贝 拷贝对象的成员变量有基础数据类型和引用数据类型,引用数据类型拷贝的是引用
        shallowCloneWithMultiDataType();

        //深拷贝 拷贝对象的成员变量有基础数据类型和引用数据类型,每个对象都需要重写clone方法
        //并且通过顶层对象,调用每一个对象的clone方法。
        deepClone();
    }

    public static void shallowCloneWithBaseDataType() throws CloneNotSupportedException {
        Student orgStudent = new Student(10,"小华");
        Student copyStudent = (Student) orgStudent.clone();
        copyStudent.setName("小明");
        //copyStudent是新对象,输出为false
        System.out.println(orgStudent.getName().equals(copyStudent.getName()));
    }

    public static void shallowCloneWithMultiDataType() throws CloneNotSupportedException {
        Student orgStudent = new Student(10,"小华");
        Person orgPerson = new Person(1, orgStudent);
        Person clonePerson = (Person)orgPerson.clone();
        clonePerson.setType(2);
        orgStudent.setName("小美");
        System.out.println(orgPerson.getType() == clonePerson.getType());
        System.out.println(orgPerson.getStudent().getName().equals(clonePerson.getStudent().getName()));
    }

    public static void deepClone() throws CloneNotSupportedException {
        Student orgStudent = new Student(10,"小华");
        Person orgPerson = new Person(1, orgStudent);
        Person clonePerson = (Person)orgPerson.clone();
        clonePerson.setType(2);
        orgStudent.setName("小美");
        System.out.println(orgPerson.getType() == clonePerson.getType());
        System.out.println(orgPerson.getStudent().getName().equals(clonePerson.getStudent().getName()));
    }


}

@Data
@AllArgsConstructor
class Person implements Cloneable{
    int type;
    Student student;

    @Override
    protected Object clone() throws CloneNotSupportedException {
        //return super.clone();

        //深拷贝,需要调用属性对象的clone方法
        Person person = (Person) super.clone();
        person.setStudent((Student) person.getStudent().clone());
        return person;
    }
}

@Data
@AllArgsConstructor
class Student implements Cloneable{
    int age;
    String name;

    //若是对Person的浅拷贝,可不实现Cloneable。
    @Override
    protected Object clone() throws CloneNotSupportedException {
        return super.clone();
    }
}

11.ConcurrentModificationException

并发修改异常出现的原因是,列表在遍历的过程中,存在插入,删除元素操作,导致modCount值变化。

    //遍历时触发ArrayList#iterator
    public Iterator<E> iterator() {
        return new Itr();
    }
   private class Itr implements Iterator<E> {
        int cursor;       // index of next element to return
        int lastRet = -1; // index of last element returned; -1 if no such
        int expectedModCount = modCount;

        public boolean hasNext() {
            return cursor != size;
        }

        @SuppressWarnings("unchecked")
        public E next() {
            //检查expectedModCount == modCount是否成立
            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];
        }

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

如果想遍历的时候改变列表的结构,使用ListIterator。添加和删除元素操作都会重新同步modCount值给expectedModCount。

        ListIterator<String> iterator = list.listIterator();
        while(iterator.hasNext()) {
            System.out.println(iterator.next());
            iterator.remove();
        }
    //ArrayList中的内部类ListItr继承Itr
    private class ListItr extends Itr implements ListIterator<E> {
        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();
            }
        }
    }

 

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值