集合源码分析,我不信看完还有人不懂(一)

ArrayList源码

创建操作

先确定是无参构造还是有参构造

首先,底层就是一个Object数组
在这里插入图片描述
transient 表示瞬间的,短暂的,表示该属性不会被序列化

无参构造:
在这里插入图片描述
默认大小为:

private static final Object[] DEFAULTCAPACITY_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 boolean add(E e) {
        ensureCapacityInternal(size + 1);  // 当前数组大小+1
        elementData[size++] = e;
        return true;
    }

这里表示执行add添加操作,会先执行这个方法

ensureCapacityInternal(size + 1);

点进去,看

private void ensureCapacityInternal(int minCapacity) {
        if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
            minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
        }

        ensureExplicitCapacity(minCapacity);
    }

先判断是否是空数组,若为空,则赋值一个数,大小为

Math.max(DEFAULT_CAPACITY, minCapacity);

后面两个数中的最大值,而DEFAULT_CAPACITY大小为10,
所以空数组第一次add会创建一个大小为10的数组
然后执行这个语句:

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

        // overflow-conscious code
        if (minCapacity - elementData.length > 0)
            grow(minCapacity);
    }

第一句:modCount++;用来记录这几集合被修改的次数,防止多线程操作出现异常!!

然后进行判断,因为我们之前设置了minCapacity赋值为10了,而我们真实的数组大小为0,所以进行扩容操作!!!

grow(minCapacity);这个方法执行扩容

private void grow(int minCapacity) {
        // overflow-conscious code
        int oldCapacity = elementData.length;
        //把数组大小赋值给oldCapacity
        int newCapacity = oldCapacity + (oldCapacity >> 1);
        //也就是原先数组大小再加上它除以2,也就是我们说的扩容1.5倍   
        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);
    }

int oldCapacity = elementData.length;
把数组大小赋值给oldCapacity

int newCapacity = oldCapacity + (oldCapacity >> 1);
也就是原先数组大小再加上它除以2,也就是我们说的扩容1.5倍
然后进行判断,扩容后减去之前赋值的minCapacity<0,为true表示,依旧比它小。所以直接给newCapacity赋值为minCapacity
继续判断,这时候的newCapacity大于我们数组的最大值2147483639,执行
hugeCapacity(minCapacity);这个方法先不看。

elementData = Arrays.copyOf(elementData, newCapacity);最后执行这个语句
把elementData原先的数组大小扩充为 newCapacity大小的数组,把原先数组的值迁过去,扩大后没有值的地方默认为空(null)。

最后,返回到了原先的add方法
在这里插入图片描述
执行elementData[size++] = e;这个语句,这时候这个elementData的数组大小已经扩容完毕了,然后对这个数组下标为size++的地方放入数据e,这里面的size指的是已经放入的元素数量

注意: 容量和size不是一回事,size是放入的元素数量,容量是数组设定的大小

Vector源码

在这里插入图片描述
和Arraylist差不多,只不过加了synchronized线程安全了
它的扩容是按照2倍扩容的,而Arraylist按1.5倍扩容
在这里插入图片描述
它的扩容体现在这段代码上

int newCapacity = oldCapacity + ((capacityIncrement > 0) ?
                                         capacityIncrement : oldCapacity);

capacityIncrement为0,所以执行后面的语句,也就是oldCapacity所以连个相加就是2倍!!!

LinkedList源码

在这里插入图片描述
对每一个节点设置为一个类,item放真正的值,next指向下一个节点,pre指向前一个结点

在这里插入图片描述
还存在两个属性,专门用来记录第一个节点和尾节点。新创建完这个LinkedList,这两个属性默认为null;

主要介绍添加

在这里插入图片描述

里面一条语句linkLast(e);意思是直接把我们要添加的数据。挂在当前尾节点后面(尾插法)
在这里插入图片描述
新建一个Node 完了赋值给这个newNode,让last指向这个newNode。这里判断l是否为空 有两种情况,如果本身链表为空,则第一个节点创建后,first和last两个属性都指向这第一个节点,所以这里判断为空的话,说明这是第一个节点,不为空的话,让链表本身最后一个节点的next指向新建的节点。

删除节点

有些同学有疑问,链表是没有下标这一说的,那我们在指定index插入,或者而删除是如何实现的,其实本质就是这段代码:

 Node<E> node(int index) {
        // assert isElementIndex(index);

        if (index < (size >> 1)) {
            Node<E> x = first;
            for (int i = 0; i < index; i++)
                x = x.next;
            return x;
        } else {
            Node<E> x = last;
            for (int i = size - 1; i > index; i--)
                x = x.prev;
            return x;
        }
    }

