栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

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

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

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

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

文章目录
  • 集合介绍
    • 集合的理解和好处
    • 集合体系架构
  • collection接口
    • Collection接口实现类的特点
    • 常用方法
    • 遍历方式
      • 迭代器
      • 增强for
  • List接口
    • List接口基本介绍
    • List的三种遍历方式
    • ArrayList
      • 注意事项
      • ArrayList底层结构和源码分析
        • 先说结论
        • ArrayList扩容机制
    • Vector
      • 基本介绍
      • Vector与ArrayList的比较
      • Vector的扩容机制
        • 无参构造
        • 有参构造器
    • LinkedList
      • LinkedList底层操作机制
        • 源码分析
          • 添加元素
          • 删除元素
      • 遍历LinkedList
      • ArrayList 和 LinkedList 比较
      • 如何选择ArrayList和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 extends Iterable 

public interface Iterable {
    
    Iterator 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 extends AbstractList
        implements List, 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;
        }

解读:
1、if (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);
    }

1、 int oldCapacity = elementData.length; 
把当前数组的容量给oldCapacity ,就是原始容量,此时为0
2、int newCapacity = oldCapacity + (oldCapacity >> 1);//右移1位就相当于除以2
直接 0+0/2,也就是说上来就是1.5倍
(这个和之前说的结论不冲突,结论是宏观的角度说的效果,这个是细节,继续看分析)
3、if (newCapacity - minCapacity < 0)
            newCapacity = minCapacity;
如果newCapacity - minCapacity < 0,意思是,1.5扩容的容量(0)小于所需的容量(10),
就把minCapacity赋值给newCapacity ,也就是说扩容容量是10
综上,第一次添加元素的时候,数组element扩容为10
4、  if (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 item;
        Node next;
        Node prev;

        Node(Node prev, E element, Node 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 last;
void linkLast(E e) {
        final Node l = last; //此时last还是null,因为还未添加任何元素
        final Node 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 prev, E element, Node next) {
            this.item = element;//元素
            this.next = next;//指向下一个节点
            this.prev = prev;//指向上一个节点
        }

经过上述分析,第一个节点添加进来后,就是这个样子

再添加元素


void linkLast(E e) {
        final Node l = last;//此时last是上一个添加的Node(这里是1那个Node)
        final Node 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 prev, E element, Node 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 f = first;//第一个节点为ox7028
        if (f == null)
            throw new NoSuchElementException();
        return unlinkFirst(f);
    }

进入unlinkFirst(f)

 private E unlinkFirst(Node f) {
        // assert f == first && f != null;
        final E element = f.item;//取出第一个Node的值,也就是1
        final Node 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的比较

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/874759.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号