首页 > 编程语言 >JAVA集合的扩容机制

JAVA集合的扩容机制

时间:2024-08-30 22:53:33浏览次数:13  
标签:扩容 Node JAVA int elementData next oldCapacity 数组 集合

ArrayList

List<Integer> list = new ArrayList<>();

不指定初始长度,默认刚开始赋值{}空集合,当 add 时,初始长度为 10。

// 初始容量
private static final int DEFAULT_CAPACITY = 10;
// 空数组
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};

当超过10之后,每次扩容为原来的 1.5 倍。核心方法是 grow()。

private Object[] grow(int minCapacity) {
	int oldCapacity = elementData.length;
	if (oldCapacity > 0 || elementData != DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
		int newCapacity = ArraysSupport.newLength(oldCapacity,
				minCapacity - oldCapacity, /* minimum growth */
				oldCapacity >> 1           /* preferred growth */);
		return elementData = Arrays.copyOf(elementData, newCapacity);
	} else {
		return elementData = new Object[Math.max(DEFAULT_CAPACITY, minCapacity)];
	}
}

每次扩容都是使用 Arrays.copyOf() 拷贝旧数组,然后操作新数组,最终替换掉旧数组。

Arrays.copyOf() 底层最终调用的是 System.arraycopy()。

System.arraycopy(original, 0, copy, 0, Math.min(original.length, newLength));

System.arraycopy() 就是ArrayList增删改慢的原因。

List<Integer> list = new ArrayList<>(11);

若设置了初始容量,则只是最开始的值变了,其他都是一样的。

第一次扩容得到的值是 16

LinkedList

典型的双链表。

结构如下:

// 头指针
transient Node<E> first;

// 前一个指针    
transient Node<E> last;

private static class Node<E> {
	E item;
	Node<E> next;
	Node<E> prev;

	Node(Node<E> prev, E element, Node<E> next) {
		this.item = element;
		this.next = next;
		this.prev = prev;
	}
}

 

Vector

当不指定初始容量时,默认也是 10,并且如果没有指定 capacityIncrement(也就是每次扩容的大小时),每次默认扩容之前的一倍。当然也跟 ArrayList 一样,会使用Arrays.copyOf()拷贝数组。

public Vector() {
	this(10);
}

public Vector(int initialCapacity) {
	this(initialCapacity, 0);
}

private Object[] grow(int minCapacity) {
	int oldCapacity = elementData.length;
	int newCapacity = ArraysSupport.newLength(oldCapacity,
			minCapacity - oldCapacity, /* minimum growth */
			capacityIncrement > 0 ? capacityIncrement : oldCapacity
									   /* preferred growth */);
	return elementData = Arrays.copyOf(elementData, newCapacity);
}

比如以下的指定,容量依次是 12 16 20 ...

List<Integer> a = new Vector<>(12, 4);

HashMap

注意此分析是针对JDK1.8的。

	//HashMap的默认初始长度16
	static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; 
	
	//HashMap的最大长度2的30次幂
	static final int MAXIMUM_CAPACITY = 1 << 30;
	
	//HashMap的默认加载因子0.75
	static final float DEFAULT_LOAD_FACTOR = 0.75f;
	
	//HashMap链表升级成红黑树的临界值
	static final int TREEIFY_THRESHOLD = 8;
	
	//HashMap红黑树退化成链表的临界值
	static final int UNTREEIFY_THRESHOLD = 6;
	
	//HashMap链表升级成红黑树第二个条件:HashMap数组(桶)的长度大于等于64
	static final int MIN_TREEIFY_CAPACITY = 64;
	
	//HashMap底层Node桶的数组
	transient Node<K,V>[] table;
	
	//扩容阈值,当你的hashmap中的元素个数超过这个阈值,便会发生扩容
	//threshold = capacity * loadFactor
	int threshold;

HashMap 是懒加载,第一次 put 时,才会进行初始化节点数组,初始容量是 16(也就是Node[16]) 。

static class Node<K,V> implements Map.Entry<K,V> {
	final int hash;
	final K key;
	V value;
	Node<K,V> next;

	Node(int hash, K key, V value, Node<K,V> next) {
		this.hash = hash;
		this.key = key;
		this.value = value;
		this.next = next;
	}
}

