容器

Java常用容器总结

1. Collection

1.1 List

有序,可重复

1.1.1 ArrayList

底层维护了一个可扩容数组。

重要成员:

private static final int DEFAULT_CAPACITY = 10;								// 默认容量
private static final Object[] EMPTY_ELEMENTDATA = {};						// 空数组,JDK7及之前均使用此变量
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};		// 默认空数组,JDK8及之后使用无参构造器时会使用该变量
transient Object[] elementData;											// 真正用于存放数据的数组
private int size;														// 当前已有元素个数量

1)创建:

  • JDK7及之前创建时若使用无参构造器,会将elementData指向EMPTY_ELEMENTDATA,只有在add时才会真正地为elementData创建Object数组。若使用有参构造器则会创建一个指定大小的Object数组。

  • JDK8及之后调用无参构造器时,会将elementData指向DEFAULTCAPACITY_EMPTY_ELEMENTDATA。在使用有参构造器时,若传入的容量不为0则会创建一个指定大小的Object数组,若传入的容量为0则会将elementData指向EMPTY_ELEMENTDATA。在第一次添加元素时,若elementData为空数组则需要扩容,这时会检查elementData是EMPTY_ELEMENTDATA还是DEFAULTCAPACITY_EMPTY_ELEMENTDATA,若elementData指向EMPTY_ELEMENTDATA则说明创建时使用的是有参构造器并且容量为0,此时会将数组大小调整为所需要的大小(若添加一个元素则需要的大小为1)。若elementData指向DEFAULTCAPACITY_EMPTY_ELEMENTDATA,则说明创建时使用的是无参构造器,此时会将数组的大小调整为默认容量与所需容量的最大值(若所需容量较小,直接调整至默认容量,若默认容量不够,则使用所需容量)。个人认为:这样做可能是因为当传入的容量为0时,则说明这个ArrayList可能并不需要太大的容量,因此在第一次扩容时得到的容量不会太大,以避免在以后扩容后造成大量未被使用的容量。

    注意:上述对数组大小的调整只说着简便,并不是直接将数组的大小调整为上述数据,上述数据只是在真正扩容时作为参考,真正扩容有其自己的规则。详细规则在添加中具体说明。

2)添加:在真正添加元素之前会调用ensureCapacityInternal(int minCapacity)方法来确保有足够的内存用以添加新元素。若容量不足时会调用grow(int minCapacity)方法,将数组扩容。思路为创建一个新数组,之后将老数组中的数据复制到新数组,最后将elementData指向新数组。该方法前期会先计算新数组的大小。首先获取老数组容量,之后将老数组容量乘以1.5得到新容量,若新容量小于所需(最小)容量,则让新容量等于所需容量。

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);
}
1.1.2 LinkedList

底层维护了一个双向链表。对LinkedList的操作与对普通双向链表的操作没什么差别。

1.1.3 Vector

与ArrayList类似,不过是线程安全的。

1.2 Set

无序,不可重复

1.2.1 HashSet

底层使用HashMap,HashMap见2.1。

1.2.2 TreeSet

底层使用TreeMap,TreeMap见2.2。

2. Map

2.1 HashMap

底层维护了一个哈希表,采用数组+链表的结构(JDK8及之后采用数组+链表+红黑树的结构)

重要成员:

static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // 默认初始容量
static final int MAXIMUM_CAPACITY = 1 << 30;		// 最大容量
static final float DEFAULT_LOAD_FACTOR = 0.75f;		// 默认负载系数
static final int TREEIFY_THRESHOLD = 8;				// 转换为红黑树的门限值
static final int UNTREEIFY_THRESHOLD = 6;			// 转换为链表的门限值
static final int MIN_TREEIFY_CAPACITY = 64;			// 最小树容量 当容量不足时会先进行扩容而不是树化
int threshold;									// 下一次扩容的门限值
transient Node<K,V>[] table;					// 实际用于存放元素的数组

1)创建:

有两个可选参数,初始化容量和负载系数,若未指定则使用上述默认值。若创建时使用到了初始容量则会调用tableSizeFor()方法,将threshold赋值为不小于所给初始容量的2的n次幂的值。具体作用在添加中说明。

2)添加:

若为第一次添加元素,table为空。此时会进行一次resize(调整大小)。在调整大小时若threshold为0,说明创建时未指定初始容量。此时会将table数组的大小调整为默认容量,并将threshold赋值为DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY。若threshold不为0,说明创建时已经指定过初始容量。此时将容量调整为threshold,并将threshold调整为newCap * loadFactor(新容量乘以负载系数)。

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

若非第一次添加元素table不为空。首先算出key的hash值,并根据hash值与table数组的大小计算出该元素在在数组中的位置。如果此节点为空,则直接将此元素封装到节点中并放入该位置。如果不为空,首先判断此节点的hash和key是否相等(因为判断相等需要使用equals()方法,并且在求hash时需要使用hashcode()方法,因此在使用HashMap等容器时要重写equals()和hashcode()方法),若相等根据参数选择onlyIfAbsent是否替换value,并且退出方法。如果不相等,首先判断该节点是否为树节点,若是树节点,则使用树节点的插入方式添加新元素。否则会遍历这条链表,若找到相同key与之前策略相同。一直到链表尾部,会将新节点插入,在遍历的过程中会计数,若链表长度大于转换为红黑树的门限值(TREEIFY_THRESHOLD)则会进行一次treeifyBin(树化)。但并非真正地直接树化,若table的大小小于最小树容量(MIN_TREEIFY_CAPACITY)则会对table进行一次调整大小(resize()),并重散列。否则才会进行树化。

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

2.2 TreeMap

内部维护了一个红黑树。

TreeMap需要有比较器,此比较器可以为内部比较器(让该类实现Comparable接口,并重写compareTo()方法)或外部比较器(写一个比较器实现Comparator接口,并重写compare()方法,并将此比较器作为参数传给TreeMap)

2.3 HashTable

与HashMap类似,不过是线程安全的。

2.4 ConcurrentHashMap

JDK7的结构为数组(Segment) + 数组(HashEntry) + 链表(HashEntry节点),每个Segment为一个锁。

JDK8的结构为Node数组+链表 / 红黑树, 类似JDK8中的hashMap,不会导致结构变化的操作使用CAS确保,导致结构变化的操作使用synchronized确保。

  • 3
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值