Java ArrayList

之前曾经参考 数据结构与算法这本书写过ArrayList的demo,本来以为实现起来都差不多,今天抽空看了下jdk中的ArrayList的实现,差距还是很大啊

首先看一下ArrayList的类图

ArrayList实现了Serializable Cloneable RandomAccess List这几个接口,可序列化,可克隆,可以随机访问

构造方法:

public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}

之前手写ArrayList的时候,都会用一个默认容量来 new 一个数组,在jdk中实现是默认一个空数组,因为有的时候ArrayList创建后并不会添加元素

当然,这两个都是静态私有域

值得注意的是 this.elementData

是一个Object的数组 transient表示这个属性不用被序列化,通过注释可以得知,element在第一次添加的时候会被扩容到默认容量(默认为10)

add 方法

public boolean add(E e) {
ensureCapacityInternal(size + 1); // Increments modCount!!
elementData[size++] = e;
return true;
}

add 方法中调用了 ensureCapacityInternal相当于确保容量最少是size+1,size就是当前ArrayList元素个数,然后在elementData末尾加入元素

接下来看一下是如何确保容量的

private static int calculateCapacity(Object[] elementData, int minCapacity) {
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
return Math.max(DEFAULT_CAPACITY, minCapacity);
}
return minCapacity;
} private void ensureCapacityInternal(int minCapacity) {
ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
} private void ensureExplicitCapacity(int minCapacity) {
modCount++; // overflow-conscious code
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}

ensureCapacityInternal首先会调用calculateCapacity,这里主要是为了计算第一次初始化的时候,因为我们在默认初始化的时候,默认容量是10,但是为什么确保阔容是Math.max(DEFAULT_CAPACITY, minCapacity);,这里主要是因为如果我们添加一个集合的话,要确保至少大小是集合中元素的大小,否则可能会多一次扩容

然后调用ensureExplicitCapacity

ensureExplicitCapacity:先设置一下当前容器已经被更改,然后判断当前最少需要容量是不是大于数组长度,如果大于,那就扩容

private void grow(int minCapacity) {
// overflow-conscious code
int oldCapacity = elementData.length;
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);
} private static int hugeCapacity(int minCapacity) {
if (minCapacity < 0) // overflow
throw new OutOfMemoryError();
return (minCapacity > MAX_ARRAY_SIZE) ?
Integer.MAX_VALUE :
MAX_ARRAY_SIZE;
}

首先获取旧数组的长度然后用旧数组长度进行扩容为1.5倍,然后判断和最小需求容量对比,如果小于最小容量,那么就扩容到最小容量那么长,然后判断是不是大于一个阈值,如果大于这个最大阈值,那么就扩容到Integer.MAX_VALUE(正整数最大值,2^31-1)

至于为什么要判断minCapacity<0,那是因为假设当前已经扩容到最大值,要是还不够,那么再扩容就是int溢出

最后把源数组copy到新的容量大小赋值给elementData,Array.copyOf底层是native方法(System.arraycopy)

之前自己写的ArrayList都是通过 oldcaptain = oldcaptain<<1+1;来进行扩容的(+1是避免旧数组长度为0的情况),jdk对于不同的情况有不同的扩容标准,而且以前自己的Copy都是用数组遍历Copy的很笨重,这里学到了

再来看一下 add(int index,T ele)

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++;
}

这个也很好理解,就是先检查index是否在范围内(0~size)如果不在就抛出一个越界异常

然后准备扩容,接下来就是数组拷贝

System.arraycopy也是一个native方法

看一下注释就是把srcsrcPos开始拷贝到destdestPos开始的位置一共copy length这么长

如果src==dst那么这个函数表现就像先拷贝到一个临时数组,再覆盖dst对应位置

不会像*dst++=*src++把后面的元素覆盖然后后面元素都是一个值

这样就是把elementData从index开始到最后一个元素,拷贝到src+1的位置

最后执行elementData[index] = element;把元素覆盖

然后我们看remove :

public E remove(int index) {
rangeCheck(index); modCount++;
E oldValue = elementData(index); int numMoved = size - index - 1;
if (numMoved > 0)
System.arraycopy(elementData, index+1, elementData, index,
numMoved);
elementData[--size] = null; // clear to let GC do its work return oldValue;
}

