ArryList

数组复制

复制层级

让另一个事物,具备另一个事物的相同属性,这就叫做拷贝,或者说复制。

要理解程序中的拷贝,首先要明白名称和内存的这两个部分。

Object
name
memory

一般来说,我们通过name去操作body,但是全部的信息都是在body中。

  • 浅拷贝

浅拷贝,所谓的浅,指的是不私有,这种拷贝,没有独立的自我空间。

Object
name
body
copy

实体并没有发生变化。

public static void main(String[] args){
    Object old = new Object();
    Object copy = old;
}

这种情况,好像多了一个对象,但是实体并没有增多。每个对象的操作,另一个能够感知,也会受到影响。

好比一间房子,两把钥匙。

进来的人,能够看到,也能装修,但是却不能阻止别人动手。

  • 深拷贝

浅拷贝存在的问题,就是没有隐私。你的修改,可能过一会,就又被改动了。

把浅拷贝看做入口分离,深拷贝就是全分离,就连实体也是全新的。

浅拷贝:单一对象,引用复制,引用和实体多对一

深拷贝:多个对象,属性复制,引用和实体一对一

内存拷贝

使用深拷贝,就会是对象之间的属性复制,一般操作是这样的。

  • 手工拷贝
public static void main(String[] args){
    Person person = new Person();
    person.name = "name";
    person.age = 99;
    Person copy = new Person();
    copy.name = person.name;
    copy.age = person.age;
}

也即是这样的流程
ArryList_第1张图片

  1. 通过名称查询实体属性
  2. 通过名称设置已获取的属性
  • 内存拷贝

对于对象,手工拷贝挺好的,但是数组作为连续内存空间,何不直接进行内存拷贝。

    public static void main(String[] args) {
        int[] oldArray = new int[]{1,2,3};
        int[] newArray = new int[3];
        System.arraycopy(oldArray,0,newArray,0,3);
        System.out.println(Arrays.toString(newArray));
    }

通过操作系统直接内存拷贝,减少人工操作,更加快速。

数组拷贝

  • System.arraycopy
paramIndex description
1 原数组
2 开始复制索引
3 目的数组
4 复制起点索引
5 复制元素个数
  • 索引不能越界
  • 长度不能超过原数组长度,也不能超过目的数组长度
  • 原数组可以就是目的数组
  • Arrays.copyOf
    public static int[] copyOf(int[] original, int newLength) {
        int[] copy = new int[newLength];
        System.arraycopy(original, 0, copy, 0,
                         Math.min(original.length, newLength));
        return copy;
    }

进一步封装了,简化参数,自带扩容功能。

初始化方法

指定长度

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

其实就是新建了默认的定长数组。

默认方法

    public ArrayList() {
        this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
    }

默认情况下是{}空数组,不过添加时会自动扩容为10

元素构造

    public ArrayList(Collection<? extends E> c) {
        elementData = c.toArray();
        if ((size = elementData.length) != 0) {
            if (elementData.getClass() != Object[].class)
                elementData = Arrays.copyOf(elementData, size, Object[].class);
        } else {
            this.elementData = EMPTY_ELEMENTDATA;
        }
    }

把传入的集合作为原始数组,还有类型转换。

扩容办法

检测办法

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

其中DEFAULT_CAPACITY就是默认的10了,如果初始化时是空数组,就会直接初始化为10

是否扩容

    private void ensureExplicitCapacity(int minCapacity) {
        modCount++;
        if (minCapacity - elementData.length > 0)
            grow(minCapacity);
    }

minCapacity一般传入的是size+1,也就是再添加一个元素是否越界。

扩容方法

    private void grow(int minCapacity) {
        int oldCapacity = elementData.length;
        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);
    }
  1. 默认扩容1.5
  2. 指定扩容和默认扩容选择容量大的进行扩容
  3. 越界会有操作
  4. 替换原有内核数据

这里明确两个点:默认以1.5倍进行扩容,扩容实际上就是替换更大容量存储实体。

这里也就明白了为何Buffer不可扩容,因为它的存储实体是final的,不可扩容啊。

越界处理

    private static int hugeCapacity(int minCapacity) {
        if (minCapacity < 0) // overflow
            throw new OutOfMemoryError();
        return (minCapacity > MAX_ARRAY_SIZE) ?
            Integer.MAX_VALUE :
            MAX_ARRAY_SIZE;
    }

这里会提示OOM,不过也会照常进行返回。

    private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;

也就是说,超过了MAX_ARRAY_SIZE临界值,就直接Integer.MAX_VALUE,下次就不好扩容了。

元素添加

直接添加

    public boolean add(E e) {
        ensureCapacityInternal(size + 1);  // Increments modCount!!
        elementData[size++] = e;
        return true;
    }

索引添加

    public void add(int index, E element) {
        rangeCheckForAdd(index);
        ensureCapacityInternal(size + 1);  // Increments modCount!!
        System.arraycopy(elementData, index, elementData, index + 1,
                         size - index);
        elementData[index] = element;
        size++;
    }

rangeCheckForAdd就是做了个index索引合法性校验。

可以看到,判断扩容后,只是把后续的元素挪动,空出该索引置放元素。

全部添加

    public boolean addAll(Collection<? extends E> c) {
        Object[] a = c.toArray();
        int numNew = a.length;
        ensureCapacityInternal(size + numNew);  // Increments modCount
        System.arraycopy(a, 0, elementData, size, numNew);
        size += numNew;
        return numNew != 0;
    }

唯一值的注意的,就是扩容的长度变更和值设置变成了数组拷贝。

元素移除

索引移除

    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);
        elementData[--size] = null; // clear to let GC do its work
        return oldValue;
    }

还是索引判断和元素挪移。

对象移除

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

可以看到,ArrayList中是可以存储null值的。

    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
    }

具体的移除操作,就是和扩容相反的压缩,让元素紧密。

索引对照

索引到对象

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

数组直接访问,没问题。

对象到索引

    public int indexOf(Object o) {
        if (o == null) {
            for (int i = 0; i < size; i++)
                if (elementData[i]==null)
                    return i;
        } else {
            for (int i = 0; i < size; i++)
                if (o.equals(elementData[i]))
                    return i;
        }
        return -1;
    }

遍历检索,找不到返回-1

倒序查找

    public int lastIndexOf(Object o) {
        if (o == null) {
            for (int i = size-1; i >= 0; i--)
                if (elementData[i]==null)
                    return i;
        } else {
            for (int i = size-1; i >= 0; i--)
                if (o.equals(elementData[i]))
                    return i;
        }
        return -1;
    }

元素包含

    public boolean contains(Object o) {
        return indexOf(o) >= 0;
    }

复用原来的索引查找进行判断。

小结

  • 默认扩容1.5倍步进
  • 可以存储null
  • 以更大容量容器进行数据复制和替换,实现扩容

数据复制,数据量大时,这种复制办法爽到不行。

你可能感兴趣的:(java)