Java集合(二)ArrayList、LinkedList使用及源码分析

  本系列文章:
    Java集合(一)集合框架概述
    Java集合(二)ArrayList、LinkedList使用及源码分析
    Java集合(三)Vector、Stack使用及源码分析
    Java集合(四)HashMap、HashTable、LinkedHashMap使用及源码分析
    Java集合(五)HashSet、LinkedHashSet使用及源码分析
    Java集合(六)ArrayBlockingQueue、LinkedBlockingQueue使用及源码分析

ArrayList

一、ArrayList简介

  • 1、ArrayList的继承关系
public class ArrayList<E> extends AbstractList<E>
        implements List<E>, RandomAccess, Cloneable, java.io.Serializable

  图示:

  从继承关系上看:

  1. ArrayList支持泛型。
  2. 实现了List接口。
  3. 实现了RandomAccess接口,表明ArrayList支持随机访问,即可以通过元素的下标快速获取元素对象。
  4. 实现了Cloneable接口,可以通过clone方法克隆对象。
  5. 实现Serializable接口,表明该类具有序列化功能。
  • 2、ArrayList底层结构
      简单来说,ArrayList是动态数组(数组的容量会随着元素数量的增加而增加,即动态变化)。它的底层结构是数组:
          
      在elementData数组中,数组元素类型为Object类型,即可以存放所有类型数据,对ArrayList类的实例的所有的操作底层都是基于数组的。
      同时,在ArrayList中,并没有规定特殊的元素操作规则(比如只能在数组两端进行增删等),所以ArrayList是操作非常自由的动态数组:
  1. 可以在数组头部 / 尾部 / 中间等任意位置插入、删除元素。
  2. 添加单个或集合中的元素时,未指明添加位置时,都是添加在尾部。
  3. 不会像队列那样在增加、取出元素时可能会产生阻塞现象。

二、ArrayList特点

  • 1、自动扩容
      ArrayList是一种支持自动扩容(1.5倍扩容,即原来大小+原来大小*0.5)的动态数组,支持随机访问,可克隆,可序列化等特性;
  • 2、线程不安全
      ArrayList是线程不安全的,如果多个线程访问ArrayList实例,且一个线程在修改该实例结构。
  • 3、访问元素效率高,增删元素效率低
      ArrayList中get、set、isEmpty等方法的时间复杂度都是O(1),即遍历和随机访问元素效率比较高;add的时间复杂度是O(n),即添加和删除元素效率比较低;
  • 4、ArrayList可添加null,有序可重复
  • 5、ArrayList中元素下标是从0开始

三、ArrayList常用方法

3.1 常见方法介绍

  • 1、构造方法
	//构造一个初始容量为10的空ArrayList(其实是构造了一个容量为空的ArrayList,
	//第一次添加元素时,扩容为10)
	public ArrayList()
	//构造具有指定初始容量的空ArrayList  
	public ArrayList(int initialCapacity)
  • 2、添加元素
	//将元素追加到此ArrayList的尾部
	public boolean add(E e)
	//在指定位置插入元素
	public void add(int index, E element)
  • 3、判断是否包含某个元素
	public boolean contains(Object o)
  • 4、返回指定位置的元素
	public E get(int index)
  • 5、返回指定元素的索引
	//返回指定元素的第一次出现的索引,如果此ArrayList不包含元素,则返回-1
	public int indexOf(Object o)
	//返回指定元素的最后一次出现的索引,如果此ArrayList不包含元素,则返回-1
	public int lastIndexOf(Object o)
  • 6、删除元素
	//删除指定位置的元素
	public E remove(int index)
	//从ArrayList中删除第一个出现指定元素的(如果存在)
	public boolean remove(Object o)
  • 7、删除指定集合中不存在的那些元素
	public boolean retainAll(Collection<?> c)
  • 8、用指定的元素替换指定位置的元素,返回原有元素
	public E set(int index, E element)
  • 9、返回此ArrayList中指定的 fromIndex (包括)和 toIndex之间的子ArrayList,即截取ArrayList
	public List< E > subList(int fromIndex, int toIndex)
  • 10、清空ArrayList
	public void clear()
  • 11、对ArrayList中的元素均执行给定的操作,直到所有元素都被处理或动作引发异常
	public void forEach(Consumer<? super E> action)
  • 12、判断ArrayList是否为空
	public boolean isEmpty()
  • 13、返回元素数
	public int size()

  ArrayList常见的遍历方式有三种:普通for循环、迭代器和增强型for循环(本质还是迭代器循环),如果是仅仅打印元素的话,用forEach也行。

3.2 常见方法使用

		//1.三种构造方法
		ArrayList<String> arrayList = new ArrayList<>();
		ArrayList<String> arrayList1 = new ArrayList<>(10);
		
		//2.添加元素
		arrayList.add("111");
		arrayList.add("222");
		arrayList.add("333");
		arrayList.add("444");
		arrayList.add("555");
		arrayList.add("666");
		System.out.println(arrayList); //[111, 222, 333, 444, 555, 666]
		
		arrayList.add(4,"777");
		System.out.println(arrayList); //[111, 222, 333, 444, 777, 555, 666]
		
		//3.判断是否包含某个元素
		System.out.println(arrayList.contains("111"));  //true
		System.out.println(arrayList.contains("11"));  //false
		
		//4.获取指定位置的元素
		System.out.println(arrayList.get(1));  //222
		//System.out.println(arrayList.get(10));  //会抛IndexOutOfBoundsException
		
		//5.检索元素位置
		System.out.println(arrayList.indexOf("888"));  //-1
		System.out.println(arrayList.indexOf("aaa"));  //-1
		System.out.println(arrayList.lastIndexOf("888"));  //-1
		System.out.println(arrayList.lastIndexOf("999"));  //-1
		
		//6.删除元素
		System.out.println(arrayList.remove(1)); //222
		System.out.println(arrayList); //[111, 333, 444, 777, 555, 666]
		
		System.out.println(arrayList.remove("444")); //true
		System.out.println(arrayList); //[111, 333, 777, 555, 666]
		
		//7.删除指定集合中不存在的那些元素
		List<String> list = new ArrayList<>();
		list.add("888");
		list.add("555");
		System.out.println(list);//[888, 555]
		System.out.println(arrayList.retainAll(list)); //true
		System.out.println(arrayList); //[555]
		
		//8.替换指定位置元素
		arrayList.add("aaa");
		arrayList.add("bbb");
		arrayList.add("ccc");
		System.out.println(arrayList.set(2, "ddd")); //bbb
		System.out.println(arrayList); //[555, aaa, ddd, ccc]
		
		//9.截取子串
		List<String> arrayList4 = arrayList.subList(0, 1);
		arrayList4.forEach(x -> System.out.print(x+" ")); //555 
		System.out.println();
		
		//10.清空ArrayList、判断ArrayList是否为空、ArrayList长度
		arrayList.clear();
		System.out.println(arrayList.isEmpty());  //true
		System.out.println(arrayList.size());  //0
		
		//11.遍历ArrayList
		for(int i=0;i<arrayList.size();i++) {
    
    
			System.out.print(arrayList.get(i)+" "); 
		}
		System.out.println();
		
		for(String str:arrayList) {
    
    
			System.out.print(str+" "); 
		}
		System.out.println();
		
		Iterator<String> iterator3 = arrayList.iterator();
		while (iterator3.hasNext()) {
    
    
			System.out.print(iterator3.next()+" "); 
		}
		System.out.println();
		
		arrayList.forEach(x  -> System.out.print(x+" ")); 

