ArrayList的了解

底层数据结构

ArrayList的底层数据结构为数组
在Java关于ArrayList的源代码中就用elementData来代表这个数组,同时还能了解到其主要的几个参数的参数:

DEFAULT_CAPACITY	//代表数组的初始大小,默认是 10;
size 				//表示当前数组的大小,类型 int,没有使用 volatile 修饰,非线程安全;
modCount 			//统计当前数组被修改的版本次数,数组结构有变动,就会 +1;

除了了解这几个参数所代表的含义外,还得了解这个类的基本注释,从中大致了解到:

  • 允许 put null 值,会自动扩容;
  • size,isEmpty,get,set,add等方法的时间复杂度为O(0);
  • 为非线程安全,多线程情况下推荐使用线程安全类:Collections#synchronizedList;
  • 增强for循环,在使用过程中,如果数组大小变化,会快速失败,抛出异常。

ArrayList的初始化

ArrayList的初始化有三个方法:无参数初始化,指定大小初始化,指定初始数据初始化。

private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};

//无参数直接初始化,数组大小为空
public ArrayList() {
    this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}

//指定初始数据初始化
public ArrayList(Collection<? extends E> c) {
    //elementData 是保存数组的容器,默认为 null
    elementData = c.toArray();
    //如果给定的集合(c)数据有值
    if ((size = elementData.length) != 0) {
        // c.toArray might (incorrectly) not return Object[] (see 6260652)
        //如果集合元素类型不是 Object 类型,我们会转成 Object
        if (elementData.getClass() != Object[].class) {
            elementData = Arrays.copyOf(elementData, size, Object[].class);
        }
    } else {
        // 给定集合(c)无值,则默认空数组
        this.elementData = EMPTY_ELEMENTDATA;
    }
}

可看到在进行无参数的初始化时,默认是一个空数组,而不是10,只有在第一次add时才会扩容到10。

扩容源码

多看源码可以更好的了解到ArrayList的性质。

private void ensureCapacityInternal(int minCapacity) {
  //如果初始化数组大小时,有给定初始值,以给定的大小为准,不走 if 逻辑
  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);
}
//扩容,并把现有数据拷贝到新的数组里面去
private void grow(int minCapacity) {
  int oldCapacity = elementData.length;
  // oldCapacity >> 1 是把 oldCapacity 除以 2 的意思
  int newCapacity = oldCapacity + (oldCapacity >> 1);

  // 如果扩容后的值 < 我们的期望值,扩容后的值就等于我们的期望值
  if (newCapacity - minCapacity < 0)
    newCapacity = minCapacity;

  // 如果扩容后的值 > jvm 所能分配的数组的最大值,那么就用 Integer 的最大值
  if (newCapacity - MAX_ARRAY_SIZE > 0)
    newCapacity = hugeCapacity(minCapacity);
 
  // 通过复制进行扩容
  elementData = Arrays.copyOf(elementData, newCapacity);
}

从源码中可以了解到:

  • 扩容的大小不是翻倍,而是变为原来大小的1.5倍;
  • ArrayList的最大值是Integer.MAX_VALUE,超过这个值,JVM就不会给数组分配内存空间;

通过上面我们了解到了扩容的机制,如果要去了解其本质的话,可以看到扩容是通过下面这段代码来实现的。

/**
 * @param src     被拷贝的数组
 * @param srcPos  从数组那里开始
 * @param dest    目标数组
 * @param destPos 从目标数组那个索引位置开始拷贝
 * @param length  拷贝的长度 
 * 此方法是没有返回值的,通过 dest 的引用进行传值
 */
public static native void arraycopy(Object src, int srcPos,
                                    Object dest, int destPos,
                                    int length);

其本质是将老数组的数据拷贝到新的数组中去,该方法是 native 的方法。

ArrayList的线程安全问题

ArrayList作为共享变量时,是会有线程安全问题的,而在作为方法的局部变量时,是没有线程安全问题的。
ArrayList线程安全的本质:
因为ArrayList的elementData、size、modConut 在进行各种操作时,都没有加锁,而且这些变量的类型并非是可见(volatile)的,所以如果多个线程对这些变量进行操作时,可能会有值被覆盖的情况。
解决方法:
使用Collections#synchronizedList 来保证线程安全,SynchronizedList 是通过在每个方法上面加上锁来实现,虽然实现了线程安全,但是性能大大降低。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值