【Java基础】了解常见集合初始容量、加载因子、扩容增量

为什么要了解扩容机制

  • 当底层实现涉及到扩容时,容器会重新分配一段更大连续内存(如果是离散分配则不需要重新分配,离散分配都是插入新元素时动态分配内存),将容器原来的数据全部复制新的内存上,会大大降低系统的性能

  • 加载因子的系数小于等于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节点时,有可能触发扩容动作:

  1. 如果新增节点之后,所在链表的元素个数达到了阈值 8,则会调用treeifyBin方法把链表转换成红黑树,不过在结构转换之前,会对数组长度进行判断,实现如下:
    如果数组长度n小于阈值MIN_TREEIFY_CAPACITY,默认是64,则会调用tryPresize方法把数组长度扩大到原来的两倍,并触发transfer方法,重新调整节点的位置。

  2. 新增节点之后,会调用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

发布了62 篇原创文章 · 获赞 109 · 访问量 5293

猜你喜欢

转载自blog.csdn.net/qq877728715/article/details/103063830