关于ArrayList你不知道的那些事

1.ArrayList变量声明

/**
 * Default initial capacity. 默认长度
 */
private static final int DEFAULT_CAPACITY = 10;

/**
 * 
 * 默认的空数组对象
 */
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};

/**
 * 
 * 声明一个object数组对象
 */
transient Object[] elementData; 

/**
 * The size of the ArrayList (the number of elements it contains).
 * 数组长度
 */
private int size;

2. 测试代码

public static void main(String[] args) {
    ArrayList<String> arrayList = new ArrayList();
    for (int i = 0; i < 10; i++) {
        arrayList.add("test" + i);
    }
    arrayList.add("test11");
}

3. 源码分析(添加及删除元素)

//数组添加方法
public boolean add(E e) {
    //判断集合长度
    ensureCapacityInternal(size + 1);  // Increments modCount!!
    elementData[size++] = e;
    return true;
}

private void ensureCapacityInternal(int minCapacity) {
    ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
}

//如果第一次进来 默认返回默认的数组长度10
private static int calculateCapacity(Object[] elementData, int minCapacity) {
    if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
        return Math.max(DEFAULT_CAPACITY, minCapacity);
    }
    return minCapacity;
}

//这里判断集合是否需要扩容
//第一次初始化add元素 集合的size为10 所以添加10个元素以内都不会触发扩容操作
private void ensureExplicitCapacity(int minCapacity) {
    modCount++;

    // overflow-conscious code
    if (minCapacity - elementData.length > 0)
        grow(minCapacity);
}

在这里插入图片描述
当添加第11个元素的时候 会触发扩容操作 扩容操作默认为原数组长度的1.5倍
当集合长度过大,大于 Integer.MAX_VALUE - 8 以后会触发hugeCapacity()方法,然后执行Arrays.copyOf复制扩容数组。
在这里插入图片描述
在这里插入图片描述

//ArrayList删除操作 遍历集合 获取删除元素的索引 调用fastRemove()方法进行删除
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;
}

// 这里主要是根据要删除的索引位置 获取删除索引后的元素集合,判断然后复制数组
// 最后将最后的位置的空位置设置为null 可以让gc回收 避免内存泄漏
// src:要复制的数组(源数组)
// srcPos:复制源数组的起始位置
// dest:目标数组
// destPos:目标数组的下标位置
// length:要复制的长度
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
}

4. 关于modCount

/*在新增和删除里都有modCount变量 modCount翻译为修改次数
这里的修改解释为修改元素的个数 即在遍历List等集合的时候调用add remove等方法使
元素个数发生改变*/

arrayList.forEach( item -> {
    arrayList.add("test11");
    System.out.println(item);
});


@Override
public void forEach(Consumer<? super E> action) {
    Objects.requireNonNull(action);
    final int expectedModCount = modCount;
    @SuppressWarnings("unchecked")
    final E[] elementData = (E[]) this.elementData;
    final int size = this.size;
    for (int i=0; modCount == expectedModCount && i < size; i++) {
        action.accept(elementData[i]);
    }
    if (modCount != expectedModCount) {
        throw new ConcurrentModificationException();
    }
}

在一个迭代器初始的时候会赋予它调用这个迭代器的对象的mCount,如何在迭代器遍历的过程中,
一旦发现这个对象的mcount和迭代器中存储的mcount不一样那就抛异常。

5.Fail-Fast 机制

我们知道 java.util.ArrayList不是线程安全的,因此如果在使用迭代器的过程中有其他线程修改了map,那么将抛出ConcurrentModificationException,这就是所谓fail-fast策略。这一策略在源码中的实现是通过 modCount 域,modCount 顾名思义就是修改次数,对HashMap 内容的修改都将增加这个值,那么在迭代器初始化过程中会将这个值赋给迭代器的 expectedModCount。在迭代过程中,判断 modCount 跟 expectedModCount 是否相等,如果不相等就表示已经有其他线程修改了 List集合。

1.所以在这里和大家建议,当大家遍历那些非线程安全的数据结构时,尽量使用迭代器。
2.如果必须在遍历时删除元素,建议使用迭代器的remove方法进行删除。
3.使用CopyOnWriteArrayList代替了ArrayList,就不会发生fail-fast异常。

Tips:在遍历删除的时候删除倒数第二个元素的时候不会发生fast-fail

public static void main(String[] args) {
        ArrayList<String> arrayList = new ArrayList();
        arrayList.add("a");
        arrayList.add("b");
        arrayList.add("c");
        arrayList.add("d");
        for (String s : arrayList) {
            if(Objects.equals("c",s)){
                arrayList.remove("c");
            }
        }
        for (String s : arrayList) {
            if(Objects.equals("d",s)){
                arrayList.remove("d");
            }
        }
        System.out.println(arrayList);
    }

6.arrayList和vector的区别

1.ArrayList是最常用的List实现类,内部是通过数组实现的,它允许对元素进行快速随机访问。数组的扩 容需要将已经有数组的数据通过System.arraycopy()方法复制到新的存储空间中。当从ArrayList的中间位置插入或者删除元素时,需要对数组进行复制、移动、代价比较高。因此,它适合随机查找和遍历,不适合插入和删除。
2.Vector与ArrayList一样,也是通过数组实现的,不同的是它支持线程的同步,即某一时刻只有一个线程能够写Vector,避免多线程同时写而引起的不一致性,但实现同步需要很高的花费,因此,访问它比访问ArrayList慢。
3.LinkedList是用链表结构存储数据的,很适合数据的动态插入和删除,随机访问和遍历速度比较慢。另外,他还提供了List接口中没有定义的方法,专门用于操作表头和表尾元素,可以当作堆栈、队列和双向队列使用。
4.vector是线程(Thread)同步(Synchronized)的,所以它也是线程安全的,而Arraylist是线程不安全的。如果不考虑到线程的安全因素,一般用Arraylist效率比较高。
5.vector扩容为数组长度的100%而arraylist的扩容是数组长度的50%。如果在集合中使用数据量比较大的数据,用vector有一定的优势。

笼统来说:
LinkedList:增删改快
ArrayList:查询快(有索引的存在) 线程不安全 扩容为原长度的1.5倍
Vector: 线程安全 查询比ArrayList慢 扩容为原长度的2倍

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值