集合List源码ArrayList、LinkedList

多线程与高并发集合中咱们了解了集合,但是只了解了存在哪些集合,以及BlockingQueue的使用,今天来完善下ArrayList和LinkedList底层是如何实现的。
多线程与高并发_集合
了解ArrayList和LinkedList之前咱们先看下他们的类图
在这里插入图片描述

简单使用

public static void main(String[] args) {
    List<Object> arrayList = new ArrayList<>();
    LinkedList<Object> linkedList = new LinkedList<>();
    arrayList.add(1);
    arrayList.get(1);
    arrayList.remove(1);

    linkedList.add(1);
    linkedList.get(1);
    linkedList.addFirst(1);
    linkedList.addLast(1);
    linkedList.removeFirst();
    linkedList.removeLast();
}

优缺点

ArrayList:
	底层是一个Object数组实现,咱们都知道,数组大小是固定的,而且是有序分布。
	线程非安全,查询快,增删慢,扩容需要重新申请空间再copy对象。
LinkedList:
	底层是使用双向链表结构实现,链表只需要维护对象的引用,不需要维护大小。
	线程非安全,增删快,查询慢,无需扩容操作。

源码

ArrayList

成员
//默认的数组空间大小
private static final int DEFAULT_CAPACITY = 10;
//数组中的元素数量
private int size;
//被修改次数
protected transient int modCount = 0;
//数组容器
transient Object[] elementData;
add(E e)方法
//添加元素
public boolean add(E e) {
    //保证数组容器可以放入元素
    ensureCapacityInternal(size + 1);
    //对数组元素个数的下标进行放入元素
    elementData[size++] = e;
    return true;
}
ensureCapacityInternal(int minCapacity)方法
private void ensureCapacityInternal(int minCapacity) {
	//第一次添加元素需要初始化数组大小
	if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
	    minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
	}
   	//确保数组容量
   	ensureExplicitCapacity(minCapacity);
}
ensureCapacityInternal(int minCapacity)方法
private void ensureExplicitCapacity(int minCapacity) {
    //修改次数+1
    modCount++;
    //如果原来的容器存放不下
    if (minCapacity - elementData.length > 0)
        //进行扩容
        grow(minCapacity);
}
grow(minCapacity)方法
//从这里可以看出,添加元素的时候可能涉及到扩容,扩容就需要申请内存,移动数组元素
private void grow(int minCapacity) {
    // 求出数组中的最大容量
    int oldCapacity = elementData.length;
    //新的容量如果是10,那么新的数组长度是15,其实就增加了50%容量
    int newCapacity = oldCapacity + (oldCapacity >> 1);
    if (newCapacity - minCapacity < 0)
        newCapacity = minCapacity;
    if (newCapacity - MAX_ARRAY_SIZE > 0)
    	//使用最大的容量MAX_ARRAY_SIZE和Integer.MAX_VALUE之间
        newCapacity = hugeCapacity(minCapacity);
    //把老数组中的数据迁移到新数组中去
    elementData = Arrays.copyOf(elementData, newCapacity);
}
get(int index)方法
public E get(int index) {
	//检查下标
    rangeCheck(index);
	//直接返回对应下标的元素
    return elementData(index);
}
remove(int index)方法

插入排序

//删除元素
public E remove(int index) {
	//检查下标
    rangeCheck(index);
	//修改数据
    modCount++;
    //先获取到对应下标的元素
    E oldValue = elementData(index);
	//找到删除元素后面的元素
    int numMoved = size - index - 1;
    //从这里可以看出,删除的时候很慢,需要移动数组元素,最好O1,最坏O(n-1)
    //如果后面有元素
    if (numMoved > 0)
    	//把后面的元素向前移一位<类似插入排序的思想,有不了解插入排序的伙伴可以百度下或者博主前面文章有插入排序>
        System.arraycopy(elementData, index+1, elementData, index,numMoved);
    //删除最后一位的引用,help gc
    elementData[--size] = null;
	//返回删除后的元素
    return oldValue;
}
iterator()方法
//获取迭代器
public Iterator<E> iterator() {
 return new Itr();
}
//ArrayList.Itr实现了Iterater接口
private class Itr implements Iterator<E> {
	//下一个元素下标
    int cursor;
    //返回元素的下标
    int lastRet = -1;
    //为了判断防止多线程操作
    int expectedModCount = modCount;

