ArrayList源码解析,完整版!

文章目录

  • 一、 ArrayLi++st继承关系
    • 1-1 Seri++alizable标记性接口
    • 1-2 Cloneable标记性接口
    • clone的案例
    • 1-3 RandomAccess标记接口
    • LinkedList测试随机访问和顺序访问
    • RandomAccess企业应用
    • 1-4 继承AbstractList抽象类
  • 二、ArrayList源码分析
    • 构造方法
    • array数据废土copy方法(扩展)
    • add数据漫游
    • set方法
    • get方法
    • toString方法
    • Iterato数组词三声r方法
    • re数据漫游是什么意思move方数据科学与大数据技术
    • 并发修改异常
    • 解决并发修改异常系统运维工作内容 (迭代器的default remove方法)
    • clear方法
    • contains方法
    • isEmpty方法
  • 三、面试题
    • ①Arra数据分析yList是怎么扩容的?
    • ②Arr数组去重ayList频繁扩容数组和链的区别导致添加性能急剧下降,如何处理?
    • ③ArrayList插入或删除元素一定比LinkedLis系统运维包括哪些内容t慢么?
    • ④ArrayLii++是什么意思st是线程安全的吗?
    • ⑤如何复制某个ArrayL数据科学与大数据技术ist到另一个系统运维工程Arr系统运维工程面试问题及答案ayList中去?
    • ⑥已知成员变量集合存储N多数据废土用户名称,在多线程的环境下,使用迭代器在读取集合数据的同linux操作系统基础知识时如何保证还可以正常的写入数据到集合?
    • ⑦ArrayList 和 LinkList区别?

此文章中使用的是JDK1.8

一、 ArrayList继承关系

1-1 Serializable标记性接口

序列化:将对象的数据写入到文件(写对象)

反序列化:将文件中对象的数据读取出来(读对象数据结构)

附:toStri数组的定义ng的优化

原始的toString方法如下所示,字符串常量在拼接的时候数据漫游会产生很多垃圾占用内存空间

    public String toString() {
return "Student{" +
"username='" + username + '\'' +
", password='" + password + '\'' +
'}';
}

使用StringBuilder可以解决此问题

    @Override
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append("Student{username='");
sb.append(this.username);
sb.append("', password=");
sb.append(this.password);
sb.append("'}");
return sb.toString();
}

1-2 Cloneable标记性接口

  1. 介绍

    一个类实现 Cloneable 接口来指示 Object.clone() 方法,该方法对于该类的实例进字段的复制是合法的。在不实现 Cloneable 接口的实例上调用对象的克隆方法会导致异常 CloneNotSupportedException 被抛出

  2. 克隆的前提条件

被克i++隆对象所在的类必须实现 Cloneable 接口

必须重写 cl数据恢复one 方法

  1. clone的基本使用
    public static void main(String[] args) {
ArrayList list = new ArrayList();
list.add("123");
list.add("456");
list.add("789");
Object clone = list.clone();
System.out.println(clone);
}
  1. clone方法底层的实现

clone的案例

需求:

案例:已知 A 对象的姓名为小胖,年龄33 。由于项目特殊要求需要数据漫游将该对象的数据复制另外一个对象B 中,并且此后 A 和 B 两个对象的数据不会相互影响

  1. 使用浅拷贝的方式实现

    步骤:

    ①需要拷贝的类实现Cloneable接口并重写其clone方法

    ②修改clone方法的访问权限为public,也可以修改返回值类型为当前类的类名

    public class Student implements Serializable,Cloneable {
    private String name;
    private Integer age;
    public String getName() {
    return name;
    }
    public void setName(String name) {
    this.name = name;
    }
    public Integer getAge() {
    return age;
    }
    public void setAge(Integer age) {
    this.age = age;
    }
    /** 此方法需要修改访问权限为public  也可以修改返回值类型为当前类的类名
    * @return
    * @throws CloneNotSupportedException
    */
    @Override
    protected Object clone() throws CloneNotSupportedException {
    return super.clone();
    }
    }
    
        @Test
    public void test01() throws CloneNotSupportedException {
    Student student = new Student();
    student.setName("小胖");
    student.setAge(33);
    Object clone = student.clone();
    System.out.println(student == clone);
    }
    

浅拷贝的局限性

存在的问题:基本javaee数据类型可以达到完全复制,引用数据类型则不可以

原因:在学生对象s被克隆的时候,数据漫游是什么意思其属性skill(引用数据类型)仅仅是拷贝了一份引用,因此当skill的值发生改变时,被克隆对象s的属性skill也将跟随改变

