G
N
I
D
A
O
L

Java-Collection - ArrayList

概念

  • ArrayList实现了List接口,顺序存储,允许放入 null 元素,底层是通过数组实现 。除了Collection的15种抽象方法外(不直接继承,没有完全实现),额外增加一些关于该集合数据的操作(都不是很常用),详细API见MaTools-java8-ArryList
  • ArrayList是List接口的实现类中最常用的实现类;查询效率高,底层使用数组实现;
  • 线程不做同步处理(锁),不安全(作为对比Vector安全效率低)。
    • 多线程并发情况,可以通过synchronized自行包装,或者Collections工具类Collections.synchronizedList
    • 效率和安全性是需要取舍

常用方法API

全API->java.util->【类摘要】ArrayList< E>
常用API:

返回值 方法 说明
boolean add(E e) 将指定的元素追加到此列表的末尾
void add(int index, E element) 在此列表中的指定位置插入指定的元素
boolean addAll(Collection<? extends E> c) 按指定集合的Iterator返回的顺序将指定集合中的所有元素追加到此列表的末尾
boolean addAll(int index, Collection<? extends E> c) 将指定集合中的所有元素插入到此列表中,从指定的位置开始
void clear() 从列表中删除所有元素
Object clone() 返回此 ArrayList实例的浅拷贝
boolean contains(Object o) 如果此列表包含指定的元素,则返回 true
void forEach(Consumer<? super E> action) 对 Iterable的每个元素执行给定的操作,直到所有元素都被处理或动作引发异常。
E get(int index) 返回此列表中指定位置的元素
int indexOf(Object o) 返回此列表中指定元素的第一次出现的索引,如果此列表不包含元素,则返回-1
boolean remove(int index) 删除该列表中指定位置的元素
boolean removeAll(Collection<?> c)) 从此列表中删除指定集合中包含的所有元素
boolean remove(Object o) 从列表中删除指定元素的第一个出现(如果存在)
boolean removeIf(Predicate<? super E> filter) 删除满足给定谓词的此集合的所有元素
boolean removeRange(int fromIndex, int toIndex) 从这个列表中删除所有索引在 fromIndex (含)和 toIndex之间的元素
E set(int index, E element) 用指定的元素替换此列表中指定位置的元素
int size() 返回此列表中的元素数
Object[] toArray() 以正确的顺序(从第一个到最后一个元素)返回一个包含此列表中所有元素的数组
void trimToSize() 修改这个 ArrayList实例的容量是列表的当前大小

ArrayList源码

1.构造ArrayList

package java.util;

private static final Object[] EMPTY_ELEMENTDATA = {};
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
transient Object[] elementData; 		//transient关键字定义的数组

// 无参构造
public ArrayList() {
        this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
    }
	
// 有参构造——集合直接转数组
public ArrayList(Collection<? extends E> c) {
        Object[] a = c.toArray();
        if ((size = a.length) != 0) {
            if (c.getClass() == ArrayList.class) {
                elementData = a;
            } else {
                elementData = Arrays.copyOf(a, size, Object[].class);
            }
        } else {
            // 空集初始化为{}
            elementData = EMPTY_ELEMENTDATA;
        }
    }
// 有参构造——指明初始化数组长度
  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);
        }
    }

有参构造函数初始化长度为集合数组长度 / 指定的int值为数组长;
无参构造函数初始化的时候并没有直接指明数组长度,而是初始化为{}

1.1 无参构造的初始化懒加载

在java8之前,ArrayList初始化长度 DEFAULTCAPACITY_EMPTY_ELEMENTDATA = 10(字节) ;
java8后,修改为动态添加载:也就是调用add()方法后再去创建一个长度为10的数组。也就是懒加载。

  • 好处:省内存,java集合自动扩容机制下,新数组很难做到百分百匹配需要的内存空间。
// 默认数组长度也为10(字节)
 private static final int DEFAULT_CAPACITY = 10;

     public boolean add(E e) {
        ensureCapacityInternal(size + 1);  // Increments modCount!!
        elementData[size++] = e;
        return true;
    }
	
	/**
	多态add()方法,都是要调用ensureCapacityInternal()方法,而ensureCapacityInternal方法就是“自动扩容” + DEFAULT_CAPACITY
	*/
	add(.....);
	

2.自动扩容机制

每当调用add方法添加元素时,都会先检查需要添加的元素个数是否会超出当前数组的长度,调用ensureCapacityInternal()。如果超出,就会自动进行扩容操作。每次扩容会开辟一个新的空间,约为原来容量的1.5倍取整(算法步骤见↓注解),同时需要将原有数组中的数据复制到这新的数组中。

这新开辟的空间只会判断是否够用!多补不退,也就是会造成内存浪费

约定:开发中,尽量避免一次性add大于10字节数据;当确实需要这样做,可以在初始化ArrayList时候指明需要的空间容量

 public void ensureCapacity(int minCapacity) {
 //判断数组是有无参数构造的,无参构造初始化长度就为10
        int minExpand = (elementData != DEFAULTCAPACITY_EMPTY_ELEMENTDATA)
            ? 0
            : DEFAULT_CAPACITY;
//若大于给定的数组长度,就扩容操作
        if (minCapacity > minExpand) {
            ensureExplicitCapacity(minCapacity);
        }
    }

    private void ensureCapacityInternal(int minCapacity) {
        if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
            minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
        }

        ensureExplicitCapacity(minCapacity);
    }
