Java 中提供了很多的集合类,诸如map 、list、set等等。他们的扩容机制不尽相同。下边分别介绍一下。
List
ArrayList
ArrayList以数组的形式存储。 ArrayList有与容量相关的构造器有两个,一个是ArrayList(int initialCapacity), 另外一个是ArrayList(),前者没有指定容量的大小,在初始创建的时候容量是0,在第一扩容的时候扩到10。第二次扩容的时候为1.5倍(newCapacity = oldCapacity + (oldCapacity >> 1);)的速度递增(0、10、15、22、33、49.....)。而且每次都是在插入之前检查容量是否足够,如果不够才进行扩容扩容。因此他的加载因子是100%。
LinkedList
链表结构,且是是双向链表, 不涉及扩容的问题。
Vector
Vector与ArrayList一样,是存储在数组结构中。 与扩容机制相关的构造器有三个,分别是
- Vector(int initialCapacity, int capacityIncrement),
- Vector(int initialCapacity)
- Vector()
其实这后两个最终都是调用了第一个构造器,不过是使用了不同的默认参数,initialCapacity是初始容量,默认是10,capacityIncrement 是每次扩容时候的递增数量,如果该数值不等于0则每次扩容的时候是原来的二倍,如果该数值大于0,则每次增长的数量等于该数值(
int newCapacity = oldCapacity + ((capacityIncrement > 0) ?
capacityIncrement : oldCapacity);
)。
比如:
Vector<String> v = new Vector<String>(); // 10 20 ,40 80 ,160,320 的速度递增
Vector<String> v2 = new Vector<>(5,3);// 5 8 11 14 17 ,20,23 的速度递增。
他的加载因子也是10
Stack
继承于Vector,所以他的扩容机制等同于Vector的默认情况,也就是2倍速度扩容,加载因子为1。
Map
hashtable
以hash桶数组的形式存储数据。他的构造器也是有多个,代码如下
public Hashtable() { this(11, 0.75f); }
由此可以得知他的默认容量是11,而加载因子是0.75. 每次插入新的数据(不包括替换)之前需要判断是否达到扩容的阈值,如果已经达到则先进行扩容,并且按照新的结构排列原有的元素,然后插入元素,hashtable尤其需要注意的是不能插入key或value为空的键值对。插入新的元素的时候,元素的寻址方式是 (hash & 0x7FFFFFFF) % tab.length; (hash & 0x7FFFFFFF) 的作用是保证这部分的数据是正数,(hash & 0x7FFFFFFF) % tab.length的作用是保证位置一定是落在该数组的索引范围内。如果引起hash冲突的情况下,该数组的索引位置存放多个元素,这些元素以链表的形式存储。
注意hashtable 是线程安全的。
HashMap
HashMap的存储结构与hashtable基本一致。HashMap提供了多个构造器,但是最典型的是
public HashMap() // 这个没有指定容量和加载因子,在初始状态下容量是0(存放数据的数组是空),在增加第一个元素之前容量为16,加载因子是0.75
public HashMap(int initialCapacity, float loadFactor) // 可以指定加载银子以及初始容量。
插入数据的的寻址方式: (n - 1) & hash 保证一定是落在数组索引取值范围内。
哈希冲突的解决方式:增加数据的时候,如果key的哈希值相同,则保存在同一个桶内,存储方式要看此时桶内的数据量,如果桶内的数据量大于8,则用红黑树的方式存储。否则使用链表的形式存储。 在删除数据的时候,如果数据少于6个了,则把红黑树转换成链表的形式。插入一个新的数据后,如果达到扩容阈值,则调用resize()函数进行扩容。
Set
关于set,因为他们都是基于对应的map实现的,所以扩容机制与对应的map保持一致,这里不再赘述。