JUC并发—1.Java集合包底层源码剖析

大纲

1.为什么要对JDK源码剖析

2.ArrayList源码一:基本原理以及优缺点

3.ArrayList源码二:核心方法的原理

4.ArrayList源码三:数组扩容以及元素拷贝

5.LinkedList源码一:优缺点和使用场景

6.LinkedList源码二:双向链表数据结构

7.LinkedList源码三:插入元素的原理

8.LinkedList源码四:获取元素的原理

9.LinkedList源码五:删除元素的原理

10.Vector和Stack:栈的数据结构和源码

11.HashMap源码一:数组 + 链表 + 红黑树

12.HashMap源码二:核心成员变量的作用

13.HashMap源码三:降低哈希冲突概率的算法

14.HashMap源码四:put操作及哈希寻址算法

15.HashMap源码五:哈希冲突时的链表处理

16.HashMap源码六:引入红黑树优化哈希冲突

17.HashMap源码七:哈希冲突时插入红黑树

18.HashMap源码八:JDK 1.7的数组扩容原理

19.HashMap源码九:JDK 1.8的数组扩容原理

20.HashMap源码十:get与remove操作源码

21.LinkedHashMap有顺序的Map数据结构

22.TreeMap可自定义排序规则的红黑树Map

23.HashSet + LinkedHashSet + TreeSet简介

24.迭代器应对多线程并发修改的Fail-Fast机制

1.为什么要对JDK源码剖析

简单的框架源码有:Spring Cloud,ByteTcc分布式事务,Redisson以及Curator等框架源码。

较难的框架源码有:ZooKeeper,Dubbo、Netty、RocketMQ、Sharding-JDBC等框架源码。

看这些源码的时候,都有一些基础性的代码逻辑,比如:内存里的各种数据结构(List、Map、Set)、并发、网络请求、磁盘IO等。

其实对于各种各样的框架系统的底层,最核心的部分,基本都是:

一.集合(在内存里面存放数据)

二.并发(系统底层都会开多个线程进行并发处理,会涉及锁、同步等)

三.IO(读写磁盘上的文件数据、或者发起网络IO通过网络读写数据)

四.网络(在分布式系统中给各个机器建立网络连接,互相发送请求通信)

2.ArrayList源码一:基本原理以及优缺点

(1)数组长度固定需要扩容和拷贝

(2)添加元素导致大量移动元素

(3)基于数组非常适合随机读

(4)总结

(1)数组长度固定需要扩容和拷贝

由于Java里的数组都是定长数组,数组的长度是固定的。比如数组大小设置为100,此时不停的往ArrayList里面塞入数据,当元素数量超过了100以后,此时就会发生数组的扩容,就会申请更大的数组,把旧数组元素拷贝到新数组里去。

这个数组扩容 + 元素拷贝的过程,相对来说会慢一些。所以使用ArrayList时要注意,不要频繁往ArraList里面去塞数据,而导致频繁的数组扩容影响性能。

(2)添加元素导致大量移动元素

由于基于数组来实现,当往数组中添加元素,会导致移动元素。

(3)基于数组非常适合随机读

由于基于数组来实现,所以非常适合随机读。可以随机的去读数组中的某个元素,这个随机读的性能是比较高的,可以直接通过内存地址来定位某个元素。

(4)总结

如果不会频繁插入元素,导致频繁的移动元素位置、List扩容,而主要用于遍历集合或通过索引随机读取元素,那么可以用ArrayList。

如果会频繁插入元素到List中,那么尽量还是不要用ArrayList,因为很可能会造成大量的元素移动 + 数组扩容 + 元素拷贝。

3.ArrayList源码二:核心方法的原理

(1)构造方法的源码

(2)add()方法的源码

(3)set()方法的源码

(4)add(index, element)方法的源码

(5)get()方法的源码

(6)remove()方法的源码

(7)源码分析的总结

(1)构造方法的源码

如果不传参直接初始化一个ArrayList对象,则会执行默认的构造方法。默认的构造方法会将内部的数组设成一个默认的空数组。

public class ArrayList<E> extends AbstractList<E> implements List<E>, RandomAccess, Cloneable, java.io.Serializable {
    private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
    transient Object[] elementData;
    ...
    
