首先我们对Jdk中集合类的命名做一个简单的梳理。ArrayList即Array+List表示这是一个集合,它的数据结构使用的数组。同理LinkedList表示这是其数据结构是链表;HashMap表示其基于hash算法实现的(本质是数组+链表+红黑树);TreeMap其数据结构是红黑树;LinkedHashMap底层是HashMap,同时进一步做了封装,对外展示是链表结构。说了这么多,我想说的是Jdk中的命名基本都是见名知意的,这些都是值得我么学习。好了,不扯了,进入正题。
1.ArrayList特征:
List有索引、有序、元素可重复。查询快,新增删除慢。
为啥这么说,通过了解它的存储结构就会明白。
2.实现接口:
List、RandomAccess、Cloneable、Serializable
实现RandomAccess便于集合元素的快速访问即查询。
实现Cloneable便于集合的拷贝,本质是数组的复制,属于浅拷贝。
实现Serializable便于集合的序列化
3.成员变量:
初始容量10、最大容量Integer.MAX_VALUE – 8(和jvm相关),元素存储位置Object[]、集合大小size。
数据结构:Object[]
//数组初始化大小
private static final int DEFAULT_CAPACITY = 10;
//数组最大容量
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
//存储元素的对象数组
transient Object[] elementData;
//数组中元素的个数
private int size;
4.构造方法::
可指定初始容量、或者直接传递一个集合实现类来构造ArrayList
public ArrayList(int initialCapacity) {}
public ArrayList() {}
public ArrayList(Collection extends E> c) {}
5.元素查询::
public E get(int index) {
//检查索引是否越界
rangeCheck(index);
//从数组中取值
return elementData(index);
}
1.通过索引获取元素时,首先检查索引是否越界。
2.从数组中取值。查询非常简单,我们知道数组在堆内存开辟的是一块连续的空间,通过索引能够很快计算出元素的存储位置,这也是为什么查询快的原因。
6.元素新增::
public boolean add(E e) {
//插入前的操作,主要是看数组是否初始化,是否需要扩容
ensureCapacityInternal(size + 1); // Increments modCount!!
//数值复制,数值元素个数size增加
elementData[size++] = e;
return true;
}
private void ensureCapacityInternal(int minCapacity) {
//数组初始化,默认长度是10
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
}
ensureExplicitCapacity(minCapacity);
}
private void ensureExplicitCapacity(int minCapacity) {
modCount++;
// overflow-conscious code
//size+1大于数组长度时扩容
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}
private void grow(int minCapacity) {
// overflow-conscious code
//数组长度变为原来的1.5倍
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);
}
1.添加元素之前,先做数组的初始化工作,数组长度默认为10。
2.当size+1大于数组长度时,进行扩容
3.扩容时,新数组的长度变为原来的1.5倍。并进行数组元素的复制,这个过程非常耗性能,所以建议构造数组时指定容量。
4.元素存储到数组指定索引处。
7.元素删除:
public E remove(int index) {
//索引越界减产
rangeCheck(index);
//数组结构变更次数增加1,对于新增、删除操作会影响数组的结构
modCount++;
E oldValue = elementData(index);
//数组元素位置调整,删除元素后,index后的所有元素往前移一个位置
//numMoved 需要移动的元素个数
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;
}
8.元素修改:
public E set(int index, E element) {
//检查索引是否越界
rangeCheck(index);
//元素替换,新值替换旧值
E oldValue = elementData(index);
elementData[index] = element;
return oldValue;
}
9.线程安全问题:
public boolean add(E e) {
ensureCapacityInternal(size + 1); // Increments modCount!!
//位置1,多线程存在风险
elementData[size++] = e;
return true;
}
ArrayList是线程不安全的。当多线程访问公共变量arrayList进行add(e)方法时,当线程1在位置1出完成了复制操作,但是size还未增加1,此时线程2进来添加元素时会覆盖掉线程1的值。即 elementData[size++] = e并非一个原子操作,会有多线程安全问题。
10.深拷贝、浅拷贝的概念:
Java中对象的复制通常通过赋值来实现,但是这种方式通过任何一方的引用操作都会改变对象。
浅拷贝:对象实现Cloneable接口,重写clone()方法,可以实现对象的复制,得到一个新的对象。对于一个对象中的属性既包含基本类型又包含引用类型,clone方法完成的拷贝是浅拷贝,因为引用类型的对象拷贝的是引用。
深拷贝:就是让对象中的任何一个属性,包括基本数据类型和引用数据类型都是不相同的,这个拷贝得到的对象就是一个深拷贝的对象。这种方法需要各个引用类型都实现Cloneable接口,重写clone方法。最后在拷贝对象clone方法中,逐一调用其属性对象的clone()方法。
以List
代码示例:其中Student对象是Person对象中的一个属性。
public class AnnotationController{
public static void main(String[] args) throws CloneNotSupportedException {
//浅拷贝 拷贝对象实现Cloneable接口,拷贝对象的成员变量都是基础数据类型
shallowCloneWithBaseDataType();
//浅拷贝 拷贝对象的成员变量有基础数据类型和引用数据类型,引用数据类型拷贝的是引用
shallowCloneWithMultiDataType();
//深拷贝 拷贝对象的成员变量有基础数据类型和引用数据类型,每个对象都需要重写clone方法
//并且通过顶层对象,调用每一个对象的clone方法。
deepClone();
}
public static void shallowCloneWithBaseDataType() throws CloneNotSupportedException {
Student orgStudent = new Student(10,"小华");
Student copyStudent = (Student) orgStudent.clone();
copyStudent.setName("小明");
//copyStudent是新对象,输出为false
System.out.println(orgStudent.getName().equals(copyStudent.getName()));
}
public static void shallowCloneWithMultiDataType() throws CloneNotSupportedException {
Student orgStudent = new Student(10,"小华");
Person orgPerson = new Person(1, orgStudent);
Person clonePerson = (Person)orgPerson.clone();
clonePerson.setType(2);
orgStudent.setName("小美");
System.out.println(orgPerson.getType() == clonePerson.getType());
System.out.println(orgPerson.getStudent().getName().equals(clonePerson.getStudent().getName()));
}
public static void deepClone() throws CloneNotSupportedException {
Student orgStudent = new Student(10,"小华");
Person orgPerson = new Person(1, orgStudent);
Person clonePerson = (Person)orgPerson.clone();
clonePerson.setType(2);
orgStudent.setName("小美");
System.out.println(orgPerson.getType() == clonePerson.getType());
System.out.println(orgPerson.getStudent().getName().equals(clonePerson.getStudent().getName()));
}
}
@Data
@AllArgsConstructor
class Person implements Cloneable{
int type;
Student student;
@Override
protected Object clone() throws CloneNotSupportedException {
//return super.clone();
//深拷贝,需要调用属性对象的clone方法
Person person = (Person) super.clone();
person.setStudent((Student) person.getStudent().clone());
return person;
}
}
@Data
@AllArgsConstructor
class Student implements Cloneable{
int age;
String name;
//若是对Person的浅拷贝,可不实现Cloneable。
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
}