当节点哈希冲突时,刚开始的时候是使用链表解决,当链表中的节点数量超过 TREEIFY_THRESHOLD (也就是8)时,进化为红黑树(避免导致因为链表数量太大导致查询不再是O(1))但是需要注意:在数组中的总 size 没有超过 MIN_TREEIFY_CAPACITY(64)时,优先考虑的是扩容而不是树化。当数组中的键值对数量 size(我们通常获取map的长度就是通过这个值) 超过了阈值 threshold(capacity * loadFactor【默认值开始就是 16 * 0.75 = 12】),则进行扩容

扩容的时候,默认会新建一个新数组,新数组的大小是老数组的两倍

通过 (n - 1)& hash 可以定位到数组的下标,当然,此时n (数组的长度)被定义为 2^n。

当然,当红黑树中的节点下降到 UNTREEIFY_THRESHOLD(也就是6)就退化为链表。 

不直接设置为7就变为链表是为了避免如果一直在边界值变来变去的化,导致一直链化和树化来回切换,影响性能,起到一个缓冲的作用。

标签:扩容,Node,JAVA,int,elementData,next,oldCapacity,数组,集合
From: https://blog.csdn.net/sanzailmn/article/details/141716621

相关文章

  • 自学 Java 怎么入门?
    如果你想自学Java入门,可以按照以下步骤进行:学习Java基础知识了解Java语言的特点和应用场景。学习Java基本语法,如变量、数据类型、控制流等。掌握面向对象编程的概念,如类、对象、继承、多态等。安装Java开发环境下载并安装Java开发工具包(JDK)。配置环境变量,以便在命令行......
  • 一个linux服务器安装多个java版本,如何选择指定的 java版本去执行
    linux中有时候可能你由于不同的项目需要使用不同版本的javajdk部署,你就需要在你的linux服务中安装很多个版本的javajdk,那么在linux中如何安装和使用不同版本的javajdk呢?1.安装第一个javajdk版本:到java官网下载一个javajdk版本,并解压,然后配置环境变量。javajdk地址:wge......
  • 最常用集合 - arraylist详解
    ArrayList介绍ArrayList实现了List接口,是顺序容器,即元素存放的数据与放进去的顺序相同,允许放入null元素,底层通过数组实现。除该类未实现同步外,其余跟Vector大致相同。每个ArrayList都有一个容量(capacity),表示底层数组的实际大小,容器内存储元素的个数不能多于当前容量。当向容器......
  • Java设计模式之外观模式详细讲解和案例示范
    1.引言在软件开发过程中,复杂的系统往往包含许多子系统和模块,随着系统功能的增加,模块之间的交互也变得更加复杂。这种复杂性可能会导致系统的可维护性和扩展性降低。外观模式(FacadePattern)是一种结构型设计模式,通过提供一个简化的接口,将复杂的子系统隐藏在幕后,使得外部客......
  • java类加载器
    类加载器一、类加载器【理解】作用负责将.class文件(存储的物理文件)加载在到内存中二、类加载的过程【理解】类加载时机创建类的实例(对象)调用类的类方法访问类或者接口的类变量,或者为该类变量赋值使用反射方式来强制创建某个类或接口对应的java.lang.Class对......
  • Java 中的各种排序:详细教程
    1.前言本文通过许多代码示例逐步解释如何对Java中原始数据类型(int、long、double等)和任何类的对象进行排序。具体来说,本文主要回答了以下问题:如何对Java中原始数据类型的数组进行排序?如何对Java中的对象数组和列表进行排序?如何在Java中并行排序?JDK内部使用哪些排......
  • JavaScript - 闭包
    使用场景数据封装闭包允许创建私有变量,这些变量在函数外部无法直接访问。通过闭包,可以创建具有私有状态的对象,从而实现数据封装。例如:functioncreateCounter(){letcount=0;//count是私有变量returnfunction(){count++;returncount;};}const......
  • 发红包案例(java)
    User类创建publicclassUser{privateStringname;privateintmoney;publicUser(){}publicUser(Stringname,intmoney){this.name=name;this.money=money;}publicvoidshow(){System.out.println(&qu......
  • Java根据经纬度计算两个坐标之间的距离(含SQL计算)
    最近接到两个需求,一个是通过小程序扫码开门的,我这边主要就是根据用户定位判断用户离扫码店铺距离小于多少米的时候才可以调远程调开门接口,另外一个就是获取用户周围有哪些店铺。需求很简单,就是根据定位获取的经度维度计算两个点之间的球面距离,这里我们主要采用Haversine公......
  • javascript 检测 麦克风状态
    <htmllang="zh"><head><metacharset="UTF-8"><metaname="viewport"content="width=device-width,initial-scale=1.0"><title>麦克风监听示例<style>body......