    //Constructs an empty list with an initial capacity of ten.
    public ArrayList() {
        this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
    }
    ...
}

ArrayList有一个默认的初始化数组大小的数值是10,可认为默认的数组初始化大小就只有10个元素,但这是往ArrayList添加元素时才触发的。

public class ArrayList<E> extends AbstractList<E> implements List<E>, RandomAccess, Cloneable, java.io.Serializable {
    //Default initial capacity.
    private static final int DEFAULT_CAPACITY = 10;
    private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
    transient Object[] elementData;
    ...
    
    public boolean add(E e) {
        ensureCapacityInternal(size + 1);//Increments modCount!!
        elementData[size++] = e;
        return true;
    }
  
    private void ensureCapacityInternal(int minCapacity) {
        ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
    }
  
    private static int calculateCapacity(Object[] elementData, int minCapacity) {
        if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
            return Math.max(DEFAULT_CAPACITY, minCapacity);
        }
        return minCapacity;
    }
  
    private void ensureExplicitCapacity(int minCapacity) {
        modCount++;
        //overflow-conscious code
        if (minCapacity - 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);
    }
    ...
}

使用ArrayList的场景,应该不会有频繁的插入、移除元素的操作,基本可以知道它里面有多少元素,所以应该不会使用默认的构造方法。

一般给ArrayList构造时,会初始化一个合适的数组大小,避免数组太小插入塞入数据时导致频繁扩容。

public class ArrayList<E> extends AbstractList<E> implements List<E>, RandomAccess, Cloneable, java.io.Serializable {
    private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
    transient Object[] 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);
        }
    }
    ...
}

(2)add()方法的源码

每次往ArrayList添加元素时,都会判断一下当前数组元素是否满了。如果满了,此时就会对数组进行扩容。然后将旧数组中的元素拷贝到新数组中,确保数组能承受更多元素。

public class ArrayList<E> extends AbstractList<E> implements List<E>, RandomAccess, Cloneable, java.io.Serializable {
    //Default initial capacity.
    private static final int DEFAULT_CAPACITY = 10;
    private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
    transient Object[] elementData;
    ...
    
    public boolean add(E e) {
        ensureCapacityInternal(size + 1);//Increments modCount!!
        elementData[size++] = e;
        return true;
    }
  
    private void ensureCapacityInternal(int minCapacity) {
        ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
    }
  
    private static int calculateCapacity(Object[] elementData, int minCapacity) {
        if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
            return Math.max(DEFAULT_CAPACITY, minCapacity);
        }
        return minCapacity;
    }
  
    private void ensureExplicitCapacity(int minCapacity) {
        modCount++;
        //overflow-conscious code
        if (minCapacity - 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);
    }
    ...
}

(3)set()方法的源码

public class ArrayList<E> extends AbstractList<E> implements List<E>, RandomAccess, Cloneable, java.io.Serializable {
    transient Object[] elementData;
    ...
    
    public E set(int index, E element) {
        rangeCheck(index);//检查是否数组越界
        E oldValue = elementData(index);
        elementData[index] = element;
        return oldValue;
    }
  
    private void rangeCheck(int index) {
        if (index >= size) {
            throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
        }
    }
    ...
}

(4)add(index, element)方法的源码

public class ArrayList<E> extends AbstractList<E> implements List<E>, RandomAccess, Cloneable, java.io.Serializable {
    transient Object[] elementData;
    ...
    
    //往随机位置index插入元素
    public void add(int index, E element) {
        rangeCheckForAdd(index);//检查是否数组越界
        ensureCapacityInternal(size + 1);
        //System.arraycopy()方法会将elementData数组的index位置后的数据进行拷贝
        System.arraycopy(elementData, index, elementData, index + 1, size - index);
        elementData[index] = element;
        size++;
    }
  
    private void rangeCheck(int index) {
        if (index >= size) {
            throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
        }
    }
    ...
}

(5)get()方法的源码

public class ArrayList<E> extends AbstractList<E> implements List<E>, RandomAccess, Cloneable, java.io.Serializable {
    transient Object[] elementData;
    ...
    
