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
1 | private static final int DEFAULT_CAPACITY = 10 ; //部分源码 |
负载因子: 1
ArrayList最大容量: Integer.MAX_VALUE - 8
1 | 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
1 | public Vector() { this ( 10 );} //部分源码 |
负载因子: 1
Vector最大容量: Integer.MAX_VALUE - 8
1 | 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
1 | static final int DEFAULT_INITIAL_CAPACITY = 1 << 4 ; // aka 16 |
HashMap最大容量:2的30次方
1 | static final int MAXIMUM_CAPACITY = 1 << 30 ; |
负载因子: 0.75
1 | 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
1 | private static final int DEFAULT_CAPACITY = 16 ; |
最大容量: 2的30次方
1 | private static final int MAXIMUM_CAPACITY = 1 << 30 ; |
加载因子: 0.75
1 | 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是因为除(近似)质数求余的分散效果好:)
//部分源码
1 2 3 4 5 6 7 8 9 10 | 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
1 | 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
· Obsidian + DeepSeek:免费 AI 助力你的知识管理,让你的笔记飞起来!
· 分享4款.NET开源、免费、实用的商城系统
· 解决跨域问题的这6种方案,真香!
· 5. Nginx 负载均衡配置案例(附有详细截图说明++)
· Windows 提权-UAC 绕过
2022-01-06 pandas ewma
2014-01-06 Spring Quartz 持久化解决方案
2014-01-06 不同版本(2.3,2.4,2.5) web.xml 的web-app头信息