根本就是判断这个下标是在整个链表的前半段还是后半段,前半段就前向遍历,后半段就后向遍历,本质就是for循环遍历,找到对应index的Node节点

remove本质就是这段代码:

E unlink(Node<E> x) {
        // assert x != null;
        final E element = x.item;
        final Node<E> next = x.next;
        final Node<E> prev = x.prev;

        if (prev == null) {
            first = next;
        } else {
            prev.next = next;
            x.prev = null;
        }

        if (next == null) {
            last = prev;
        } else {
            next.prev = prev;
            x.next = null;
        }

        x.item = null;
        size--;
        modCount++;
        return element;
    }

先记录要删除的节点的前一个结点和后一个节点。
之后的两个判断很简单,就是判断要删除的节点是否是头节点或是尾节点。
然后,让next,pre互相指一下就行了。

其它方法不再赘述,很简单。

HashSet(底层是hashmap,其实看hashmap源码)

在这里插入图片描述
底层是HashMap,而HashMap的底层是 数组+链表+红黑树

例如add方法我们点进入底层看见,是map结构,且key为我们add的值,而value为 PRESENT
在这里插入图片描述

private static final Object PRESENT = new Object();

PRESENT它只是起到一个占位的作用,且被static final修饰,不会再改变,每次add的时候放入的都是同一个值,不会每次都新建,所以也就不占啥内存,由此可得。hashset是hashmap键的集合(hashmap的value是可以重复的,key不允许重复)

底层一个一个数据是如何存储的?

 static class Node<K,V> implements Map.Entry<K,V> {
        final int hash;
        final K key;
        V value;
        Node<K,V> next;

        Node(int hash, K key, V value, Node<K,V> next) {
            this.hash = hash;
            this.key = key;
            this.value = value;
            this.next = next;
        }

        public final K getKey()        { return key; }
        public final V getValue()      { return value; }
        public final String toString() { return key + "=" + value; }

        public final int hashCode() {
            return Objects.hashCode(key) ^ Objects.hashCode(value);
        }

        public final V setValue(V newValue) {
            V oldValue = value;
            value = newValue;
            return oldValue;
        }

        public final boolean equals(Object o) {
            if (o == this)
                return true;
            if (o instanceof Map.Entry) {
                Map.Entry<?,?> e = (Map.Entry<?,?>)o;
                if (Objects.equals(key, e.getKey()) &&
                    Objects.equals(value, e.getValue()))
                    return true;
            }
            return false;
        }
    }

放在一个Node类中,hash值,key和value,还有指向下一个节点的next

添加操作,第一次插入数据(扩容)

点进去add操作
在这里插入图片描述
到map的put方法
在这里插入图片描述
在计算hash(key)得到key对应的hash值
在这里插入图片描述
赋值,然后进入putVal方法(哦豁,这下开始加进去了)

这里才是重点

final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
                   boolean evict) {
        Node<K,V>[] tab; Node<K,V> p; int n, i;
        if ((tab = table) == null || (n = tab.length) == 0)
            n = (tab = resize()).length;
        if ((p = tab[i = (n - 1) & hash]) == null)
            tab[i] = newNode(hash, key, value, null);
        else {
            Node<K,V> e; K k;
            if (p.hash == hash &&
                ((k = p.key) == key || (key != null && key.equals(k))))
                e = p;
            else if (p instanceof TreeNode)
                e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
            else {
                for (int binCount = 0; ; ++binCount) {
                    if ((e = p.next) == null) {
                        p.next = newNode(hash, key, value, null);
                        if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
                            treeifyBin(tab, hash);
                        break;
                    }
                    if (e.hash == hash &&
                        ((k = e.key) == key || (key != null && key.equals(k))))
                        break;
                    p = e;
                }
            }
            if (e != null) { // existing mapping for key
                V oldValue = e.value;
                if (!onlyIfAbsent || oldValue == null)
                    e.value = value;
                afterNodeAccess(e);
                return oldValue;
            }
        }
        ++modCount;
        if (++size > threshold)
            resize();
        afterNodeInsertion(evict);
        return null;
    }
Node<K,V>[] tab; Node<K,V> p; int n, i;

这一行是定义一些变量,待使用!

