List —— 源码分析(jdk1.8)

在这里插入图片描述

List接口的三个主要实现类。

ArrayList源码分析

ArrayList的数据结构

ArrayList底层使用了数组来存储数据,支持自动动态扩容,元素可重复,允许元素为null,数组元素类型为Object,当向集合中加入基本数据类型时会自动装箱。对ArrayList的所有基本操作都是基于数组的。由于数组的内存连续,可以根据下标以O(1)的时间读写元素,因此时间效率很高。

ArrayList的继承关系

在这里插入图片描述
继承AbstractList抽象类,实现了List, RandomAccess, Cloneable, Serializable接口。

ArrayList的安全性

ArrayList在多线程下是线程不安全的。
比如添加操作是分为两个步骤实现的,先将元素添加进object[size]的位置上,然后进行size++。
当有两个线程都执行添加元素操作时,线程 A 先将元素存放在位置0,但是此时 CPU 调度线程A暂停,线程 B 得到运行的机会
线程B也向此ArrayList 添加元素,因为此时 Size 仍然等于 0 (注意哦,我们假设的是添加一个元素是要两个步骤哦,而线程A仅仅完成了步骤1),所以线程B也将元素存放在位置0
然后线程A和线程B都继续运行,都增 加 Size 的值。
现在我们来看看 ArrayList 的情况,元素实际上只有一个,存放在位置 0,而Size却等于 2,这就是“线程不安全”了

ArrayList的主要成员变量

public class ArrayList<E> extends AbstractList<E>
        implements List<E>, RandomAccess, Cloneable, java.io.Serializable
{ 
    // 序列化UID,由于ArrayList实现了Serializable接口,所以添加了一个方便于序列化和反序列化的UID。
    private static final long serialVersionUID = 8683452581122892189L;
    // 缺省容量 当ArrayList没有指定容量时,使用默认值10
    private static final int DEFAULT_CAPACITY = 10;
    // 空对象数组 当ArrayList指定容量为0时,将该数组赋值给elementData
    private static final Object[] EMPTY_ELEMENTDATA = {};
    // 缺省空对象数组 当ArrayList没有指定容量时,将该数组赋值给elementData
    private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
    // 元素数组 用于存放元素的数组 transient修饰的变量不可以被序列化和反序列化。
    transient Object[] elementData;
    // 实际元素大小,默认为0
    private int size;
    // 最大数组容量
    private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
}


ArrayList的三个构造方法

    // 有参构造 传入参数为集合的初始容量大小
    public ArrayList(int initialCapacity) {
    	// 如果传入参数大于0 则创建一个对应大小的数组赋给真正数组
        if (initialCapacity > 0) {
            this.elementData = new Object[initialCapacity];
        } else if (initialCapacity == 0) {
        	// 如果传入参数等于0 就将成员变量中的空对象数组赋值给真正的数组
            this.elementData = EMPTY_ELEMENTDATA;
        } else {
        	// 小于0抛出异常
            throw new IllegalArgumentException("Illegal Capacity: "+
                                               initialCapacity);
        }
    }

    // 无参构造 将成员变量中的缺省空对象数组赋值给真正的数组
    public ArrayList() {
        this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
    }

    // 传入参数为一个集合
    public ArrayList(Collection<? extends E> c) {
    	// 将传入集合转化为数组a
        Object[] a = c.toArray();
        // 判断数组长度是否为0
        if ((size = a.length) != 0) {
        	// 如果传入的集合类型是ArrayList 将转化后的数组直接赋值即可
            if (c.getClass() == ArrayList.class) {
                elementData = a;
            } else {
            	// 如果不是就将数组拷贝即可
                elementData = Arrays.copyOf(a, size, Object[].class);
            }
        } else {
            // 将成员变量中的空数组赋值
            elementData = EMPTY_ELEMENTDATA;
        }
    }

ArrayList常用API源码分析

在这里插入图片描述