// 计算容量
	private void ensureCapacityInternal(int minCapacity) {
        ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
    }

    private void ensureExplicitCapacity(int minCapacity) {
        modCount++;

        if (minCapacity - elementData.length > 0)
            grow(minCapacity);
    }


//JVM规定int数组最大空间为。其中,Integer.MAX_VALUE:为int类型容量,2^31-1
    private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;


    private void grow(int minCapacity) {
        int oldCapacity = elementData.length;
		// ① 位移运算,新容量右移1位:oldCapacity + (oldCapacity÷2^1) ≈ oldCapacity * 1.5
        int newCapacity = oldCapacity + (oldCapacity >> 1);
        if (newCapacity - minCapacity < 0)
            newCapacity = minCapacity;
        if (newCapacity - MAX_ARRAY_SIZE > 0)
            newCapacity = hugeCapacity(minCapacity);
        // 复制旧数组数据到新数组
        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;
    }

image

3.常用实现函数

add 和 addAll

操作值的插入。插入值可以是元素或者集合,如果参数没有指明索引(add(E e) / addAll(Collection<? extends E> c)),就直接插入到数组末端;参数指明索引(add(int index, E element) / addAll(int index, Collection<? extends E> c)),根据索引查询到数组地址,把后面元素向后移动,腾出空间并在该位置插入值。

public void add(int index, E element) {
	rangeCheckForAdd(index);
	ensureCapacityInternal(size + 1);  // 扩容机制判断和增容
	System.arraycopy(elementData, index, elementData, index + 1,	//复制数组。系统自带静态方法arraycopy实现②
					 size - index);
	elementData[index] = element;
	size++;		//全局变量,记录该ArrayList包含元素个数
}

    public boolean addAll(Collection<? extends E> c) {
        Object[] a = c.toArray();
        int numNew = a.length;
        ensureCapacityInternal(size + numNew);  // 扩容机制判断和增容
        System.arraycopy(a, 0, elementData, size, numNew);
        size += numNew;
        return numNew != 0;
    }

//其他add或addAll基本也类似
......

//避免add 和 addAll索引越界
    private void rangeCheckForAdd(int index) {
        if (index > size || index < 0)
            throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
    }

get 和 set

既然底层数据结构为数组,自然设置了get和set、

//rangeCheck:检查数组下标是否越界

public E get(int index) {
    rangeCheck(index);
    return (E) elementData[index];
}

public E set(int index, E element) {
    rangeCheck(index);
    E oldValue = elementData(index);
    elementData[index] = element;	//修改元素
	// set方法是有返回,return (E) elementData[index]
    return oldValue;
}

trimToSize()

将底层数组的容量调整为当前列表保存的实际元素的大小的功能

  public void trimToSize() {
        modCount++;
        if (size < elementData.length) {
            elementData = (size == 0)
              ? EMPTY_ELEMENTDATA
              : Arrays.copyOf(elementData, size);
        }
    }

spliterator()

spliterator:并行遍历迭代器。有点类似顺序遍历迭代器Iterator

  • Iterator是单线程内,对元素进行迭代
  • spliterator针对并行情况。
    • 主要特点是后期绑定(late-binding)和快速故障机制。
    • 主要作用:做并行数据处理。将并行集合切割成好几段,每个线程执行一段,达到并行的目的。主要是搭配stream使用。详见《java8实战(英)CUrma》第7章7.3节 ③
  • 前面提到ArrayList不是线程安全的,但通过手段让其规避线程问题。用spliterator处理并行需求也是一种手段
//就是创建新的spliterator
public Spliterator<E> spliterator() {
            checkForComodification();
            return new ArrayListSpliterator<E>(ArrayList.this, offset,
                                               offset + this.size, this.modCount);
        }

附录

① 位移运算(>>):
按二进制形式把所有的数字向右边移动对应位数,低位舍弃,高位补符号位(正数1,负数0);
也就是,正数右移结果为这个数除以2^n取整(注意非四舍五入)。例如:15>>1 = 15÷2^1 取整 = 7
② System.arraycopy
public static native void arraycopy(Object src, int srcPos,Object dest, int destPos,int length):
是系统自带的静态方法,Static修饰,在System类编译时创建,而System又是main入口的主类,所以arraycopy类是jdk提供实现方法。
作用:将指定源数组中的数组从指定位置开始复制到目标数组的指定位置 (顺带完成数组内元素移动)
③ 《java8实战(英)CUrma》实体书扫影PDF 链接: https://pan.baidu.com/s/1Wf2nE15-71Rgf9GCAVHnZg?pwd=6n6x 提取码: 6n6x 复制这段内容后打开百度网盘手机App,操作更方便哦

posted @ 2023-06-04 16:44  Bingo39  阅读(16)  评论(0)    收藏  举报