    public E get(int index) {
        rangeCheck(index);//检查是否数组越界
        return elementData[index];
    }
  
    private void rangeCheck(int index) {
        if (index >= size) {
            throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
        }
    }
    ...
}

get()方法最简单,基于数组直接定位到元素,这是ArrayList性能最好的一个操作。

(6)remove()方法的源码

public class ArrayList<E> extends AbstractList<E> implements List<E>, RandomAccess, Cloneable, java.io.Serializable {
    transient Object[] elementData;
    ...
    
    public E remove(int index) {
        rangeCheck(index);
        modCount++;
        E oldValue = elementData(index);
        int numMoved = size - index - 1;
        if (numMoved > 0) {
            System.arraycopy(elementData, index + 1, elementData, index, numMoved);
        }
        elementData[--size] = null;
        return oldValue;
    }
    ...
}

(7)源码分析的总结

一.remove()和add(index, element)

都会导致数组的拷贝(System.arraycopy()),因此性能都不是太高。所以基于ArrayList来进行随机位置的插入和删除,性能不会太高。

二.add()和add(index, element)

都可能会导致数组需要扩容(ensureCapacityInternal())。由于数组长度是固定的,默认初始大小是10。如果往数组里添加数据,可能会导致数组不停扩容,影响性能。

三.set()和get()

可以基于数组实现随机位置的直接定位,性能很高。

4.ArrayList源码三:数组扩容以及元素拷贝

(1)每次添加元素都判断是否扩容

(2)数组扩容一半时会进行数组拷贝

(1)每次添加元素都判断是否扩容

ArrayList里最关键的就是:如果数组满了,如何进行扩容,每当往ArrayList添加元素都会调用ensureCapacityInternal()方法。

public class ArrayList<E> extends AbstractList<E> implements List<E>, RandomAccess, Cloneable, java.io.Serializable {
    transient Object[] elementData;
    ...
    
    public boolean add(E e) {
        ensureCapacityInternal(size + 1);//Increments modCount!!
        elementData[size++] = e;
        return true;
    }
  
    private void ensureCapacityInternal(int minCapacity) {
        ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
    }
  
    private void ensureExplicitCapacity(int minCapacity) {
        modCount++;
        if (minCapacity - elementData.length > 0) {
            grow(minCapacity);
        }
    }
    ...
}

(2)数组扩容一半时会进行数组拷贝

假设ArrayList使用的是默认数组大小,也就是10。现已经往数组添加了10个元素,数组的size = 10,capacity = 10。此时再调用add()方法插入一个元素,也就是需要插入第11个元素,那么肯定是插入不进去的。此时执行的是ensureCapacityInternal(11)。

elementData已经填充了10个元素,minCapacity = 11。elementData.length是默认的值,也就是10。现在要放第11个元素,所以就会调用grow()方法对数组进行扩容。

根据"int newCapacity = oldCapacity + (oldCapacity >> 1);"以及"elementData = Arrays.copyOf(elementData, newCapacity);"可知:老的大小 + 老的大小 >> 1(相当于扩容一半),得出新数组大小。然后调用Arrays.copyOf()进行数组拷贝。

public class ArrayList<E> extends AbstractList<E> implements List<E>, RandomAccess, Cloneable, java.io.Serializable {
    transient Object[] elementData;
    ...
    
    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);
    }
    ...
}

5.LinkedList源码一:优缺点和使用场景

(1)LinkedList的优缺点

(2)LinkedList的使用场景

(1)LinkedList的优缺点

LinkedList,底层是基于链表来实现的。LinkedList的优点就是非常适合频繁插入各种元素。LinkedList的缺点就是不太适合获取某个随机位置的元素。

比如LinkedList.get(10)这种操作,性能就较低。因为需要遍历链表,直到找到index = 10的这个元素为止。

而ArrayList.get(10),则不需要遍历,直接根据内存的地址,根据指定的index,直接定位到那个元素,不需要遍历数组。

ArrayList和LinkedList区别就是数组和链表的区别。

(2)LinkedList的使用场景

一.ArrayList的使用场景

一般会使用ArrayList来代表一个集合。只要别频繁插入大量元素即可,遍历或者随机查都可以。