add(E)

	//数组最大容量,由于数组做对象需要对象头,对象头信息最大占用内存不可超过8字节。
	private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
	
    public boolean add(E e) {
        ensureCapacityInternal(size + 1);  // 使数组有容量可以容纳该元素
        elementData[size++] = e;  //添加操作
        return true;
    }
     
    private void ensureCapacityInternal(int minCapacity) {
        ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
    }
    
    // 确认当前需要数组容量的大小
    private static int calculateCapacity(Object[] elementData, int minCapacity) {
    	// 判断该数组是不是缺省空对象数组 (只针对于使用无参构造所构造的集合第一次扩容)
        if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
        	// 返回缺省容量与当前需要容量的最大值
            return Math.max(DEFAULT_CAPACITY, minCapacity);
        }
        //直接返回当前需要容量大小 
        return minCapacity;
    }

    private void ensureExplicitCapacity(int minCapacity) {
    	// 修改次数
        modCount++;

		/*
			用到modCount的地方都是线程不安全的,只要我们修改了集合的结构、长度等…
			都会修改modCount值,然后再次遍历集合的时候,会判断当前的modCount是否相等,
			如果不相等则证明此线程在遍历时有其他线程对集合发生 了修改
		*/

        // 判断当前所需的容量是否大于数组长度,如果大于,则执行grow方法扩容。
        if (minCapacity - elementData.length > 0)
        	// 扩容
            grow(minCapacity);
    }

    private void grow(int minCapacity) {
       	// minCapacity为当前所需的容量
       	// oldCapacity为当前数组容量
        int oldCapacity = elementData.length;
        // newCapacity为当前数组扩容后的容量 为当前容量的1.5倍
        int newCapacity = oldCapacity + (oldCapacity >> 1);
        // 判断新的容量是否可以满足当前所需容量
        if (newCapacity - minCapacity < 0)
        // 不满足就将当前所需容量作为最新容量
            newCapacity = minCapacity;
        // 判断新的容量是否大于数组最大的长度
        if (newCapacity - MAX_ARRAY_SIZE > 0)
        // 对数组长度做一个确定
            newCapacity = hugeCapacity(minCapacity);
        // 将原数组并且携带长度复制到新数组
        elementData = Arrays.copyOf(elementData, newCapacity);
    }

    private static int hugeCapacity(int minCapacity) {
        if (minCapacity < 0) // overflow
            throw new OutOfMemoryError();
        // 当前需要容量大于最大容量则最新容量为Integer.MAX_VALUE 小于则为最大容量
        return (minCapacity > MAX_ARRAY_SIZE) ?
            Integer.MAX_VALUE :
            MAX_ARRAY_SIZE;
    }



add(int index, E element)

第一个参数为插入下标的索引 第二个为所插入的数据

    public void add(int index, E element) {
    	// 判断当前下标是否存在 不存在则抛出异常
        rangeCheckForAdd(index);
        
		// 使数组有容量可以容纳该元素
        ensureCapacityInternal(size + 1);  // Increments modCount!!
        //将数组从下标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);

addAll(Collection<? extends E> c)

添加一个集合

    public boolean addAll(Collection<? extends E> c) {
    	// 将集合转换为数组
        Object[] a = c.toArray();
        // 求出转换后数组的长度
        int numNew = a.length;
        // 扩容等操作使得目的集合可以容纳参数集合
        ensureCapacityInternal(size + numNew);  // Increments modCount
        //将新数组的内容从size开始添加至elementData数组中去
        System.arraycopy(a, 0, elementData, size, numNew);
        //更新数组长度size
        size += numNew;
        return numNew != 0;
    }

addAll(int index, Collection<? extends E> c)

在指定下标处添加一整个集合了。

    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;
        //若后半部分的长度大于0,则将后半部分后移,将待插入的数组的长度空出
  	    //若等于0,则相当于直接添加至末尾,不需要后移元素
        if (numMoved > 0)
            System.arraycopy(elementData, index, elementData, index + numNew,
                             numMoved);
		//从目标位置插入数组
        System.arraycopy(a, 0, elementData, index, numNew);
        //更新长度
        size += numNew;
        return numNew != 0;
    }

总结

  • 无论是add还是addAll,都是先判断是否越界,如果越界就扩容,然后再移动数组
  • 如果需要扩容。默认扩容原来的一般大小;如果还不够,那就直接将目标的size作为扩容后的大小
  • 当没有指定集合大小时,使用成员变量中的默认数组,在遇到第一次数据的增加时扩容至10,后续按照上述扩容

remove(int index)

