Java集合专题1——ArrayList、Vector、LinkedList源码分析与比较

提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档


集合介绍

集合的理解和好处

我们保存数据经常使用数组进行,保存,但是数组有以下特点:

  • 数组
    1)长度开始时必须指定,而一旦指定,不能更改
    2)保存的必须为同一类型的元素
    3)使用数组增加或者删除元素比较麻烦

比如:
Person[] pers = new Person[1];//我们创建了一个大小为1的Person数组
添加元素:pers[0] = new Person();
如果我们想再添加一个元素,因为数组不能扩容,只能再创建一个新的数组,并把之前的元素复制过去。

所以就得有了集合,集合有一下特点:

  • 集合
    1)可以动态保存多个对象,使用比较方便(动态扩容,关于各个集合怎么实现动态扩容的,我们下面会一一讲解,这个是集合的难点也是重点,功利点说面试也是必问的)
    2)提供了一系列方便的操作对象的方法:add 、remove、set、get等

集合体系架构

在这里插入图片描述
我们今天就对上述集合一个一个剖析

首先看一下继承关系图
在这里插入图片描述
在这里插入图片描述
选中collection,然后ctrl+alt+B
在这里插入图片描述
一个个添加进去就好了,最终得到这么一个继承关系图
在这里插入图片描述
在这里插入图片描述

单例集合和双列集合

  • 单例集合:集合里放的都是单个的元素,比如List、Set
  • 双列集合:一个完整的数据,需要两个元素来描述,比如:Map,里面存放的是entry (k-v)
public class Collection_ {
    public static void main(String[] args) {

        ArrayList arrayList = new ArrayList();
        arrayList.add("jack");
        arrayList.add("tom");

        HashMap hashMap = new HashMap();
        hashMap.put("NO1","北京");
        hashMap.put("NO2","上海");
    }
}

collection接口

在这里插入图片描述

Collection接口实现类的特点

在这里插入图片描述

常用方法

在这里插入图片描述

遍历方式

迭代器

  • Iterator对象称为迭代器,主要用于遍历Collection集合中的元素。
  • 所有实现了Collection接口的集合类都有一个iterator()方法,用以返回一个实现了Iterator接口的对象,即可以返回一个迭代器。
  • Iterator 的结构
  • Iterator 仅用于遍历集合,Iterator 本身并不存放对象。
public interface Collection<E> extends Iterable<E> 

public interface Iterable<T> {
    /**
     * Returns an iterator over elements of type {@code T}.
     *
     * @return an Iterator.
     */
    Iterator<T> iterator()

在这里插入图片描述
迭代的执行原理
在这里插入图片描述

在这里插入图片描述示例:

//得到一个集合的迭代器
Iterator iterator = coll.iterator();
//hasNext():判断是否还有下一个元素
while(iterator.hasNext()){
    //next(): 返回迭代中的下一个元素
    System.out.printIn(iterator.next());
}

在调用it.next()方法之前必须调用it.hasNext()进行检测。若不调用,且下一条记录无效,直接调用it.next会抛出 NoSuchElementException异常

public class CollectionIterator {
    public static void main(String[] args) {

        Collection col = new ArrayList();

        col.add(new book("三国演义","罗贯中",20.2));
        col.add(new book("红楼梦","曹雪芹",150.3));
        col.add(new book("小李飞刀","古龙",25.3));

        System.out.println(col);

//        遍历集合
//        1. 得到集合对应的迭代器
        Iterator iterator = col.iterator();
//        2. 使用while循环遍历
        while (iterator.hasNext()){ //判断是否还有数据
            // 返回下一个元素,类型是Object
            Object object = iterator.next();
            System.out.println("onject="+object);
        }
//        提示: 快速生成while循环快捷键 => itit
//        显示快捷提示键  => ctrl+J

    }
}
class book{
    public String name;
    public String author;
    public double price;

