聊一聊jdk1.8中的ArrayList 底层数组是如何扩容的

一、结论先行

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))

以上这篇聊一聊jdk1.8中的ArrayList 底层数组是如何扩容的就是小编分享给大家的全部内容了,希望能给大家一个参考,也希望大家多多支持我们。

(0)

相关推荐

  • JAVA JDK8 List分组的实现和用法

    概述 对List进行分组是日常开发中,经常遇到的,在JDK 8中对List按照某个属性分组的代码,超级简单. package test; import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.serializer.SerializerFeature; import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util

  • java8实现list集合中按照某一个值相加求和,平均值等操作代码

    集合: List<User> user = new User(); user .stream().collect(Collectors.summingInt(User::getAge)) 参数类型: summarizingDouble 统计数据(double)状态, 其中包括count min max sum和平均值 summarizingInt 统计数据(int)状态, 其中包括count min max sum和平均值 summarizingLong 统计数据(long)状态, 其中包括c

  • Java数组扩容实现方法解析

    这篇文章主要介绍了Java数组扩容实现方法解析,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下 第一种 int[] arr2=new int[arr1.length*2] //新数组的长度 第二种 int[] arr2=java.util.Arrays.copyOf(原数组名,新数组的长度); 第三种 int[] arr2=new int[arr1.length*2] System.arraycopy(原数组名,起始下标,新数组名,起始下标,复制

  • 聊一聊jdk1.8中的ArrayList 底层数组是如何扩容的

    一.结论先行 ArrayList在JDK1.8与JDK1.7底层区别 JDK1.7:ArrayList像饿汉式,直接创建一个初始容量为10的数组,当数组的长度不能容下所添加的内容时候,数组会扩容至原大小的1.5倍 JDK1.8:ArrayList像懒汉式,一开始创建一个长度为0的数组,当添加第一个元素时再创建一个始容量为10的数组,当数组的长度不能容下所添加的内容时候,数组会扩容至原大小的1.5倍 二.JDK1.8 ArrayList源码分析 1.ArrayList 属性 /** * 默认容量的

  • PowerShell中使用ArrayList实现数组插入、删除、添加例子

    PowerShell中对数组进行插入.删除.添加数组元素的操作是很不方便,而且效率也是很低下的.那是因为数组对象本身并没有插入和删除的功能,每次的操作都是将数组整个拷贝到一个新的数组中.这个过程太消耗资源. 如果我们把Array对象转换为ArrayList对象,那一切问题都解决了.ArrayList有InsertAt()和RemoveAt()方法,所以在处理数组元素的插入和删除操作时更方便快捷,而且事实上效率也更高. $array = 1..10 [System.Collections.Arra

  • JDK1.8中ArrayList是如何扩容的

    ArrayList简介: ArrayList实现了List接口它是一个可调整大小的数组可以用来存放各种形式的数据.并提供了包括CRUD在内的多种方法可以对数据进行操作但是它不是线程安全的,外ArrayList按照插入的顺序来存放数据. 在讲扩容机制之前,我们需要了解一下ArrayList中最主要的几个变量: private static final int DEFAULT_CAPACITY = 10;//数组默认初始容量 private static final Object[] EMPTY_E

  • Java中如何将 int[] 数组转换为 ArrayList(list)

    目录 Java中数组转List的四种方式 第一种方式(未必最佳):使用ArrayList.asList(strArray) 第二种方法(支持增删查改): 第三种方式(通过集合工具类Collections.addAll()方法(最高效)) 第四种方式通过JDK8的Stream流将3总基本类型数组转为List java数组转list误区 一.不能把基本数据类型转化为列表 二.asList方法返回的是数组的一个视图 下面介绍下Java中如何将 int[] 数组转换为 List(ArrayList) 前

  • Android中ArrayList和数组相互转换

    List-–>数组 在大家开发中应该经常碰到List与数组类型之间的相互转换,举一个简单的例子: package test.test1; import java.util.ArrayList; import java.util.List; public class Test { /** * @param args */ public static void main(String[] args) { List list=new ArrayList(); list.add("王利虎"

  • java中申请不定长度数组ArrayList的方法

    如下所示: import java.util.ArrayList; //java中申请不定长度数组 public class Test01 { public static void main(String[] args) { // TODO Auto-generated method stub ArrayList list=new ArrayList(); list.add("123"); list.add("5"); list.add("5")

  • 简单聊一聊Go语言中的数组和切片

    目录 1. 数组 2. 切片(Slice) append 函数 总结 1. 数组 数组是一个由固定长度的特定类型元素组成的序列,一个数组可以由零个或多个元素组成.因为数组的长度是固定的,因此在 Go 语言中很少直接使用数组.和数组对应的类型是 Slice(切片),它是可以增长和收缩的动态序列,slice 功能也更灵活. 数组的每个元素可以通过索引下标来访问,索引下标的范围是从 0 开始到数组长度减 1 的位置.内置的 len 函数将返回数组中元素的个数. var a [3]int // arra

  • Java中为什么ArrayList初始化容量大小为10

    目录 背景 为什么HashMap的初始化容量为16? ArrayList的初始化容量是10吗? 为什么ArrayList的初始化容量为10? 小结 背景 看ArrayList源码时,无意中看到ArrayList的初始化容量大小为10,这就奇怪了!我们都知道ArrayList和HashMap底层都是基于数组的,但为什么ArrayList不像用HashMap那样用16作为初始容量大小,而是采用10呢? 于是各方查找资料,求证了这个问题,这篇文章就给大家讲讲. 为什么HashMap的初始化容量为16?

  • 你真的理解Java中的ArrayList吗

    目录 1. 为什么需要ArrayList? 2. ArrayList底层是如何实现的? 3. 结合源码分析主要成员变量 4. 个人的一点总结 1. 为什么需要ArrayList? 图1 图2 记得在刚刚学习Java的时候,我们首先是学习了数组,这是我们学到的第一个可以存储多个对象的实例或者基本类型的具体值,数组存储的特点如下: 只能存储同种类型的数据. 在定义数组时,必须指定该数组的大小,并且在不改变数组的前提下,不可修改其长度. 以上特性就会导致很多弊端.比如:我们往往不希望数组只能存储一种数

  • Golang中的Slice与数组及区别详解

    在golang中有数组和Slice两种数据结构,Slice是基于数组的实现,是长度动态不固定的数据结构,本质上是一个对数组字序列的引用,提供了对数组的轻量级访问.那么我们今天就给大家详细介绍下Golang中的Slice与数组, 1.Golang中的数组 数组是一种具有固定长度的基本数据结构,在golang中与C语言一样数组一旦创建了它的长度就不允许改变,数组的空余位置用0填补,不允许数组越界. 数组的一些基本操作:      1.创建数组: func main() { var arr1 = [.

随机推荐