浅析List、Set和Map集合扩容机制

一、List集合

1、ArrayList

下面是有关ArrayList容量的源码

private static final int DEFAULT_CAPACITY = 10;//默认容量10
private static final Object[] EMPTY_ELEMENTDATA = {};
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};//默认空数组
transient Object[] elementData;
private int size;//默认为0
public ArrayList() {//无参构造方法,Object数组长度为0
    this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
private void ensureCapacityInternal(int minCapacity) {
//此方法目的是在没有指定集合容量时,第一次add时给集合分配10的默认空间
    if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
        minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
    }

    ensureExplicitCapacity(minCapacity);
}
public ArrayList(int initialCapacity) {//有参构造方法,Object数组长度为initialCapacity
    if (initialCapacity > 0) {
        this.elementData = new Object[initialCapacity];
    } else if (initialCapacity == 0) {
        this.elementData = EMPTY_ELEMENTDATA;
    } else {
        throw new IllegalArgumentException("Illegal Capacity: "+
                                           initialCapacity);
    }
}
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;//数组集合最大长度2^31-1-8
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);
    }

在上面的代码中由 int newCapacity = oldCapacity + (oldCapacity >> 1);可知集合容量是以1.5倍来扩容的,而且若使用无参构造方法实例化集合,那么集合的默认长度为10,也就是说扩容时是以
10,15,22,33, 50 , 75 , 112 。。。。来进行。
若使用有参构造方法指定初始容量,例如100,就会以100,150,220,330,750。。。来进行。

2、LinkedList

LinkedList,见名知意底层是由链表来实现的集合。

public LinkedList() {
}
public LinkedList(Collection<? extends E> c) {
	this();
    addAll(c);
}

两个构造方法都没有涉及容量,其实很容易理解,在JDK1.5之后LinkedList就采用了双向链表来实现,没有初始长度,没有容量限制,只要在链表最后一直添加就行了。
另外,此集合内存储的是一个个“节点”:

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

二、Set集合与Map集合

1、HashSet

HashSet:HashMap中k-v中的k部分,所以我们直接分析HahMap集合
一般来说如果想要查看一个集合的初识容量只需要分析集合中第一次add,或是put方法即可,
add方法的跳转过程是:HashSet中的add方法→HashMap中的put方法→HashMap中的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;
    }


上面的tab = resize()即是对数组的初始化,找到resize()

//resize代码过长,已经超出作者能力,不再具体分析,下面只介绍初识容量已经扩容机制
newCap = oldCap << 1//新容量是旧容量的二倍
static final int DEFAULT_INITIAL_CAPACITY = 1 << 4;//DEFAULT_INITIAL_CAPACITY 值为16
newCap = DEFAULT_INITIAL_CAPACITY;//数组容量为16
static final float DEFAULT_LOAD_FACTOR = 0.75f;//加载因子

加载因子作用是标志一个阈值,该值为自身容量*加载因子,若达到阈值则扩容。

2、HashTable

初始化容量的方法:

protected void rehash() {
        int oldCapacity = table.length;
        Entry<?,?>[] oldMap = table;

        // overflow-conscious code
        int newCapacity = (oldCapacity << 1) + 1;
        if (newCapacity - MAX_ARRAY_SIZE > 0) {
            if (oldCapacity == MAX_ARRAY_SIZE)
                // Keep running with MAX_ARRAY_SIZE buckets
                return;
            newCapacity = MAX_ARRAY_SIZE;
        }
        Entry<?,?>[] newMap = new Entry<?,?>[newCapacity];

        modCount++;
        threshold = (int)Math.min(newCapacity * loadFactor, MAX_ARRAY_SIZE + 1);
        table = newMap;

        for (int i = oldCapacity ; i-- > 0 ;) {
            for (Entry<K,V> old = (Entry<K,V>)oldMap[i] ; old != null ; ) {
                Entry<K,V> e = old;
                old = old.next;

                int index = (e.hash & 0x7FFFFFFF) % newCapacity;
                e.next = (Entry<K,V>)newMap[index];
                newMap[index] = e;
            }
        }
    }

可以看到int newCapacity = (oldCapacity << 1) + 1;旧容量*2+1为新容量

public Hashtable() {
	this(11, 0.75f);
}
public Hashtable(int initialCapacity, float loadFactor) {
	if (initialCapacity < 0)
		throw new IllegalArgumentException("Illegal Capacity: "+
	                                   initialCapacity);
	if (loadFactor <= 0 || Float.isNaN(loadFactor))
		throw new IllegalArgumentException("Illegal Load: "+loadFactor);
	
	if (initialCapacity==0)
		initialCapacity = 1;
	this.loadFactor = loadFactor;
	table = new Entry<?,?>[initialCapacity];
	threshold = (int)Math.min(initialCapacity * loadFactor, MAX_ARRAY_SIZE + 1);
}

观察上述代码可见若不指定则HashTable初始长度为11.

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值