    Itr() {}
	//判断是否有下一个元素,用当前遍历到的元素与元素的个数进行比较
    public boolean hasNext() {
        return cursor != size;
    }
	//获取当前元素 完全由cursor和lastRet成员控制
    @SuppressWarnings("unchecked")
    public E next() {
    	//判断modCount和expectedModCount是否一致,不一致说明存在别的线程更新list,这里明确的告诉了我们,ArrayList不支持多线程
        checkForComodification();
        //获取到初始位置,默认从第一个元素开始遍历
        int i = cursor;
        //判断是否下标越界
        if (i >= size)
            throw new NoSuchElementException();
        //获取到容器数组
        Object[] elementData = ArrayList.this.elementData;
        //当前游标不能大于等于数组长度,这种情况也是只有多想称情况才能出现
        if (i >= elementData.length)
            throw new ConcurrentModificationException();
        //对下一个元素的下标+1操作
        cursor = i + 1;
        //返回元素并且把原来的游标赋值给lastRet
        return (E) elementData[lastRet = i];
    }
	//迭代器中删除元素
    public void remove() {
    	//验证最后一次返回的元素,从这里可以看出,咱们删除元素之前最好显示的调用next()方法
        if (lastRet < 0)
            throw new IllegalStateException();
        //检查是否有多线程参与
        checkForComodification();
        try {
        	//调用ArrayList删除方法
            ArrayList.this.remove(lastRet);
            //删除元素后把游标放到删除的元素位置
            cursor = lastRet;
            //?????这里不明白为什么要设置成-1,lastRet对next方法无影响
            lastRet = -1;
            //重置expectedModCount
            expectedModCount = modCount;
        } catch (IndexOutOfBoundsException ex) {
            throw new ConcurrentModificationException();
        }
    }
}

LinkedList

大家有兴趣的可以看看ArrayDeque源码,底层使用数组实现。咱们今天就不过赘述了

成员

LinkedList设计巧妙的地方是维护了元素的个数,记录了头尾节点

//记录元素个数
transient int size = 0;
//头节点
transient Node<E> first;
//尾节点
transient Node<E> last;
//记录修改的次数
protected transient int modCount = 0;
//节点对象 双向链表
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;
    }
}
addFirst(E e)
//向链表头部插入元素
public void addFirst(E e) {
    linkFirst(e);
}
linkFirst(e)
private void linkFirst(E e) {
	//获取到头节点
    final Node<E> f = first;
    //创建一个新节点,pre=null、next=f
    final Node<E> newNode = new Node<>(null, e, f);
    //把最新添加的Node节点设置为头节点
    first = newNode;
    //这里就比较有意思了,它把一个链表切分成两份,减少根据索引查找的时间复杂度
    //如果头节点为null
    if (f == null)
    	//尾节点也设置成新增加的Node节点,从这里可以看出,删除的时候一样也会有类似的操作,咱们继续向下看
    	//这样头尾节点就能连接起来了
        last = newNode;
    else
    	//头节点不为null正常添加元素,把old first的prev引用设置为newNode
        f.prev = newNode;
    //元素个数+1
    size++;
    //修改次数+1
    modCount++;
}
addLast(E e)
//向尾部添加元素
public void addLast(E e) {
 linkLast(e);
}
linkLast(e)
void linkLast(E e) {
	//得到尾节点
    final Node<E> l = last;
    //创建新节点
    final Node<E> newNode = new Node<>(l, e, null);
    //把新节点设置为尾节点
    last = newNode;
    //如果原来的尾节点为null
    if (l == null)
    	//把新节点也设置为头节点
        first = newNode;
    else
    	//不为null正常增加新节点到尾节点
        l.next = newNode;
    //元素+1
    size++;
    //修改次数+1
    modCount++;
}
removeFirst()
//删除头部元素
public E removeFirst() {
    final Node<E> f = first;
    //如果头节点为null,那么尾节点一定为null
    if (f == null)
        throw new NoSuchElementException();
    return unlinkFirst(f);
}
unlinkFirst(Node f)
private E unlinkFirst(Node<E> f) {
    //得到当前元素,用于返回
    final E element = f.item;
    //找到下一节点
    final Node<E> next = f.next;
    //解开引用 help GC
    f.item = null;
    f.next = null;
    //下一个节点升级为头节点
    first = next;
    if (next == null)
    	//如果下一个节点为null,说明first = last
        last = null;
    else
    	//断开引用
        next.prev = null;
    //元素-1
    size--;
    //修改+1
    modCount++;
    //返回删除的元素
    return element;
}
unlinkFirst(Node f)
//删除尾节点
public E removeLast() {
    final Node<E> l = last;
    if (l == null)
        throw new NoSuchElementException();
    return unlinkLast(l);
}
unlinkFirst(Node f)
private E unlinkLast(Node<E> l) {
	//获取当前元素
    final E element = l.item;
    //得到前置节点
    final Node<E> prev = l.prev;
    //解引用
    l.item = null;
    l.prev = null; 
    //前置节点升级为尾节点
    last = prev;
    if (prev == null)
    	//如果前置节点为null,头节点一定为null
        first = null;
    else
    	//断开引用
        prev.next = null;
    //元素个数-1
    size--;
    //修改次数+1
    modCount++;
    //返回删除的元素
    return element;
}

getLast()和pollLast()有兴趣的自己看下吧,这两个方法上面都有了解

好了,今天的分享就到这里了,对文章敢兴趣的加加关注啦~~~

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值