List

ArrayList

扩容原理

源码

     /**
     * The maximum size of array to allocate. 数组可以分配的最大空间
     * Some VMs reserve some header words in an array. 一些虚拟机会保存一些头文字在数组中
     * Attempts to allocate larger arrays may result in
     * OutOfMemoryError: Requested array size exceeds VM limit 试图分配最大空间将会导致内存溢出(因为需要的数组容量大于了虚拟机的限制)
     */
    private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
     /**
     * Default initial capacity. 初始容量
     */
    private static final int DEFAULT_CAPACITY = 10;

     /**
     * Increases the capacity of this <tt>ArrayList</tt> instance, if
     * necessary, to ensure(确保) that it can hold at least the number of elements
     * specified by the minimum capacity argument.
     *
     * @param   minCapacity   the desired minimum capacity  预期需要最小的容量
     */
    public void ensureCapacity(int minCapacity) {
        int minExpand = (elementData != EMPTY_ELEMENTDATA)
            // any size if real element table
            ? 0
            // larger than default for empty table. It's already supposed to be
            // at default size.
            : DEFAULT_CAPACITY;

        if (minCapacity > minExpand) {
            ensureExplicitCapacity(minCapacity);
        }
    }
    /**
     *         int newCapacity = oldCapacity + (oldCapacity >> 1);
     *  新容量为原来的1.5倍
     */
    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();
        //如果大于MAX_ARRAY_SIZE(Integer.MAX_VALUE - 8)
        //返回Interger的最大值0x7fffffff(2147483647)
        //否则返回MAX_ARRAY_SIZE
        return (minCapacity > MAX_ARRAY_SIZE) ?
            Integer.MAX_VALUE :
            MAX_ARRAY_SIZE;
    }

2017.03.05 新增


ArrayList新增元素

ArrayList的初始容量为10
那么在添加10个元素之后,再添加第11个元素的时候会进行依次扩容

Created with Raphaël 2.1.01.client添加一个元素2.如果当前的容量小与增加完元素的所需容量3.增加之1.5*old容量,并拷贝至当前新建的数组4.添加成功yesno

所涉及到的源码

//客户端调用的add方法 
public boolean add(E e) {
        ensureCapacityInternal(size + 1);  
        elementData[size++] = e;
        return true;
    }
//上面函数的第一步,确保容量可以容纳下新加的元素
private void ensureCapacityInternal(int minCapacity) {
        //修改次数加1,因为是添加,所以修改次数+1
        modCount++;
        // 如果添加这个元素会导致数组溢出
        if (minCapacity - elementData.length > 0)
            grow(minCapacity);//增加最小的容量
    }
//grow()
private void grow(int minCapacity) {
        // overflow-conscious code
        int oldCapacity = elementData.length;
        int newCapacity = oldCapacity + (oldCapacity >> 1);  //1.5倍
        if (newCapacity - minCapacity < 0) //防止溢出int型,溢出之后是负数
            newCapacity = minCapacity;
        if (newCapacity - MAX_ARRAY_SIZE > 0) //如果新的容量大于数组系统默认容量
            newCapacity = hugeCapacity(minCapacity);
        // 拷贝原来的数组至新开辟空间的数组内,是一个native方法
        elementData = Arrays.copyOf(elementData, newCapacity);
    }
//hugeCapacity
private static int hugeCapacity(int minCapacity) {
        if (minCapacity < 0) // int型溢出
            throw new OutOfMemoryError();
        return (minCapacity > MAX_ARRAY_SIZE) ?
            Integer.MAX_VALUE :
            MAX_ARRAY_SIZE;//int类型和数组系统默认容量取最大
    }
//java中默认最大为(2^31-1)-8
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;

set方法

set方法设置固定下标位置为指定的元素。

public E set(int index, E e) {
           //判断下标是否在数组范围内,如果不在 会抛出下标溢出边界异常IndexOutOfBoundsException();
            rangeCheck(index);
            E oldValue = ArrayList.this.elementData(offset + index);
            ArrayList.this.elementData[offset + index] = e;
            return oldValue;
        }

像是remove这种方法

这是种什么方法呢
remove(int index)
remove(Object o)

既能根据下标删除,又能根据元素删除,乍一看没什么关系吧,
仔细想一想好像没什么问题

但是int与Integer有什么不一样。。。

如果是删除Integer方法必须要手动包装int


根据对象删除的时候只会删除第一个

public boolean remove(Object o) {
        //如果是null 不能调用equals方法,如果是对象可以调用equals方法
        //先找出下标再删除
        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;
    }

    //fastremove与根据下标删除,与下面的方法仅仅少了一步rangeCheck 不知为何不使用此方法。
    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; // Let gc do its work
    }

根据下标删除