四、ArrayList源码

  ArrayList源码中最先出现的是定义了几个变量:

    //版本号,用于序列化机制。简单来说,反序列化时,需要验证此变量,
    //变量一致才能进行相应的反序列化操作。
	private static final long serialVersionUID = 8683452581122892189L;
    //默认的初始容量
	private static final int DEFAULT_CAPACITY = 10;
    //初始容量为0时,elementData指向该数组。
	private static final Object[] EMPTY_ELEMENTDATA = {
    
    };
    //也是个空数组。简单来说,差异的地方在于:没有定义初始容量时,
    //elementData指向该数组。
	private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {
    
    };
    //元素数组,存储具体数组元素。此处特殊的是出现transient关键字,
    //该关键作用,简单来说就是修饰不需要序列化的变量,那么elementData
    //为什么要用transient来修饰呢?其实是处于空间利用的考虑,ArrayList
    //根据真实元素个数size去序列化真实的元素,而不是根据数组的长度去序列
    //化元素,这样就减少了空间占用。
	transient Object[] elementData;
    //元素个数
	private int size;

  DEFAULT_CAPACITY代表ArrayList的初始容量,默认是10。
  elementData是一个缓存数组,它通常会预留一些容量,等容量不足时再扩充容量,那么有些空间可能就没有实际存储元素,采用上述的方式来实现序列化时,就可以保证只序列化实际存储的那些元素,而不是整个数组,从而节省空间和时间。

4.1 构造方法

  • 1、ArrayList(int initialCapacity)
	//构造具有指定初始容量的空列表
    public ArrayList(int initialCapacity) {
    
    
        if (initialCapacity > 0) {
    
    
            this.elementData = new Object[initialCapacity];
        //初始容量为0时,elementData指向EMPTY_ELEMENTDATA
        } else if (initialCapacity == 0) {
    
    
            this.elementData = EMPTY_ELEMENTDATA;
        //初始容量<0,抛异常
        } else {
    
    
            throw new IllegalArgumentException("Illegal Capacity: "+
                                               initialCapacity);
        }
    }

  这是最常用的构造方法,不指定初始容量的方式。此时ArrayList的容量为10,数组中元素的数量size为0:

  

  • 2、ArrayList( )
    public ArrayList() {
    
    
    	//DEFAULTCAPACITY_EMPTY_ELEMENTDATA是个空数组,但无参构造方法的注释却
    	//是“ Constructs an empty list with an initial capacity of ten ”,
    	//即构造一个初始容量为10的空列表,这是怎么回事呢?其实注释和方法体并不
    	//矛盾,因为在添加元素时,第一次对数组进行扩容时,会将数组容量扩展为10。
        this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
    }
  • 3、ArrayList(Collection<? extends E> c)
    public ArrayList(Collection<? extends E> c) {
    
    
    	//将集合中的元素转化为数组中元素的形式存入到elementData中
        elementData = c.toArray();
        if ((size = elementData.length) != 0) {
    
    
            if (elementData.getClass() != Object[].class)
            	//存入elementData数组的元素个数为size
                elementData = Arrays.copyOf(elementData, size, Object[].class);
        } else {
    
    
            // replace with empty array.
            this.elementData = EMPTY_ELEMENTDATA;
        }
    }

  那创建ArrayList时,怎么选择构造方法呢?可以简单遵从以下规则:

  1. 知道ArrayList中需要存储多少元素时,使用指定容量构造方法,可避免扩容带来的运行开销,提高程序运行效率;
  2. 需要复用Collection对象时,使用指定集合构造方法,如:Arrays.asList;
  3. 剩下的情况就使用无参构造方法。

4.2 添加元素

  • 1、add(E e)
    //添加元素到数组现有元素末尾
    public boolean add(E e) {
    
    
    	//判断是否需要扩容,确保处理后的容器能存下size + 1个元素
        ensureCapacityInternal(size + 1);
        //将元素追加到数组尾部
        elementData[size++] = e;
        return true;
    }

  以之前创建的10个容量的ArrayList为例,添加一个元素后的ArrayList:

  如果此时再调用add方法先后添加2、3两个元素后,ArrayList会变成:

  • 2、add(int index, E element)
	//在指定位置添加元素
    public void add(int index, E element) {
    
    
    	//先判断index位置是否合法
        rangeCheckForAdd(index);
		//判断是否需要扩容
        ensureCapacityInternal(size + 1); 
        //将index到尾部的元素向后移动一位
        System.arraycopy(elementData, index, elementData, index + 1,
                         size - index);
        //将元素放置在index位置上
        elementData[index] = element;
        size++;
    }
	//index参数不合法,就抛IndexOutOfBoundsException
    private void rangeCheckForAdd(int index) {
    
    
        if (index > size || index < 0)
            throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
    }

  之前的1、2、3都是追加到ArrayList现有元素的后面的,ArrayList当然还支持在指定位置添加元素,假设调用add(1,4)方法,就会在下标索引为1的位置添加元素4。这个过程分为以下几步:

  • 3、addAll(Collection<? extends E> c)
	//将集合中的元素添加在数组尾部
    public boolean addAll(Collection<? extends E> c) {
    
    
    	//将集合转换为数组
        Object[] a = c.toArray();
        int numNew = a.length;
        //判断是否需要扩容,size为数组目前的实际元素的数量,numNew为集合的元素数
        ensureCapacityInternal(size + numNew); 
        //将集合中的元素拷贝到数组中
        System.arraycopy(a, 0, elementData, size, numNew);
        //数组的元素个数size也进行适当的增加
        size += numNew;
        return numNew != 0;
    }

  addAll(Collection<? extends E> c)addAll(int index, Collection<? extends E> c)方法作用类似,不同的地方在于插入的位置。接下来以addAll(Collection<? extends E> c)为例,演示一下添加一个集合的过程:

  • 4、addAll(int index, Collection<? extends E> c)
	//将集合中的元素添加在数组指定位置
    public boolean addAll(int index, Collection<? extends E> c) {
    
    
    	//先判断index位置是否合法
        rangeCheckForAdd(index);

        Object[] a = c.toArray();
        int numNew = a.length;
        //判断是否需要扩容
        ensureCapacityInternal(size + numNew);  // Increments modCount

        int numMoved = size - index;
        //如果插入的元素位置比ArrayList中元素个数数少,将ArrayList中从index位置的元素往后移动
        if (numMoved > 0)
            System.arraycopy(elementData, index, elementData, index + numNew,
                             numMoved);
		//最后将集合中的元素从index位置开始插入
        System.arraycopy(a, 0, elementData, index, numNew);
        size += numNew;
        return numNew != 0;
    }