    public book(String name, String author, double price) {
        this.name = name;
        this.author = author;
        this.price = price;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getAuthor() {
        return author;
    }

    public void setAuthor(String author) {
        this.author = author;
    }

    public double getPrice() {
        return price;
    }

    public void setPrice(double price) {
        this.price = price;
    }

    @Override
    public String toString() {
        return "book{" +
                "name='" + name + '\'' +
                ", author='" + author + '\'' +
                ", price=" + price +
                '}';
    }
}

增强for

在这里插入图片描述
增强for循环,可以替代iterator迭代器,特点:增强for就是简化版的iterator,本质一样。只能用于遍历集合或数组。

for(元素类型 元素名 : 集合名或数组名){
    访问元素
}

public class CollectionFor {
    public static void main(String[] args) {
        Collection col = new ArrayList();

        col.add(new book("三国演义","罗贯中",20.2));
        col.add(new book("红楼梦","曹雪芹",150.3));
        col.add(new book("小李飞刀","古龙",25.3));

        System.out.println(col);

//        使用增强for循环遍历
//        增强for循环底层依然是迭代器
//        提示:快捷键 =>  col.for
        for(Object book : col){
            System.out.println("book="+book);
        }
//        增强for也可以用在数组上
        int[] nums = {1,25,13,8,5};
        for(int i : nums){
            System.out.println("i="+i);
        }

    }
}

List接口

在这里插入图片描述

List接口基本介绍

  • List集合类中元素有序(即添加顺序和取出顺序一致)、且可重复。
  • List集合中的每个元素都有其对应的顺序索引,即支持索引。
  • List容器中的元素都对应一个整数型的序号记载其在容器中的位置,可以根据序号存取容器中的元素。
  • List接口常用的实现类有:ArrayList、LinkedList 和 Vector。

接口常用方法
在这里插入图片描述

public class ListMethod {
    public static void main(String[] args) {
        List list = new ArrayList();
        list.add("tom'");
        list.add("张三");

//        void add (int index,Object ele)在index位置插入ele元素
        list.add(1,"zhagsan");
        System.out.println(list);

//        boolean addAll(int index,Collection eles)从index位置开始将eles中的所有元素添加进来
        List list2 = new ArrayList();
        list2.add("jack");
        list2.add("tom");

        list.addAll(1,list2);
        System.out.println(list);

//        Object get(int index)获取指定index位置的元素
        Object o = list.get(1);
        System.out.println(o);

//        int indexOf(Object obj)返回obj在集合中首次出现的位置
        System.out.println(list.indexOf("tom"));

//        int lastIndexOf(Object obj)返回obj在当前集合中末次出现的位置
        list.add("jack");
        System.out.println(list.lastIndexOf("jack"));

//        Object remove(int index)移除指定index位置的元素,并返回此元素
        Object remove = list.remove(5);
        System.out.println(remove);

//        Object set(int index,Object ele)设置指定index位置的元素为ele,相当于是替换
        list.set(1,"mary");
        System.out.println(list);

//        List subList(int fromeIndex,int toIndex)返回从fromIndex 到 toIndex 位置的子集合
//        子集合范围为前闭后开   fromeIndex <= subList  <  toIndex
        List subList = list.subList(2, 4);
        System.out.println(subList);

    }
}

List的三种遍历方式

  • 使用iterator
  • 使用增强for
  • 使用普通for
public class ListFor {
    public static void main(String[] args) {

        List list = new ArrayList();
        list.add("jack");
        list.add("tom");
        list.add("marry");
        list.add("bob");
        
//        遍历
        System.out.println("=======迭代器遍历");
//        1. 迭代器
        Iterator iterator = list.iterator();
        while (iterator.hasNext()) {
            Object obj =  iterator.next();
            System.out.println(obj);
        }

        System.out.println("=======增强for循环");
//      2.  增强for循环
        for (Object o : list) {
            System.out.println("o="+o);
        }

        System.out.println("=======普通for循环");
//        3. 普通for循环
        for (int i = 0; i < list.size(); i++) {
            System.out.println("对象="+list.get(i));
        }



    }
}

ArrayList

注意事项