remove方法跟add基本同理,但是不需要扩容而且最后覆盖元素的时候是使用null填充最后一个元素

之前实现的时候没考虑到用null覆盖,这样会导致在GC的时候,本来需要删除的元素还可以通过ArrayList找到,然后就无法GC,这里学到了

remove一个对象

public boolean remove(Object o) {
if (o == null) {
for (int index = 0; index < size; index++)
if (elementData[index] == null) {
fastRemove(index);
return true;
}
} else {
for (int index = 0; index < size; index++)
if (o.equals(elementData[index])) {
fastRemove(index);
return true;
}
}
return false;
}

找对应元素的话基本都是大同小异,主要是fastRemove跟自己实现的不太一样

private void fastRemove(int index) {
modCount++;
int numMoved = size - index - 1;
if (numMoved > 0)
System.arraycopy(elementData, index+1, elementData, index,
numMoved);
elementData[--size] = null; // clear to let GC do its work
}

fastRemove里面跟remove基本相同,少了一个index判断也没有返回值

clear:

public void clear() {
modCount++; // clear to let GC do its work
for (int i = 0; i < size; i++)
elementData[i] = null; size = 0;
}

clear方法之前一直以为是直接把size设为0,但是jdk里面实现是遍历一下设null,但是这里我总觉得应该再多提供一个fastclear什么的比较好吧

设为null会让对象索引不到,可以被垃圾回收,但是如果频繁add clear的话总觉得不值得啊

再看一下一些跟集合的操作

通过一个集合初始化:

public ArrayList(Collection<? extends E> c) {
elementData = c.toArray();
if ((size = elementData.length) != 0) {
// c.toArray might (incorrectly) not return Object[] (see 6260652)
if (elementData.getClass() != Object[].class)
elementData = Arrays.copyOf(elementData, size, Object[].class);
} else {
// replace with empty array.
this.elementData = EMPTY_ELEMENTDATA;
}
}

这里首先调用集合的toArray()方法,不过要确保elementData真的是一个Object[]数组

Java 中对象数组子类数组引用也可以转换为超类的引用

比方说 Manager 继承了 Employee

Manager[]managers = new Manager[10];

那么我们可以

Employee[]employees = managers;//完全没问题

但是如果我们在使用employees的时候在里面存放了一个new Employees,那么就会发生一个异常

这个jdk的bug可以查一下

https://blog.csdn.net/aitangyong/article/details/30274749

Java集合中toArray一般情况下都是Object[]数组,不过手动实现一个集合,有可能出问题,所以jdk采用这种方式避免了不必要的麻烦

就是避免这种情况:

ArrayList<Integer> integers = new ArrayList<>(0);
integers.add(1);
System.out.println(integers.toArray().getClass());
Integer[]integers_array = new Integer[2];
integers_array[0]=1;
integers_array[1]=2;
Class c = Arrays.asList(integers_array).toArray().getClass();
System.out.println(c);

Array.asList就是包装一个视图,里面使用add remove什么的都会抛一个异常