根据下标删除元素

    public E remove(int index) {
    	// 先判断该下标是否超出范围
        rangeCheck(index);
		//修改集合结构和长度,要先修改modCount
        modCount++;
        // 根据下表取出对应的元素
        E oldValue = elementData(index);
		// 利用numMoved来判断删除的元素是否是最后一个元素,如果不是,则要将index后面的元素往前移动 将元素覆盖实现删除
        int numMoved = size - index - 1;
        if (numMoved > 0)
            System.arraycopy(elementData, index+1, elementData, index,
                             numMoved);
        //将最末尾元素置null
        elementData[--size] = null; // clear to let GC do its work
		//返回所删除的元素
        return oldValue;
    }

remove(Object o)

删除指定元素

    public boolean remove(Object o) {
    	//根据所传入值是否为null分为两类情况
    	//数据类型直接使用==判断 
        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 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
    }

removeAll(Collection<?> c)

删除所有与所传入的集合中的相同的元素

    public boolean removeAll(Collection<?> c) {
        Objects.requireNonNull(c);
        //通过batchRemove实现删除
        return batchRemove(c, false);
    }

其中Objects.requireNonNull©是检查指定的对象引用是否为空。该方法主要用于在方法和构造函数中进行参数验证,为空则报异常

    private boolean batchRemove(Collection<?> c, boolean complement) {
    	//将该集合中的数组设置为final类型
        final Object[] elementData = this.elementData;
        //设置两个标志 r用来遍历集合 w用来存放应该留下的元素
        int r = 0, w = 0;
        boolean modified = false;
        try {
        	// 遍历元素
            for (; r < size; r++)
            	//如果传入集合中不存在该值 就将该值赋值给原数组 w++
            	//将两个集合中不重复的元素放在最前面
                if (c.contains(elementData[r]) == complement)
                    elementData[w++] = elementData[r];
        } finally {
        	//将后面的元素全部制空
            // Preserve behavioral compatibility with AbstractCollection,
            // even if c.contains() throws.
            // r不等于size时 将r后的所有元素移至w下标后面
            if (r != size) {
                System.arraycopy(elementData, r,
                                 elementData, w,
                                 size - r);
                w += size - r;
            }
            //w不等于size时 将w后面的数据制为null
            if (w != size) {
                // clear to let GC do its work
                for (int i = w; i < size; i++)
                    elementData[i] = null;
                // 移动几个数 就改变几次
                modCount += size - w;
                // 元素数量为w
                size = w;
                modified = true;
            }
        }
        // 如果没有删除元素 则返回false
        return modified;
    }

retainAll(Collection<?> c)

这个删除操作意为只保留两个集合的交集。这个方法和removeAll(Collection<?> c)方法挺像,只不过就是更改了一个参数complement。上一个是将所有不同的元素放在前面,而这个是将所有相同的元素放在前面,然后置空后面的数据。

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

clear()

将集合中的所有元素清除

    public void clear() {
        modCount++;
		// 将数组中的所有元素赋值为null
        // clear to let GC do its work
        for (int i = 0; i < size; i++)
            elementData[i] = null;

        size = 0;
    }

总结

  • 若根据index删除,先判断是否为最后一个元素,如果不是最后一个元素则将该元素后面的元素向前移动,使最后一位为null。
  • 根据元素删除分为两类 null和其他元素,主要体现在判断相等不同,找到对应元素的下标,进行根据下标删除的操作即可。
  • 根据集合删除,通过循环将两集合中不重复的元素移至前面,然后再将后面的元素置null
  • retainAll与上面相反,将相同的留下
  • 每一次删除都会修改modCount

set(int index, E element)

修改指定下标的数据。

    public E set(int index, E element) {
    	// 先判断该下标是否存在
        rangeCheck(index);
		// 获取原下标对应的元素
        E oldValue = elementData(index);
        // 修改为指定元素
        elementData[index] = element;
        return oldValue;
    }

修改操作不需要修改modCount

get(int index)

根据下标查找对应元素

    public E get(int index) {
    	//判断下标是否存在
        rangeCheck(index);
		//根据下标返回元素
        return elementData(index);
    }

不需要修改modCount

是否包含

contains(Object o)

是否包含该元素

    public boolean contains(Object o) {
        return indexOf(o) >= 0;
    }

