一、概述

  一上来,先来看看源码中的这一段注释,我们可以从中提取到一些关键信息:

  Resizable-array implementation of the List interface. Implements all optional list operations, and permits all elements, including null. In addition to implementing the List interface, this class provides methods to manipulate the size of the array that is used internally to store the list. (This class is roughly equivalent to Vector, except that it is unsynchronized.)

  从这段注释中,我们可以得知ArrayList是一个动态数组,实现了List接口以及list相关的所有方法,它允许所有元素的插入,包括null。另外,ArrayList和Vector除了线程不同步之外,大致相等。

  二、属性

  //默认容量的大小

  private static final int DEFAULT_CAPACITY = 10;

  //空数组常量

  private static final Object[] EMPTY_ELEMENTDATA = {};

  //默认的空数组常量

  private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};

  //存放元素的数组,从这可以发现ArrayList的底层实现就是一个Object数组

  transient Object[] elementData;

  //数组中包含的元素个数

  private int size;

  //数组的最大上限

  private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;

  ArrayList的属性非常少,就只有这些。其中最重要的莫过于elementData了,ArrayList所有的方法都是建立在elementData之上。接下来,我们就来看一下一些主要的方法吧。

  三、方法

  1、构造方法

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

  }

  }

  public ArrayList() {

  this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;

  }

  从构造方法中我们可以看见,默认情况下,elementData是一个大小为0的空数组,当我们指定了初始大小的时候,elementData的初始大小就变成了我们所指定的初始大小了。

  2、get方法

  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];

  }

  因为ArrayList是采用数组结构来存储的,所以它的get方法非常简单,先是判断一下有没有越界,之后就可以直接通过数组下标来获取元素了,所以get的时间复杂度是O(1)。

  3、add方法

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

  //调用一个native的复制方法,把index位置开始的元素都往后挪一位

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

  }

  ArrayList的add方法也很好理解,在插入元素之前,它会先检查是否需要扩容,然后再把元素添加到数组中最后一个元素的后面。在ensureCapacityInternal方法中,我们可以看见,如果当elementData为空数组时,它会使用默认的大小去扩容。所以说,通过无参构造方法来创建ArrayList时,它的大小其实是为0的,只有在使用到的时候,才会通过grow方法去创建一个大小为10的数组。

  第一个add方法的复杂度为O(1),虽然有时候会涉及到扩容的操作,但是扩容的次数是非常少的,所以这一部分的时间可以忽略不计。如果使用的是带指定下标的add方法,则复杂度为O(n),因为涉及到对数组中元素的移动,这一操作是非常耗时的。

  4、set方法

  public E set(int index, E element) {

  rangeCheck(index);

  E oldValue = elementData(index);

  elementData[index] = element;

  return oldValue;

  }

  set方法的作用是把下标为index的元素替换成element,跟get非常类似,所以就不在赘述了,时间复杂度度为O(1)。

  5、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带指定下标的方法非常类似,也是调用系统的arraycopy方法来移动元素,时间复杂度为O(n)。

  6、grow方法

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

  }郑州治疗妇科哪个医院好 http://www.120kdfk.com/

  grow方法是在数组进行扩容的时候用到的,从中我们可以看见,ArrayList每次扩容都是扩1.5倍,然后调用Arrays类的copyOf方法,把元素重新拷贝到一个新的数组中去。

  7、size方法

  public int size() {

  return size;

  }

  size方法非常简单,它是直接返回size的值,也就是返回数组中元素的个数,时间复杂度为O(1)。这里要注意一下,返回的并不是数组的实际大小。

  8、indexOf方法和lastIndexOf

  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;

  }

  public int lastIndexOf(Object o) {

  if (o == null) {

  for (int i = size-1; i >= 0; i--)

  if (elementData[i]==null)

  return i;

  } else {

  for (int i = size-1; i >= 0; i--)

  if (o.equals(elementData[i]))

  return i;

  }

  return -1;

  }

  indexOf方法的作用是返回第一个等于给定元素的值的下标。它是通过遍历比较数组中每个元素的值来查找的,所以它的时间复杂度是O(n)。

  lastIndexOf的原理跟indexOf一样,而它仅仅是从后往前找起罢了。

  Vector

  Vector很多方法都跟ArrayList一样,只是多加了个synchronized来保证线程安全。所以Vector与ArrayList的不同点了解一下就可以了。

  Vector比ArrayList多了一个属性:

  protected int capacityIncrement;

  这个属性是在扩容的时候用到的,它表示每次扩容只扩capacityIncrement个空间就足够了。该属性可以通过构造方法给它赋值。先来看一下构造方法:

  public Vector(int initialCapacity, int capacityIncrement) {

  super();

  if (initialCapacity < 0)

  throw new IllegalArgumentException("Illegal Capacity: "+initialCapacity);

  this.elementData = new Object[initialCapacity];

  this.capacityIncrement = capacityIncrement;

  }

  public Vector(int initialCapacity) {

  this(initialCapacity, 0);

  }

  public Vector() {

  this(10);

  }

  从构造方法中,我们可以看出Vector的默认大小也是10,而且它在初始化的时候就已经创建了数组了,这点跟ArrayList不一样。再来看一下grow方法:

  private void grow(int minCapacity) {

  // overflow-conscious code

  int oldCapacity = elementData.length;

  int newCapacity = oldCapacity + ((capacityIncrement > 0) ? capacityIncrement : oldCapacity);

  if (newCapacity - minCapacity < 0)

  newCapacity = minCapacity;

  if (newCapacity - MAX_ARRAY_SIZE > 0)

  newCapacity = hugeCapacity(minCapacity);

  elementData = Arrays.copyOf(elementData, newCapacity);

  }

  从grow方法中我们可以发现,newCapacity默认情况下是两倍的oldCapacity,而当指定了capacityIncrement的值之后,newCapacity变成了oldCapacity+capacityIncrement。

  总结

  1、ArrayList创建时的大小为0;当加入第一个元素时,进行第一次扩容时,默认容量大小为10。

  2、ArrayList每次扩容都以当前数组大小的1.5倍去扩容。

  3、Vector创建时的默认大小为10。

  4、Vector每次扩容都以当前数组大小的2倍去扩容。当指定了capacityIncrement之后,每次扩容仅在原先基础上增加capacityIncrement个单位空间。

  5、ArrayList和Vector的add、get、size方法的复杂度都为O(1),remove方法的复杂度为O(n)。

  6、ArrayList是非线程安全的,Vector是线程安全的。

