文章目录
- 为什么要了解扩容机制
- 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相关的默认容量以及扩容机制
- 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