集合类

一、继承体系

二、存储特点

三、代码阅读

3.1 ArrayList

3.1.1 ArrayList定义

3.1.2 ArrayList概述

3.1.3 ArrayList和LinkedList的区别

3.1.4 ArrayList和Vector的区别

3.1.5 动态扩容

3.2 LinkedList

  3.2.1 LinkedList定义

  3.2.2 LinkedList概述

3.3 HashMap

 3.3.1 HashMap概述

3.3.2 put函数大致流程

3.3.3 ConcurrentHashMap

3.4 Set 

一、继承体系

俩类:一类继承Collection、一类继承Map

二、存储特点

Map系:HashMap, LinkedHashMap, TreeMap, WeakHashMap, EnumMap;

List系:ArrayList, LinkedList, Vector, Stack;

Set系:HashSet, LinkedHashSet, TreeSet;

工具类:Collections,Arrays

三、代码阅读

3.1 ArrayList

3.1.1 ArrayList定义

package java.util;

public class ArrayList extends AbstractList

implements List, RandomAccess, Cloneable, java.io.Serializable{

private static final int DEFAULT_CAPACITY = 10;

private static final Object[] EMPTY_ELEMENTDATA = {};

private transient Object[] elementData;

private int size;

//其余省略

}

3.1.2 ArrayList概述

ArrayList以数组实现,允许重复。超出限制时会增加50%的容量(grow()方法中实现,如下所示),每次扩容都底层采用System.arrayCopy()复制到新的数组,因此最好能给出数组大小的预估值。默认第一次插入元素时创建数组的大小为10.按数组下标访问元素—get(i)/set(i,e) 的性能很高,这是数组的基本优势。直接在数组末尾加入元素—add(e)的性能也高,但如果按下标插入、删除元素—add(i,e), remove(i), remove(e),则要用System.arraycopy()来移动部分受影响的元素,性能就变差了,这是基本劣势。

3.1.3 ArrayList和LinkedList的区别

1.ArrayList是实现了基于动态数组的数据结构,LinkedList基于链表的数据结构。

2.对于随机访问get和set,ArrayList觉得优于LinkedList,因为LinkedList要移动指针。

3.对于新增和删除操作add和remove,LinkedList比较占优势,因为ArrayList要移动数据。

3.1.4 ArrayList和Vector的区别

1.Vector和ArrayList几乎是完全相同的,唯一的区别在于Vector是同步类(synchronized),属于强同步类。因此开销就比ArrayList要大,访问要慢。正常情况下,大多数的Java程序员使用ArrayList而不是Vector,因为同步完全可以由程序员自己来控制。

2.Vector每次扩容请求其大小的2倍空间,而ArrayList是1.5倍。

3.Vector还有一个子类Stack

3.1.5 动态扩容

private void ensureCapacityInternal(int minCapacity) {

    //如果是空数组 容量默认是10

    if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {

        minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);

    }

    ensureExplicitCapacity(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;

    //>>位运算,右移动一位。 整体相当于原先的1.5倍

    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.2 LinkedList

  3.2.1 LinkedList定义

package java.util;

public class LinkedList

    extends AbstractSequentialList

    implements List, Deque, Cloneable, java.io.Serializable{

    transient int size = 0;

    transient Node first;

    transient Node last;

}

  3.2.2 LinkedList概述

 LinkedList以双向链表实现,允许重复。

  链表无容量限制,但双向链表本身使用了更多空间,也需要额外的链表指针操作。

  按下标访问元素—get(i)/set(i,e) 要悲剧的遍历链表将指针移动到位(如果i>数组大小的一半,会从末尾移起)

        插入、删除元素时修改前后节点的指针即可,但还是要遍历部分链表的指针才能移动到下标所指的位置,只有在链表两头的操作—add(), addFirst(),removeLast()或用iterator()上的remove()能省掉指针的移动。

3.3 HashMap

 3.3.1 HashMap概述

工作原理:通过hash的方法,通过put和get存储和获取对象。存储对象时,我们将K/V传给put方法时,它调用hashCode计算hash从而得到bucket位置,进一步存储,HashMap会根据当前bucket的占用情况自动调整容量(超过Load Factor则resize为原来的2倍)。获取对象时,我们将K传给get,它调用hashCode计算hash从而得到bucket位置,并进一步调用equals()方法确定键值对。如果发生碰撞的时候,Hashmap通过链表将产生碰撞冲突的元素组织起来,在Java 8中,如果一个bucket中碰撞冲突的元素超过某个限制(默认是8),则使用红黑树来替换链表,从而提高速度。

以Entry[]数组实现的哈希桶数组,用Key的哈希值取模桶数组的大小可得到数组下标。

    基于Map接口实现、允许null键/值、非同步、不保证有序(比如插入的顺序)、也不保证序不随时间变化。HashMap存储着Entry(hash, key, value, next)对象。

在HashMap中有两个很重要的参数,容量(Capacity)和负载因子(Load factor)。扩容2倍

Capacity的默认值为16:static final int DEFAULT_INITIAL_CAPACITY = 1 << 4;

负载因子的默认值为0.75:static final float DEFAULT_LOAD_FACTOR = 0.75f;

hashmap 1.7和1.8对比 :1.主要增加红黑树的概念,2Java7 是先扩容后插入新值的,Java8 先插值再扩容,

3.3.2 put函数大致流程

public V put(K key, V value) {

    return putVal(hash(key), key, value, false, true);

}

final V putVal(int hash, K key, V value, boolean onlyIfAbsent,

               boolean evict) {

    Node[] tab; Node 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 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)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;

}

