PeterPu
发布于 2025-08-09 / 0 阅读
0
0

集合扩容机制

ArrayList 和Vector扩容机制总结:

ArrayList扩容规则

ArrayList() 会使用长度为零的数组

ArrayList(int initialCapacity) 会使用指定容量的数组

public ArrayList(Collection c) 会使用 c 的大小作为数组容量

add(Object o) 首次扩容为 10,再次扩容为上次容量的 1.5 倍

addAll(Collection c) 没有元素时,扩容为 Math.max(10, 实际元素个数),有元素时为 Math.max(原容量 1.5 倍, 实际元素个数)

ArrayList 和Vector,底层都是Object数组,默认加载因子都是1(元素满了才扩展容量).默认容量都是10;但是ArrayList 在jdk1.8时默认为空,当添加元素时,才初始化为10个容量。ArrayList:新容量为原容量的1.5倍,Vector:新容量为原容量的2倍.

ArrayList 默认初始容量为10,(jdk8的时候底层Object[] elementData数组初始化为{},并没有创建长度为10的数组。在add元素时才创建了10个容量。)

线程不安全,查询速度快

    底层数据结构是数组结构

    扩容增量:原容量的 0.5倍,新容量为原容量的1.5倍。

    如 ArrayList的容量为10,一次扩容后是容量为15

同样可以通过分析源码知道:

Vector:

默认初始容量为10,(jdk7和jdk8一样都初始容量为10)。

线程安全,但速度慢

    底层数据结构是数组结构

    加载因子为1:即当 元素个数 超过 容量长度 时,进行扩容

    扩容增量:原容量的 1倍,新容量为原容量的2倍。

    如 Vector的容量为10,一次扩容后是容量为20

LinkedList没有扩容机制:

LinkedList:没有扩容机制,因为其底层是双向链表结构。不存在数组的扩容一说,没有初始化大小,也没有扩容的机制,就是一直在前面或者后面新增就好。

总结二:Set下的三个实现类集合HashSet和LinkedHashSet,TreeSet,扩容总结:LinkedHashSet,TreeSet没有数组的扩容机制。

HashSet和HashMap扩容机制总结

hashmap基本数据结构

1.7 数组 + 链表

1.8 数组 + (链表 | 红黑树)

树化规则

当链表长度超过树化阈值 8 时,先尝试扩容来减少链表长度,如果数组容量已经 >=64,才会进行树化

退化规则

情况1:在扩容时如果拆分树时,树元素个数 <= 6 则会退化链表

情况2:remove 树节点时,若 root、root.left、root.right、root.left.left 有一个为 null ,也会退化为链表

索引计算方法

首先,计算对象的 hashCode()

再进行调用 HashMap 的 hash() 方法进行二次哈希

二次 hash() 是为了综合高位数据,让哈希分布更为均匀(可以让模数组容量之后的值更加分布均匀)

最后 & (capacity – 1) 得到索引

HashSet和HashMap都是默认初始容量是16(jdk1.7的),但是jdk1.8做了优化,初始容量为0,第一次存元素的时候才扩容为16,加载因子是0.75,扩容为原来的2倍。而带LinkedHashSet和LinkedHashMap是链表不存在扩容的,HashSet:底层是数组+链表的结构。

Set(集) 元素无序的、不可重复。

HashSet:线程不安全,存取速度快

     底层实现是一个HashMap(保存数据),HashSet:底层是数组+链表的结构,实现Set接口

     默认初始容量为16(jdk1.8及以后)(为何是16,见下方对HashMap的描述)

     加载因子为0.75:即当 元素个数 超过 容量长度的0.75倍 时,进行扩容

     扩容增量:原容量的 1 倍,新容量为原容量的2倍。

      如 HashSet的容量为16,一次扩容后是容量为32。

      因为构造一个HashSet,其实相当于新建一个HashMap,然后取HashMap的Key。

扩容机制和HashMap一样。

Map是一个双列集合:

HashMap:jdk1.8默认初始容量0,当第一次put元素的时候才扩容为16,jdk1.7是初始化容量为16

     (为何是16:16是2^4,可以提高查询效率,另外,32=16<<1 –>至于详细的原因可另行分析,或分析源代码)

     加载因子为0.75:即当 元素个数 超过 容量长度的0.75倍 时,进行扩容

     扩容增量:原容量的 1 倍,新容量为原容量的2倍。

      如 HashSet的容量为16,一次扩容后是容量为32

Hashtable扩容机制:

public Hashtable() {

this(11, 0.75f);

}

1

2

3

Hashtable默认初始容量11。

二、扩容加载因子(0.75),当超出默认长度(int)(11*0.75)=8时,扩容为oldx2+1。新容量为原容量的2倍+1.

int newCapacity = (oldCapacity << 1) + 1;

      

总结:

1.HashMap如果用默认构造方法的话,HashMap开始容量为0,第一次put元素的时候才扩容为16;

2.HashMap使用有参构造方法的话,HashMap容量并不是我们参数给定的大小,而是大于等于给定容量参数的 最接近 2的次幂。如我们给定容量为7,则是2的3次幂,即8容量,因为2的次幂可以通过右移快速得到。

3.扩容为原来的2n

1HashTable如果使用默认的构造方法的话,初始容量为11;

2.HashTable如果使用有参构造方法的话,初始容量即为给定的参数容量值,如3,7等就直接用了;

3.扩容为原来的2n+1

小结:HashTable和HashMap区别

第一,继承不同。

public class Hashtable extends Dictionary implements Map

public class HashMap extends AbstractMap implements Map

第二:

Hashtable 中的方法是同步的,而HashMap中的方法在缺省情况下是非同步的。在多线程并发的环境下,可以直接使用

Hashtable,但是要使用HashMap的话就要自己增加同步处理了。

第三,Hashtable中,key和value都不允许出现null值。

在HashMap中,null可以作为键,这样的键只有一个;可以有一个或多个键所对应的值为null。当get()方法返回null值时,

即可以表示 HashMap中没有该键,也可以表示该键所对应的值为null。因此,在HashMap中不能由get()方法来判断HashMap中

是否存在某个键, 而应该用containsKey()方法来判断。

第四,两个遍历方式的内部实现上不同。

Hashtable、HashMap都使用了 Iterator。而由于历史原因,Hashtable还使用了Enumeration的方式 。

第五,哈希值的使用不同,HashTable直接使用对象的hashCode。而HashMap重新计算hash值。

第六,Hashtable和HashMap它们两个内部实现方式的数组的初始大小和扩容的方式。HashTable中hash数组默认大小是11,增加的方式是 old2+1。HashMap中hash数组的默认大小是16, 增加的方式是 old2。

StringBuffer和StringBuilder:

StringBuilder和StringBuffer的初始容量都是16,程序猿尽量手动设置初始值。以避免多次扩容所带来的性能问题,默认数组容量扩充为原数组容量的2倍+2。

1.StringBuilder是jdk1.5引进的,而StringBuffer在1.0就有了;

2.StringBuilder和StringBuffer都是可变的字符串。能够通过append或者insert等方法改动串的内容;

3.StringBuffer是线程安全的而StringBuilder不是,因而在多线程的环境下优先使用StringBuffer,而其它情况下推荐使用

StringBuilder,由于它更快。

4.StringBuilder和StringBuffer都继承自AbstractStringBuilder类,AbStractStringBuilder主要实现了扩容、append、

insert方法。StrngBuilder和StringBuffer的相关方法都直接调用的父类。

5.StringBuilder和StringBuffer的初始容量都是16,程序猿尽量手动设置初始值。以避免多次扩容所带来的性能问题;

6.StringBuilder和StringBuffer的扩容机制是这种:首先试着将当前数组容量扩充为原数组容量的2倍加上2,假设这个新容

量仍然小于预定的最小值(minimumCapacity),那么就将新容量定为(minimumCapacity),最后推断是否溢出,若溢出,

则将容量定为整型的最大值0x7fffffff。


评论