4.3 删除元素

  • 1、remove(int index)
    public E remove(int index) {
    
    
    	//校验index是否合法
        rangeCheck(index);

        modCount++;
        //取出index位置元素(为了返回该元素)
        E oldValue = elementData(index);
        //删除元素后,要移动的元素数
        int numMoved = size - index - 1;
        //如果删除的元素不是最后一个元素,就要将被删除元素后面的元素向前移动
        if (numMoved > 0)
            System.arraycopy(elementData, index+1, elementData, index,
                             numMoved);
        //将数组元素的最后一个位置置为null,为了GC
        elementData[--size] = null; 

        return oldValue;
    }
	//index参数超过数组元元素数的最大位置,抛IndexOutOfBoundsException
    private void rangeCheck(int index) {
    
    
        if (index >= size)
            throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
    }
  • 2、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; 
    }

  以上的两个删除元素的方法,其实大致思路相似,先找到index,再删除对应位置上的元素,图示:

  • 3、removeAll(Collection<?> c)
	//删除数组中,指定集合所包含的元素
    public boolean removeAll(Collection<?> c) {
    
    
    	//对集合进行非空判断
        Objects.requireNonNull(c);
        //删除集合c中的元素
        return batchRemove(c, false);
    }
	//该方法代码在Objects类中
    public static <T> T requireNonNull(T obj) {
    
    
        if (obj == null)
            throw new NullPointerException();
        return obj;
    }

    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 {
    
    
            //r:该位置之后的是未遍历的元素
            //w:该位置之前的元素是数组中包含的集合中的元素,即要删除的元素
            //将r之后的元素移动到w位置上
            if (r != size) {
    
    
                System.arraycopy(elementData, r,
                                 elementData, w,
                                 size - r);
                //更新w,此时的w代表的将不删除的元素向前移动后,不需要保留的元素的起始位置
                w += size - r;
            }
            //w后面元素置为null,为了GC
            if (w != size) {
    
    
                for (int i = w; i < size; i++)
                    elementData[i] = null;
                modCount += size - w;
                size = w;
                modified = true;
            }
        }
        return modified;
    }
  • 4、clear()
    public void clear() {
    
    
        modCount++;
        //清空数组元素,置为null,为了GC
        for (int i = 0; i < size; i++)
            elementData[i] = null;

        size = 0;
    }

  clear方法很容易理解,将存储元素的每个位置都置为null。

4.4 更新元素

  • 1、set(int index, E element)
	//替换指定位置的元素
    public E set(int index, E element) {
    
    
    	//先校验位置元素是否合法
        rangeCheck(index);
		//取出该位置旧元素,放置新元素,再将旧元素返回
        E oldValue = elementData(index);
        elementData[index] = element;
        return oldValue;
    }

4.5 获取元素

    public E get(int index) {
    
    
    	//检验位置参数是否合法
        rangeCheck(index);
		//将该位置元素返回
        return elementData(index);
    }

4.6 检索元素

  检索元素的方法有两个:从前向后遍历和从后向前遍历。

  • 1、indexOf(Object o)
	//从前向后遍历,返回指定元素(或null)第一次出现的位置
    public int indexOf(Object o) {
    
    
    	//判断要检索的元素是否为null
        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;
    }
  • 2、lastIndexOf(Object o)
	//从后向前遍历,返回指定元素(或null)最后一次出现的位置
    public int lastIndexOf(Object o) {
    
    
    	//判断要检索的元素是否为null
        if (o == null) {
    
    
            for (int i = size-1; i >= 0; i--)
                if (elementData[i]==null)
                    return i;
        } else {
    
    
            for (int i = size-1; i >= 0; i--)
                if (o.equals(elementData[i]))
                    return i;
        }
        return -1;
    }

4.7 迭代器

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

  iterator()是返回一个普通迭代器的方法,Itr是实现了Iterator接口的内部类,支持next()、hasNext()、remove()等常规迭代方法,还支持JDK1.8中新增的forEachRemaining这种支持lambda表达式表达式的方法。

  • 2、listIterator()
    public ListIterator<E> listIterator() {
    
    
        return new ListItr(0);
    }

    public ListIterator<E> listIterator(int index) {
    
    
        if (index < 0 || index > size)
            throw new IndexOutOfBoundsException("Index: "+index);
        return new ListItr(index);
    }

  这两个方法是一类,都是创建了一个ListItr对象,这个ListItr也是一个具有迭代功能的内部类,继承关系:

	private class ListItr extends Itr implements ListIterator<E>

  该类实现ListIterator,所以支持向前遍历,其多于Itr的方法有:

	public boolean hasPrevious()
	public int nextIndex() 
	public int previousIndex()
	public E previous()
	//set(E e)为替换最后一个元素
	public void set(E e)  
	public void add(E e)
  • 3、spliterator()
    public Spliterator<E> spliterator() {
    
    
        return new ArrayListSpliterator<>(this, 0, -1, 0);
    }

  spliterator()是创建一个Spliterator迭代器的方法,其中ArrayListSpliterator的继承关系为:

	static final class ArrayListSpliterator<E> implements Spliterator<E>

  简单来说,Spliterator是用来多线程并行迭代的迭代器,这个迭代器的主要作用就是把集合分成了好几段,每个线程执行一段,因此是线程安全的。

4.8 fail-fast机制

  • 1、fail-fast示例
      fail-fast 机制是java集合(Collection)中的一种错误机制。 当多个线程对同一个集合的内容进行操作时,就可能会产生fail-fast事件。
      示例:
public class ArrayListTest {
    
    
	
	private static List<String> list = new ArrayList<String>();
    public static void main(String[] args) {
    
    
    
        // 同时启动两个线程对list进行操作
        new ThreadOne().start();
        new ThreadTwo().start();
    }

    private static void printAll() {
    
    
        System.out.println();

        Iterator<String> iter = (Iterator<String>) list.iterator();
        while(iter.hasNext()) {
    
    
            System.out.print(iter.next()+", ");
        }
    }

    /* 向list中依次添加0,1,2,3,4,5,每添加一个数之后,就通过printAll()遍历整个list */
    private static class ThreadOne extends Thread {
    
    
        public void run() {
    
    
            int i = 0;
            while (i<6) {
    
    
                list.add(String.valueOf(i));
                printAll();
                i++;
            }
        }
    }

    /* 向list中依次添加10,11,12,13,14,15,每添加一个数之后,就通过printAll()遍历整个list */
    private static class ThreadTwo extends Thread {
    
    
        public void run() {
    
    
            int i = 10;
            while (i<16) {
    
    
                list.add(String.valueOf(i));
                printAll();
                i++;
            }
        }
    }
}

  将上述代码运行多次,就有可能出现如下ConcurrentModificationException:

  产生该异常的原因是:两个线程在同时操作同一个ArrayList对象,当一个线程在遍历的时候,另一个线程去修改了ArrayList对象中的元素,就会触发fail-fast机制,抛出异常

  • 2、fail-fast触发原理
      在ArrayList源码中,有多处代码都能抛出ConcurrentModificationException,以Itr内部类为例:
    private class Itr implements Iterator<E> {
    
    
    	//迭代器游标
        int cursor;     
        //最后一个元素索引为-1
        int lastRet = -1; 
        //初始化"期望的modCount"expectedModCount,如果在迭代过程中集合元素
        //发生了变化,则modCount就会跟着变化,此时用expectedModCount和modCount
        //比较,就能知道这次集合元素变化
        int expectedModCount = modCount;
		//是否还有下一个元素
        public boolean hasNext() {
    
    
            return cursor != size;
        }
        //获取下一个元素
        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 {
    
    
                ArrayList.this.remove(lastRet);
                cursor = lastRet;
                lastRet = -1;
                expectedModCount = modCount;
            } catch (IndexOutOfBoundsException ex) {
    
    
                throw new ConcurrentModificationException();
            }
        }
		//省略一些代码
		//检测在迭代过程中,集合元素是否发生了变化
        final void checkForComodification() {
    
    
            if (modCount != expectedModCount)
                throw new ConcurrentModificationException();
        }
    }

  modCount是对ArrayList 真正的修改次数,对ArrayList 内容的修改都将增加这个值,在迭代器初始化过程中会将这个值赋给迭代器的 expectedModCount。
  在迭代过程中,会判断 modCount 跟 expectedModCount 是否相等,如果不相等就表示已经有其他线程修改了ArrayList ,此时就会抛出异常。
  modCount 声明为 volatile,保证线程之间修改的可见性

