java:List、Map、Set等初始容量、加载因子、扩容增量
为什么要了解扩容机制
ArrayList
LinkedList
Vector
Stack
java.util.concurrent.CopyOnWriteArrayList
Queue相关的默认容量以及扩容机制
java.util.concurrent.ArrayBlockingQueue
java.util.concurrent.ConcurrentLinkedQueue
java.util.concurrent.DelayQueue、PriorityQueue
java.util.concurrent.LinkedBlockingQueue
Map相关的默认容量以及扩容机制
HashMap
ConcurrentHashMap
Hashtable
LinkedHashMap
TreeMap
WeakHashMap
Set相关的默认容量以及扩容机制
为什么要了解扩容机制
当底层实现涉及到扩容时,容器会重新分配一段更大的连续内存(如果是离散分配则不需要重新分配,离散分配都是插入新元素时动态分配内存),将容器原来的数据全部复制到新的内存上,会大大降低系统的性能
加载因子的系数小于等于1,即当元素个数超过 容量长度*加载因子的系数时,进行扩容。另外,扩容也是有默认的倍数的,不同的容器扩容情况不同。
ArrayList
ArrayList默认容量: 10
private static final int DEFAULT_CAPACITY = 10;//部分源码
负载因子: 1
ArrayList最大容量: Integer.MAX_VALUE - 8
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;//部分源码
ArrayList扩容机制: 按原数组长度的1.5倍扩容。如果扩容后的大小小于实际需要的大小,将数组扩大到实际需要的大小 即:
扩容:oldCapacity + (oldCapacity >> 1),即原集合长度的1.5倍。
int newCapacity = (oldCapacity * 3)/2 + 1;
int newCapacity = (oldCapacity * 3)/2 + 1;
如 : ArrayList的容量为10,一次扩容后是容量为16
LinkedList
LinkedList是用双链表实现的。对容量没有要求,也不需要扩容
Vector
Vector是线程安全的ArrayList,内部实现都是用数组实现的。Vector将调用方法用synchronized修饰保证线程安全
Vector默认容量: 10
public Vector() {this(10);}//部分源码
负载因子: 1
Vector最大容量: Integer.MAX_VALUE - 8
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
Vector扩容机制: 如果用户没有指定扩容步长,按原数组长度的2倍扩容,否则按用户指定的扩容步长扩容。如果扩容后的大小小于实际需要的大小,将数组扩大到实际需要的大小。
如:Vector的容量为10,一次扩容后是容量为20
Stack
Stack继承自Vector。添加了同步的push(E e),pop(),peek(),search()方法,默认容量和扩容机制同Vector
Stack默认容量是10
负载因子: 1
Stack最大容量: Integer.MAX_VALUE - 8
Stack扩容机制: 如果用户没有指定扩容步长,按原数组长度的2倍扩容,否则按用户指定的扩容步长扩容。如果扩容后的大小小于实际需要的大小,将数组扩大到实际需要的大小
java.util.concurrent.CopyOnWriteArrayList
CopyOnWriteArrayList是并发包下线程同步的数组集合。CopyOnWriteArrayList使用场景主要是多线程环境下,查询、遍历操作明显多于增加、删除操作。
CopyOnWriteArrayList默认容量是0,从0开始
CopyOnWriteArrayList没有规定最大容量(适合在查询操作频繁的场景下使用,容量变化不大)
CopyOnWriteArrayList扩容机制,每次+1
CopyOnWriteArrayList在做修改操作时,每次都是重新创建一个新的数组,在新数组上操作,最终再将新数组替换掉原数组。因此,在做修改操作时,仍可以做读取操作,读取直接操作的原数组。读和写操作的对象都不同,因此读操作和写操作互不干扰。只有写与写之间需要进行同步等待。另外,原数组被声明为volatile,这就保证了,一旦数组发生变化,则结果对其它线程(读线程和其它写线程)是可见的。
CopyOnWriteArrayList并不像ArrayList一样指定默认的初始容量。它也没有自动扩容的机制,而是·添加几个元素,长度就相应的增长多少·。CopyOnWriteArrayList适用于读多写少,既然是写的情况少,则不需要频繁扩容。并且修改操作每次在生成新的数组时就指定了新的容量,也就相当于扩容了,所以不需要额外的机制来实现扩容。
ArrayList为非线程安全的,处理效率上较Vector快,若同时考虑线程安全和效率,可以使用 CopyOnWriteArrayList。
Queue相关的默认容量以及扩容机制
java.util.concurrent.ArrayBlockingQueue
ArrayBlockingQueue是基于数组实现的线程安全的有界队列。它的容量是用户传递进来的。(内部使用ReentrantLock实现线程同步)
java.util.concurrent.ConcurrentLinkedQueue
ConcurrentLinkedQueue是基于单链表实现的线程安全的无界队列。(内部使用CAS实现线程同步是乐观锁)
java.util.concurrent.DelayQueue、PriorityQueue
非线程安全的无界队列。
java.util.concurrent.LinkedBlockingQueue
LinkedBlockingQueue是基于单链表实现的线程安全的无界队列。(内部使用takeLock和putLock读写分离锁实现)
Map相关的默认容量以及扩容机制
HashMap
HashMap是基于数组和链表/红黑树(1.7数组+链表 1.8数组+链表+红黑树)实现的。HashMap的容量必须是2的幂次方(原因是(n-1)&hash是取模操作,n必须是2的幂次方)
HashMap默认容量是16
static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16
HashMap最大容量:2的30次方
static final int MAXIMUM_CAPACITY = 1 << 30;
负载因子: 0.75
static final float DEFAULT_LOAD_FACTOR = 0.75f;
HashMap扩容机制: 原数组的两倍
扩容加载因子为(0.75),第一个临界点在当HashMap中元素的数量大于 table数组长度*加载因子(16*0.75=12),则按oldThr << 1(原长度*2)扩容。
ConcurrentHashMap
ConcurrentHashMap(1.8版本)它是采用Node<K,V>类型(继承了Map.Entry)的数组table+单向链表+红黑树结构。table数组的大小默认为16,数组中的每一项称为桶(bucket),桶中存放的是链表或者是红黑树结构,取决于链表的长度是否达到了阀值8(大于等于8)(默认),如果是,接着再判断数组长度是否小于64,如果小于则优先扩容table容量来解决单个桶中元素增多的问题,如果不是则转换成红黑树结构存放。
ConcurrentHashMap默认容量是16
private static final int DEFAULT_CAPACITY = 16;
最大容量: 2的30次方
private static final int MAXIMUM_CAPACITY = 1 << 30;
加载因子: 0.75
private static final float LOAD_FACTOR = 0.75f;
当往hashMap中成功插入一个key/value节点时,有可能触发扩容动作:
如果新增节点之后,所在链表的元素个数达到了阈值 8,则会调用treeifyBin方法把链表转换成红黑树,不过在结构转换之前,会对数组长度进行判断,实现如下:
如果数组长度n小于阈值MIN_TREEIFY_CAPACITY,默认是64,则会调用tryPresize方法把数组长度扩大到原来的两倍,并触发transfer方法,重新调整节点的位置。
新增节点之后,会调用addCount方法记录元素个数,并检查是否需要进行扩容,当数组元素个数达到阈值时,会触发transfer方法,重新调整节点的位
Hashtable
Hashtable默认容量是11(Hashtable默认大小是11是因为除(近似)质数求余的分散效果好:)
//部分源码
public Hashtable(int initialCapacity) { this(initialCapacity, 0.75f); } public Hashtable() { this(11, 0.75f); } public Hashtable(Map<? extends K, ? extends V> t) { this(Math.max(2*t.size(), 11), 0.75f); putAll(t); }
负载因子: 0.75
Hashtable最大容量:Integer.MAX_VALUE - 8
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
Hashtable扩容机制: 原数组的两倍+1
扩容加载因子(0.75),当超出默认长度(int)(11*0.75)=8时,扩容为old*2+1。
int newCapacity = (oldCapacity << 1) + 1;
LinkedHashMap
继承自HashMap扩容机制同HashMap
TreeMap
TreeMap由红黑树实现,容量方面没有限制
WeakHashMap
同HashMap
Set相关的默认容量以及扩容机制
Set底层实现都是使用Map来进行保存数据的,因为创建HashSet,其实相当于新建一个HashMap,然后取HashMap的Key。
默认初始容量为16
加载因子为0.75:即当 元素个数 超过 容量长度的0.75倍 时,进行扩容,扩容机制和HashMap一样。
扩容机制:原容量的 2 倍
如 HashSet的容量为16,一次扩容后是容量为32