  • ArrayList 可以加入null,并且多个
  • ArrayList是由数组来实现的
  • ArrayList基本等同于Vector,除了 ArrayList是线程不安全(执行效率高),在多线程情况下,不建议使用ArrrayList。

ArrayList底层结构和源码分析

先说结论
  • ArrayList 中维护了一个 Object类型的数组 elementData。
    transient Object[] elementData; // transient 标识瞬间,短暂的,表示该属性不会被序列化
  • 当创建ArrayList对象时,如果使用的是无参构造器,则初始elementData 容量为0,第一次添加,则扩容elementData为10,如需再次扩容,则扩容elementData为1.5倍。
  • 如果使用的是指定大小的构造器,则初始elementData容量为指定大小,如果需要扩容,则直接扩容elementData为1.5倍。

关键源码

public class ArrayList<E> extends AbstractList<E>
        implements List<E>, RandomAccess, Cloneable, java.io.Serializable
{
`     transient Object[] elementData; // non-private to simplify nested class access``
      private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
      private static final Object[] EMPTY_ELEMENTDATA = {};


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


    public ArrayList() {
        this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
    }
ArrayList扩容机制

我们以下面这个例子解读一下ArrayList的扩容机制

public class ArrayListSource {
    public static void main(String[] args) {

        //注意,注意,注意,Idea 默认情况下,Debug 显示的数据是简化后的,如果希望看到完整的数据
        //需要做设置.
        //使用无参构造器创建ArrayList对象
        //ArrayList list = new ArrayList();
        ArrayList list = new ArrayList(8);
        //使用for给list集合添加 1-10数据
        for (int i = 1; i <= 10; i++) {
            list.add(i);
        }
        //使用for给list集合添加 11-15数据
        for (int i = 11; i <= 15; i++) {
            list.add(i);
        }
        list.add(100);
        list.add(200);
        list.add(null);

    }
}

一、首先使用无参构造器创建一个list

ArrayList list = new ArrayList();

我们之前也说过,使用无参构造器会创建一个空数组,下面是关机键代码

    private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
    public ArrayList() {
        this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
    }

因此elementData 就是一个空数组——{}。
往list中添加第一个元素
在这里插入图片描述
进入add().
在这里插入图片描述
执行add操作的时候,每次都先执行ensureCapacityInternal,才执行赋值操作elementData[size++] = e;
在这里插入图片描述

  ensureCapacityInternal(size + 1);
  //size表示当前数组的容量,+1是因为此时向list中添加了一个元素,size+1大小表示list需要的最小容量

ensureCapacityInternal就是ArrayList的扩容机制。继续debug,进入此方法

 private void ensureCapacityInternal(int minCapacity) {
        ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
    }

我们发现ensureExplicitCapacity里还有一个方法calculateCapacity

calculateCapacity(elementData, minCapacity)
//左边参数就是之前传进来的数组list,创建的时候是空的{},右边是当前list需要的最小容量

我们进入calculateCapacity()方法
在这里插入图片描述

 private static int calculateCapacity(Object[] elementData, int minCapacity) {
        if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
            return Math.max(DEFAULT_CAPACITY, minCapacity);
        }
        return minCapacity;
        }

解读:
1if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA)
表示如果当前数组是默认的空数组DEFAULTCAPACITY_EMPTY_ELEMENTDATA,就执行
return Math.max(DEFAULT_CAPACITY, minCapacity);
2、否则直接返回minCapacity
3、我们知道elementData, 之前没有对它进行任何赋值,所以它就是我们创建list的空数组。
4、所以执行
  return Math.max(DEFAULT_CAPACITY, minCapacity);
  返回DEFAULT_CAPACITY和minCapacity的最大的那个
  而 private static final int DEFAULT_CAPACITY = 10;
5、因此,calculateCapacity的返回值是10

calculateCapacity,见名知意,意思就是计算容量,确切的说是此次添加元素,所需的数组的容量。

在这里插入图片描述
再进入ensureExplicitCapacity()

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

        // overflow-conscious code
        if (minCapacity - elementData.length > 0)
            grow(minCapacity);
    }
minCapacity 就是当前所需要的容量,我们由上面知道第一次添加元素的时候,
执行calculateCapacity返回的是10,就是说当前需要的数组容量是10,
而elementData.length是目前数组真正的容量,我们之前没任何添加动作,
所以是0,因此 grow(minCapacity)是要执行的,这是真正的扩容方法。
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);
    }

1int oldCapacity = elementData.length; 
把当前数组的容量给oldCapacity ,就是原始容量,此时为0
2int newCapacity = oldCapacity + (oldCapacity >> 1);//右移1位就相当于除以2
直接 0+0/2,也就是说上来就是1.5倍
(这个和之前说的结论不冲突,结论是宏观的角度说的效果,这个是细节,继续看分析)
3if (newCapacity - minCapacity < 0)
            newCapacity = minCapacity;
如果newCapacity - minCapacity < 0,意思是,1.5扩容的容量(0)小于所需的容量(10),
就把minCapacity赋值给newCapacity ,也就是说扩容容量是10
综上,第一次添加元素的时候,数组element扩容为10
4if (newCapacity - MAX_ARRAY_SIZE > 0)
//10小于MAX_ARRAY_SIZE 所以下面不执行先忽略后面说
            newCapacity = hugeCapacity(minCapacity);
5、 elementData = Arrays.copyOf(elementData, newCapacity);
把原来的数组,复制到一个新的大小为newCapacity的数组上
记住Arrays.copyOf其实每次都创建一个新的数组。
这也就解释了为什么不频繁的扩容,第一次添加元素的时候就给初始容量10.
前面也说了,数组是不能动态扩容的,每次用完想再添加元素,只能创建一个新的。
会占用更多的堆内存

继续debug
在这里插入图片描述

在这里插入图片描述

到此,第一个元素,添加完毕!
当添加下一个元素时,因为数组一开始就给10容量,所以再继续添加,再10容量范围内也不会再扩容,直到10用完。
我们不停的往list中添加10个元素
在这里插入图片描述
当前数组容量用完时,添加第11个元素,继续分析之前的方法
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

因为数组已经有元素了,所以所需的大小就不是10了,而是11
在这里插入图片描述

private void ensureExplicitCapacity(int minCapacity) {
        modCount++;

        // overflow-conscious code
        if (minCapacity - elementData.length > 0)
            grow(minCapacity);
    }
现在所需的容量是11(minCapacity ),而当前数组大小是10(elementData.length)
所以继续扩容

进入grow(minCapacity)
在这里插入图片描述

private void grow(int minCapacity) {
        // overflow-conscious code
        int oldCapacity = elementData.length; //10
         //直接1.5倍。所以newCapacity为15
        int newCapacity = oldCapacity + (oldCapacity >> 1);
         //扩容后的容量(newCapacity )是15,大于所需容量(minCapacity )10
         //所以下面方法不执行
        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:
        //把原来数组的复制到一个大小为newCapacity(15)的新数组上,思想是一样的
        elementData = Arrays.copyOf(elementData, newCapacity);
    }

至此,数组由10扩容为1.5倍,也就是15.

当容量15再用完时,还是会按照上述进行继续扩容1.5倍。。。。。

二、当使用有参的构造器创建ArrayList

 ArrayList list = new ArrayList(8);
@SuppressWarnings({"all"})
public class ArrayListSource {
    public static void main(String[] args) {
        ArrayList list = new ArrayList(8);
        //使用for给list集合添加 1-10数据
        for (int i = 1; i <= 10; i++) {
            list.add(i);
        }
        //使用for给list集合添加 11-15数据
        for (int i = 11; i <= 15; i++) {
            list.add(i);
        }
        list.add(100);
        list.add(200);
        list.add(null);

    }
}

构造器

public ArrayList(int initialCapacity) {
        if (initialCapacity > 0) {//8>0
            this.elementData = new Object[initialCapacity];
        } else if (initialCapacity == 0) {
            this.elementData = EMPTY_ELEMENTDATA;
        } else {
            throw new IllegalArgumentException("Illegal Capacity: "+
                                               initialCapacity);
        }
    }

因此对于有参构造器,直接创建一个new Object[initialCapacity]的数组,并赋值给elementData 。所以此时elementData 容量为8
在这里插入图片描述

第一次添加元素时,继续判断是否要扩容
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

elementData创建的时候,就给了容量8, 不是空数组了,因为所需容量为1(minCapacity)

在这里插入图片描述

所需容量为1(minCapacity),当前容量为8(elementData),因此不需要扩容。

继续往list中添加元素,当初始容量8用完后,再添加元素。
在这里插入图片描述

同样进入add()方法
在这里插入图片描述

在这里插入图片描述
根据之前的分析,所需容量是9,当前数组容量是8,需要扩容

在这里插入图片描述
在这里插入图片描述
同样直接1.5倍,将原数组的元素复制到容量为12的新数组上
在这里插入图片描述

在这里插入图片描述
以后再用完,再按照上面扩容1.5倍。

Vector

基本介绍

在这里插入图片描述

Vector与ArrayList的比较

在这里插入图片描述

Vector的扩容机制

无参构造
public class Vector_ {
    public static void main(String[] args) {
        //无参构造器
        Vector vector = new Vector();
        for (int i = 0; i < 10; i++) {
            vector.add(i);
        }
        vector.add(100);
        System.out.println("vector=" + vector);
 

    }
}

     public Vector() {
        this(10);
    }
     public Vector(int initialCapacity) {
        this(initialCapacity, 0);
    }
    public Vector(int initialCapacity, int capacityIncrement) {
        super();
        if (initialCapacity < 0)
            throw new IllegalArgumentException("Illegal Capacity: "+
                                               initialCapacity);
        this.elementData = new Object[initialCapacity];
        this.capacityIncrement = capacityIncrement;
    }

它是在空参构造器Vector()中,调用一个参数的有参构造器Vector(int initialCapacity),再调用两个参数的有参构造器Vector(int initialCapacity, int capacityIncrement),
最终执行this.elementData = new Object[initialCapacity];
所以对于Vector,如果使用无参构造器创建的话,会直接创建一个容量为10的数组。

添加第一个元素
在这里插入图片描述
进入add(),synchronized修饰,线程安全

 public synchronized boolean add(E e) {
        modCount++;
        ensureCapacityHelper(elementCount + 1);
        elementData[elementCount++] = e;
        return true;
    }

还是一样先判断要不要扩容
在这里插入图片描述
比较所需容量大小(minCapacity)和当前数组容量大小(elementData.length),判断是否需要扩容,因为当前容量10大于所需容量1,所以不扩容
在这里插入图片描述
当容量10用完,再向里面添加元素
在这里插入图片描述

判断是否进行扩容,所需容量11大于当前容量10,所以扩容
在这里插入图片描述
在这里插入图片描述

   private void grow(int minCapacity) {
        // overflow-conscious code
        int oldCapacity = elementData.length;//原容量10
        int newCapacity = oldCapacity + ((capacityIncrement > 0) ?
                                         capacityIncrement : oldCapacity);
        if (newCapacity - minCapacity < 0)
            newCapacity = minCapacity;
        if (newCapacity - MAX_ARRAY_SIZE > 0)
            newCapacity = hugeCapacity(minCapacity);
        elementData = Arrays.copyOf(elementData, newCapacity);
    }
capacityIncrement为0,所以直接2

当以后容量再不够时,直接2倍扩容

有参构造器
public class Vector_ {
    public static void main(String[] args) {
        //无参构造器
        //有参数的构造
        Vector vector = new Vector(8);
        for (int i = 0; i < 10; i++) {
            vector.add(i);
        }
        vector.add(100);
        System.out.println("vector=" + vector);

    }
}

构造器

public Vector(int initialCapacity) {
        this(initialCapacity, 0);
    }

直接创建一个容量为8的数组,当容量用完了,直接2倍扩容

LinkedList

  • LinkedList底层实现了双向链表和双端队列特点
  • 可以添加任意元素(元素可以重复),包括null
  • 线程不安全,没有实现同步

LinkedList底层操作机制

  • LinkedList 底层维护了一个双向链表

  • LinkedList中维护了两个属性 first 和 last 分别指向 首节点和尾节点

  • 每个节点(Node对象),里面又维护了prev、next、item、三个属性,其中通过prev指向前一个,通过next指向后一个节点。最终实现双向链表

  • LinkedList的元素的添加和删除,不是通过数组完成的,相对来说效率较高。

在这里插入图片描述

一个元素就是一个Node节点
Node其实是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;
        }
    }
源码分析
添加元素
@SuppressWarnings({"all"})
public class LinkedListCRUD {
    public static void main(String[] args) {

        LinkedList linkedList = new LinkedList();
        linkedList.add(1);
        linkedList.add(2);
        linkedList.add(3);
        System.out.println("linkedList=" + linkedList);

       }
}


构造器:

public LinkedList() {
}

添加第一个元素
在这里插入图片描述

进入add()
在这里插入图片描述

 public boolean add(E e) {
        linkLast(e);
        return true;
    }

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

transient Node<E> last;
void linkLast(E e) {
        final Node<E> l = last; //此时last还是null,因为还未添加任何元素
        final Node<E> newNode = new Node<>(l, e, null);//创建一个新的节点
        last = newNode;//尾节点指向新Node
        if (l == null)//此时l为空,因为是第一个元素
            first = newNode;//头节点也指向新Node
        else
            l.next = newNode;
        size++;//大小加1
        modCount++;//记录被修改的次数
    }

//这里额外给出Node的构造器
  Node(Node<E> prev, E element, Node<E> next) {
            this.item = element;//元素
            this.next = next;//指向下一个节点
            this.prev = prev;//指向上一个节点
        }

经过上述分析,第一个节点添加进来后,就是这个样子
在这里插入图片描述

再添加元素
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

void linkLast(E e) {
        final Node<E> l = last;//此时last是上一个添加的Node(这里是1那个Node)
        final Node<E> newNode = new Node<>(l, e, null);//再创建一个新Node
        last = newNode;//last指向新Node
        if (l == null)//不是第一次添加,下面不执行
            first = newNode;
        else
            l.next = newNode;//把上一个节点的next执行新node
       size++;//大小加1
        modCount++;//记录被修改的次数
    }
//这里额外给出Node的构造器
  Node(Node<E> prev, E element, Node<E> next) {
            this.item = element;//元素
            this.next = next;//指向下一个节点
            this.prev = prev;//指向上一个节点
        }

经过分析,节点添加进来后,就是这个样子
在这里插入图片描述

再添加元素时,继续按照上述规则,最终形成一个双向链表
在这里插入图片描述

删除元素
public class LinkedListCRUD {
    public static void main(String[] args) {

        LinkedList linkedList = new LinkedList();
        linkedList.add(1);
        linkedList.add(2);
        linkedList.add(3);
        System.out.println("linkedList=" + linkedList);

        //演示一个删除结点的
        linkedList.remove(); // 这里默认删除的是第一个结点
     
    }
}

在这里插入图片描述
进入remove()
在这里插入图片描述
所以默认删除的是第一个节点

 public E removeFirst() {
        final Node<E> f = first;//第一个节点为ox7028
        if (f == null)
            throw new NoSuchElementException();
        return unlinkFirst(f);
    }

进入unlinkFirst(f)

在这里插入图片描述

 private E unlinkFirst(Node<E> f) {
        // assert f == first && f != null;
        final E element = f.item;//取出第一个Node的值,也就是1
        final Node<E> next = f.next;//取被删除节点的下一个节点
        f.item = null;//node属性值置为null
        f.next = null; // help GC  // 断掉被删除节点执向下一个节点的引用
        first = next;//链表的头节点改为指向下一个节点
        if (next == null)//如果当前节点不存在下一个节点了,当前节点就是最后一个
            last = null;//断掉尾节点指向它的引用
        else//否则
            next.prev = null;//下一个节点作为第一个元素
        size--;//大小减1
        modCount++;//修改次数加1
        return element;//返回1,也就是说删除操作是可以知道删除的是哪一个的。
    }

在这里插入图片描述

我们也可以删除指定的元素
linkedList.remove(2);
操作是差不多,就是改变各种执行该节点的引用,让它孤立出来等待垃圾回收,然后重新形成一个双向链表。

其实这种孤立出来的节点叫做不可触及对象,想学习JVM,可以看我关于JVM的博客

遍历LinkedList

Iterator iterator = linkedList.iterator();
        while (iterator.hasNext()) {
            Object next =  iterator.next();
            System.out.println("next=" + next);

        }

        System.out.println("===LinkeList遍历增强for====");
        for (Object o1 : linkedList) {
            System.out.println("o1=" + o1);
        }
        System.out.println("===LinkeList遍历普通for====");
        for (int i = 0; i < linkedList.size(); i++) {
            System.out.println(linkedList.get(i));
        }

ArrayList 和 LinkedList 比较

在这里插入图片描述

如何选择ArrayList和LinkedList:

  • 如果改查的操作较多,选择ArrayList
  • 如果增删的操作较多,选择LinkedList
  • 一般来说,程序中,80%-90%都是查询,因此大部分情况下选择ArrayList
  • 在一个项目中,根据业务灵活选择,也可能这样,一个模块使用的是ArrayList,另外一个模块是LinkedList。根据业务合理选择。
  • 如果想要线程安全就有Vector

总结

  • ArrayList底层数据结构和源码分析:
    1、ArrayList可以加入null,并可以为多个
    2、ArrayList由数组来实现存储的
    3、ArrayList基本等同于Vector,但是ArrayList是线程不安全的

  • ArrayList的扩容机制
    1、ArrayList维护了一个Object类型的数组elementData
    2、当创建对象的时,如果使用的是无参构造器,则初始化elementData容量为0,
    3、当添加元素时:先判断是否需要扩容,如果需要扩容,则调用grow方法,否则直接添加元素到适合的位置
    4、如果使用的是无参构造,如果第一次添加,需要扩容的话,则扩容为10,如果需要再次扩容的话,则扩容为1.5倍
    5、如果使用的是指定容量capacity的构造器,初始数组容量为capacity,如果需要扩容,则直接扩容为1.5倍

  • Vector:
    1、Vector的底层也是一个对象数组(同ArrayList)
    2、Vector是线程同步的,即线程安全,Vector的操作方法带有synchronized
    3、在开发中,需要线程同步安全时,需要考虑使用Vector

  • Vector和ArrayList的比较

底层结构线程安全效率扩容倍数
ArrayList可变数组不安全效率高如果有参1.5。如果无参:1、第一次10;2、从第二次开 始按1.5倍扩
Vector可变数组安全效率低如果无参,默认10,满后按照2倍扩如果指定大小,每次直接按2倍扩

注意对于空参的方式,verctor是创建的时候,直接初始化数组容量为10,而ArrayList是在第一次添加元素的时候,才初始化容量为10

  • LinkedList
    1、LinkedList实现了双向链表和双端队列的特点
    2、可以添加任意元素,包括null
    3、线程不安全,没有实现同步

  • LinkedList底层操作机制
    1、LinkedList底层维护了一个双向链表
    2、LinkedList中维护了两个属性first和last,分别指向首节点和尾节点
    3、每个节点(node),里面维护了prev、next、item三个属性,其中通过prev指向前一个,通过next指向后一个节点,最终实现双向链表

-Array List和LinkedList的比较
在这里插入图片描述

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值