if ((tab = table) == null || (n = tab.length) == 0)
            n = (tab = resize()).length;

执行这一行代码,其中 table是定义好的一个默认的空的Node数组在这里插入图片描述
主要是判断数组为空,然后进行resize(),给数组设置个初始大小,点进去

真正的第一步,给数组设置大小(数组扩容也在这里面)
final Node<K,V>[] resize() {
        Node<K,V>[] oldTab = table;
        int oldCap = (oldTab == null) ? 0 : oldTab.length;
        int oldThr = threshold;
        int newCap, newThr = 0;
        if (oldCap > 0) {
            if (oldCap >= MAXIMUM_CAPACITY) {
                threshold = Integer.MAX_VALUE;
                return oldTab;
            }
            else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&
                     oldCap >= DEFAULT_INITIAL_CAPACITY)
                newThr = oldThr << 1; // double threshold
        }
        else if (oldThr > 0) // initial capacity was placed in threshold
            newCap = oldThr;
        else {               // zero initial threshold signifies using defaults
            newCap = DEFAULT_INITIAL_CAPACITY;
            newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);
        }
        if (newThr == 0) {
            float ft = (float)newCap * loadFactor;
            newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ?
                      (int)ft : Integer.MAX_VALUE);
        }
        threshold = newThr;
        @SuppressWarnings({"rawtypes","unchecked"})
            Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap];
        table = newTab;
        if (oldTab != null) {
            for (int j = 0; j < oldCap; ++j) {
                Node<K,V> e;
                if ((e = oldTab[j]) != null) {
                    oldTab[j] = null;
                    if (e.next == null)
                        newTab[e.hash & (newCap - 1)] = e;
                    else if (e instanceof TreeNode)
                        ((TreeNode<K,V>)e).split(this, newTab, j, oldCap);
                    else { // preserve order
                        Node<K,V> loHead = null, loTail = null;
                        Node<K,V> hiHead = null, hiTail = null;
                        Node<K,V> next;
                        do {
                            next = e.next;
                            if ((e.hash & oldCap) == 0) {
                                if (loTail == null)
                                    loHead = e;
                                else
                                    loTail.next = e;
                                loTail = e;
                            }
                            else {
                                if (hiTail == null)
                                    hiHead = e;
                                else
                                    hiTail.next = e;
                                hiTail = e;
                            }
                        } while ((e = next) != null);
                        if (loTail != null) {
                            loTail.next = null;
                            newTab[j] = loHead;
                        }
                        if (hiTail != null) {
                            hiTail.next = null;
                            newTab[j + oldCap] = hiHead;
                        }
                    }
                }
            }
        }
        return newTab;
    }

threshold
threshold表示当HashMap的size(加入的节点数量,不是占用的数组大小)大于threshold时会执行resize操作。第一次创建后的第一次put,它的值为0

我们继续看代码:
第一次创建后的第一次put oldCap oldThrnt newCap, newThr 他们的值都是0
然后进入到这个判断

else {               // zero initial threshold signifies using defaults
            newCap = DEFAULT_INITIAL_CAPACITY;
            newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);
        }

给newCap设置为16(DEFAULT_INITIAL_CAPACITY为16)

给newThr设置为12(0.75*16) 做缓冲,不能直接设置为16,表示只要添加了12个节点,再进行添加,就进行扩容
再然后,执行:

 threshold = newThr;
        @SuppressWarnings({"rawtypes","unchecked"})
            Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap];
        table = newTab;

给threshold设置为12,用来之后判断只要节点数量超过12就扩容

然后新建一个Node<K,V>[] newTab 大小为16 赋值给table
之后的判断先不管,直接看到最后返回return newTab;数组大小为16

到此,第一步的resize()结束,目的就是给数组设置大小

将数据放进去

之后回到putVal方法,执行这个语句

if ((p = tab[i = (n - 1) & hash]) == null)
            tab[i] = newNode(hash, key, value, null);

让传入的hash值与数组长度减一进行按位与运算,得到要放入到数组的哪个位置上去。
把这个位置上的数据赋值给Node p,有两种情况,一是为空,也就是这个位置上面本来没有数据,直接newNode放进去。

还有就是不为空,本来位置上有数据、执行下一个判断,先掠过这里

最后执行到这段代码:

++modCount;
        if (++size > threshold)
            resize();
        afterNodeInsertion(evict);
        return null;

++modCount;表示对集合的操作次数