//实体类中Skill是引用数据类型
public class Student implements Serializable,Cloneable {
private String name;
private Integer age;
private Skill skill;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
public Skill getSkill() {
return skill;
}
public void setSkill(Skill skill) {
this.skill = skill;
}
public Student(String name, Integer age, Skill skill) {
this.name = name;
this.age = age;
this.skill = skill;
}
/** 此方法需要修改访问权限为public  也可以修改返回值类型为当前类的类名
* @return
* @throws CloneNotSupportedException
*/
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
", skill=" + skill +
'}';
}
}
    @Test
public void test02() throws CloneNotSupportedException {
Skill skill = new Skill("倒拔垂杨柳");
Student student = new Student("小胖",33,skill);
Object clone = student.clone();
System.out.println(skill == clone);
System.out.println(student);
System.out.println(clone);
System.out.println("===========================");
skill.setName("拳打giao方杰");
System.out.println(student);
System.out.println(clone);
}

结果如下图所示linux是什么操作系统


                                            ArrayList源码解析,完整版!

  1. 使用深拷贝的方式解决上面的问题

    解决步骤:

    ①Studen数组排序t中的引用数据类型也实现Cloneable接口

    ②修改Student对象的clone方法,手动clone引用数据类型并赋值

    public class Skill implements Serializable,Cloneable {
    //詳細代碼以忽略
    }
    
    //Student类重写clone方法
    @Override
    protected Object clone() throws CloneNotSupportedException {
    /*return super.clone();*/
    Student student = (Student) super.clone();
    Skill skill = (Skill) this.skill.clone();
    student.setSkill(skill);
    return student;
    }
    

    执行结果:

    
                                            ArrayList源码解析,完整版!

1-3 RandomAccess标记接口

此接口的主要目的是允许通用算法linux系统安装更改其行为,以便在应用于java培训随机访问列顺序访问列表linux必学的60个命令提供良好的性能。

随机访问如下

for (int i=0, n=list.size(); i < n; i++)
list.get(i);

顺序访问如下

for (Iterator i=list.iterator(); i.hasNext(); )
i.next();

结论:数据库

使用了此接口后,随机访问效率会比顺序访问的效率要高。

    @Test
public void test03(){
List list = new ArrayList();
for(int i=0;i<1000000;i++){
list.add(i);
}
//进行随机访问
long startTime = System.currentTimeMillis();
for(int i=0;i<1000000;i++){
list.get(i);
}
long endTime = System.currentTimeMillis();
System.out.println("随机访问用时: " + ( endTime - startTime ));
//进行顺序访问
startTime = System.currentTimeMillis();
Iterator iterator = list.iterator();
while (iterator.hasNext()){
iterator.next();
}
endTime = System.currentTimeMillis();
System.out.println("顺序访问用时: " + ( endTime - startTime ));
}

执行结果:数组词


                                            ArrayList源码解析,完整版!

LinkedList测试随机访问和顺序访问

    @Test
public void test04(){
List<String> list = new LinkedList();
for(int i=0;i<100000;i++){
list.add(i+"a");
}
//进行随机访问
long startTime = System.currentTimeMillis();
for(int i=0;i<list.size();i++){
list.get(i);
}
long endTime = System.currentTimeMillis();
System.out.println("Linked使用随机访问:" + (endTime-startTime));
startTime = System.currentTimeMillis();
Iterator iterator = list.iterator();
while (iterator.hasNext()){
iterator.next();
}
endTime = System.currentTimeMillis();
System.out.println("Linked使用顺序访问:" + (endTime-startTime));
}

结论:

LinkedLijavascriptst底层并没有实现RandomAccess接口,使用随机访问(也就是通过索引进行获取i+++j怎么算时)的java编译器时间要远大于进行顺序访问。

执行结果如下:


                                            ArrayList源码解析,完整版!

RandomAccess企业应用

场景:

从数据库查询出来的数据,框架会自动封装到List中。如果这时候要对List系统运维工资一般多少用for循环进行遍历,需要判断其List有没有实现RandomAccess接口,如果实现了的话 则使用随机访问效率比较高。如果没有实现,则需要使用顺序访问(增强for或者是迭代器)

具体优化代码如下:

		if (list instanceof RandomAccess){
//进行随机访问
for (int i=0;i<list.size();i++){
System.out.println(list.get(i));
}
}else{
//进行顺序访问
for (String s : list){
System.out.println(s);
}
}

1-4 继承AbstractLjava面试题ist抽java编译器系统运维工程师

二、ArrayList源码分析