indexOf(o)返回指定元素在列表中第一次出现的索引,如果列表中不包含该元素,则返回-1 根据null和其他元素分为两类进行判断

    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;
    }

判空方法

isEmpty()

根据长度判断当前集合是否为空

    public boolean isEmpty() {
        return size == 0;
    }

缩容

trimToSize()

将集合的数组大小修改成为集合中元素的个数多少,此方法在类中未被使用过,在使用过程中调用完成对集合中数组的大小进行修改
将这个ArrayList实例的容量调整为列表的当前大小。应用程序可以使用这个操作来最小化ArrayList实例的存储空间。

    public void trimToSize() {
  	    //第一步:修改modCount
        modCount++;
        //第二步:如果size为0,则直接把EMPTY_ELEMENTDATA复制过去,否则则调用Arrays.copyOf方法复制
        if (size < elementData.length) {
            elementData = (size == 0)
              ? EMPTY_ELEMENTDATA
              : Arrays.copyOf(elementData, size);
        }
    }

迭代器Iterator

Iterator是一种接口,为各种不同的数据结构提供统一的访问机制。 任何数据结构只要部署 Iterator 接口,就可以完成遍历操作(即依次处理该数据结构的所有成员)。

Iterator是一个特殊的对象:

  1. 它具有next()方法,调用该方法就会返回一个结果对象
  2. 结果对象有两个属性值:value和done。
  3. value表示具体的返回值;done是布尔类型,表示集合是否完成遍历,没有则返回true,否则返回false
  4. 内部有一个指针,指向数据结构的起始位置。每调用一次next()方法,指针都会向后移动一个位置,直到指向最后一个位置

迭代器的作用

  • 为各种数据结构,提供一个统一的、简便的访问接口
  • 使得数据结构的成员能够按某种次序排列
  • 创造一种新的遍历命令for…of循环,Iterator 接口主要供for…of消费

迭代器的简单使用

Collection<String> coll = new ArrayList<String>();	//多态
coll.add("abc1");
coll.add("abc2");
coll.add("abc3");
coll.add("abc4");
// 迭代器,对集合ArrayList中的元素进行取出
// 调用集合的方法iterator()获取Iterator接口的实现类的对象
Iterator<String> it = coll.iterator();
// 接口实现类对象,调用方法hasNext()判断集合中是否有元素
// boolean b = it.hasNext();
// System.out.println(b);
// 接口的实现类对象,调用方法next()取出集合中的元素
// String s = it.next();
// System.out.println(s);
// 迭代是反复内容,使用循环实现,循环的终止条件:集合中没元素, hasNext()返回了false
while (it.hasNext()) {
	String s = it.next();
	System.out.println(s);
}

输出为:

abc1
abc2
abc3
abc4

迭代器原理

构造方法

ArrayList中

    public Iterator<E> iterator() {
        return new Itr();
    }

这个方法创建一个Itr对象并且返回,下面讲解Itr类。(ArrayList内部类)
在这里插入图片描述

Itr中的属性

//下一个元素的下标
int cursor;
//上一次返回元素的下标
int lastRet = -1; 
//保存modCount,用于判断集合是否被修改过
int expectedModCount = modCount;

hasNext()方法

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

判断是否存在下一个元素,如果下一个元素的小标等于该数组长度,证明后面已经没有元素了。

next()方法

返回数据

        public E next() {
        	// 判断是否修改过List的结构,如果修改了就抛异常
            checkForComodification();
            int i = cursor;
            //判断下一个元素下标是否大于等于当前数组长度,满足条件就会抛出异常
            if (i >= size)
                throw new NoSuchElementException();
            //获取当前集合内的数组
            Object[] elementData = ArrayList.this.elementData;
            //再次判断是否越界,有可能在我们这里的操作时,有异步线程修改了List
            if (i >= elementData.length)
                throw new ConcurrentModificationException();
            //指针加1
            cursor = i + 1;
            //返回数据,并记录上一次的下标
            return (E) elementData[lastRet = i];
        }

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

其中lastRet的记录用于假如遍历下一节点,此时我们将下一节点删除,那么此时我们应退回至上一遍历位置,next方法会返回一个对象,迭代器遍历不可逆。

remove()方法