4.9 自动扩容机制

          
  此处先介绍自动扩容,是因为自动扩容机制是每次向数组中添加元素前,必须要做的判断操作。先看最常见的添加元素的方法 public boolean add(E e) ,方法如下:

    public boolean add(E e) {
    
    
    	//自动扩容判断的最开始调用的方法
        ensureCapacityInternal(size + 1);  // Increments modCount!!
        elementData[size++] = e;
        return true;
    }

	//得到最小扩容量
    private void ensureCapacityInternal(int minCapacity) {
    
    
    	//先判断一下是否是使用无参构造方法创建的ArrayList对象,如果是的话,
    	//就设置默认数组大小为DEFAULT_CAPACITY(10)
        if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
    
    
            minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
        }

        ensureExplicitCapacity(minCapacity);
    }

    private void ensureExplicitCapacity(int minCapacity) {
    
    
        modCount++;
        //如果修改后的(最小)数组容量 minCapacity 大于当前的数组长度 elementData.length,
        //那么就需要调用grow方法进行扩容,反之则不需要
        if (minCapacity - elementData.length > 0)
            grow(minCapacity);
    }

    private void grow(int minCapacity) {
    
    
        //目前数组长度
        int oldCapacity = elementData.length;
        //先将当前数组容量扩充至1.5倍
        int newCapacity = oldCapacity + (oldCapacity >> 1);
        //newCapacity:扩容1.5倍后的新数组容量
        //minCapacity:数组存储元素所需要的最小容量
        //检查新容量是否大于最小需要容量,若还是小于最小需要容量,那么
        //就把最小需要容量当作数组的新容量
        if (newCapacity - minCapacity < 0)
            newCapacity = minCapacity;
        //如果新容量大于 MAX_ARRAY_SIZE(Integer.MAX_VALUE - 8),调用
        //hugeCapacity方法来
        if (newCapacity - MAX_ARRAY_SIZE > 0)
            newCapacity = hugeCapacity(minCapacity);
        //将源数组复制到newCapcity大小的新数组上
        elementData = Arrays.copyOf(elementData, newCapacity);
    }

    private static int hugeCapacity(int minCapacity) {
    
    
        if (minCapacity < 0) // overflow
            throw new OutOfMemoryError();
        //对minCapacity和MAX_ARRAY_SIZE进行比较
        //若minCapacity大,将Integer.MAX_VALUE作为新数组的大小
        //若MAX_ARRAY_SIZE大,将MAX_ARRAY_SIZE作为新数组的大小
        //MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
        return (minCapacity > MAX_ARRAY_SIZE) ?
            Integer.MAX_VALUE :
            MAX_ARRAY_SIZE;
    }

	private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;

  综上所述,ArrayList自动扩容主要经历了以下几步:

  • 1、是否用无参构造方法创建的ArrayList对象,如果是,则赋予ArrayList新容量为10;
  • 2、经过第一步后,新容量是否够用,不够用就进入扩容流程核心方法grow;
  • 3、在grow扩容时,涉及到两个变量:

newCapacity:扩容1.5倍后的新数组容量;
minCapacity:数组存储元素所需要的最小容量,由现有数组元素数量size+1得来。

  取两者较大者作为newCapacity,然后与MAX_ARRAY_SIZE(Integer.MAX_VALUE - 8)比较。

  • 4、如果newCapacity>MAX_ARRAY_SIZE,则进入以下比较流程:

如果minCapacity > MAX_ARRAY_SIZE,则minCapacity=Integer.MAX_VALUE;
如果minCapacity < MAX_ARRAY_SIZE,则minCapacity=MAX_ARRAY_SIZE。

  • 5、将源数组复制到newCapcity大小的新数组上。

五、ArrayList相关问题

5.1 多线程场景下如何使用 ArrayList

  常见的方法有以下几种:

  • 1、通过 Collections 的 synchronizedList方法将其转换成线程安全的容器后再使用
List<String> synchronizedList = Collections.synchronizedList(list);
  • 2、加锁
synchronized(list.get()) {
    
    
	list.get().add(model);
}
  • 3、使用线程安全的 CopyOnWriteArrayList 代替线程不安全的 ArrayList
List<Object> list1 = new CopyOnWriteArrayList<Object>();

5.2 System.arraycopy和Arrays.copyOf

  • 1、System.arraycopy
      在ArrayList的源码中,增删元素时,要移动元素,而移动数组内的元素都是通过System.arraycopy方法来实现的。看下这个方法:

public static void arraycopy(Object src, int srcPos, Object dest, int destPos, int length)
  Object src : 原数组
  int srcPos : 从元数据的起始位置开始
  Object dest : 目标数组
  int destPos : 目标数组的开始起始位置
  int length : 要copy的数组的长度

  看个例子:

		int[] arrs = {
    
    1,2,3,4,5,6,7,8,9,10};
		for(int i=0;i<arrs.length;i++)
			System.out.print(arrs[i]+" "); //1 2 3 4 5 6 7 8 9 10
			
		System.out.println();
		//将arrs数组从下标为5的位置开始复制,总共复制5个元素,
		//将上面的元素复制到arrs数组,起始位置的下标为0
        System.arraycopy(arrs, 5,arrs, 0,5);
        
		for(int i=0;i<arrs.length;i++)
			System.out.print(arrs[i]+" "); //6 7 8 9 10 6 7 8 9 10
  • 2、Arrays.copyOf
      在数组进行扩容时,原有数组中元素的迁移是通过Arrays.copyOf方法来实现的:

public static < T > T[ ] copyOf(T[ ] original, int newLength)
  original:要复制的原数组
  newLength指定要建立的新数组长度,如果新数组的长度超过原数组的长度,则保留数组默认值
  返回值:新的数组对象,改变传回数组中的元素值,不会影响原来的数组

  看个例子:

		 	int[] arr1 = {
    
    1, 2, 3, 4, 5}; 
		    int[] arr2 = Arrays.copyOf(arr1, 5);
		    int[] arr3 = Arrays.copyOf(arr1, 10);
		    for(int i = 0; i < arr2.length; i++) 
		        System.out.print(arr2[i] + " "); 
		    		System.out.println();  //1 2 3 4 5 
		    for(int i = 0; i < arr3.length; i++) 
		        System.out.print(arr3[i] + " ");  //1 2 3 4 5 0 0 0 0 0
			}

5.3 ArrayList的扩容因子为什么是1.5

  关于扩容因子,有一个很通俗的解释,扩容因子最适合范围为(1, 2)。
  为什么不取扩容固定容量呢?扩容的目的需要综合考虑这两种情况:

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

  扩容固定容量,很难决定到底取多少值合适,取任何具体值都不太合适,因为所需数据量往往由数组的客户端在具体应用场景决定。依赖于当前已经使用的量 * 系数, 比较符合实际应用场景。
  为什么是1.5,而不是1.2,1.25,1.8或者1.75?因为1.5 可以充分利用移位操作,减少浮点数或者运算时间和运算次数。

// 新容量计算
int newCapacity = oldCapacity + (oldCapacity >> 1);

LinkedList

一、LinkedList概述

  • 1、LinkedList的继承关系