构造方法


                                            ArrayList源码解析,完整版!

  1. 无参构造方法

    //无参的构造方法会将真正存数据的elementData 初始化为一个空的Object数组
    public class ArrayList<E> {
    /**
    * 默认初始容量
    */
    private static final int DEFAULT_CAPACITY = 10;
    /**
    * 空数组
    */
    private static final Object[] EMPTY_ELEMENTDATA = {};
    /**
    * 默认容量的空数组
    */
    private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
    /**
    * 集合真正存储数组元素的数组
    */
    transient Object[] elementData;
    /**
    * 集合的大小
    */
    private int size;
    public ArrayList() {
    this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
    }
    }
    
  2. 有参系统运维包括哪些内容构造一

    	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);
    }
    }
    
  3. 有参构造二

    
                                            ArrayList源码解析,完整版!

arraycopy方法(扩展)

    @Test
public void test11(){
String[] s = new String[]{"000","001","002","003","004","005","006"};
String[] ss = new String[6];
//第一个参数代表数据源(也就是想要拷贝的对象)
//第二个参数为拷贝数据源的起始位置  这里是1,也就是s[1]也就是从001开始拷贝
//第三个参数为拷贝目的数组对象
//第四个参数为拷贝目的数组对象的位置  这里是5,也就是从ss[5]开始插入拷贝的数据
//第五个参数为拷贝数据源的长度,这里为1  也就是只在s数组中拷贝一个元素
System.arraycopy(s,1,ss,5,1);
for (String s1 : ss) {
System.out.println(s1);
}
}

执行结果图


                                            ArrayList源码解析,完整版!

add方法


                                            ArrayList源码解析,完整版!

  1. 添加的方法

    
                                            ArrayList源码解析,完整版!


                                            ArrayList源码解析,完整版!

  1. 带索引的添加元素


                                            ArrayList源码解析,完整版!

  1. addAll方法

    
                                            ArrayList源码解析,完整版!

  2. addAll方数据分析师法(带索引)

    @Test
    public void test12(){
    List list1 = new ArrayList();
    list1.add("000");
    list1.add("001");
    list1.add("002");
    list1.add("003");
    list1.add("004");
    List list2 = new ArrayList();
    list2.add("一");
    list2.add("二");
    list2.add("三");
    list1.addAll(3,list2);
    System.out.println(list1);
    }
    

    底层源码

    
                                            ArrayList源码解析,完整版!

    
                                            ArrayList源码解析,完整版!

set方法

    @Test
public void test13(){
List list = new ArrayList();
list.add("小胖");
list.add("小瘦");
list.add("giao方杰");
System.out.println(list.set(1, "凹凸曼"));
System.out.println(list);
}


                                            ArrayList源码解析,完整版!

get方法


                                            ArrayList源码解析,完整版!

toStrilinux操作系统基础知识ng方法


                                            ArrayList源码解析,完整版!

Iter数组公式ator方法

    @Test
public void test15(){
List list = new ArrayList();
list.add("小胖");
list.add("小瘦");
list.add("giao方杰");
Iterator iterator = list.iterator();
while (iterator.hasNext()){
Object next = iterator.next();
System.out.println(next);
}
}


                                            ArrayList源码解析,完整版!

remove方法


                                            ArrayList源码解析,完整版!


                                            ArrayList源码解析,完整版!

并发修改异常

    @Test
public void test17(){
List list = new ArrayList();
list.add("小胖");
list.add("der子");
list.add("giao方杰");
list.add("凹凸曼");
Iterator iterator = list.iterator();
while (iterator.hasNext()){
Object next = iterator.next();
if (next.equals("der子")){
list.remove(next);
}
}
}


                                            ArrayList源码解析,完整版!

结论:

  1. 集合在执行add和remove方法的时候,实际修改次linux数都会+1
  2. 在获取迭代器的时候,集合数据分析只会执java编译器行一次

判断下面的代码i++和++i的区别会不会出现并发修改异常

    @Test
public void test17(){
List list = new ArrayList();
list.add("小胖");
list.add("der子");
list.add("giao方杰");
list.add("凹凸曼");
Iterator iterator = list.iterator();
while (iterator.hasNext()){
Object next = iterator.next();
if (next.equals("giao方杰")){
list.remove(next);
}
}
System.out.println(list);
}

执行结果:


                                            ArrayList源码解析,完整版!

原因:

删除元素是会让实际修改次数(modCount)+1。同时从remove的源码可以看到,会让size-1.

当删除倒数第二个元素时,删除完成后会刚好指针(cursor)== size


                                            ArrayList源码解析,完整版!

这时hasNext会返回false,数据就不会进入到while循环中,也就不会执行iterator.next()方法。就不会出现并发修改异常了。

解决并发修改异常 (迭代器的default remove方法)

代码如下:

	@Test