size
hashmap中的size这个属性表示返回map中key-value映射的数量。也就是size表示HashMap中存放KV的数量(为链表和树中的KV的总和)。
在判断,是否该size存放的kv数量是否超过threshold,超过就扩容,不超过,直接return null 表示成功

以上第一次插入一个数据,向一个新建的集合里面添加一条数据,就是以上操作。

之后,简述第二次添加数据操作源码解析

跟第一次一样,进入到putVal方法
不再执行这个判断:

 if ((tab = table) == null || (n = tab.length) == 0)
            n = (tab = resize()).length;

因为这里的table已经在第一次的resize()里面的这一句赋值了一个大小为16的数组
在这里插入图片描述
所以进行下一个判断

 if ((p = tab[i = (n - 1) & hash]) == null)
            tab[i] = newNode(hash, key, value, null);

根据这个判断,往数组的哪一个里面放数据,,看存不存在,不存在直接放进去,存在,则进行其他判断。

以上就上添加第二个数据,不重复的情况下!!!

添加一个重复的数据,也就是让key相同

也是走到putVal方法**
走到这个判断,发生变化

 if ((p = tab[i = (n - 1) & hash]) == null)
            tab[i] = newNode(hash, key, value, null);

这时候key已经存在过了,所以这里计算的hash值相同,所以数组的这个位置肯定不为空,走到一个全新的领域

这里的数组位置不为空,走到下面的代码

Node<K,V> e; K k;
            if (p.hash == hash &&
                ((k = p.key) == key || (key != null && key.equals(k))))
                e = p;
            else if (p instanceof TreeNode)
                e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
            else {
                for (int binCount = 0; ; ++binCount) {
                    if ((e = p.next) == null) {
                        p.next = newNode(hash, key, value, null);
                        if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
                            treeifyBin(tab, hash);
                        break;
                    }
                    if (e.hash == hash &&
                        ((k = e.key) == key || (key != null && key.equals(k))))
                        break;
                    p = e;
                }
            }
            if (e != null) { // existing mapping for key
                V oldValue = e.value;
                if (!onlyIfAbsent || oldValue == null)
                    e.value = value;
                afterNodeAccess(e);
                return oldValue;
            }
        }
if (p.hash == hash &&
                ((k = p.key) == key || (key != null && key.equals(k))))

p.hash == hash 这里的p为数组这个位置不为空的第一个元素 ,p.hash就是第一个元素的hash值 ,后面的hash表示你要添加的元素的hash值

(k = p.key) == key p.key表示第一个元素的key == 要添加元素的key 要注意这里的 == 等号比较的是两个key的引用是否相同

(key != null && key.equals(k))) 表示key不为空,并且他们的equals是否相同,这里的equals比较的是本身也就是内容是否相同

这里不懂得,看看== 与 equals的区别

之后判断为true后,将这个p赋值给e,在走到这个判断

if (e != null) { // existing mapping for key
                V oldValue = e.value;
                if (!onlyIfAbsent || oldValue == null)
                    e.value = value;
                afterNodeAccess(e);
                return oldValue;
            }

e.value也就是我们set集合默认的value

 private static final Object PRESENT = new Object();

走到最后返回一个值,不为空
回到前面
在这里插入图片描述
返回不为空,也就是添加失败

如果正好添加的时候,数组位置不为空,但是key不重复的情况

走到putVal方法的这个判断
在这里插入图片描述
判断p这个节点是否为红黑树的结构,是的话走到红黑树底层添加的方法(这个也很复杂,先不看)

又或者不是红黑树,走到这个判断
在这里插入图片描述
p不是红黑树,那就是链表了,先判断,头节点是否有next,没有就直接把我们要添加的数据,尾插法跟到后面,如果next不为空,则依次比较后面的节点与我们要插入的数据是否相同,还是那个判断,比较hash,比较key引用是否相同,比较内容是否相同,如果都相同,说明重复就退出,遍历到解为都不重复,就将数据尾插法插到最后。

中间还有这段代码,
在这里插入图片描述
如果链表长度达到8,就准备转为红黑树,执行这个方法
在这里插入图片描述
先判断数组长度是否达到64,且链表长度到达8,再进行转换!!!

注意:

数组扩容有个阈值,也就是*0.75后得到的值,扩容的条件是添加进去的节点数量(size)大于这个阈值,就进行扩容,而不是占用数组的长度达到阈值!!!!!!

到此为止,添加操作大多讲完了!!!

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值