ArryList深度剖析
一、底层数据结构
1、增删改查
增: 是先new一个新数组,然后将老的数组复制过去,在将新增内容填上
删:也要new一个新数组,然后将数组中要删除的元素的索引位置移除
改和查都不会new新数组
2、是List的可变数组的实现
二、继承实现关系
2.1、实现Serializable接口
可以序列化:将对象写到文件中
可以反序列化:将文件中的对象信息读取出来
2.2、实现Cloneable接口
如果要实现clone方法必须有两个条件
实现Cloneable的接口
重写clone()方法
源码实现
*
* @return a clone of this <tt>ArrayList</tt> instance
*/
public Object clone() {
try {
ArrayList<?> v = (ArrayList<?>) super.clone();
v.elementData = Arrays.copyOf(elementData, size);
v.modCount = 0;
return v;
} catch (CloneNotSupportedException e) {
// this shouldn't happen, since we are Cloneable
throw new InternalError(e);
}
}
这里调用了super(最大父类object的克隆方法),然后将数组的数据传到新的ArrayList集合中的数组数据中,最后再返回新的ArrayList集合
A:浅拷贝
基于clone的浅拷贝
1、创建实体类
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Student implements Cloneable {
private String name;
private int age;
@Override
public Student clone() throws CloneNotSupportedException {
return (Student) super.clone();
}
}
1、实现Cloneabe的接口
2、重写clone的方法,这里原本是返回Object对象,可以强转
Student student1=new Student("zqs",24);
Student student2 = student1.clone();
System.out.println(student1==student2);
System.out.println("原对象"+student1);
System.out.println("浅拷贝的对象"+student2);
浅拷贝的局限性
基本类型可以完全被拷贝,引用地址则不能
因为浅拷贝拷贝的只是引用地址
B:深拷贝
主要是让引用的类也实现Cloneabe的接口
主对象
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Student implements Cloneable {
private String name;
private int age;
private Play play;//引用的对象
@Override
public Object clone() throws CloneNotSupportedException {
Student student=(Student) super.clone();
Play play1=(Play) play.clone();
student.setPlay(play1);
return student;
}
}
引用对象
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Play implements Cloneable {
private String play;
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
}
深拷贝
public static void main(String[] args) throws CloneNotSupportedException {
Play play=new Play("篮球");
Student student1=new Student("zqs",24 ,play);
Student student2 = (Student) student1.clone();
System.out.println(student1==student2);
System.out.println("原对象"+student1);
System.out.println("浅拷贝的对象"+student2);
System.out.println("----------修改引用对象属性------------");
play.setPlay("乒乓");
System.out.println("修改后的原对象"+student1);
System.out.println("修改后的浅拷贝对象"+student2);
}
结果
2.3、实现RandomAccess接口
使得随机访问的比顺序访问的时间要短一些,提高效率
这里对比LinkedList (都add一个字符串10000次)发现,LinkeLlist随机访问时间19932和顺序访问4
原因:LinkedList没有实现RandomAccess接口
所以对于LinkedList尽量使用迭代器方式add元素
实际开发运用
查询一个集合不知道类型时
判断是否实现了RandomAccess接口
2.4、继承AbstractSequentialList抽象类
主要是提供一些必要重写的抽象方法
三、源码分析
3.1、构造方法
A:无参构造
创建一个初始容量为10的空数组
/**
* Default initial capacity.
*/
private static final int DEFAULT_CAPACITY = 10;
B:有参构造(第一种)
自定义一个容量的容器
ArrayList<Student> arrayList=new ArrayList<>(6);
public ArrayList(int initialCapacity) {
if (initialCapacity > 0) {
//给定容量为6,就创建一个容量为6的数组
this.elementData = new Object[initialCapacity];
} else if (initialCapacity == 0) {
this.elementData = EMPTY_ELEMENTDATA;
} else {
throw new IllegalArgumentException("Illegal Capacity: "+
initialCapacity);
}
}
//initialCapacity == 0时
private static final Object[] EMPTY_ELEMENTDATA = {};
C:有参构造(第二种)
传一个集合进去
//自定义代码
ArrayList<String> arrayList=new ArrayList<>();
arrayList.add("篮球");
ArrayList arrayList1=new ArrayList(arrayList);
//源码
public ArrayList(Collection<? extends E> c) {
//1、先将原集合转为数组存到新集合的底层数组中
elementData = c.toArray();
if ((size = elementData.length) != 0) {
// c.toArray might (incorrectly) not return Object[] (see 6260652)
if (elementData.getClass() != Object[].class)
//数组的创建和拷贝
elementData = Arrays.copyOf(elementData, size, Object[].class);
} else {
// replace with empty array.
this.elementData = EMPTY_ELEMENTDATA;
}
}
3.2、添加方法
A:add()
public boolean add(E e) {
ensureCapacityInternal(size + 1); // Increments modCount!!
elementData[size++] = e;
return true;
}
private void ensureCapacityInternal(int minCapacity) {
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
//private static final int DEFAULT_CAPACITY = 10; minCapacity =10
minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
}
ensureExplicitCapacity(minCapacity);
}
private void ensureExplicitCapacity(int minCapacity) {
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}
/***
*扩容核心
*/
private void grow(int minCapacity) {
int oldCapacity = elementData.length;
//扩容 新的容器长度=老的容器长度+老的……/2 (新=1.5*老)
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.5倍 int newCapacity = oldCapacity + (oldCapacity >> 1);
默认为10
B:add(int index, E element)
public void add(int index, E element) {
//验证index是否合法(大于0小于集合长度)
rangeCheckForAdd(index);
ensureCapacityInternal(size + 1);
System.arraycopy(elementData, index, elementData, index + 1,
size - index);
elementData[index] = element;
size++;
}
//给默认容器
private void ensureCapacityInternal(int minCapacity) {
//这里elementData 不等于空数组了,所以不走if里面
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
}
ensureExplicitCapacity(minCapacity);//minCapacity=2
}
private void ensureExplicitCapacity(int minCapacity) {
modCount++;
// 这里的因为扩容过了elementData.length=0,所以也不走if
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}
最后只需要将元素拷贝放到index位置,再将后面的元素复制到index之后
elementData[index] = element;‘
C:addAll(Collection<? extends E> c)
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;
}
3.3、修改方法
主要是将index出的元素索引出来,再覆盖值
public E set(int index, E element) {
rangeCheck(index);
E oldValue = elementData(index);
elementData[index] = element;
return oldValue;
}
3.4、获取方法
主要是通过index索引到对应的值直接返回
public E get(int index) {
rangeCheck(index);
return elementData(index);
}
E elementData(int index) {
return (E) elementData[index];
}
3.5、toString()方法 AbstractList()接口中的方法
底层主要是用迭代器判断有没有元素,没有直接返回[ ]
有就用StringBuilder拼接字符串
public String toString() {
Iterator<E> it = iterator();
if (! it.hasNext())
return "[]";
StringBuilder sb = new StringBuilder();
sb.append('[');
for (;;) {
E e = it.next();
sb.append(e == this ? "(this Collection)" : e);
if (! it.hasNext())
return sb.append(']').toString();
sb.append(',').append(' ');
}
}
3.6、Iterator方法(迭代不能改变元素)
迭代删除代码(错误的)
package com.redis.test;
import java.util.ArrayList;
import java.util.Iterator;
/**
* @ClassName
* @Description TODO
* @Author ZQS
* @Date 2020/8/21 0019 22:27
* @Version 1.0
**/
public class TestArrayList {
public static void main(String[] args) throws CloneNotSupportedException {
ArrayList<String> arrayList=new ArrayList<>();
arrayList.add("篮球");
arrayList.add("rap");
Iterator<String> it =arrayList.iterator();
while (it.hasNext()){
String e=it.next();
if(e=="rap"){
arrayList.remove("rap");
}
}
}
}
主要是在迭代的时候,是要判断 实际修改值和预期修改值判断,如果不一样就抛异常
再删除元素之后还会迭代,这时发现调用remove()后导致实际修改次数+1了(modCount++)
final void checkForComodification() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
}
//删除方法
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;
}
//删除调用这个方法
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 class TestArrayList {
public static void main(String[] args) throws CloneNotSupportedException {
ArrayList<String> arrayList=new ArrayList<>();
arrayList.add("乒乓");
arrayList.add("rap");
arrayList.add("篮球");
Iterator<String> it =arrayList.iterator();
while (it.hasNext()){
String e=it.next();
if(e=="rap"){
arrayList.remove("rap");
}
}
System.out.println(arrayList);
}
}
这里主要是调用hashNext()方法的时候,光标的值和集合的长度一样,就会返回fase,就不会走next()方法获取元素,南无底就不会产生并发异常
删除两次后光标的值为
删除两次后size的值为
于是调用hashNext()方法
直接return了,就不走后面的next()方法,就不会验证(校验),所以就不会报异常
调用Iterator类的remove()方法就行
源码实现
1、调用ArryaList里面的remove(int index)方法,根据索引删除元素
2、当删除元素后又将实际修改值赋值给预期修改值
所以一直相等,就不会抛异常
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 void remove() {
if (lastRet < 0)
throw new IllegalStateException();
checkForComodification();
try {
ArrayList.this.remove(lastRet);
cursor = lastRet;
lastRet = -1;
//重点如下
expectedModCount = modCount;
} catch (IndexOutOfBoundsException ex) {
throw new ConcurrentModificationException();
}
}
final void checkForComodification() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
}
3.7、清空集合方法
原理:
1、先将集合中的所有元素清空 设置为null
(主要是尽早的让垃圾回收器回收垃圾,释放空间)
2、再将集合的长度设置为0 (size=1)
源码
public void clear() {
modCount++;
// clear to let GC do its work
for (int i = 0; i < size; i++)
elementData[i] = null;
size = 0;
}
四、常见问题的处理(优化ArrayList)
4.1、扩容问题
尽量使用初始化容器的构造方法
这样避免了集合的多次扩容问题
eg:向一个容器中加1000000条数据,就会扩容很多次
这样就会使得效率降低很多
4.2、和LinkedList对比
add和remove方法不一定比LindedList慢
1、因为对于(根据索引删除)LinkedList底层涉及到循环,加节点的操作(删除解绑节点),效率也很低
2、Arraylist底层是由动态数组构成,因此不是每次操作都会创建新的数组
4.3、线程安全问题
1、ArrayList 本身是线程不安全的,因为底层没有加同步代码块和锁等
2、可以用Collections中的方法,原理就是加锁的方式
3、对于不需要线程安全的地方尽量不要加锁,因为加锁会导致效率降低
4、成员变量的集合中不需要线程安全,因为在一个方法中,每个调用都是一个单线程
当存在大量的写和读同时发生时可以用CopyOnWriteArrayList集合
创建线程,创建一个共有的CopyOnWriteArrayList集合
public class CollectionThread implements Runnable {
private static CopyOnWriteArrayList<String> list =new CopyOnWriteArrayList<>();
static {
list.add("zqs");
list.add("gd");
list.add("zxx");
}
@Override
public void run() {
for (String s : list) {
System.out.println(s);
list.add("星星");
}
}
}
开四个线程同时读和写
public class TestArrayList {
public static void main(String[] args) throws CloneNotSupportedException {
CollectionThread myThread=new CollectionThread() ;
for (int i = 0; i <4 ; i++) {
new Thread(myThread).start();
}
}
}
结果如下
没有发生并发异常