数组ArrayList源码分析

数组ArrayList源码分析

(在公众号“数据结构与算法”中做的学习笔记,不想抄袭,有自己的拓展和理解)

get()和set()方法都会先判断是否越界,然后进行相应操作,如果越界则抛出相应异常警告。

if (index >= size)
  throw new IndexOutOfBoundsException(outOfBoundsMsg(index));

但是在add()和remove()方法中,我们需要进行数组大小的变动,比如在add()方法中,源码如下:

public void add(int index, E element) {
    if (index > size || index < 0)
        throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
    ensureCapacityInternal(size + 1);  // Increments modCount!!
    System.arraycopy(elementData, index, elementData, index + 1,size - index);
    elementData[index] = element;
    size++;
}

除了首先还是判断越界异常外,还需要进行ensureCapacityInternal()方法进行判断是否需要扩容,源码如下:

private void ensureCapacityInternal(int minCapacity) {
    if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
        minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
    }
    ensureExplicitCapacity(minCapacity);
}
private void ensureExplicitCapacity(int minCapacity) {
    modCount++;
    // overflow-conscious code
    if (minCapacity - elementData.length > 0)
        grow(minCapacity);
}

新建数组的时候,数组的默认初始化大小(private static final int DEFAULT_CAPACITY = 10)为10,如果数组为空,直接add(),那么minCapacity为10,扩容为直接扩到10。一旦需要扩容,如果我们需要的空间大于数组长度的时候,说明数组不够用了,要进行扩容,就会执行下面的grow方法,我们来看一下grow方法的代码。

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);
}

代码也比较简单,扩容的时候在第4行还会增加一半的大小,比如原来数组大小是10,第一次扩容后会是15。

里面有几点:1.MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;数组有点特殊性,数组对象要额外存储数组元素长度在头部,少了这8个长度可能与此有关。

尝试分配大于 MAX_ARRAY_SIZE 长度的数组会导致 OOM (换句话说,超过了该虚拟机的数组长度限制)。

(Redis 的动态字符串和这个有点类似,只不过扩容策略有差异 。

那么为什么Redis 动态字符串扩容会有两个处理方式?为什么大于1M 每次增加1M 而且有最大长度限制呢?
1 首先想下 Redis 和 Java中的ArrayList的使用场景。
Redis 通常用作缓存,而且失效时间相对较长(少则几秒钟,多则几分钟,几个小时等)。而ArrayList 通常在某个函数中用,一般来说生命周期很短,出栈后就可以回收。
2 Redis 为啥要有最大值限制。可能是因为通常和使用者不在同一个服务器上,需要通过网络进行传输,如果很大,传输很容易超时,而且Redis 主任务为单线程,很容易阻塞其他任务的执行。
3 小于1M)

在ArrayList中无论使用add还是remove都会使用这样一个方法

  System.arraycopy(elementData, index+1, elementData, index,numMoved);

函数原型为:public static void arraycopy(Object src, int srcPos,Object dest, int destPos, int length)
其中:src表示源数组,srcPos表示源数组要复制的起始位置,desc表示目标数组,length表示要复制的长度

这说明对数组的查找是比较方便的,因为只是复制不改变原来数组中的元素的顺序,但对数组的增删就没那么方便了,因为数组是一块连续的内存空间,如果在前面增加和删除,都会导致后面元素位置的变动。

ArraList是线程不安全,如果使用线程安全的可以用Vector,还有一个线程安全的类

CopyOnWriteArrayList,他只在add和remove的时候,也就是修改数据的时候会先synchronized,在get的时候没有,我们来看一下代码:

private E get(Object[] a, int index) {
    return (E) a[index];
}

我们再来看一下他的add方法

 public boolean add(E e) {
    synchronized (lock) {
        Object[] elements = getArray();
        int len = elements.length;
        Object[] newElements = Arrays.copyOf(elements, len + 1);
        newElements[len] = e;
        setArray(newElements);
        return true;
    }
}

他不像ArrayList每次扩容的时候,size都会增加一半,他是每次add一个元素的时候size只会加1,同理remove的时候size只会减1。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值