Java数据结构——ArrayList简介
2015-11-30 13:45
411 查看
ArrayList是Java中最基础的数据结构之一,即顺序表。本篇文章将从源码角度简单介绍ArrayList的基本实现原理。
(本文内容中涉及的源码使用JDK1.6版本,在高版JDK中可能源码做了简单调整,但数据结构的实现机制依然是一样的)
顺序表,顾名思义,是一个有序的数组。数据按顺序在内存中存储,这样的数据结构利于快速的查找,但在数组中间插入或删除数据会导致整个数组发生变动。
ArrayList类中,核心变量有两个:
存储数据的数组,ArrayList存储能力取决于此数组总长度(注:transient修饰词指此变量不会被序列化)
ArrayList的真实长度,即其中存放的数据个数
ArrayList共有3个构造方法:
默认构造方法,10为默认的初始长度
当你已知数据长度超过10时候,使用此方法可以减少ArrayList扩容次数。
当初始值小于0时候,会抛出异常。
当你已经有一个ArrayList时候,使用此方法形成一个新的ArrayList。注释的意思是,此处的toArray方法可能会错误地没有返回一个Object类型数组,因此在下面进行判断,若不是,则转为Object类型数组。
ArrayList中最常用的方法包括:add、set、get、remove,这些方法可能有不同的参数而形成了多个重载函数。
add方法:
第一个方法:
首先,此方法调用了ensureCapacity方法,它的意思是,如果当数组容量不足的时候,需要扩充容量(后面会介绍)。之后只要简单的将新的数据添加到数组中即可。
注意,此方法一定返回true。
第二个方法:
此方法中,指定了插入数据的位置。首先对index进行判断,当其值为负或大于当前数组长度时候,会抛出异常。(数组中最后一个数据的位置对应的是size-1)。
当index合法时候,判断是否需要扩容,之后将执行arraycopy这个方法,此方法是个native方法(由C语言封装),它的意思是将elementData这个数组中从index开始的数据复制到从index+1开始(相当于从index开始后移一位)。
当我执行add(2, 10)的时候,数组会发生如下变动:如图:
由此可见,数组发生了较大的变动,index后面的数据全部平移了1位。因此如果数据大量插入,会耗费一定时间。
set方法:
set只有一个方法,即修改index的值为element。
它的实现:
RangeCheck方法即监测index是否越界(后面会介绍)。之后只要将对应位置数据修改了即可。此方法会返回oldValue,即原来index位置的值
get方法:
get只有一个方法,即获取index位置的值。
很简单,只是返回这个值即可。当然,也是要先经过RangeCheck,看index是否越界。
remove方法:
第一个方法:
凡是方法中参数涉及index,都要经过RangeCheck判断是否越界。
此方法中有一个值为modCount,这是父类中的一个值,具体作用下面会介绍。
remove方法核心就是,根据index取得这个原先的值,然后将其删除。删除的本质是通过移动表来实现的,同样使用到了arraycopy这个方法。
举例:
当我执行remove(2)的时候,数组会发生如下变动:如图:
numMoved表示如果我删除的是数组中的最后一个值,直接置空即可,不需要移动数据。
删除同样可能导致数据的移动,因此大量删除操作会耗费一定时间。
第二个方法:
另一个remove方法是根据值去删除数据,先遍历查到要删除的值的index,然后调用fastRemove方法删除,可以看到,fastRemove就是remove第一个方法中的一部分,通过移动数组来删除数据,和上面是完全一样的。
除了上述的基本方法外,ArrayList还有一些其他常用方法,如:
addAll方法主要使用arraycopy实现,arraycopy具体内容上面已有介绍。
subList方法是ArrayList的父类AbstractList中的内部类方法,具体实现并不复杂,因此不再多说,有兴趣可以去看一看。
下面补充一下上面涉及到的一些方法和变量的介绍。
1、ensureCapacity
2、RangeCheck
3、modCount
ensureCapacity,从名字上就可以看出,此方法是确认空间是否足够,即是否越界。
首先获取数组长度,之后判断传入的参数minCapacity是否大于数组长度,如果大于的话,将计算新的长度,是old * 3 / 2 + 1;即原来的1.5倍+1的,如果变化后的值依然较小,即将新长度设置为minCapacity。
随后执行copyOf方法生成一个新的组数,此数组长度为newCapacity,并将之前elementData中的值复制到其中,这样来完成扩容。
RangeCheck方法较为简单,只是比较index和size的值的关系,如果index大于了当前值,那么抛出越界异常。
modCount
这个是一个较为重要的值。它字面意思为modify count,即修改次数。
当数组进行了诸如添加、删除之类的操作,此值会变化。
那么问题是,它到底有什么用?
我们知道ArrayList有一种foreach的循环方式:
for (Integer i : list) {
}
如果在迭代过程中,进行对ArrayList的修改操作,如add remove等,那么将报出ConcurrentModificationExceptions
这个异常的意思是同时进行迭代和修改而抛出的异常。它的大致原因是,在迭代时候,修改数据导致ArrayList长度发生了变化,因此在check时候会抛出这个异常。此处暂时不对具体源码进行分析。
如果需要在循环中删除某个元素,应当如下写法:
(本文内容中涉及的源码使用JDK1.6版本,在高版JDK中可能源码做了简单调整,但数据结构的实现机制依然是一样的)
顺序表,顾名思义,是一个有序的数组。数据按顺序在内存中存储,这样的数据结构利于快速的查找,但在数组中间插入或删除数据会导致整个数组发生变动。
ArrayList类中,核心变量有两个:
private transient Object[] elementData;
存储数据的数组,ArrayList存储能力取决于此数组总长度(注:transient修饰词指此变量不会被序列化)
private transient Object[] elementData;
ArrayList的真实长度,即其中存放的数据个数
ArrayList共有3个构造方法:
public ArrayList() { this(10); }
默认构造方法,10为默认的初始长度
public ArrayList(int initialCapacity) { super(); if (initialCapacity < 0) throw new IllegalArgumentException("Illegal Capacity: "+ initialCapacity); this.elementData = new Object[initialCapacity]; }使用此构造方法,可以为ArrayList指定一个初始长度。
当你已知数据长度超过10时候,使用此方法可以减少ArrayList扩容次数。
当初始值小于0时候,会抛出异常。
public ArrayList(Collection<? extends E> c) { elementData = c.toArray(); size = elementData.length; // c.toArray might (incorrectly) not return Object[] (see 6260652) if (elementData.getClass() != Object[].class) elementData = Arrays.copyOf(elementData, size, Object[].class); }
当你已经有一个ArrayList时候,使用此方法形成一个新的ArrayList。注释的意思是,此处的toArray方法可能会错误地没有返回一个Object类型数组,因此在下面进行判断,若不是,则转为Object类型数组。
ArrayList中最常用的方法包括:add、set、get、remove,这些方法可能有不同的参数而形成了多个重载函数。
add方法:
public boolean add(E e) public void add(int index, E element)
第一个方法:
public boolean add(E e) { ensureCapacity(size + 1); elementData[size++] = e; return true; }
首先,此方法调用了ensureCapacity方法,它的意思是,如果当数组容量不足的时候,需要扩充容量(后面会介绍)。之后只要简单的将新的数据添加到数组中即可。
注意,此方法一定返回true。
第二个方法:
public void add(int index, E element) { if (index > size || index < 0) throw new IndexOutOfBoundsException( "Index: "+index+", Size: "+size); ensureCapacity(size+1); System.arraycopy(elementData, index, elementData, index + 1, size - index); elementData[index] = element; size++; }
此方法中,指定了插入数据的位置。首先对index进行判断,当其值为负或大于当前数组长度时候,会抛出异常。(数组中最后一个数据的位置对应的是size-1)。
当index合法时候,判断是否需要扩容,之后将执行arraycopy这个方法,此方法是个native方法(由C语言封装),它的意思是将elementData这个数组中从index开始的数据复制到从index+1开始(相当于从index开始后移一位)。
当我执行add(2, 10)的时候,数组会发生如下变动:如图:
原数组 | 23 | 24 | 25 | 26 | 1 | 2 | 13 | ||
位移后 | 23 | 24 | 25 | 25 | 26 | 1 | 2 | 13 | |
完成 | 23 | 24 | 10 | 25 | 26 | 1 | 2 | 13 |
set方法:
public E set(int index, E element)
set只有一个方法,即修改index的值为element。
它的实现:
public E set(int index, E element) { RangeCheck(index); E oldValue = (E) elementData[index]; elementData[index] = element; return oldValue; }
RangeCheck方法即监测index是否越界(后面会介绍)。之后只要将对应位置数据修改了即可。此方法会返回oldValue,即原来index位置的值
get方法:
public E get(int index)
get只有一个方法,即获取index位置的值。
public E get(int index) { RangeCheck(index); return (E) elementData[index]; }
很简单,只是返回这个值即可。当然,也是要先经过RangeCheck,看index是否越界。
remove方法:
public E remove(int index) public boolean remove(Object o)
第一个方法:
public E remove(int index) { RangeCheck(index); modCount++; E oldValue = (E) elementData[index]; int numMoved = size - index - 1; if (numMoved > 0) System.arraycopy(elementData, index+1, elementData, index, numMoved); elementData[--size] = null; return oldValue; }
凡是方法中参数涉及index,都要经过RangeCheck判断是否越界。
此方法中有一个值为modCount,这是父类中的一个值,具体作用下面会介绍。
remove方法核心就是,根据index取得这个原先的值,然后将其删除。删除的本质是通过移动表来实现的,同样使用到了arraycopy这个方法。
举例:
当我执行remove(2)的时候,数组会发生如下变动:如图:
原数组 | 23 | 24 | 25 | 26 | 1 | 2 | 13 | ||
位移后 | 23 | 24 | 26 | 1 | 2 | 13 | 13 | ||
完成 | 23 | 24 | 26 | 1 | 2 | 13 | 置空 |
删除同样可能导致数据的移动,因此大量删除操作会耗费一定时间。
第二个方法:
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; } 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; }
另一个remove方法是根据值去删除数据,先遍历查到要删除的值的index,然后调用fastRemove方法删除,可以看到,fastRemove就是remove第一个方法中的一部分,通过移动数组来删除数据,和上面是完全一样的。
除了上述的基本方法外,ArrayList还有一些其他常用方法,如:
public boolean addAll(Collection<? extends E> c) public boolean addAll(int index, Collection<? extends E> c) public List<E> subList(int fromIndex, int toIndex)
addAll方法主要使用arraycopy实现,arraycopy具体内容上面已有介绍。
subList方法是ArrayList的父类AbstractList中的内部类方法,具体实现并不复杂,因此不再多说,有兴趣可以去看一看。
下面补充一下上面涉及到的一些方法和变量的介绍。
1、ensureCapacity
2、RangeCheck
3、modCount
public void ensureCapacity(int minCapacity) { modCount++; int oldCapacity = elementData.length; if (minCapacity > oldCapacity) { Object oldData[] = elementData; int newCapacity = (oldCapacity * 3)/2 + 1; if (newCapacity < minCapacity) newCapacity = minCapacity; // minCapacity is usually close to size, so this is a win: elementData = Arrays.copyOf(elementData, newCapacity); } }
ensureCapacity,从名字上就可以看出,此方法是确认空间是否足够,即是否越界。
首先获取数组长度,之后判断传入的参数minCapacity是否大于数组长度,如果大于的话,将计算新的长度,是old * 3 / 2 + 1;即原来的1.5倍+1的,如果变化后的值依然较小,即将新长度设置为minCapacity。
随后执行copyOf方法生成一个新的组数,此数组长度为newCapacity,并将之前elementData中的值复制到其中,这样来完成扩容。
private void RangeCheck(int index) { if (index >= size) throw new IndexOutOfBoundsException( "Index: "+index+", Size: "+size); }
RangeCheck方法较为简单,只是比较index和size的值的关系,如果index大于了当前值,那么抛出越界异常。
modCount
这个是一个较为重要的值。它字面意思为modify count,即修改次数。
当数组进行了诸如添加、删除之类的操作,此值会变化。
那么问题是,它到底有什么用?
我们知道ArrayList有一种foreach的循环方式:
for (Integer i : list) {
}
如果在迭代过程中,进行对ArrayList的修改操作,如add remove等,那么将报出ConcurrentModificationExceptions
这个异常的意思是同时进行迭代和修改而抛出的异常。它的大致原因是,在迭代时候,修改数据导致ArrayList长度发生了变化,因此在check时候会抛出这个异常。此处暂时不对具体源码进行分析。
如果需要在循环中删除某个元素,应当如下写法:
int i = 0; while (i < list.size()) { if (list.get(i) == 5) { list.remove(i); } else { i++; } }
相关文章推荐
- 数据结构的扩张—算法导论第14章(194)
- 数据结构基础7.7:基数排序
- 数据结构基础7.6:表排序
- 数据结构--向量--斐波拉契查找
- 数据结构基础7.5:快速排序
- 数据结构_链队列
- 数据结构基础7.4:归并排序
- 数据结构_链队列实验——火车车厢重排问题
- 数据结构--Chapter1(绪论)
- c++ 数据结构 链表
- 数据结构和算法学习(12)-堆
- 【数据结构】 图的最短路径——dijkstra的一个计算实例
- 数据结构和算法学习(11)-哈希表
- 数据结构实例参考——“查找”的原理
- JavaScript数据结构 --- 链表
- 数据结构实验 第一单元 学生成绩管理系统(链表版)
- 数据结构实验 第一单元 学生成绩管理系统(链表版)
- 数据结构--向量--二分查找
- Ceph 基本数据结构(1)-object
- 数据结构基础7.3:堆排序