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

  
 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

posted @ 2023-01-06 06:44  锐洋智能  阅读(547)  评论(0编辑  收藏  举报