聊一聊jdk1.8中的ArrayList 底层数组是如何扩容的
作者:光哥_帅 发布时间:2023-11-16 08:55:50
一、结论先行
ArrayList在JDK1.8与JDK1.7底层区别
JDK1.7:ArrayList像饿汉式,直接创建一个初始容量为10的数组,当数组的长度不能容下所添加的内容时候,数组会扩容至原大小的1.5倍
JDK1.8:ArrayList像懒汉式,一开始创建一个长度为0的数组,当添加第一个元素时再创建一个始容量为10的数组,当数组的长度不能容下所添加的内容时候,数组会扩容至原大小的1.5倍
二、JDK1.8 ArrayList源码分析
1、ArrayList 属性
/**
* 默认容量的大小
*/
private static final int DEFAULT_CAPACITY = 10;
/**
* 空数组常量
*/
private static final Object[] EMPTY_ELEMENTDATA = {};
/**
* 默认的空数组常量,我们将其与EMPTY_ELEMENTDATA区分开来,
* 以便知道添加第一个元素时需要膨胀多少
*/
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
/**
* 存放元素的数组,从这可以发现ArrayList的底层实现就是一个Object数组
*/
transient Object[] elementData;
/**
* 数组中包含元素的个数
*/
private int size;
/**
*数组的最大上限,超过上限可能导致OutOfMemoryError错误
*/
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
2、构造方法
/**
* 指定大小的时候,elementData就变成了我们所指定的初始化大小了
*/
public ArrayList(int initialCapacity) {
if (initialCapacity > 0) {
this.elementData = new Object[initialCapacity];
} else if (initialCapacity == 0) {
this.elementData = EMPTY_ELEMENTDATA;
} else {
throw new IllegalArgumentException("Illegal Capacity: "+
initialCapacity);
}
}
/**
* 根据上面的属性可知,DEFAULTCAPACITY_EMPTY_ELEMENTDATA={},所以默认创建的是 一个大小为0的空数组
*/
public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
从上面我可以知道,jkd1.8中默认的是大小为0空数组,这个和jdk1.7之前都是不一样的,这和设计模式的懒汉式很有相似之处
3、add 方法,底层扩容机制
/**
* 在指定的位置插入指定的元素
*/
public void add(int index, E element) {
rangeCheckForAdd(index);
ensureCapacityInternal(size + 1); // Increments modCount!!
System.arraycopy(elementData, index, elementData, index + 1,
size - index);
elementData[index] = element;
size++;
}
/**
* 将指定的参数添加到列表的末尾,其中size是数组中包含元素的个数
* @param e 以附加到此列表中
*/
public boolean add(E e) {
ensureCapacityInternal(size + 1);
// 数组的下标从0开始,所以size++保证elementData每次添加完一个元素,元素个数也随之加1
elementData[size++] = e;
return true;
}
// 参数minCapacity 表达的意思是所需数组的最小容量,也就是size+1, 上面传的值
private void ensureCapacityInternal(int minCapacity) {
ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
}
/**
* 计算容量的方法,
*/
private static int calculateCapacity(Object[] elementData, int minCapacity) {
// 如果是一个空数组,
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
// DEFAULT_CAPACITY从属性可知默认是10,minCapactity为数组的大小
// 由此可以看出他是在添加第一个元素的时候,才创建了长度为10的数组
return Math.max(DEFAULT_CAPACITY, minCapacity);
}
return minCapacity;
}
private void ensureExplicitCapacity(int minCapacity) {
modCount++;
// 如果此时所需要的最小的长度大于原数组的长度,则需要进行扩容
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}
/**
* 增加容量,以确保它至少可以容纳minCapacity指定的元素数量。
* @param minCapacity 表示所需要的扩容的量
*/
private void grow(int minCapacity) {
int oldCapacity = elementData.length; // 原数组的长度
//原数组的长度+原数组的长度/2,表示扩容了原来大小的1.5倍,newCapacity :表示需要扩容的量
int newCapacity = oldCapacity + (oldCapacity >> 1);
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;
// 如果需要的扩容量大于了本类中定义的最大扩容限制,则扩容到 int 类型最大长度
if (newCapacity - MAX_ARRAY_SIZE > 0)
newCapacity = hugeCapacity(minCapacity);
// 调用的是数组的复制方法,实现扩容
elementData = Arrays.copyOf(elementData, newCapacity);
}
private static int hugeCapacity(int minCapacity) {
if (minCapacity < 0) // overflow
throw new OutOfMemoryError();
// 如若需要扩容的量大于最大限制,则扩容量改为 int 最大限制量:2147483647,否则为本类中所限制长度:2147483647-8
return (minCapacity > MAX_ARRAY_SIZE) ?Integer.MAX_VALUE :MAX_ARRAY_SIZE;
}
解释:int newCapacity = oldCapacity + (oldCapacity >> 1);
这个>>1 表示的是右移一位,转为二进制我们应该一下就明白了:比如16,转为2进制为
10000,右移一位则成为01000,换为十进制就是8,扩容的大小也就相当于oldCapacity +oldCapacity /2了
4、总结
jdk1.8中:
add方法总结起来就是在插入数据之前,会先检查是否需要扩容,通过无参构造方法来创建 ArrayList 时,它的大小其实是为 0 的,只有在使用到 的时候,才会通过 grow 方法去创建一个大小为 10 的数组。
public boolean add(E e) 方法的复杂度为O(1),涉及到扩容的操作是非常少的,可以忽略不计,它的本质是添加元素到数组中最后一个元素的后面。
public void add(int index, E element) 这个是带指定下标的add 方法,复杂度为O(n),因为涉及到数组中元素的移动,这一操作非常耗时,由此可见ArrayList不适合插入和删除操作。
三、ArrayList与Vector的区别
现在Vector已经很少有人用了,这里只是简单的记录下二者区别:
1、ArrayList线程不安全,Vector是线程安全的
通过Vector源码我们可以知道很多方法都是加了synchronized关键字,所以Vector是线程安全的。
2、ArrayList创建的默认大小为0,Vector创建时的默认大小是10。
3、ArrayList 每次扩容都以当前数组大小的 1.5 倍去扩容, Vector 每次扩容都以当前数组大小的 2 倍去扩容。当指定了 capacityIncrement 之 后,每次扩容仅在原先基础上增加 capacityIncrement 个单位空间。
补充知识:ArrayList详解,底层是数组,实现Serializable接口
一、对于ArrayList需要掌握的七点内容
ArrayList的创建:即构造器
往ArrayList中添加对象:即add(E)方法
获取ArrayList中的单个对象:即get(int index)方法
删除ArrayList中的对象:即remove(E)方法
遍历ArrayList中的对象:即iterator,在实际中更常用的是增强型的for循环去做遍历
判断对象是否存在于ArrayList中:contain(E)
ArrayList中对象的排序:主要取决于所采取的排序算法(以后讲)
二、源码分析
2.1、ArrayList的创建(常见的两种方式)
List<String> strList = new ArrayList<String>();
List<String> strList2 = new ArrayList<String>(2);
ArrayList源代码:
基本属性:
//对象数组:ArrayList的底层数据结构
private transient Object[] elementData;
//elementData中已存放的元素的个数,注意:不是elementData的容量
private int size;
注意:
transient关键字的作用:在采用Java默认的序列化机制的时候,被该关键字修饰的属性不会被序列化。
ArrayList类实现了java.io.Serializable接口,即采用了Java默认的序列化机制
上面的elementData属性采用了transient来修饰,表明其不使用Java默认的序列化机制来实例化,但是该属性是ArrayList的底层数据结构,在网络传输中一定需要将其序列化,之后使用的时候还需要反序列化,那不采用Java默认的序列化机制,那采用什么呢?直接翻到源码的最下边有两个方法,发现ArrayList自己实现了序列化和反序列化的方法
View Code
构造器:
/**
* 创建一个容量为initialCapacity的空的(size==0)对象数组
*/
public ArrayList(int initialCapacity) {
super();//即父类protected AbstractList() {}
if (initialCapacity < 0)
throw new IllegalArgumentException("Illegal Capacity:" + initialCapacity);
this.elementData = new Object[initialCapacity];
}
/**
* 默认初始化一个容量为10的对象数组
*/
public ArrayList() {
this(10);//即上边的public ArrayList(int initialCapacity){}构造器
}
在我们执行new ArrayList<String>()时,会调用上边的无参构造器,创造一个容量为10的对象数组。
在我们执行new ArrayList<String>(2)时,会调用上边的public ArrayList(int initialCapacity),创造一个容量为2的对象数组。
注意:
上边有参构造器的super()方法是ArrayList父类AbstractList的构造方法,这个构造方法如下,是一个空构造方法:
protected AbstractList() {
}
在实际使用中,如果我们能对所需的ArrayList的大小进行判断,有两个好处:
节省内存空间(eg.我们只需要放置两个元素到数组,new ArrayList<String>(2))
避免数组扩容(下边会讲)引起的效率下降(eg.我们只需要放置大约37个元素到数组,new ArrayList<String>(40))
2.2、往ArrayList中添加对象(常见的两个方法add(E)和addAll(Collection<? extends E> c))
来源:https://blog.csdn.net/jerry11112/article/details/106951235
猜你喜欢
- 本文实例为大家分享了Java图片验证码代码,供大家参考,具体内容如下网页显示效果:index.jsp 使用两种方式强制图片更新: 1、设置图
- 一.你了解类吗?在Java中,类文件是以.java为后缀的代码文件,在每个类文件中最多只允许出现一个public类,当有public类的时候
- java8的stream流能完美解对象集合去重问题. List<UserCar> list1 = new ArrayList()
- 建造者模式(Builder Pattern)使用多个简单的对象一步一步构建成一个复杂的对象。这种类型的设计模式属于创建型模式,它提供了一种创
- JDK12的五大重要新特性Java12在March 19, 2019发布了。在2017年发布Java 9之后,Java平台发布节奏已从每3年
- 最近做一个需求,需求中的bean只用于生成一次json使用,所以想通过配置来动态的生成,查了一下,java还真有这个实现。java动态的生成
- 使用通配符增强泛型1.题目泛型是JAVA重要的特性,使用泛型编程,可以使代码复用率提高。实现:在泛型方法中使用通配符2.解题思路创建一个类:
- 前言 短时间提升自己最快的手段就是背面试题,最近总结了Java常用的面试题,分享给大家,希望大家都能圆梦大厂,加油,我命由我不由天
- 1. 前言Spring最重要的一个概念当属Bean了,我们写的Controller、Service、Dao凡是加了对应注解交给Spring管
- 最近 IDEA 2020最后一个版本发布了 ,已经内置了Lombok插件,SpringBoot 2.1.x之后的版本也在Starter中内置
- 添加MyBatis的代码并修改以下部分:1.添加MyBatisConfigpackage myshop.config;import java
- 定义与结构 备忘录(Memento)模式又称标记(Token)模式。GOF给备忘录模式的定义为:在不破坏
- 本文实例为大家分享了Android自定义带圆点的半圆形进度条,供大家参考,具体内容如下仅限用于半圆形,如须要带圆点的圆形进度条,圆点会出现错
- 本文实例讲述了Java实现的不同图片居中剪裁生成同一尺寸缩略图功能。分享给大家供大家参考,具体如下:因为业务需要,写了这样一个简单类,希望能
- 本文实例讲述了Android TextView跑马灯效果实现方法。分享给大家供大家参考,具体如下:public class MyTextVi
- 简单的理解,MyBatis逆向工程,就是通过相应插件,自动生成MyBatis数据库连接的一些文件。mybatis需要编写sql语句,myba
- 前言我们知道volatile关键字的作用是保证变量在多线程之间的可见性,它是java.util.concurrent包的核心,没有volat
- 项目中很多时候需要读取自定义配置文件,本地开发工具怎么写都成功但是部署到服务其上就出现问题,异常BOOT-INF/classes!/conf
- 参考链接亲测试以下版本成功激活附激活教程。idea下载链接(对应版本号下载):https://www.jetbrains.com/idea/
- Java8 LocalDateTime与timestamp转换将timestamp转为LocalDateTimepublic LocalDa