2019独角兽企业重金招聘Python工程师标准>>>

ArrayList 底层存储和动态扩容逻辑

ArrayList 作为最常用的容器之一,通常用来存储一系列的数据对象,O(1)级别的数据读写

I. 底层数据模型

查看源码,其内部定义的成员变量


// 默认数组容量
private static final int DEFAULT_CAPACITY = 10;// 静态成员,创建一个空的ArrayList时,内部数组实际使用这个
// 避免每次创建一个ArrayList对象,都要新创建一个对象数组
private static final Object[] EMPTY_ELEMENTDATA = {};private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};// 实际保存数据的数组
transient Object[] elementData; // non-private to simplify nested class accessprivate int size;

因此ArrayList的底层数据模型比较清晰,就是一个数组,默认初始容量为10

II. 新增,删除,读取逻辑

因为底层的数据结构为数组,所以根据index查询元素是常量级别开销,等同于获取数组中所索引为index处的元素

因此需要关注的就是新增一个元素,若数组容量不够,如何进行扩容

删除一个元素,数组的连续性又是如何保障

1. 获取接口

获取List中某索引处的值,实现逻辑比较简单,如下

public E get(int index) {// 判断是否数组越界rangeCheck(index);// 获取数组中的元素return elementData(index);
}private void rangeCheck(int index) {if (index >= size)throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}E elementData(int index) {return (E) elementData[index];
}

另一个比较常见的读取接口就是containindexOf两个接口,用于判断列表中是否包含某个元素or某个元素在数组中的索引

若让我们自己来设计上面两个接口,多半是遍历数组,依次判断每个元素,是否满足要求

JDK实际实现代码如下

public boolean contains(Object o) {return indexOf(o) >= 0;
}public int indexOf(Object o) {if (o == null) {for (int i = 0; i < size; i++)if (elementData[i]==null)return i;} else {for (int i = 0; i < size; i++)if (o.equals(elementData[i]))return i;}return -1;
}

从具体实现,可以注意一下几点

  • size表示列表中元素的实际个数
  • 列表中允许保存NULL
  • 列表中允许多次加入统一个对象,但indexOf返回的是第一个匹配的位置
  • 方法indexOf返回-1表示不存在

2. 删除元素

在添加元素之前,先看删除元素的接口实现,因为不涉及到动态扩容问题, 在分析中考虑下面几点

  1. 删除中间的元素,是否会造成后续的数组迁移
  2. 删除最后一个元素,是否会造成重排(还是直接size-1即可)

首先看删除指定索引处的值

public E remove(int index) {// 数组越界判断rangeCheck(index);modCount++;E oldValue = elementData(index);int numMoved = size - index - 1;if (numMoved > 0) { // 如果移动不是最后一个则需要数组拷贝// native 方法实现数组拷贝System.arraycopy(elementData, index+1, elementData, index,numMoved);}// 消灭最后一个元素elementData[--size] = null; // clear to let GC do its workreturn oldValue;
}

从源码解决上面两个问题

  1. 删除中间元素,会导致数组拷贝
  2. 删除最后一个元素,不用数组拷贝,直接将最后一个元素设置为null
  3. 删除不会导致数组容量缩水,也就是List只有扩容的逻辑,没有缩小容量的逻辑

3. 新增元素

结合删除的逻辑,新增元素逻辑应该比较清晰,将添加索引处及之后的元素,整体后移一位,然后赋值新的值; 需要注意扩容的机制

添加一个元素的实现

public boolean add(E e) {ensureCapacityInternal(size + 1);  // Increments modCount!!elementData[size++] = e;return true;
}
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++;
}

扩容的逻辑如下