public void test18(){
List list = new ArrayList();
list.add("小胖");
list.add("der子");
list.add("giao方杰");
list.add("凹凸曼");
Iterator iterator = list.iterator();
while (iterator.hasNext()){
Object next = iterator.next();
if (next.equals("der子")){
iterator.remove();
}
}
System.out.println(list);
}


                                            ArrayList源码解析,完整版!

总结:

①其实迭代器的remove底层还是调用的集合的remove方法

②只不过每次都会将预期修改次数进行赋值 所以不会产生并发修改异常

clear方法

作用:清空集合中的所有元素


                                            ArrayList源码解析,完整版!

contains方法

作用:查找集合中是否包含某个元素


                                            ArrayList源码解析,完整版!

isEmpty方法


                                            ArrayList源码解析,完整版!

三、面试题

①ArrayList是怎么扩容的?

源码分析查看标题二中的add方法

第一次扩容10
以后每次都是原容量的1.5倍

②Arr数组词三声ayList频繁扩容导致添加性能急剧下降,如何处理?

使用ArrayList指定容量的构造方法创建ArrayList

    @Test
public void test21(){
long startTime = System.currentTimeMillis();
List list = new ArrayList();
for(int i=0;i<10000000;i++){
list.add(i);
}
long endTime = System.currentTimeMillis();
System.out.println("不断扩容所用的时间:" + (endTime-startTime));
startTime = System.currentTimeMillis();
List list1 = new ArrayList(10000000);
for(int i=0;i<10000000;i++){
list1.add(i);
}
endTime = System.currentTimeMillis();
System.out.println("初始化容量所用时间:" + (endTime-startTime));
}

测试结果:


                                            ArrayList源码解析,完整版!

③ArrayList插入或删除元素一定比LinkedList慢么?

根据索引删除元i++c语言

LinkedList在删除元素的时候不一定比ArrayList快,有时候结果可能相反


                                            ArrayList源码解析,完整版!

下图为Li数组公式nkedList删除数据


                                            ArrayList源码解析,完整版!

④Array数组指针Lii++c语言st是线程安全的吗?

答案:不是

解决方案:

使用Vector

    public void test23() throws InterruptedException {
List list = new Vector();
list.add("123");
}

使用集合工具类将ArrayList转为线程安全的

    @Test
public void test23() throws InterruptedException {
List list = new ArrayList();
List list1 = Collections.synchronizedList(list);
list1.add("111");
}

⑤如何复制某个数组去重ArrayList到另一个ArrayLijavaeest中去?

使用cloni++<4e()方法

    @Test
public void test23() throws InterruptedException {
ArrayList list = new ArrayList();
list.add("小胖");
list.add("giao方杰");
list.add("小瘦");
ArrayList list1 = new ArrayList();
list1 = (ArrayList) list.clone();
System.out.println(list);
System.out.println(list1);
}

使用ArrayList构造方法

    public void test23() throws InterruptedException {
List list = new ArrayList();
list.add("小胖");
list.add("giao方杰");
list.add("小瘦");
List list1 = new ArrayList(list);
System.out.println(list1);
}

使用addAll方法

    public void test23() throws InterruptedException {
List list = new ArrayList();
list.add("小胖");
list.add("giao方杰");
list.add("小瘦");
List list1 = new ArrayList();
list1.addAll(list);
}

⑥已知成员变量集合存储N多用户名称,在多线程的环境下,使用迭代器在读取集合数据的同时如何保证还可以正常的写入数据到集合?

这时需要使用读写数据库分离的集合CopyOnWriteArrayList

/
/线程任务类
class CollectionThread implements Runnable{
private static CopyOnWriteArrayList<String> list = new CopyOnWriteArrayList<>();
static{
list.add("Jack");
list.add("Lucy");
list.add("Jimmy");
}
@Override
public void run() {
for (String value : list) {
System.out.println(value);
//在读取数据的同时又向集合写入数据
list.add("coco");
}
}
}
//测试类
public class ReadAndWriteTest {
public static void main(String[] args) {
//创建线程任务
CollectionThread ct = new CollectionThread();
//开启10条线程
for (int i = 0; i < 10; i++) {
new Thread(ct).start();
}
}
}

⑦ArrayList 和 LinkList区别?

ArrayList

基于动态数组的数据结构

对于随i++和++i的区别机访问的get和set,ArrayList要优于LinkedList

对于随机操作的add和remove,ArrayList不一定比LinkedLislinux创建文件t慢 (Arrjava语言ayList底层由于是动态系统/运维数组,因此
并不是每次a数据分析师djava怎么读d和remove的时候都需要创建新数组)

LinkedList

基于链表的数据结构
对于顺序操作,LinkedList不一定比ArrayLisjava语言t慢
对于随机操作,数据结构LinkedList效率明显较低