public class LinkedList<E>
    extends AbstractSequentialList<E>
    implements List<E>, Deque<E>, Cloneable, java.io.Serializable

  图示:

  从继承关系可以看出:

  1. 继承了AbstractSequentialList抽象类:在遍历LinkedList的时候,官方更推荐使用顺序访问,也就是迭代器。
      虽然LinkedList也提供了get(int index)方法,但是底层的实现是:每次调用get(int index)方法的时候,都需要从链表的头部或者尾部进行遍历,每一的遍历时间复杂度是O(index),而ArrayList每次遍历的时间复杂度都是O(1)。所以不推荐通过get(int index)遍历LinkedList。
      至于使用迭代器进行遍历:官方源码对遍历进行了优化:通过判断索引index更靠近链表的头部还是尾部来选择遍历的方向。
  2. 实现了List接口,支持List操作。
  3. 实现了Cloneable接口,支持浅克隆。
  4. 实现了Deque接口,支持Deque操作。
  5. 实现了Serializable接口,支持序列化。
  • 2、LinkedList底层结构
      简单来说,LinkedList 是一个双向链表,借用网上一张图:

      LinkedList中存放的不是普通的某个中类型的元素,而是节点,一个节点中包含前驱指针、节点值和后继指针三个部分,源码:
    private static class Node<E> {
    
    
    	//节点的值
        E item;
        //当前节点的后一个节点的引用
        Node<E> next;
        //当前节点的前一个节点的引用
        Node<E> prev;

        Node(Node<E> prev, E element, Node<E> next) {
    
    
            this.item = element;
            this.next = next;
            this.prev = prev;
        }
    }

  LinkedList通过prev、next将不连续的内存块串联起来使用,LinkedList是双向链表,除头节点的每一个元素都有指针同时再指向它的上一个元素,除尾节点的每一个元素都有指针同时再指向它的下一个元素。

二、LinkedList特点

  LinkedList特点:

  • 1、LinkedList底层实现是双向链表
      LinkedList内部是一个双向链表(可以双向遍历)的实现(LinkedList实现了Deque接口,所以也是双向队列的实现),一个节点除了存储自身的数据外,还持有前,后两个节点的引用。所以就数据存储上来说,它比使用数组作为底层存储结构的ArrayList来说,会更加耗费空间。
  • 2、相比于ArrayList,LinkedList增删快、查询慢
      LinkedList具有对前后元素的引用,删除、插入节点很快,因为不需要移动其他元素,只需要改变部分节点的引用即可。同时LinkedList不支持随机访问(都由于是双向链表,所以支持双向遍历),所以查询速度相比于ArrayList,是比较慢的。
  • 3、线程不安全
      LinkedList是线程不安全的,多线程环境下可以使用Collections.synchronizedList(new LinkedList())保证线程安全。
  • 4、LinkedList中的元素有序可重复

三、LinkedList使用

3.1 常见方法介绍

  • 1、 常见构造方法
	//构造一个空链表
	public LinkedList()
  • 2、添加元素
	//将元素追加到此链表末尾
	public boolean add(E e)
	//在链表的指定位置插入元素
	public void add(int index, E element) 
	//在链表的首部插入元素
	public void addFirst(E e)
	//将元素追加到链表末尾
	public void addLast(E e)
	//将元素添加到链表的尾部
	public boolean offer(E e)
	//在链表的首部插入元素
	public boolean offerFirst(E e)
	//在链表的末尾插入元素
	public boolean offerLast(E e)
	//检索但不删除链表的首部元素
	public E peek()
	//在链表的头部添加元素
	public void push(E e)
  • 3、清空链表
	public void clear()
  • 4、判断链表中是否包含某个元素
	public boolean contains(Object o)
  • 5、查找元素
	//检索但不删除链表首部元素
	public E element()
	//返回链表指定位置的元素
	public E get(int index)
	//返回链表中的第一个元素
	public E getFirst()
	//返回链表中的最后一个元素
	public E getLast()
  • 6、检索元素位置
	//返回链表中指定元素的第一次出现的索引,如果此列表不包含元素,则返回-1
	public int indexOf(Object o)
	//返回链表中指定元素的最后一次出现的索引,如果此列表不包含元素,则返回-1
	public int lastIndexOf(Object o)
  • 7、List迭代器
	//返回ListIterator
	public ListIterator< E > listIterator(int index)
  • 8、检索但不删除元素
	//检索但不删除链表的第一个元素,如果链表为空,则返回 null 
	public E peekFirst()
	//检索但不删除链表的最后一个元素,如果链表为空,则返回 null 
	public E peekLast()
  • 9、检索并删除元素
	//检索并删除链表的首部元素
	public E poll()
	//检索并删除链表的第一个元素,如果此LinkedList为空,则返回 null 
	public E pollFirst()
	//检索并删除链表的最后一个元素,如果链表为空,则返回 null 
	public E pollLast()
	//删除并返回链表的第一个元素
	public E pop()
	//检索并删除链表的首部元素
	public E remove()
  • 10、删除元素
	//删除链表中指定位置的元素
	public E remove(int index)
	//从链表中删除指定元素的第一个出现(如果存在)
	public boolean remove(Object o)
	//从链表中删除并返回第一个元素
	public E removeFirst()
	//删除链表中指定元素的第一个出现(从头到尾遍历列表时)
	public boolean removeFirstOccurrence(Object o)
	//从链表中删除并返回最后一个元素
	public E removeLast()
	//删除链表中指定元素的最后一次出现(从头到尾遍历列表时)
	public boolean removeLastOccurrence(Object o)
  • 11、替换元素
	//用指定的元素替换链表中指定位置的元素
	public E set(int index, E element)
  • 12、返回链表中的元素数
	public int size()
  • 13、LinkedList常见的遍历方式有三种:普通for循环、迭代器和增强型for循环(本质还是迭代器循环),如果是仅仅打印元素的话,用forEach也行

