Java中ArrayList和LinkedList基本介绍

文章目录

    • 前言
    • 一、底层数据结构
    • 二、扩容机制
    • 三、常用方法
    • 四、对比
    • 五、小结


前言

List:一个有序(元素存入集合的顺序和取出的顺序一致)容器,元素可以重复,可以插入多个null元素,元素都有索引,常见的实现类有ArrayLust、LinkedList、Vector(线程安全)
ArrayList和LinkedList作为两款单线程环境下常用的List集合(线程不安全),有必要对它们进行较为深入的了解与区分。

一、底层数据结构

  • ArrayList
    ArrayList底层数据结构是动态数组
    其源码如下:
    public class ArrayList<E> extends AbstractList<E> implements List<E>, RandomAccess, Cloneable, Serializable {
     
  private static final long serialVersionUID = 8683452581122892189L;
  private static final int DEFAULT_CAPACITY = 10;
  private static final Object[] EMPTY_ELEMENTDATA = new Object[0];
  private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = new Object[0];
  transient Object[] elementData;
  private int size;
  private static final int MAX_ARRAY_SIZE = 2147483639;
  • LinkedList
    LinkedList底层数据结构是双向链表
    其源码如下:
public class LinkedList<E> extends AbstractSequentialList<E> implements List<E>, Deque<E>, Cloneable, Serializable {
     
    transient int size;
    transient LinkedList.Node<E> first;
    transient LinkedList.Node<E> last;
    private static final long serialVersionUID = 876323262645176354L;
 
  • 测试:
    数组访问快,增删慢;链表访问慢,增删快。下面具体测试下
import java.util.ArrayList;
import java.util.LinkedList;
  public class ListDemo {
     
   public static void main(String[] args) {
     
        ArrayList<Integer> arrayList=new ArrayList<Integer>();
        LinkedList<Integer> linkedList=new LinkedList<Integer>();
        Long ArrayAddStart=System.currentTimeMillis();
        for (int i = 0; i <50000 ; i++) {
     
           arrayList.add(i);
       }
       Long ArrayAddEnd=System.currentTimeMillis();
        Long LinkedAddStart=System.currentTimeMillis();
       for (int i = 0; i <50000 ; i++) {
     
           linkedList.add(i);
       }
        Long LinkedAddEnd=System.currentTimeMillis();
       System.out.println("ArrayList添加50000个数据时间:"+(ArrayAddEnd-ArrayAddStart)+"ms");
       System.out.println("LinkedList添加50000个数据时间:"+(LinkedAddEnd-LinkedAddStart)+"ms");
       Long ArrayGetStart=System.currentTimeMillis();
       for (int i = 0; i <50000 ; i++) {
     
           arrayList.get(i);
       }
       Long ArrayGetEnd=System.currentTimeMillis();
       Long LinkedGetStart=System.currentTimeMillis();
       for (int i = 0; i <50000 ; i++) {
     
           linkedList.get(i);
       }
       Long LinkedGetEnd=System.currentTimeMillis();
       System.out.println("ArrayList访问50000个数据时间:"+(ArrayGetEnd-ArrayGetStart)+"ms");
       System.out.println("LinkedList访问50000个数据时间:"+(LinkedGetEnd-LinkedGetStart)+"ms");
   }
}
/*
程序运行结果:
ArrayList添加50000个数据时间:10ms
LinkedList添加50000个数据时间:0ms
ArrayList访问50000个数据时间:10ms
LinkedList访问50000个数据时间:901ms
*/

二、扩容机制

  • ArrayList
    • 初始化
    /*
    ArrayList有三个构造方法
    第一个构造方法是规定其初始化数组长度,如果传参大于0,则构造传参大小的elementData数组;如果传参小于0,抛异常;如果传参等于0,将默认的EMPTY_ELEMENTDATA数组传给elementData数组
    第二个构造方法是无参构造,将默认的DEFAULTCAPACITY_EMPTY_ELEMENTDATA数组传给elementData数组
    第三个构造方法是传入另一个集合,如果传入集合长度大于0,则调用Arrays.copyOf()方法将传入集合浅拷贝给elementData数组;如果传入集合长度等于0,将默认的EMPTY_ELEMENTDATA数组传给elementData数组
    */
        public ArrayList(int initialCapacity) {
           
        if (initialCapacity > 0) {
           
            this.elementData = new Object[initialCapacity];
        } else {
           
            if (initialCapacity != 0) {
           
                throw new IllegalArgumentException("Illegal Capacity: " + initialCapacity);
            }
    
            this.elementData = EMPTY_ELEMENTDATA;
        }
    
    }
    
    public ArrayList() {
           
        this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
    }
    
    public ArrayList(Collection<? extends E> c) {
           
        this.elementData = c.toArray();
        if ((this.size = this.elementData.length) != 0) {
           
            if (this.elementData.getClass() != Object[].class) {
           
                this.elementData = Arrays.copyOf(this.elementData, this.size, Object[].class);
            }
        } else {
           
            this.elementData = EMPTY_ELEMENTDATA;
        }
    
    }
    
    • 扩容
    /*
    1:add(E e):调用this.add(e, this.elementData, this.size)方法
    2:add(E e, Object[] elementData, int s):如果当前ArrayList的size(即实际ArrayList中元素个数)和elementData.length(即实际ArrayList中一共有多少位置可以放元素)相等,则需要调用this.grow()方法给elementData动态扩容,之后在进行赋值和size++操作;如果长度不相等则直接进行赋值和size++操作
    3:private Object[] grow():调用重载方法this.grow(this.size + 1),将当前集合所需要的最小容量作为传参
    4:private Object[] grow(int minCapacity):调用Arrays.copyOf(this.elementData, this.newCapacity(minCapacity))方法给elementData进行扩容,关键是this.newCapacity(minCapacity)方法获得新的长度。
    5:private int newCapacity(int minCapacity):
      首先newCapacity = oldCapacity + (oldCapacity >> 1),进行1.5倍的容量扩容;
      接着进行判断:
      (1):newCapacity<=minCapacity:则判断当前elementData是否是DEFAULTCAPACITY_EMPTY_ELEMENTDATA(即是否是无参构造出来的),如果是则return Math.max(10, minCapacity)(这就是无参构造时ArrayList初始容量为10的原因),如果minCapacity<0,直接抛出错误,否则返回minCapacity。这里之所以会出现对minCapacity小于0的判断,是因为当this.size=2147483647时,this.size+1=-2147483648(溢出)
      (2):newCapacity>minCapacity:如果1.5倍扩容后的newCapacity<=2147483639,则返回newCapacity,否则返回2147483647(所以ArrayList实际上的最大容量为2147483639)。
      最后依次返回到private Object[] grow()方法,elementData数组长度就扩容完成。
    */
    public boolean add(E e) {
           
        ++this.modCount;
        this.add(e, this.elementData, this.size);
        return true;
    }
    
    private void add(E e, Object[] elementData, int s) {
           
        if (s == elementData.length) {
           
            elementData = this.grow();
        }
    
        elementData[s] = e;
        this.size = s + 1;
    }
    
    private Object[] grow(int minCapacity) {
           
        return this.elementData = Arrays.copyOf(this.elementData, this.newCapacity(minCapacity));
    }
    
    private Object[] grow() {
           
        return this.grow(this.size + 1);
    }
    
    private int newCapacity(int minCapacity) {
           
        int oldCapacity = this.elementData.length;
        int newCapacity = oldCapacity + (oldCapacity >> 1);
        if (newCapacity - minCapacity <= 0) {
           
            if (this.elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
           
                return Math.max(10, minCapacity);
            } else if (minCapacity < 0) {
           
                throw new OutOfMemoryError();
            } else {
           
                return minCapacity;
            }
        } else {
           
            return newCapacity - 2147483639 <= 0 ? newCapacity : hugeCapacity(minCapacity);
        }
    }
    
    private static int hugeCapacity(int minCapacity) {
           
        if (minCapacity < 0) {
           
            throw new OutOfMemoryError();
        } else {
           
            return minCapacity > 2147483639 ? 2147483647 : 2147483639;
        }
    }
    
  • LinkedList
    • 初始化
    /*
    LinkedList只有两个构造方法
    第一个构造方法是无参构造,初始化当前 LinkedList大小为0
    第二个构造方法是传入另外一个集合,将其赋给LinkedList
    注意:linkedList=new LinkedList();()内不能填数字,因为LinkedList没有含有初始大小的构造方法!!!
    */
       public LinkedList() {
           
       this.size = 0;
    }
    public LinkedList(Collection<? extends E> c) {
           
       this();
       this.addAll(c);
    }
    
    • 扩容
      因为LinkedList底层是链表不是数组,增加元素时直接在链表末尾添加就行,因此不需要扩容。

三、常用方法

  • List(ArrayList和LinkedList共有)
    • public int size()返:回此列表中的元素数。
    • public boolean contains(Object o):如果此列表包含指定的元素,则返回true。
    • public int indexOf(Object o):返回此列表中指定元素的第一次出现的索引,如果此列表不包含元素,则返回-1。
    • public int lastIndexOf(Object o):返回此列表中指定元素的最后一次出现的索引,如果此列表不包含元素,则返回-1。
    • public E get(int index):返回此列表中指定位置的元素。
    • public E set(int index, E element):用指定的元素替换此列表中指定位置的元素。
    • public boolean add(E e):将指定的元素追加到此列表的末尾。
    • public void add(int index,E element):在此列表中的指定位置插入指定的元素。
    • public E remove(int index):删除该列表中指定位置的元素。 将任何后续元素移动到左侧(从其索引中减去一个元素)。
    • public boolean remove(Object o):从列表中删除指定元素的第一个出现(如果存在)。
    • public void clear()从列表中删除所有元素。 此呼叫返回后,列表将为空。
    • public Object[] toArray():以正确的顺序(从第一个到最后一个元素)返回一个包含此列表中所有元素的数组。注意该方法返回的数组可以强制转换为基本类型的包装类数组,但不可以转换成基本类型的数组
    • public Object clone():返回此ArrayList实例的浅拷贝。即拷贝的集合和原集合地址不同
    • public Iterator iterator()以正确的顺序返回该列表中的元素的迭代器
    • public void sort(Comparator c) : List使用提供的Comp-arator对此列表进行排序,以比较元素。 底层为优化的快速排序,效率高)
  • LinkedList(LinkedList独有)
    • public boolean offer(E e): 将指定的元素添加为此列表的尾部(最后一个元素)。
    • public E peek():检索但不删除此列表的头(第一个元素)。
    • public E poll()检索并删除此列表的头(第一个元素)。
    • public E remove():检索并删除此列表的头(第一个元素)。
    • 因为LinkedList是由双向链表实现的,因此它的add方法有对应的public void addFirst(E e),public void addLast(E e)方法,同样地,offer,peek,poll,remove方法也有对应的方法
    • remove()方法和poll()方法的区别是,如果此列表为空,remove()抛NoSuchElementException异常,而poll()返回null
    • public E pop():从此列表表示的堆栈中弹出一个元素。 换句话说,删除并返回此列表的第一个元素。 此方法相当于removeFirst() 。
    • public void push(E e)将元素推送到由此列表表示的堆栈上。 换句话说,在该列表的前面插入元素。 此方法相当于addFirst(E) 。

四、对比

  • 数据结构: ArrayList是动态数组数据结构实现;LinkedList是双向链表数据结构实现
  • 访问效率: ArrayList比LinkedList的访问效率要高
  • 增删效率: 在非首尾的增加和删除操作中,LinkedList比ArrayList效率要高
  • 内存空间:LinkedList比ArrayList效率要高,因为LinkedList的节点处理存储数据还存储了两个引用(指向前后元素)

五、小结

在需要频繁读取集合中的元素时,推荐使用ArrayList,而在插入和删除操作较多时,推荐使用LinkedList

你可能感兴趣的:(Java,java,集合)