删除迭代器指向的当前元素

        public void remove() {
        	//进行边界判断,是由next方法拿出来一个对象时,lastRet才会不小于0
        	
            if (lastRet < 0)
                throw new IllegalStateException();
            // 判断是否修改过List的结构,如果修改了就抛异常
            checkForComodification();

            try {
            	// 调用ArrayList的remove方法移除数据
                ArrayList.this.remove(lastRet);
                //将指针回到上一遍历点
                cursor = lastRet;
                //重新置为 -1,当连续删除时抛出异常
                lastRet = -1;
                //修改modCOunt
                expectedModCount = modCount;
            } catch (IndexOutOfBoundsException ex) {
                throw new ConcurrentModificationException();
            }
        }

只有先进行next方法后才能remove

总结

  • Iterator是一种接口,为各种不同的数据结构提供统一的访问机制。
  • 迭代器遍历集合的过程是不可逆的
  • 迭代过程实际上是把集合元素通过next()方法一个个拿出的过程
  • 在迭代过程中不能自己修改集合结构,但可通过迭代器的remove方法实现对集合元素的删除
  • 只有当迭代器通过next方法拿出一个对象后才可以进行删除操作

Vector源码

Vector的数据结构

Vector底层也使用了数组来存储数据,支持自动动态扩容,元素可重复,允许元素为null,数组元素类型为Object,当向集合中加入基本数据类型时会自动装箱。

Vector的继承关系

在这里插入图片描述

Vector的安全性

Vector是线程同步的,即线程安全,Vector类的操作方法带有synchronized,也导致了性能低于ArrayList。

Vector的主要成员变量

在这里插入图片描述

Vector的构造方法

在这里插入图片描述

Vector常用API

基本底层实现与ArrayList异曲同工,主要区别在于扩容机制,Vector扩容是按照2倍进行的,还有一点在于Vector线程安全,使用synchronized关键字。

add(E e)

在这里插入图片描述

ensureCapacityHelper(int minCapacity)

在这里插入图片描述

grow(int minCapacity)

在这里插入图片描述
拷贝数组函数最终调用了System.arraycopy方法,他是一个native方法
在这里插入图片描述

Vector与ArrayList的比较

在这里插入图片描述

LinkedList源码

LinkedList的数据结构

底层使用双向链表实现,允许null的存在。LinkedList 中的元素就是一个个的节点,而真正的数据则存放在 Node 之中。

LinkedList的继承关系

在这里插入图片描述

LinkedList的安全性

LinkedList 的操作单线安全,多线程不安全。

LinkedList的主要成员变量

在这里插入图片描述
还有一个可序列化的UID

LinkedList的构造方法

在这里插入图片描述

内部的Node节点

在这里插入图片描述
Node节点中有两个指针数据和一个数据。
Node之所以是静态是为了防止内存泄漏,Node 类是在 LinkedList 类中的,也就是一个内部类,若不使用 static 修饰,那么 Node 就是一个普通的内部类,在 java 中一个普通内部类在实例化之后,默认会持有外部类的引用,这就有可能造成内存泄露。但使用 static 修饰过的内部类(称为静态内部类),就不会有这种问题,在 Android 中有很多这样的情况,如 Handler 的使用。

LinkedList中常用API

add(E e)

在这里插入图片描述linkLast(e)
在这里插入图片描述

add(int index, E element)

在这里插入图片描述node(int index)

在这里插入图片描述

linkBefore(E e, Node succ)

    /**
     * Inserts element e before non-null Node succ.
     * 将元素插入指定位置
     */
    void linkBefore(E e, Node<E> succ) {
        // assert succ != null;
        final Node<E> pred = succ.prev;
        final Node<E> newNode = new Node<>(pred, e, succ);
        succ.prev = newNode;
        if (pred == null)
            first = newNode;
        else
            pred.next = newNode;
        size++;
        modCount++;
    }

remove(int index)