二.LinkedList的使用场景

适合频繁在list中插入和删除元素,LinkedList可以当队列来使用。如果要在内存中实现一个内存队列,那么可以使用LinkedList。

6.LinkedList源码二:双向链表数据结构

(1)LinkedList的双向链表数据结构

(2)ArrayList和LinkedList的区别

(3)LinkedList的主要操作

(1)LinkedList的双向链表数据结构

public class LinkedList<E> extends AbstractSequentialList<E> implements List<E>, Deque<E>, Cloneable, java.io.Serializable {
    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;
        }
    }
    ...
}

LinkedList的数据结构如下:

图片

(2)ArrayList和LinkedList的区别

一般的回答:ArrayList是数组实现的,LinkedList是链表实现的。

深入的回答:结合ArrayList的源码,介绍add、remove、get、set方法的实现原理。介绍ArrayList数组扩容、元素移动的原理、以及优缺点是什么。LinkedList则是基于双向链表实现的,可以介绍一下它的数据结构。介绍LinkedList一些常见操作的原理、node是怎么变化的、以及优缺点,以及在哪个项目哪个业务场景下用过ArrayList和LinkedList。

(3)LinkedList的主要操作

在双向链表头部添加一个元素 / 获取一个元素 / 删除一个元素;

在双向链表尾部添加一个元素 / 获取一个元素 / 删除一个元素;

在双向链表中插入一个元素 / 获取一个元素 / 删除一个元素;

7.LinkedList源码三:插入元素的原理

(1)add()方法在尾部插入元素(尾插法)

(2)addFirst()方法在头部插入元素(头插法)

(3)add(index, e)方法在中间插入元素

(4)LinkedList的主要方法

(1)add()方法在尾部插入元素(尾插法)

add()方法会向双向链表尾部添加元素,add(2, e)会在index = 2的位置插入一个元素,都使用了尾插法。

public class LinkedList<E> extends AbstractSequentialList<E> implements List<E>, Deque<E>, Cloneable, java.io.Serializable {
    transient int size = 0;
    transient Node<E> first;
    transient Node<E> last;
    protected transient int modCount = 0;
    ...
    
    public boolean add(E e) {
        linkLast(e);
        return true;
    }
  
    public void add(int index, E element) {
        checkPositionIndex(index);
        if (index == size) {
            linkLast(element);
        } else {
            linkBefore(element, node(index));
        }
    }
  
    void linkLast(E e) {
        final Node<E> l = last;
        final Node<E> newNode = new Node<>(l, e, null);
        last = newNode;
        if (l == null) {
            first = newNode;
        } else {
            l.next = newNode;
        }
        size++;
        modCount++;
    }
  
    void linkBefore(E e, Node<E> succ) {
        //assert succ != null;
        final Node<E> pred = succ.prev;
        final Node<E> newNode = new Node<>(pred, e, succ);
        succ.prev = newNode;
        if (pred == null) {
            first = newNode;
        } else {
            pred.next = newNode;
        }
        size++;
        modCount++;
    }
    ...
}

(2)addFirst()方法在头部插入元素(头插法)

public class LinkedList<E> extends AbstractSequentialList<E> implements List<E>, Deque<E>, Cloneable, java.io.Serializable {
    transient int size = 0;
    transient Node<E> first;
    transient Node<E> last;
    protected transient int modCount = 0;
    ...
    
    public void addFirst(E e) {
        linkFirst(e);
    }
  
    private void linkFirst(E e) {
        final Node<E> f = first;
        final Node<E> newNode = new Node<>(null, e, f);
        first = newNode;
        if (f == null) {
            last = newNode;
        } else {
            f.prev = newNode;
        }
        size++;
        modCount++;
    }
    ...
}

(3)add(index, e)方法在中间插入元素

add(index, e)方法在调用linkBefore()方法时会调用node()方法,这个node()方法就是用来返回位置为index的那个结点的。node()方法会根据index判断是在队列的前半部分还是后半部分,然后决定从头进行遍历还是从尾进行遍历。获取到位置为index的结点后,便可以通过linkBefore()方法插入元素。

public class LinkedList<E> extends AbstractSequentialList<E> im
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值