private void ensureCapacityInternal(int minCapacity) {if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);}ensureExplicitCapacity(minCapacity);
}private void ensureExplicitCapacity(int minCapacity) {modCount++;if (minCapacity - elementData.length > 0) {// 当前的数组容量,已经超过数组长度grow(minCapacity);}
}private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;private void grow(int minCapacity) {// overflow-conscious codeint oldCapacity = elementData.length;// 扩容原则: // 新增原来容量的一半,即变为之前容量的 1.5倍// 如果上面容量依然不够,则选择扩容到恰好容下所有元素的容量int newCapacity = oldCapacity + (oldCapacity >> 1);if (newCapacity - minCapacity < 0) // 扩充后的容量依然不够newCapacity = minCapacity;if (newCapacity - MAX_ARRAY_SIZE > 0)newCapacity = hugeCapacity(minCapacity);// minCapacity is usually close to size, so this is a win:elementData = Arrays.copyOf(elementData, newCapacity);
}

针对上面的逻辑进行小结:

  • 先扩容,后数组迁移,最后进行赋值
  • 扩容逻辑:
    • 优先扩容原来容量的1.5倍
    • 若依旧不够,则扩容到恰好能容纳所有元素
  • 在列表的最后添加元素,不要使用add(index,object)方法,会造成没必要的数组迁移调用

插入删除示意图

III. 遍历逻辑

容器基本上都是实现了 Iterable 接口,所以遍历则主要是依据迭代器的next()方法来实现

List的遍历,说白了就是数组的遍历,实现逻辑比较简单,唯一有意思的就是并发修改抛异常的问题

先看下迭代器类

private class Itr implements Iterator<E> {int cursor;       // index of next element to returnint lastRet = -1; // index of last element returned; -1 if no suchint expectedModCount = modCount;public boolean hasNext() {return cursor != size;}@SuppressWarnings("unchecked")public E next() {// 下面方法确保在遍历过程中,若有其他线程修改了列表的内容,则抛异常checkForComodification();int i = cursor;if (i >= size)throw new NoSuchElementException();Object[] elementData = ArrayList.this.elementData;if (i >= elementData.length)throw new ConcurrentModificationException();// 遍历的实际逻辑,就是索引的递增cursor = i + 1;return (E) elementData[lastRet = i];}public void remove() {// ...}@Override@SuppressWarnings("unchecked")public void forEachRemaining(Consumer<? super E> consumer) {//}final void checkForComodification() {if (modCount != expectedModCount)throw new ConcurrentModificationException();}
}

IV. 小结

  1. ArrayList的底层存储为数组
  2. ArrayList中可保存null,一个对象可以塞入多次
  3. 初始容量为10, 新增元素,若实际个数超过数组容量,则触发扩容逻辑
    • 优先扩容原来容量的1.5倍
    • 若依旧不够,则扩容到恰好能容纳所有元素
  4. 只有添加元素会导致数组容量变化,删除不会
  5. 线程非安全,遍历过程中不允许修改列表

微信公众号

转载于:https://my.oschina.net/u/566591/blog/1553648

JDK容器学习之ArrayList:底层存储和动态扩容相关推荐

  1. 详解java集合之ArrayList——底层实现是一个Object数组。分析ArrayList的自动扩容,原来不一定是1.5倍

    ArrayList的底层实现--非private权限的Object数组 ArrayList的类结构图 1. ArrayList的创建 1.1 参数为空的构造方法--ArrayList内部的Object ...

  2. 给jdk写注释系列之jdk1.6容器(1):ArrayList源码解析

    前言: 工作中经常听到别人讲"容器",各种各样的容器,话说到底什么是容器,通俗的讲"容器就是用来装东西的器皿,比如:水桶就是用来盛水的,水桶就是一个容器." o ...

  3. JDK源码学习之Arraylist与LinkedList

    ArrayList和LinkedList是我们在开发过程中常用的两种集合类,本文将从底层源码实现对其进行简单介绍. 下图是Java集合类所涉及的类图. 一.ArrayList 从上面的集合类图可以看出 ...

  4. Docker容器学习笔记一

    Docker容器学习笔记一 狂神说B站视频:https://www.bilibili.com/video/BV1og4y1q7M4?p=1 Docker容器学习笔记二:https://blog.csd ...

  5. 手撕ArrayList底层,透彻分析源码

    ArrayList概述 Hello大家好,今天就来介绍一下ArrayList,说到ArrayList,很多人都知道它的底层是使用数组实现的,线程不安全的,说到它的特点,都会说查找快,增删慢,因为面试题 ...

  6. Docker容器学习梳理--日常操作总结

    使用Docker已有一段时间了,今天正好有空梳理下自己平时操作Docker时的一些命令和注意细节: Docker 命令帮助 $ sudo docker Commands:attach Attach t ...

  7. 容器List之ArrayList详解

    目录 什么是ArrayList? 源码解析 类属性 构造方法: 类方法 @ 什么是ArrayList? ArrayList是Java集合常用的数据结构之一,继承自AbstractList,实现了Lis ...

  8. HashMap的底层存储结构和实现原理

    文章目录 前言 一.HashMap是什么? 二. 数组 三. 链表 四.哈希算法 五.哈希冲突 总结 前言 HashMap实现了Map接口,我们常用来put/get操作读存键值对数据,比较典型的key ...

  9. 学习笔记——ArrayList总结

    ArrayList可以说是一种很常用很常用的数据结构了. ArrayList底层是通过数组实现的.可以看下源码(基于JDK1.8) 首先从构造函数说起,总共有三种构造函数: public ArrayL ...

最新文章

  1. win8计算机安全模式,安全模式,教您Win8怎么进入安全模式
  2. GoDaddy万用https ssl证书如何通过DNS审核
  3. linux shell awk 单引号分割
  4. 初识jvm-1.Java类的加载机制
  5. 不定高度的弹框 垂直水平居中
  6. android 许可协议,Android 基本控件的使用二(注册许可协议)(CheckBox)
  7. c/c++面试试题(二)
  8. micopython 18b20_[MicroPython]stm32f407控制DS18B20检测温度
  9. php判断一个字符串是否为纯数字,php判断变量是否为纯数字字符串的方法
  10. 习题3.10 汉诺塔的非递归实现 (25分)
  11. html的form表单详解
  12. HTML+CSS+JS制作炫酷特效代码
  13. oracle查询根据条件赋值,Oracle条件语句
  14. 应用代码(4)——基于ADS1110芯片的高精度温度(PT1000)采集
  15. 在 iOS 微信浏览器中自动播放 HTML5 audio(音乐) 的正确方式
  16. gnuplot:散点图
  17. httpd +jk_虚拟主机+ Apache httpd服务器+ Tomcat + mod_jk连接器
  18. 2021-2022学年广州市南武中学七年级第一学期期中考试英语试题
  19. Cocos Creator苹果应用商城上架指南
  20. Ac Rush 楼天成回忆录

热门文章

  1. Linux安装Composer
  2. HBase数据压缩编码探索
  3. Spring Boot 入门之基础篇(一)
  4. OSChina 周一乱弹 —— 程序员用什么浏览器能看出来品位么
  5. Spring 3 MVC and XML example
  6. ios仿淘宝管理收货地址demo
  7. [Java]Stack栈和Heap堆的区别(终结篇)[转]
  8. linux环境下的小练习
  9. 验证身份证和中文名字
  10. Java设计模式—代理模式