让另一个事物,具备另一个事物的相同属性,这就叫做拷贝,或者说复制。
要理解程序中的拷贝,首先要明白名称和内存的这两个部分。
一般来说,我们通过name
去操作body
,但是全部的信息都是在body
中。
浅拷贝,所谓的浅,指的是不私有
,这种拷贝,没有独立的自我空间。
实体并没有发生变化。
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;
}
对于对象,手工拷贝挺好的,但是数组作为连续内存空间,何不直接进行内存拷贝。
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.5
倍这里明确两个点:默认以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
值数据复制,数据量大时,这种复制办法爽到不行。