Java ArrayList 源代码分析的更多相关文章

  1. Android系统进程间通信Binder机制在应用程序框架层的Java接口源代码分析

    文章转载至CSDN社区罗升阳的安卓之旅,原文地址:http://blog.csdn.net/luoshengyang/article/details/6642463 在前面几篇文章中,我们详细介绍了A ...

  2. java TreeMap 源代码分析 平衡二叉树

    TreeMap 的实现就是红黑树数据结构,也就说是一棵自平衡的排序二叉树,这样就可以保证当需要快速检索指定节点. TreeSet 和 TreeMap 的关系 为了让大家了解 TreeMap 和 Tre ...

  3. Java ThreadLocal 源代码分析

    Java ThreadLocal 之前在写SSM项目的时候使用过一个叫PageHelper的插件 可以自动完成分页而不用手动写SQL limit 用起来大概是这样的 最开始的时候觉得很困惑,因为直接使 ...

  4. Java ConcurrentHashMap 源代码分析

    Java ConcurrentHashMap jdk1.8 之前用到过这个,但是一直不清楚原理,今天抽空看了一下代码 但是由于我一直在使用java8,试了半天,暂时还没复现过put死循环的bug 查了 ...

  5. Java HashMap 源代码分析

    Java HashMap jdk 1.8 Java8相对于java7来说HashMap变化比较大,在hash冲突严重的时候java7会退化为链表,Java8会退化为TreeMap 我们先来看一下类图: ...

  6. Java中arraylist和linkedlist源代码分析与性能比較

    Java中arraylist和linkedlist源代码分析与性能比較 1,简单介绍 在java开发中比較经常使用的数据结构是arraylist和linkedlist,本文主要从源代码角度分析arra ...

  7. Android应用程序进程启动过程的源代码分析

    文章转载至CSDN社区罗升阳的安卓之旅,原文地址: http://blog.csdn.net/luoshengyang/article/details/6747696 Android 应用程序框架层创 ...

  8. java源代码分析----jvm.dll装载过程

    简述众所周知java.exe是java class文件的执行程序,但实际上java.exe程序只是一个执行的外壳,它会装载jvm.dll(windows下,以下皆以windows平台为例,linux下 ...

  9. Java源代码分析与生成

    源代码分析:可使用ANTLRANTLR是开源的语法分析器,可以用来构造自己的语言,或者对现有的语言进行语法分析. JavaParser 对Java代码进行分析 CodeModel 用于生成Java代码 ...

随机推荐

  1. Linux 下LNMP环境搭建_【all】

    LNMP = Linux + Nginx + Mysql + PHP 1.0 Linux环境搭建 Linux 系统安装[Redhat] 1.1. FastCGI介绍 1.什么是CGI(common g ...

  2. DRAM(动态)存储器

    一.DRAM的存储元电路 常见的DRAM存储元电路有四管式和单管式两种,它们的共同特点是靠电容存储电荷的原理来存储信息.电容上存有足够多的电荷表示“1”,电容上无电荷表示“0”. 由于电容存储的电荷会 ...

  3. Linux命令--目录处理

    ls命令 Linux ls命令用于显示指定工作目录下之内容(列出目前工作目录所含之文件及子目录). 语法 ls [-alrtAFR] [name...] 参数 : -a 显示所有文件及目录 (ls内定 ...

  4. ECharts 图表设置标记的大小 symbolSize 和获取标记的值

    ECharts 是百度出品,一个纯 Javascript 的图表库,可以流畅的运行在 PC 和移动设备上,兼容当前绝大部分浏览器(IE8/9/10/11,Chrome,Firefox,Safari等) ...

  5. 关于C++学习笔记

    以清华大学出版社<C++语言程序设计> 第四版,郑莉,董渊,何江舟 三位老师编著为蓝本. 写这学习笔记,是为了自己清晰梳理C++.重粘代码也是为了方便更容易认清结构.

  6. pip-修改为国内镜像源

    pip 常用命令 pip install ./downloads/SomePackage-1.0.4.tar.gz pip install http://my.package.repo/SomePac ...

  7. BZOJ1089:[SCOI2003]严格n元树(DP,高精度)

    Description 如果一棵树的所有非叶节点都恰好有n个儿子,那么我们称它为严格n元树.如果该树中最底层的节点深度为d (根的深度为0),那么我们称它为一棵深度为d的严格n元树.例如,深度为2的严 ...

  8. 监听器中spring注入相关的问题

    问题描述: 需求是要求在项目启动自动触发一个service中的线程的操作,使用监听器来实现,但是自定义监听器中spring注解service失败,通过WebApplicationContextUtil ...

  9. 百度Apollo安装说明

    前言:最近在和百度Apollo合作,Apollo的人很nice,大家都在全力帮助我们解决问题.但Apollo系统有点难搞,安装起来很费劲,为了避免再次踩坑,留下笔记,流传后人,O(∩_∩)O. 1. ...

  10. inux下使用自带mail发送邮件告警

    安装mailx工具,mailx是一个小型的邮件发送程序. 具体步骤如下: 1.安装 [root@localhost ~]# yum -y install mailx 2.编辑配置文件 [root@lo ...