1.先判断table(存放bullet的数组,初始类定义:transient Entry[] table = (Entry[]) EMPTY_TABLE;)是否为空,如果为空则扩充table,其中包括确保table的大小为2的整数倍。

2.如果key值为null,则特殊处理,调用putForNullKey(V value),hash值为0,存入table中,返回。

3.如果key值不为null,则计算key的hash值;

4.然后计算key在table中的索引index;

5.遍历table[index]的链表,如果发现链表中有bullet中的键的hash值与key相等,并且调用equals()方法也返回true,则替换旧值(oldValue),保证key的唯一性;

6.如果没有,在插入之前先判断table中阈值的大小,如果table中的bullet个数size超过阈值(threshold)则扩容(resize)两倍;注意扩容的顺序,扩容之前old1->old2->old3,扩容之后old3→old2→old1,扩展之前和扩容之后的table的index不一定相同,对于原bullet中的链表中的数据在扩容之后也不一定还在一个链表中。

final Node[] resize() {

    Node[] 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[] newTab = (Node[])new Node[newCap];

    table = newTab;

    if (oldTab != null) {

        for (int j = 0; j < oldCap; ++j) {

            Node 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)e).split(this, newTab, j, oldCap);

                else { // preserve order

                    Node loHead = null, loTail = null;

                    Node hiHead = null, hiTail = null;

                    Node 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;

}

7.插入新的bullet。注意插入的顺序,原先table[index]的链表比如 old1->old2->old3,插入新值之后为new1->old1->old2→old3.

3.3.3 ConcurrentHashMap

ConcurrentHashMap 和 HashMap 思路是差不多的,但是它支持并发操作

ConcurrentHashMap 由一个个 Segment 组成,Segment 代表”部分“或”一段“的意思,所以很多地方都会将其描述为分段锁

Segment 通过继承 ReentrantLock 来进行加锁,Segment 内部是由数组+链表组成的。

concurrencyLevel:并行级别 默认16  理论上支持16个线程并发执行 初始化后不可扩容

初始化完成后:

Segment 数组长度为 16,不可以扩容

Segment[i] 的默认大小为 2,负载因子是 0.75,得出初始阈值为 1.5,也就是以后插入第一个元素不会触发扩容,插入第二个会进行第一次扩容

这里初始化了 segment[0],其他位置还是 null,至于为什么要初始化 segment[0],后面的代码会介绍

当前 segmentShift 的值为 32 - 4 = 28,segmentMask 为 16 - 1 = 15,姑且把它们简单翻译为移位数和掩码,这两个值马上就会用到

3.4 Set 

 HashSet是基于HashMap来实现的,操作很简单,更像是对HashMap做了一次“封装”,而且只使用了HashMap的key来实现各种特性,而HashMap的value始终都是PRESENT。

    HashSet不允许重复(HashMap的key不允许重复,如果出现重复就覆盖),允许null值,非线程安全。

你可能感兴趣的:(集合类)