ArrayList由数组实现,当增加或删除数据元素时,除了最后位置的元素外, ArrayList需要移动其被添加或删除的元素后面的全部元素。对于遍历所有的元素,ArrayList是具有良好的效率的。
特点:
- 查、改效率高:直接通过下标获取元素,直接通过下标设置元素。因为数组元素在物理上是连续的,知道其中一个的内存地址,就可以推算出其他元素的内存地址。
- 尾部增、删效率高:每次增加或删除,都需要移动指定位置后面的元素,所以增删的位置越靠前,需要移动的元素就越多,效率就越低;反之,效率就越高。
- 线程不安全:未实现同步。
适用场景:
ArrayList 适用于存储需要显示并不会对其进行修改的数据,仅仅是用于显示。
ArryList中的数组扩容:
ArrayList 中数组的动态扩容的实现,采用的是 Arrays.copyOf( T[] original, int newLength ) 方法。它的默认大小 10,最大长度 Integer.MAX_VALUE - 8。最后通过下面的方式,实现了数组的动态扩容:
elementData = Arrays.copyOf(elementData, newCapacity);
数组扩容的原理
因为数组的大小是固定不变的,所以数组对象是不可以扩容的。但是使用数组复制的方法,是可以实现数组扩容的,Arrays.copyOf() 可以方便的创建数组副本。创建副本的同时增加数组的长度,便实现了数组的动态扩容。
System.arraycopy(Object src, int srcPos, Object dest, int destPos, int srcLength),
上面的代码,也是实现复制数组的一个方法,Arrays.copyOf() 方法中就是创建一个新的数组,然后调用 System.arraycopy() 来实现的。
System.arraycopy
看源码可知,它调用的是native层的方法,实现的数组复制。它是线程不安全的。
@FastNative
public static native void arraycopy(
Object src,
int srcPos,
Object dest,
int destPos,
int length
)
参数介绍:
- Src 源数组;
- srcPos 源数组中开始复制的位置;
- dest 目标数组,即新的数组;
- destPos 目标数组中,粘贴数据的起始位置;
- length 源数组中需要复制的长度;
深拷贝 和 浅拷贝
拷贝前和拷贝之后,如果被拷贝的对象在堆里的地址没有变化,则称为 “浅拷贝”;
拷贝前和拷贝之后,如果被拷贝的对象在堆里的地址发生变化,则称为 “深拷贝”;
1. 浅拷贝
首先我们来看一下下面的代码,一个 Teacher 类,一个 Student类,Teacher 类持有 Student 类的引用。然后我们通过 Cloneable,对 Teacher 类 进行拷贝操作,来详细讲解一下 深拷贝与浅拷贝的区别:
public class Teacher implements Cloneable{
private String name;
private int age;
private Student stu;
@Override
public Teacher clone() {
try {
return (Teacher) super.clone();
} catch (CloneNotSupportedException e) {
throw new AssertionError();
}
}
......
}
public class Student {
private long stuNo;
private String stuName;
public long getStuNo() {
return stuNo;
}
public void setStuNo(long stuNo) {
this.stuNo = stuNo;
}
......
}
主程序代码如下:
public class ShallowMain {
public static void main(String[] args) {
Student student = new Student(2016021006, "李四");
Teacher teacher = new Teacher("鲁班", 24);
teacher.setStu(student);
// 克隆教师对象
Teacher teacher1 = teacher.clone();
System.out.println(teacher == teacher1);
// 原对象
System.out.println(teacher);
// 克隆对象
System.out.println(teacher1);
System.out.println("-------------------修改克隆对象的name以及年龄---------------------");
// 修改克隆对象的name以及年龄
teacher1.setName("鲁班大师");
teacher1.setAge(34);
// 原对象
System.out.println("原对象:" + teacher);
// 克隆对象
System.out.println("克隆对象:" + teacher1);
System.out.println("-------------------修改原对象的学生属性---------------------");
// 修改原对象的学生属性
student.setStuName("张三");
student.setStuNo(666666);
// 原对象
System.out.println("原对象:" + teacher);
// 克隆对象
System.out.println("克隆对象:" + teacher1);
}
}
输出结果如下:
得出结论 :
-
teacher == teacher1打印出的结果是false,则说明是创建了一个新的对象,说明 clone() 方法导致的一个结果是 深拷贝。
-
修改克隆出的对象的name和age属性,发现并不会影响原对象的属性值,说明值类型的成员也发生了改变
-
修改克隆出的对象的学生属性,导致原对象的学生属性也被修改了,说明 Student 对象被拷贝的仅仅只是 地址空间里的引用地址,并未创建新的 Student 对象出来,所以对于 Student 而言,是一个 浅拷贝 的操作。
浅拷贝中:
值类型的字段会复制一份,而
引用类型的字段拷贝的仅仅是引用地址,而该引用地址指向的实际对象空间其实只有一份。
![](https://img-blog.csdnimg.cn/cd3c60c18c6a43328a29605e76a84dfc.png?x-oss-process=image/watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBA6Z2e6JGX5ZCN56iL5bqP5ZGYOuW8oOW8oA==,size_20,color_FFFFFF,t_70,g_se,x_16)
2. 深拷贝
如果 Student 类也执行 Cloneable 的操作,则将是一个 深拷贝的过程
public class Student implements Cloneable{
private long stuNo;
private String stuName;
@Override
public Student clone() {
try {
return (Student) super.clone();
} catch (CloneNotSupportedException e) {
throw new AssertionError();
}
}
......
}
public class Teacher implements Cloneable{
private String name;
private int age;
private Student stu;
@Override
public Teacher clone() {
try {
//先克隆Teacher对象
Teacher teacher = (Teacher) super.clone();
//克隆Student对象,复值给克隆出的teacher对象中的stu属性
teacher.stu = stu.clone();
return teacher;
} catch (CloneNotSupportedException e) {
throw new AssertionError();
}
}
......
}
执行结果如下:
深拷贝中:
深拷贝相对于浅拷贝,在复制
值类型的字段时,也会将引用类型所指向的内存拷贝一份。
![](https://img-blog.csdnimg.cn/da480766263543bc8213645209eb4332.png?x-oss-process=image/watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBA6Z2e6JGX5ZCN56iL5bqP5ZGYOuW8oOW8oA==,size_20,color_FFFFFF,t_70,g_se,x_16)
Cloneable 属于深拷贝,重新分配内存,并将元素复制过去。对克隆后的数组进行修改,不会影响到原数组。
System.arraycopy 则是直接复制内存引用,不改变内存地址。因此,在数据量很大的时候,效率就很明显了。