3.2 常见方法使用

		//1.构造方法
		LinkedList<String> linkedList = new LinkedList<>();
		
		//2.添加元素
		linkedList.add("111");
		linkedList.add("222");
		linkedList.add("333");
		System.out.println(linkedList); //[111, 222, 333]
		
		linkedList.add(2, "444");
		System.out.println(linkedList); //[111, 222, 444, 333]
		
		linkedList.addFirst("555");
		linkedList.addLast("666");
		System.out.println(linkedList);  //[555, 111, 222, 444, 333, 666]
		
		//3.从链表中删除所有元素
		linkedList.clear();
		System.out.println(linkedList); //[]
		
		//4.判断链表是否包含某个元素
		System.out.println(linkedList.contains("777")); //false
		
		//5.检索但不删除链表的首部元素
		linkedList.add("111");
		linkedList.add("222");
		linkedList.add("333");
		System.out.println(linkedList.element()); //111
		
		//6.获取指定位置的元素
		linkedList.add("888");
		System.out.println(linkedList.get(1)); //222
		
		//7.获取首部元素
		System.out.println(linkedList.getFirst()); //111
		
		//8.获取尾部元素
		System.out.println(linkedList.getLast()); //888
		
		//9.检索元素位置
		System.out.println(linkedList.indexOf("999")); //-1
		System.out.println(linkedList.lastIndexOf("000")); //-1
		
		//10.添加元素
		linkedList.offer("111");
		System.out.println(linkedList); //[111, 222, 333, 888, 111]
		
		//11.在链表的首部插入元素
		linkedList.offerFirst("222");
		System.out.println(linkedList); //[222, 111, 222, 333, 888, 111]
		
		//12.在链表的末尾插入元素
		linkedList.offerLast("333");
		System.out.println(linkedList); //[222, 111, 222, 333, 888, 111, 333]
		
		//13.检索但不删除链表的首部元素
		System.out.println(linkedList.peek()); //222
		System.out.println(linkedList);  //[222, 111, 222, 333, 888, 111, 333]
		
		//14.检索但不删除链表的第一个元素
		System.out.println(linkedList.peekFirst()); //222
		System.out.println(linkedList); //[222, 111, 222, 333, 888, 111, 333]
		
		//15.检索但不删除链表的最后一个元素
		System.out.println(linkedList.peekLast()); //333
		System.out.println(linkedList); //[222, 111, 222, 333, 888, 111, 333]
		
		//16.检索并删除链表的首部元素
		System.out.println(linkedList.poll()); //222
		System.out.println(linkedList); //[111, 222, 333, 888, 111, 333]
		 
		//17.检索并删除链表的第一个元素
		System.out.println(linkedList.pollFirst());  //111
		System.out.println(linkedList); //[222, 333, 888, 111, 333]
		
		//18.检索并删除链表的最后一个元素
		System.out.println(linkedList.pollLast()); //333
		System.out.println(linkedList); //[222, 333, 888, 111]
		 
		//19.删除并返回链表的第一个元素
		System.out.println(linkedList.pop()); //222
		System.out.println(linkedList); //[333, 888, 111]
		
		//20.往链表添加元素
		linkedList.push("444");
		System.out.println(linkedList); //[444, 333, 888, 111]
		
		linkedList.push("555");
		linkedList.push("666");
		linkedList.push("777");
		linkedList.push("888");
		linkedList.push("999");
		//21.从链表中删除指定元素的第一个出现
		System.out.println(linkedList.remove()); //999
		System.out.println(linkedList); //[888, 777, 666, 555, 444, 333, 888, 111]
		
		//22.从链表中删除并返回第一个元素
		System.out.println(linkedList.removeFirst());  //888
		System.out.println(linkedList); //[777, 666, 555, 444, 333, 888, 111]
		
		//23.删除指定位置的元素
		System.out.println(linkedList.remove(1));  //666
		System.out.println(linkedList); //[777, 555, 444, 333, 888, 111]
		
		//24.删除指定元素
		System.out.println(linkedList.remove("000")); //false
		System.out.println(linkedList); //[777, 555, 444, 333, 888, 111]
		
		//25.删除链表中指定元素的第一个出现
		System.out.println(linkedList.removeFirstOccurrence("111")); //true
		System.out.println(linkedList); //[777, 555, 444, 333, 888]

		//26.删除并返回最后一个元素
		System.out.println(linkedList.removeLast()); //888
		System.out.println(linkedList); //[777, 555, 444, 333]
		
		//27.删除链表中指定元素的最后一次出现
		System.out.println(linkedList.removeLastOccurrence("222")); //false
		System.out.println(linkedList); //[777, 555, 444, 333]
		
		//28.替换元素
		System.out.println(linkedList.set(0, "333")); //777
		System.out.println(linkedList); //[333, 555, 444, 333]
		
		//29.返回元素数
		System.out.println(linkedList.size()); //4

		//30.遍历元素
		for(int i=0;i<linkedList.size();i++) {
    
    
			System.out.print(linkedList.get(i)+" "); //333 555 444 333 
		}
		
		for(String str:linkedList) {
    
    
			System.out.print(str+" "); //333 555 444 333   
		}
		
		Iterator iterator = linkedList.iterator();
		while (iterator.hasNext()) {
    
    
			System.out.print(iterator.next()+" ");  //333 555 444 333 
		}
		
		linkedList.forEach(x  -> System.out.print(x+" ")); //333 555 444 333

四、LinkedList源码分析

  LinkedList源码中最先出现的是定义了几个变量:

    //元素个数
    transient int size = 0;
    //链表的头结点
	transient Node<E> first;
    //链表的尾结点
	transient Node<E> last;

  可以看出:头结点和尾节点的节点类型,都是内部类Node。

4.1 构造方法

  因为LinkedList是基于链表的,所以不需要指定初始容量。

  • 1、LinkedList()
    public LinkedList() {
    
    
    }
  • 2、LinkedList(Collection<? extends E> c)
    public LinkedList(Collection<? extends E> c) {
    
    
        this();
        //将集合中的所有元素追加到现有链表的末尾
        addAll(c);
    }