在这里插入图片描述
unlink(Node x)

		//断开非空节点x的连接
		E unlink(Node<E> x) {
        // 获取对应节点的三个属性
        final E element = x.item;
        final Node<E> next = x.next;
        final Node<E> prev = x.prev;

		//判断当前节点的上一个节点是否为null,为null则说明x为头结点
        if (prev == null) {
            first = next;
        } else {
            prev.next = next;
            x.prev = null;
        }

		//判断当前节点的下一个节点是否为null,为null则说明x是尾节点
        if (next == null) {
            last = prev;
        } else {
            next.prev = prev;
            x.next = null;
        }
		//将节点x的值置为空
        x.item = null;
        //维护size
        size--;
        //用来记录LinkedList结构性变化的次数
        modCount++;
        return element;
    }

remove(Object o)

在这里插入图片描述

remove()

删除头节点

    public E remove() {
        return removeFirst();
    }

set(int index, E element)

修改指定下标的元素
在这里插入图片描述

get(int index)

在这里插入图片描述

LinkedList总结

由于LinkedList实现了Deque接口,因此实现了队列的一些操作,观察源码发现其内部实现还是基于双链表。
LinkedList的其余操作也都是基于双链表,在clear中需要遍历释放每个节点的值从而释放内存。

小结

1. 为什么不采取扩容固定容量呢?
扩容的目的需要综合考虑这两种情况:

  • 扩容容量不能太小,防止频繁扩容,频繁申请内存空间 + 数组频繁复制
  • 扩容容量不能太大,需要充分利用空间,避免浪费过多空间;

而扩容固定容量,很难决定到底取多少值合适,取任何具体值都不太合适,因为所需数据量往往由数组的客户端在具体应用场景决定。依赖于当前已经使用的量 系数, 比较符合实际应用场景。

2. 为什么是1.5,而不是1.2,1.25,1.8或者1.75?
因为1.5 可以充分利用移位操作,减少浮点数或者运算时间和运算次数。

3. 为什么数组长度的最大值MAX_ARRAY_size是Integer.MAX_VALUE - 8
数组作为一个对象,需要一定的内存存储对象头信息,对象头信息最大占用内存不可超过8字节。

4. modCount到底是什么东西
modCount字面意思就是修改次数,用到modCount的地方,比如ArrayList、hashMap、LinkedList等等,他们都是线程不安全的,所以只要是修改了结构的地方,就会将modCount++,然后再遍历时就会去判断当前的modCount是否相等,如果不相等则证明此线程在遍历时有其他线程对集合发生了修改。

5. Fial-Fast机制:优先考虑出现异常的场景,当异常产生时,直接抛出异常,程序终止。
我们都知道这些集合是线程不安全的,如果在使用迭代器的过程中,有其他线程对集合进行了修改,那么就会抛出ConcurrentModificationException异常,这就是Fail-Fast策略。而这个时候源码中就通过modCount进行了操作。迭代器在创建时,会创建一个变量等于当时的modCount,如果在迭代过程中,集合发生了变化,modCount就是++。这时迭代器中的变量的值和modCount不相等了,那就抛异常。
所以,遍历线程不安全的集合时,尽量使用迭代器

6. 如何实现线程安全的ArrayList

  1. 所有涉及到改变 modCount 值得地方全部加上 synchronized
  2. 直接使用 Collections.synchronizedList,Collections.synchronizedList使用及源码
  3. 使用Vector,它通过添加synchronized关键字使得修改等操作线程安全,但在迭代时仍有可能抛出ConCurrenceModificationException的异常。
  4. 使用CopyOnWriteArrayList替换ArrayList

7. ArrayList和Vector的区别

  • ArrayList线程不安全,Vector线程安全。都允许值为null。
  • 默认大小都是10
  • 扩容的时候ArrayList默认扩容原大小的1.5倍,Vector默认扩容原大小的2倍(可自定义)。

8. ArrayList和LinkedList

  • 都允许空值。
  • ArrayList是数组实现的,有扩容操作。LinkedList是链表实现,双向链表。
  • ArrayList的get/set性能好,LinkedList的插入和删除性能好。可是事实并不是这样,如果是从头部开始插入,LinkedList性能比ArrayList好;从中间插入,ArrayList却远好于LinkedList;从尾端插入,ArrayList比LinkedList好。
  • LinkedList同时还支持堆栈、队列的API,所以也可以把它当做堆栈、队列使用。
  • ArrayList的遍历就是简单的从0开始遍历,而LinkedList他会判断当前的值是前半部分还是后半部分,对应的就从头还是尾开始遍历。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值