深入java8的集合:ArrayList的实现原理的更多相关文章

  1. Java集合---ArrayList的实现原理

    目录: 一. ArrayList概述 二. ArrayList的实现 1) 私有属性 2) 构造方法 3) 元素存储 4) 元素读取 5) 元素删除                 6) 调整数组容量 ...

  2. ava集合---ArrayList的实现原理

    一.ArrayList概述 ArrayList是基于数组实现的,是一个动态数组,其容量能自动增长,类似于C语言中的动态申请内存,动态增长内存 ArrayList不是线程安全的,只能用在单线程环境下,多 ...

  3. Java集合:ArrayList的实现原理

    Java集合---ArrayList的实现原理   目录: 一. ArrayList概述 二. ArrayList的实现 1) 私有属性 2) 构造方法 3) 元素存储 4) 元素读取 5) 元素删除 ...

  4. Java基础知识强化之集合框架笔记23:ArrayList的实现原理

    1. ArrayList的实现原理: 这个可以直接参考网友的博客:http://www.cnblogs.com/ITtangtang/p/3948555.html

  5. ArrayList/Vector的原理、线程安全和迭代Fail-Fast

    疑问 * ArrayList是非线程非安全的,具体是指什么?具体会产生什么问题?* ArrayList的内部原理是什么?为什么可以动态扩容?* Vector是线程安全的,具体是如何实现的?为什么不再推 ...

  6. 简单复习一下ArrayList的扩容原理

    刚刚跟几个好朋友喝完小酒回家,简单大概复习一下ArrayList的扩容原理,由于头有点小晕,就只大概说一下扩容的原理哈: 首先ArrayList实现了List接口,继承了AbstractList,大家 ...

  7. ArrayList使用及原理

    之前面试时,经常被问到ArrayList的原理,今天整理了一些ArrayList的使用原理和必问的知识点. ArrayList的继承关系 定义一个ArrayList的方法 ArrayList的三个构造 ...

  8. Java ArrayList和Vector、LinkedList与ArrayList、数组(Array)和列表集合(ArrayList)的区别

    ArrayList和Vector的区别ArrayList与Vector主要从二方面来说.  一.同步性:   Vector是线程安全的,也就是说是同步的,而ArrayList是线程序不安全的,不是同步 ...

  9. 集合ArrayList

    /*集合ArrayList * 例如: * 1.创建:ArrayList<Egg> myList = new ArrayList<Egg>(); *      Egg类型的集合 ...

  10. 面向对象之集合ArrayList

    using System; using System.Collections; using System.Collections.Generic; using System.Linq; using S ...

随机推荐

  1. Window常用且通用快捷键

    Ctrl系列: Ctrl +z :回撤,后退 Ctrl +a :全选 Alt系列: Alt+Tab :切换窗口 Window系列 Window+R:打开“运行”窗口 Window+D:显示桌面 其中常 ...

  2. Windows远程桌面多用户登录的问题

    RDP WRAPPER 同时登录 多用户补丁   https://cloud.tencent.com/developer/article/1460728   解决系统更新导致无法多用户登录的问题 问题 ...

  3. Webshell篇

    常用方法简介: 一.0day拿webshell 参考工具:织梦漏洞利用小工具 二.通过注入漏洞拿Webshell 前提条件:具有足够权限,对写入木马的文件夹要有写入权限,知道网站绝对路径. 对于mss ...

  4. requests---发送post请求完成登录

    前段时间写过一个通过cookies完成登录,今天我们写一篇通过post发送请求完成登录豆瓣网 模拟登录 1.首先找到豆瓣网的登录接口 打开豆瓣网站的登录接口,请求错误的账号密码,通过F12或者抓包工具 ...

  5. 字符设备驱动程序之poll机制(韦大仙)

    明确为什么要引用poll机制? while(1) { read(fd,&key_val,1);//如果没有按键按下,它会一直在等待.现在想做这么一件事情:如果5s后,没有按键按下,它就会返回. ...

  6. day4_7.2

    流程语句 1.if判断语句 在python中if语句可以依据判断的条件,决定执行哪个语句.其格式如下: if 条件: 代码1 else: 代码2 当满足条件1时,执行代码1,否则执行代码2.所以条件语 ...

  7. 剑指Offer-3.从尾到头打印链表(C++/Java)

    题目: 输入一个链表,按链表从尾到头的顺序返回一个ArrayList. 分析: 很简单的一道题,其实也就是从尾到头打印链表,题目要求返回ArrayList,其实也就是一个数组. 可以将链表中的元素全部 ...

  8. LG3119 「USACO2015JAN」Grass Cownoisseur

    问题描述 LG3119 题解 显然,如果有个环,一定是全部走完的. 所以缩点,缩出一个 \(\mathrm{DAG}\) . 只能走一次反向,于是在正图和反图上各跑一次,枚举边,取 \(\mathrm ...

  9. 【ECNU620】数学题(结论题)

    点此看题面 大致题意: 求\((n-1)!\ mod\ n\)的值. 大力猜结论 首先,看到样例,我们可以猜测: 当\(n\)为质数时,答案为\(n-1\). 当\(n\)为合数时,答案为\(0\). ...

  10. mysql不是内部或外部命令--windows环境下报错的解决

    安装Mysql后,当我们在cmd中敲入mysql时会出现‘Mysql’不是内部或外部命令,也不是可运行的程序或其处理文件. 处理: 我的电脑右键属性>高级系统设置>高级>环境变量&g ...