public E remove(int index) {
        rangeCheck(index);

        modCount++;
        E oldValue = elementData(index);

        int numMoved = size - index - 1;//需要移位的元素个数
        if (numMoved > 0)
            System.arraycopy(elementData, index+1, elementData, index,
                             numMoved);//调用native方法通过复制内存块来复制数组
        elementData[--size] = null; // 手动设置gc

        return oldValue;
    }

2017.12.16 新增

部分注意项

方法 注意点
扩容 1.5倍,使用system.arraycopy(),是一个native方法,通过复制内存空间的方式来实现数组拷贝

特殊问题

复制某个ArrayList到另一个ArrayList
  1. 使用ArrayList构造方法,比如:ArrayList myObject = new ArrayList(myTempObject)浅拷贝;
  2. 使用Collection的copy方法,通过迭代器的方式一个一个的设值,浅拷贝
  3. 序列化和反序列化,深拷贝
public class ArrayListCopyTest {
    public static void main(String[] args) throws IOException, ClassNotFoundException {
        User u1 = new User("user1", 12);
        User u2 = new User("user2", 13);
        User u3 = new User("user3", 14);
        User u4 = new User("user4", 15);
        User u5 = new User("user5", 15);
        User u6 = new User("user6", 15);
        User u7 = new User("user7", 15);
        User u8 = new User("user8", 15);


        List<User> userSrc = new ArrayList<>();
        userSrc.add(u1);
        userSrc.add(u2);
        userSrc.add(u3);
        userSrc.add(u4);

        List<User> userDest = new ArrayList<>(4);
        userDest.add(u5);
        userDest.add(u6);
        userDest.add(u7);
        userDest.add(u8);

        // 浅拷贝
        Collections.copy(userDest, userSrc);

        System.out.println(userDest.get(0) == u1);
        u1.setName("new_user1");
        System.out.println(userDest.get(0).getName());
        System.out.println(userSrc.get(0).getName());
        for (User user : userDest) {
            System.out.println(user.getName());
        }

        // 浅拷贝2
        List<User> userDest2 = new ArrayList<>(userSrc);
        System.out.println(userDest2.get(0) == userSrc.get(0));

        // 序列化方式 拷贝,这只是jdk的一种格式 还有很多种,比如json/ protobuff
        ByteArrayOutputStream bos = new ByteArrayOutputStream();
        ObjectOutputStream os = new ObjectOutputStream(bos);
        os.writeObject(userSrc);
        os.flush();
        os.close();
        byte[] userSrcByte = bos.toByteArray();

        ByteArrayInputStream bis = new ByteArrayInputStream(userSrcByte);
        ObjectInputStream is = new ObjectInputStream(bis);
        List<User> userDest3 = (List<User>) is.readObject();
        System.out.println(userDest3.get(0));
        System.out.println(userDest3.get(0).getName());
        System.out.println(userDest3.get(0) == userSrc.get(0));
        bos.close();


    }
}
当传递ArrayList到某个方法中,或者某个方法返回ArrayList,什么时候要考虑安全隐患?如何修复安全违规这个问题呢?
  1. 首先要判空
  2. 复制一个新数组的备份,方式原来的数组中的引用改变导致当前的线程安全。
与linkedList的区别

随机读写测试


public class ListCompare {

    public static final int N = 10000000;

    public static void main(String[] args) {
        Random random = new Random();

        List<Integer> arrayList = new ArrayList<>();
        List<Integer> linkedList = new LinkedList<>();
        for (int i = 0; i < N; i++) {
            arrayList.add(1);
        }
        for (int i = 0; i < N; i++) {
            linkedList.add(1);

        }
        System.out.println("初始化ArrayList与LinkList成功");
        long begin1 = System.currentTimeMillis();

        randomGetAndSet(random, arrayList);

        long end1 = System.currentTimeMillis();
        System.out.println("ArrayList: " + (end1 - begin1) + " ms");
        long begin2 = System.currentTimeMillis();

        randomGetAndSet(random, linkedList);

        long end2 = System.currentTimeMillis();
        System.out.println("LinkedList: " + (end2 - begin2) + " ms");
    }

    private static void randomGetAndSet(Random random, List<Integer> list) {
        for (int i = 0; i < N; i++) {
            if (i % 10000 == 0) {
                System.out.println("--------------我还活着-----------------");
            }
            if (random.nextBoolean()) {
                list.set(random.nextInt(N), 10);
            } else {
                list.get(random.nextInt(N));
            }
        }
    }

}

顺序读写的情况下,明显是ArrayList快,因为相对于O(1)的读取速度,和每次都是O(n)的读取速度。ArrayList的扩容导致的system.arraycopy()的性能可以忽略

如果是大数量顺序写的话LinkedList 明显要比ArrayList快,因为它不需要扩容。当然如果每一次都能预知到ArrayList的初始容量的话,复杂度也是一样的。

顺序读的话两种数据结构复杂度是O(n)


本文转载:CSDN博客