4.2 添加元素

  LinkedList中添加元素的方法有六个,添加单个元素的方法有四个,添加集合中元素的方法有两个。

  • 1、add(E e) / addLast(E e)
	//add(E e)和addLast(E e)都是将元素添加到链表尾部,
	//区别就是一个有boolean返回值,一个没有
    public boolean add(E e) {
    
    
        linkLast(e);
        return true;
    }
    public void addLast(E e) {
    
    
        linkLast(e);
    }

    void linkLast(E e) {
    
    
    	//用临时变量l存储尾节点
        final Node<E> l = last;
        //构造了一个新节点newNode,该节点的前一个节点为l,节点值为e,后一个节点为null
        final Node<E> newNode = new Node<>(l, e, null);
        //将新创建的newNode作为尾节点
        last = newNode;
        //如果添加newNode前,之前的尾节点为null,代表链表为bull,因此newNode同时也就成了首节点
        if (l == null)
            first = newNode;
        //否则将newNode追加到原来的尾节点之后
        else
            l.next = newNode;
        //链表容量+1
        size++;
        //链表操作数+1
        modCount++;
    }

  add(E e) / addLast(E e)方法的作用都是将元素追加到链表尾部,图示:

  • 2、push(E e)
	//在链表的头部添加元素
    public void push(E e) {
    
    
        addFirst(e);
    }

    public void addFirst(E e) {
    
    
        linkFirst(e);
    }

    private void linkFirst(E e) {
    
    
    	//创建临时变量f存储链表原来的首节点
        final Node<E> f = first;
        //构造一个新节点newNode,前一个节点为null,该节点值为e,后一个节点为f
        final Node<E> newNode = new Node<>(null, e, f);
        //将新节点newNode作为first节点
        first = newNode;
        //如果原有first节点为null,则代表原来链表为null,因此新创建的节点newNode同时为头节点和尾节点
        if (f == null)
            last = newNode;
        //否则新节点newNode作为原来头结点的前一个节点
        else
            f.prev = newNode;
        //链表元素数+1
        size++;
        modCount++;
    }

  push(E e)方法的作用都是将元素追加到链表首部,图示:

  • 3、add(int index, E element)
	//在链表的任意位置插入一个元素
    public void add(int index, E element) {
    
    
    	//校验index参数
        checkPositionIndex(index);
		//判断位置参数是否与链表长度一致,如果一致的话,直接将元素追加到链表末尾
        if (index == size)
            linkLast(element);
        //否则就添加到index位置,原来的index位置的元素成为新插入元素的后继元素
        else
            linkBefore(element, node(index));
    }
	//在指定节点succ之前插入指定元素e。指定节点succ不能为null
    void linkBefore(E e, Node<E> succ) {
    
    
        //获得指定节点的前驱节点
        final Node<E> pred = succ.prev;
        //创建新节点newNode,其前驱节点为pred,节点值为e,后继节点为succ
        final Node<E> newNode = new Node<>(pred, e, succ);
        //succ的前驱节点为新节点newNode
        succ.prev = newNode;
        
        //判断原来该位置节点的前驱节点是否为null,如果为null,代表原来该位置的节点succ为首节点
        //因此,新创建的节点newNode就成为头节点
        if (pred == null)
            first = newNode;
        //否则为原来该位置节点的前驱节点的后继节点
        else
            pred.next = newNode;
        size++;
        modCount++;
    }

  add(int index, E element)方法的作用都是将元素追加到链表任意位置,图示:

  • 4、addAll(Collection<? extends E> c) / addAll(int index, Collection<? extends E> c)
	//在链表的末尾添加集合的全部元素
    public boolean addAll(Collection<? extends E> c) {
    
    
        return addAll(size, c);
    }
	//在链表的任意位置插入集合的全部元素
    public boolean addAll(int index, Collection<? extends E> c) {
    
    
    	//检验index参数合法性
        checkPositionIndex(index);
		//将集合转化成了数组
        Object[] a = c.toArray();
        //获取要插入集合的元素个数numNew 
        int numNew = a.length;
        if (numNew == 0)
            return false;
		//pred代表要插入位置的前驱节点,succ代表要插入位置的节点
        Node<E> pred, succ;
        //如果index和链表的长度相等,那么此时的pred就是原有的last节点,succ为null
        //因为链表在get(index)元素时,下标也是从0开始的
        if (index == size) {
    
    
            succ = null;
            pred = last;
        //否则succ就是原来index位置上的节点,pred就是succ的前驱节点
        } else {
    
    
            succ = node(index);
            pred = succ.prev;
        }
		//迭代的插入元素过程
        for (Object o : a) {
    
    
            @SuppressWarnings("unchecked") 
            E e = (E) o;
            //将集合中的元素取出来,作为新创建的节点的节点值,pred代表要插入位置的前驱节点,后继节点为null
            Node<E> newNode = new Node<>(pred, e, null);
            //如果pred为null,代表要插入的位置前面没有节点了,所以该节点就要成为头结点
            if (pred == null)
                first = newNode;
            //否则前一个节点的后继节点是新节点
            else
                pred.next = newNode;
            //至关重要的一步,将新插入的节点作为pred节点,以便继续向后增加节点
            pred = newNode;
        }
		//当succ==null,也就是新添加的节点位于LinkedList集合的最后一个元素的后面,
		//那么在之前添加的最后一个元素pred就成为了尾节点
        if (succ == null) {
    
    
            last = pred;
        //否则succ是pred的后继节点,pred是succ的前驱节点
        } else {
    
    
            pred.next = succ;
            succ.prev = pred;
        }

        size += numNew;
        modCount++;
        return true;
    }
  • 5、offer(E e) / offerFirst(E e) / offerLast(E e)
	//在链表尾部添加元素
    public boolean offer(E e) {
    
    
        return add(e);
    }
	//在链表首部添加元素
    public boolean offerFirst(E e) {
    
    
        addFirst(e);
        return true;
    }
	//在链表尾部添加元素
    public boolean offerLast(E e) {
    
    
        addLast(e);
        return true;
    }

4.3 删除元素

  • 1、remove(int index)
	//按索引删除指定元素
    public E remove(int index) {
    
    
    	//检验index参数是否合法,不合法就抛出IndexOutOfBoundsException
        checkElementIndex(index);
        return unlink(node(index));
    }

    private void checkElementIndex(int index) {
    
    
        if (!isElementIndex(index))
            throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
    }
    //删除节点
    E unlink(Node<E> x) {
    
    
        // assert x != null;
        //取出该节点的数据(用于方法最后作为返回值)
        final E element = x.item;
        //取出该节点的前后节点:prev、next 
        final Node<E> next = x.next;
        final Node<E> prev = x.prev;
		//判断该节点的前驱节点是否存在,不存在的话,该节点的后继节点就是first节点
        if (prev == null) {
    
    
            first = next;
        //否则就将next节点(要删除的节点的后继节点)赋于原有的前驱节点的后继节点,
        //index位置的前驱节点置空
        } else {
    
    
            prev.next = next;
            x.prev = null;
        }
		//判断该节点的后继节点是否存在,如果为空,要删除的节点的前驱节点就变成last节点
        if (next == null) {
    
    
            last = prev;
        //否则后继节点的前驱节点直接prev,index位置的后继节点置空
        } else {
    
    
            next.prev = prev;
            x.next = null;
        }
		//将index位置节点的数据置空。这样原有index位置节点的前驱、后继指针及数据
		//全都为空,便于GC回收,最后size--,返回原有位置的数据
        x.item = null;
        size--;
        modCount++;
        return element;
    }
  • 2、removeFirstOccurrence(Object o) / remove(Object o)
	//删掉第一个出现指定数据的节点
    public boolean removeFirstOccurrence(Object o) {
    
    
        return remove(o);
    }

    public boolean remove(Object o) {
    
    
    	//判断要删除的元素是否为null
    	//如果是null,删除null
        if (o == null) {
    
    
            for (Node<E> x = first; x != null; x = x.next) {
    
    
                if (x.item == null) {
    
    
                    unlink(x);
                    return true;
                }
            }
        //如果不是null,则删除对应的元素
        } else {
    
    
            for (Node<E> x = first; x != null; x = x.next) {
    
    
                if (o.equals(x.item)) {
    
    
                    unlink(x);
                    return true;
                }
            }
        }
        return false;
    }

  remove(int index)、removeFirstOccurrence(Object o) /、remove(Object o),这些方法可以理解为删除链表中间某个节点,图示:

  • 3、pop() / remove() / removeFirst()
	//删除链表首部元素
    public E pop() {
    
    
        return removeFirst();
    }
	//删除链表首部元素
    public E remove() {
    
    
        return removeFirst();
    }
	//删除链表首部元素
    public E removeFirst() {
    
    
        final Node<E> f = first;
        if (f == null)
            throw new NoSuchElementException();
        return unlinkFirst(f);
    }

    private E unlinkFirst(Node<E> f) {
    
    
        // assert f == first && f != null;
        final E element = f.item;
        final Node<E> next = f.next;
        //将原来first节点的数据域和next指针都置空
        f.item = null;
        f.next = null; 
        first = next;
        //判断原有first的next节点是否为空,如果为空,说明此时链表已空,last = null
        if (next == null)
            last = null;
        //否则不处理last节点,只需将原来的next节点的前驱指针置空即可(这样这个节点就变成了first节点)
        else
            next.prev = null;
        size--;
        modCount++;
        return element;
    }

  pop() / remove() / removeFirst()这些方法的作用是删除链表首部元素,图示:

  • 4、removeLast()
	//删除最后一个节点
    public E removeLast() {
    
    
        final Node<E> l = last;
        if (l == null)
            throw new NoSuchElementException();
        return unlinkLast(l);
    }

    private E unlinkLast(Node<E> l) {
    
    
        // assert l == last && l != null;
        //先保存一下last节点的前驱节点,然后将last节点的数据和前驱指针置空
        final E element = l.item;
        final Node<E> prev = l.prev;
        l.item = null;
        l.prev = null; 
        last = prev;
        //判断前驱节点是否为空,如果是的话,就将first节点也置空(此时为空链表)
        if (prev == null)
            first = null;
        //否则将原有前驱节点的后继指针置空
        else
            prev.next = null;
        //size减1
        size--;
        modCount++;
        //返回原有last节点值
        return element;
    }

  removeLast()方法的作用是删除链表尾部元素,与上面的过程相似,不过操作的位置是链表的尾部,图示:

  • 5、removeLastOccurrence(Object o)
	//删除最后一个出现的指定数据
    public boolean removeLastOccurrence(Object o) {
    
    
    	//如果指定元素为null,就从后向前删除null
        if (o == null) {
    
    
            for (Node<E> x = last; x != null; x = x.prev) {
    
    
                if (x.item == null) {
    
    
                    unlink(x);
                    return true;
                }
            }
        //如果指定元素不为null,就从后向前删除对应的元素
        } else {
    
    
            for (Node<E> x = last; x != null; x = x.prev) {
    
    
                if (o.equals(x.item)) {
    
    
                    unlink(x);
                    return true;
                }
            }
        }
        return false;
    }
  • 6、clear()
	//清空链表
    public void clear() {
    
    
    	//每个节点的前驱、后继指针和数据域全部清空,置为null
        for (Node<E> x = first; x != null; ) {
    
    
            Node<E> next = x.next;
            x.item = null;
            x.next = null;
            x.prev = null;
            x = next;
        }
        first = last = null;
        //size置为0
        size = 0;
        modCount++;
    }

  clear()方法的作用是清空链表,图示:

4.4 更新元素

    public E set(int index, E element) {
    
    
    	//检验index参数合法性
        checkElementIndex(index);
        //取出旧的节点值,替换为新的节点值
        Node<E> x = node(index);
        E oldVal = x.item;
        x.item = element;
        //将旧的节点值返回
        return oldVal;
    }

  此处有个node(index)方法,之前一直没介绍:

	//链表中按索引查找一个节点
    Node<E> node(int index) {
    
    
		//判断该索引在链表的前半部分还是在后半部分
		//如果在前半部分,从前向后检索
        if (index < (size >> 1)) {
    
    
            Node<E> x = first;
            for (int i = 0; i < index; i++)
                x = x.next;
            return x;
        //如果在后半部分,从后向前检索
        } else {
    
    
            Node<E> x = last;
            for (int i = size - 1; i > index; i--)
                x = x.prev;
            return x;
        }
    }

  node(index)方法是根据索引获取链表中元素的方法。由于LinkedList是双向链表,存在从前向后、从后向前两种遍历方式。所以在获取某个位置元素时,就可以根据这个元素的索引在前半部还是后半部来决定采用哪种方式获取元素,这也就是node方法的全部,图示:

4.5 获取元素

  • 1、get(int index)
	//获取指定位置节点元素
    public E get(int index) {
    
    
    	//检验index参数合法
        checkElementIndex(index);
        //按索引获取对应元素
        return node(index).item;
    }
  • 2、element() / getFirst() / getLast()
	//获取首节点
    public E element() {
    
    
        return getFirst();
    }
	//获取首节点
    public E getFirst() {
    
    
        final Node<E> f = first;
        if (f == null)
            throw new NoSuchElementException();
        return f.item;
    }
	//获取尾节点
    public E getLast() {
    
    
        final Node<E> l = last;
        if (l == null)
            throw new NoSuchElementException();
        return l.item;
    }
  • 3、peek() / peekFirst() / peekLast()
	//获取但不删除首节点
    public E peek() {
    
    
        final Node<E> f = first;
        return (f == null) ? null : f.item;
    }
	//获取但不删除首节点
    public E peekFirst() {
    
    
        final Node<E> f = first;
        return (f == null) ? null : f.item;
     }
	//获取但不删除尾节点
    public E peekLast() {
    
    
        final Node<E> l = last;
        return (l == null) ? null : l.item;
    }
  • 4、poll() / pollFirst() / pollLast()
	//删除并返回首节点
    public E poll() {
    
    
        final Node<E> f = first;
        return (f == null) ? null : unlinkFirst(f);
    }
	//删除并返回首节点
    public E pollFirst() {
    
    
        final Node<E> f = first;
        return (f == null) ? null : unlinkFirst(f);
    }
	//删除并返回尾节点
    public E pollLast() {
    
    
        final Node<E> l = last;
        return (l == null) ? null : unlinkLast(l);
    }

4.6 检索元素

  • 1、indexOf(Object o)
	//从first节点开始,从前向后检索某个元素所在位置
    public int indexOf(Object o) {
    
    
        int index = 0;
        //判断要检索的元素是否为null,是的话检索null,否则检索元素
        if (o == null) {
    
    
            for (Node<E> x = first; x != null; x = x.next) {
    
    
                if (x.item == null)
                    return index;
                index++;
            }
        } else {
    
    
            for (Node<E> x = first; x != null; x = x.next) {
    
    
                if (o.equals(x.item))
                    return index;
                index++;
            }
        }
        return -1;
    }
  • 2、lastIndexOf(Object o)
	//从last节点开始,从后向前检索某个元素所在位置
    public int lastIndexOf(Object o) {
    
    
        int index = size;
        //判断要检索的元素是否为null,是的话检索null,否则检索元素
        if (o == null) {
    
    
            for (Node<E> x = last; x != null; x = x.prev) {
    
    
                index--;
                if (x.item == null)
                    return index;
            }
        } else {
    
    
            for (Node<E> x = last; x != null; x = x.prev) {
    
    
                index--;
                if (o.equals(x.item))
                    return index;
            }
        }
        return -1;
    }

4.7 迭代器

  • 1、listIterator(int index)
    public ListIterator<E> listIterator(int index) {
    
    
    	//检验index参数合法性
        checkPositionIndex(index);
        //返回一个ListItr对象
        return new ListItr(index);
    }

    private void checkPositionIndex(int index) {
    
    
        if (!isPositionIndex(index))
            throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
    }

	private class ListItr implements ListIterator<E>

  ListItr 类方法较多,不再详细展开,大致介绍一下作用即可,从这些方法中也能看出链表具有双向操作的特点:

	//从指定位置开始,构造一个ListItr迭代器对象
	ListItr(int index)
	//是否还有下一个节点
	public boolean hasNext()
	//获取下一个节点
	public E next()
	//是否有上一个节点
	public boolean hasPrevious()
	//获取上一个节点
	public E previous()
	//获取下一个节点的索引
	public int nextIndex()
	//获取前一个节点的索引
	public int previousIndex()
	//删除最后一个节点
	public void remove()
	//设置最后一个节点的数据
	public void set(E e)
	//在制定位置添加元素
	public void add(E e)
	//按lambda表达式操作链表中的元素
	public void forEachRemaining(Consumer<? super E> action)
  • 2、descendingIterator()
	//获取一个与原链表元素顺序相反的迭代器
    public Iterator<E> descendingIterator() {
    
    
        return new DescendingIterator();
    }

    private class DescendingIterator implements Iterator<E> {
    
    
        private final ListItr itr = new ListItr(size());
        //判断是否有前驱元素
        public boolean hasNext() {
    
    
            return itr.hasPrevious();
        }
        //next取的是前驱元素
        public E next() {
    
    
            return itr.previous();
        }
        public void remove() {
    
    
            itr.remove();
        }
    }
  • 3、spliterator()
    public Spliterator<E> spliterator() {
    
    
        return new LLSpliterator<E>(this, -1, 0);
    }

  这是JDK1.8中增加的创建Spliterator的方法,LLSpliterator的继承关系为:

static final class LLSpliterator<E> implements Spliterator<E>

猜你喜欢

转载自blog.csdn.net/m0_37741420/article/details/107013695