数据结构 ArrayList解析 Java8
ArrayList
介绍:一种顺序存储结构,其内部其实就是维护了一个数组,定义了一些常用的方法来对数组进行操作,简化用户使用。
首先我们来上一下整体结构和源码,后面再慢慢聊。

import java.util.Arrays;
public class ArrayList<T> {
private T[] elements;
private int size;
private int maxSize = 10;
private final int defaultSize = 10;
/**
* 构造一个定长的数组。
* @param size
*/
public ArrayList(int size) {
if (size >= defaultSize){
maxSize = size + (size >> 1);
elements = (T[]) new Object[maxSize];
}else if(size > 0){
elements = (T[]) new Object[maxSize];
} else if (size == 0){
elements = (T[])new Object[]{};
} else{
throw new IllegalArgumentException("Illegal Capacity: " + size);
}
this.size = size;
}
/**
* 构造一个默认大小的数组
*/
public ArrayList() {
elements = (T[]) new Object[defaultSize];
size = 0;
}
/**
* 以数组arr建立ArrayList
* @param arr
*/
public ArrayList(T[] arr){
if (arr.length >= defaultSize){
maxSize = arr.length +( arr.length >> 1);
}
elements = (T[])new Object[maxSize];
System.arraycopy(arr, 0, elements, 0, arr.length);
size = arr.length;
}
/**
* 返回数组大小
* @return
*/
public int size(){
return size;
}
/**
* 返回数组是否为空
* @return
*/
public boolean isEmpty(){
return size == 0;
}
/**
* 找到element对应的第一个数组元素下标
* @param element
* @return
*/
public int indexOf(T element){
if(element == null){
for (int i = 0; i < size; i++) {
if (elements[i] == null){
return i;
}
}
}else{
for (int i = 0; i < size; i++) {
if (elements[i].equals(element)){
return i;
}
}
}
return -1;
}
public int lastIndexOf(T element){
if(element == null){
for (int i = size-1; i >= 0; i--) {
if (elements[i] == null){
return i;
}
}
}else{
for (int i = size-1; i >= 0; i--) {
if (elements[i].equals(element)){
return i;
}
}
}
return -1;
}
/**
* 判断数组中是否包含element元素
* @param element
* @return
*/
public boolean contains(T element){
return indexOf(element) >= 0;
}
/**
* 添加元素到数组中
* @param element
*/
public void add(T element){
judgeGrow(++size);
elements[size-1] = element;
}
/**
* 向指定坐标添加数组
* @param index
* @param element
*/
public void add(int index, T element){
if (index < 0 || index >= size){
throw new IndexOutOfBoundsException("IndexOutOfBoundsException:" + index);
}else{
judgeGrow(++size);
System.arraycopy(elements, index, elements,index+1, size-index);
elements[index] = element;
}
}
/**
* 返回对应下标的元素
* @param index
* @return
*/
public T get(int index){
if (index < 0 || index >= size) {throw new IndexOutOfBoundsException("IndexOutOfBoundsException" + index);}
return elements[index];
}
/**
* 设置对应数组下标下的值,返回原来下标下的位置
* @param index
* @param element
* @return
*/
public T set(int index, T element){
if (index < 0 || index >= size) {throw new IndexOutOfBoundsException("IndexOutOfBoundsException" + index);}
T oldElement = elements[index];
elements[index] = element;
return oldElement;
}
/**
* 移除对应下标下的元素
* @param index
* @return
*/
public T remove(int index){
if (index < 0 || index >= size) {throw new IndexOutOfBoundsException("IndexOutOfBoundsException" + index);}
T element = elements[index];
System.arraycopy(elements, index+1, elements,index, size-index-1);
elements[size-1] = null;
size--;
return element;
}
/**
* 移除数组中第一个o
* @param o
* @return
*/
public T remove(T o){
int index = indexOf(o);
return index == -1 ? null : remove(index);
}
/**
* 移除数组中最后一个o
* @param o
* @return
*/
public T removeLast(T o){
int index = lastIndexOf(o);
return index == -1 ? null : remove(index);
}
/**
*清除数组中所有元素
*/
public void clear(){
Arrays.fill(elements, 0, size, null);
size = 0;
}
/**
* 判断是否需要加长数组长度
* @param index
*/
private void judgeGrow(int index){
if (index < 0){throw new IndexOutOfBoundsException("IndexOutOfBoundsException:"+index);}
else if (index >= maxSize){
grow();
}
}
/**
* 对数组进行增长
*/
private void grow(){
maxSize = maxSize + (maxSize >> 1);
T[] newElements = (T[]) new Object[maxSize];
System.arraycopy(elements, 0, newElements, 0, elements.length);
elements = newElements;
}
/**
* 重写toString,方便我一会测试
* @return
*/
@Override
public String toString() {
return "ArrayList{" +
"elements=" + Arrays.toString(elements) +
'}';
}
}
ArrayList中的成员变量和方法
成员变量
//ArrayList内部所维护的数组
private T[] elements;
//数组当前user所展示出的大小
private int size;
//数组实际大小
private int maxSize = 10;
//默认大小
private final int defaultSize = 10;
这里简单聊一下这三个size
size是ArrayList对user所展示出来的大小,用户对其数组进行加入/删除元素,都会引发size的变化
maxsize 这里我们默认为10,和defaultSize是一样大小,表示当前数组的真实长度
每当size == maxSize时,我们将自动对数组进行扩容处理
defaultSize当我们未指定数组大小时,数组的默认大小。
部分方法:
/**
* 找到element对应的第一个数组元素下标
* @param element
* @return
*/
public int indexOf(T element){
if(element == null){
for (int i = 0; i < size; i++) {
if (elements[i] == null){
return i;
}
}
}else{
for (int i = 0; i < size; i++) {
if (elements[i].equals(element)){
return i;
}
}
}
return -1;
}
/**
* 判断数组中是否包含element元素
* @param element
* @return
*/
public boolean contains(T element){
return indexOf(element) >= 0;
}
这边时模仿Java原生ArrayList中的这两个方法去写的
最开始写contains时想的是直接写个循环,查到了就直接返回true
循环正常结束就返回false
但等到我去看官方的源码是发现是直接调用indexof
根据其返回值进行判断是否含有这个值
这种方法减少了编写时的代码量,不愧是源码。
//计算容量
//对elementData进行判空,如果是,将更改后大小与默认大小10比较返回最大值,不为空直接返回更改后大小
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);
}
//增加容量函数
/**
* Increases the capacity to ensure that it can hold at least the
* number of elements specified by the minimum capacity argument.
*
* @param minCapacity the desired minimum capacity
*/
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;
}
public boolean add(E e) {
ensureCapacityInternal(size + 1); // Increments modCount!!
elementData[size++] = e;
return true;
}
在看ArraysList添加元素的源码中我们经常会看到ensureCapacityInternal()这个函数
翻译过来也就是确保内部容量函数
这里面调用了计算容量函数,当数组为空时,会默认返回10
这是我在写ArrayList中所没有考虑到的东西
试想我生成了一个长度为0的ArrayList的数组,然后为其添加元素
那么它自然会超出最大长度0,那么它就会调用grow,但0加上0的二分之一还是0
数组不会产生增长。
再来看一下它的grow函数
minCapacity代表最小范围
当增长完成后,会对最小返回进行比较,小于就直接用最小增长范围
之后会与MAX_ARRAY_SIZE = Integer.MAX_VALUE-8进行比较(减法比较)
这边感觉有必要提一下这个减法,倘若我们单纯用大于小于号进行比较,那么当minCapacity超出Integer.MAX_VALUE时会 变成负数,那么得到的答案就是minCapacity不大于MAX_ARRAY_SIZE,但实际并不是这样
而我们用减法比较可以解决这个问题
倘若大于,那我们就去拿Integer.MAX_VALUE,如果发现数据溢出了,那么就抛出异常。
最后根据我们拿到的数据去copy数组(为什么是Array.copy 而不是System.copyarr,不明白,有了解的可以评论说一下)
不愧是源码,考虑太周到了
/**
* 移除对应下标下的元素
* @param index
* @return
*/
public T remove(int index){
if (index < 0 || index >= size) {throw new IndexOutOfBoundsException("IndexOutOfBoundsException" + index);}
T element = elements[index];
System.arraycopy(elements, index+1, elements,index, size-index-1);
elements[size-1] = null;
size--;
return element;
}
/**
* 移除数组中第一个o
* @param o
* @return
*/
public T remove(T o){
int index = indexOf(o);
return index == -1 ? null : remove(index);
}
/**
* 移除数组中最后一个o
* @param o
* @return
*/
public T removeLast(T o){
int index = lastIndexOf(o);
return index == -1 ? null : remove(index);
}
remove传入删除对象,我们之间调用之前写过的indexOf获取下标,然后调用remove(index)
基本上大致功能就这些了,跟源码比起来,写的还是存在一些BUG,如果还有其他问题可以在评论中给出
请注意,此实现不同步。如果多个线程同时访问一个ArrayList实例,并且至少有一个线程在结构上修改了列表,则必须在外部进
行同步。 (结构修改是添加或删除一个或多个元素,或显式调整后备数组大小的任何操作;仅设置元素的值不是结构修改。)这通
常通过同步一些自然封装的对象来完成列表。如果不存在这样的对象,则应使用Collections.synchronizedList方法“包装”
该列表。这最好在创建时完成,以防止对列表的意外不同步访问:
List list = Collections.synchronizedList(new ArrayList(...));
从源码里面扒出来的一段话,大致意思就是ArrayList线程不安全,想要使其安全可以使用上面那个语句。

浙公网安备 33010602011771号