博客
关于我
ArrayList、LinkedList原理
阅读量:652 次
发布时间:2019-03-15

本文共 5065 字,大约阅读时间需要 16 分钟。

List

ArrayList

ArrayList的底层是基于动态数组实现

transient Object[] elementData;     private int size;

常用的方法

ArrayList
arrayList = new ArrayList<>(); //或者ArrayList
arrayList = new ArrayList<>(4); arrayList.add("a"); //添加元素,在数组“尾部”添加 速度快 arrayList.add(0,"c"); //添加元素,在数组“中间”添加 速度慢 System.out.println(arrayList.get(0)); //获取指定下标的元素 arrayList.remove("a"); //删除指定元素,通过遍历 arrayList.remove(0); //删除指定下标的元素 arrayList.set(0,"b"); //修改

深入原理

初始化

ArrayList<String> arrayList = new ArrayList<>();

当调用ArrayList的无参构造器时,数组容量为0的Object数组;

当调用add()方法添加第一个数据时,才会分配容量,默认容量为10

private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};    public ArrayList() {        this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;    }

ArrayList<String> arrayList = new ArrayList<>(10);

当调用ArrayList的有参构造器时,会创建一个指定大小的Object数组;

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);        }    }

add方法、扩容

public boolean add(E e) {        // 检查当前容量是否还可以容纳一个元素,不够则扩容        ensureCapacityInternal(size + 1);  // Increments modCount!!        // 添加到数组末尾        // 这个语句可以分解为        // elementData[size] = e;        // size += 1;        elementData[size++] = e;        return true;    }
  1. 在使用无参构造器初始化ArrayList时,便会创建一个空的数组;

  2. 当调用add()方法添加第一个数据时,便会对数组进行扩容(调用Arrays.copyOf方法),默认容量为10;

  3. 若添加数据后的容量 超出 当前数组长度,则进行扩容,扩容之后的容量为之前容量的1.5倍


https://blog.csdn.net/zhouhengzhe/article/details/108319369

把原数组的数据,原封不动的复制到新数组中,然后把ArrauList的地址指向新数组

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-fEgSRtqY-1621011446037)(https://note.youdao.com/yws/res/85142/74452E4097A64D99AAF89E5C4DF3DD38)]

1.7的时候是初始化就创建一个容量为10的数组,1.8后是初始化先创建一个空数组,第一次add时才扩容为10;

LinkedList

LinkedList是通过双向链表实现的

transient int size = 0;    transient Node
first; transient Node
last;

常用方法

LinkedList基本使用与ArrayList相同;

LinkedList
linkedList = new LinkedList<>(); //注意,LinkedList没有有参构造器 linkedList.add("a"); linkedList.set(0,"b"); //效果等同linkedList.add(0,"c"); linkedList.remove(0); linkedList.remove("c"); System.out.println(linkedList.get(0));

remove

remove操作分2种,一种是根据下标删除,一种是根据元素内容删除;

根据元素内容删除时,需要遍历全部元素;

根据下标删除remove(int index)

若index < size/2,则从头遍历,否则从尾部遍历;

get(int ndex)

若index < size/2,则从头遍历,否则从尾部遍历;

ArrayList与LinkedList对比

ArrayList在使用无参构造器时,默认初始化一个容量为10的数组,当添加元素后 元素个数 大于 现有容量,则按1.5倍容量扩容,扩容 消耗性能,所以初始化时最后指定容量;

LinkedList是双向链表,当指定下标 添加、删除元素时,若index < size/2则从头节点遍历,否则从尾节点开始遍历;

当指定下标添加(不是修改指定下标元素)或者删除元素时,只需修改指针即可

ArrayList
  1. 指定下标删除元素 list.remove(int index)
  2. 指定下标添加元素 list.add(int index,E e)
  3. 指定下标查询快 list.get(int index)
LinkedList
  1. 指定下标删除元素 list.remove(int index)
  2. 指定下标添加元素 list.add(int index,E e)
  3. 指定下标查询慢 list.get(int index)
    需要先定位到元素所在

List与线程安全

Collections.synchronizedList()

ArrayList 与 LinkendList都不是线程安全的,但可以通过 java.util.Collections.synchronizedList(List list) 方法,获取一个线程安全的 List 实例对象;

Collections.synchronizedList(List list) 方法源码public static 
List
synchronizedList(List
list) { return (list instanceof RandomAccess ? new SynchronizedRandomAccessList<>(list) : new SynchronizedList<>(list));}

当传入的 list 是 ArrayList 时,返回 SynchronizedRandomAccessList 对象;传入 LinkedList 时,返回 SynchronizedList 对象。

get、set、add 等操作都添加了synchronized锁;

public E get(int index) {            synchronized (mutex) {return list.get(index);}        }        public E set(int index, E element) {            synchronized (mutex) {return list.set(index, element);}        }        public void add(int index, E element) {            synchronized (mutex) {list.add(index, element);}        }        public E remove(int index) {            synchronized (mutex) {return list.remove(index);}        }

CopyOnWriteArrayList


坑点

迭代器

增强for循环 的 原理就是 迭代器,这点与普通的for循环有着本质的区别;

https://juejin.cn/post/6844903569095671816

在迭代器中,不能对list进行增删操作,只能查询;即list的add、remove会报ConcurrentModificationException异常
public static void main(String[] args) {        ArrayList
strings = new ArrayList
(); strings.add("a"); strings.add("b"); for (String string : strings) { if ("e".equals(string)) { strings.remove(string); } } } Exception in thread "main" java.util.ConcurrentModificationException at java.util.ArrayList$Itr.checkForComodification(ArrayList.java:859) at java.util.ArrayList$Itr.next(ArrayList.java:831) at main.java.mo.basic.ConcurrentModificationExceptionTest.main(ConcurrentModificationExceptionTest.java:17)
Iterator实现类有2个重要的方法hasNext()和next() Iterator中有一个expectedModCount属性与list的modCount属性同步,当list执行remove时会modCount++,而Iterator中有expectedModCount却不会变化;  当 Iterator调用next()方法,会使用checkForComodification做校验,modCount与 expectedModCount不同就会报错;

解决方法,使用普通的for循环,不要使用迭代器;

你可能感兴趣的文章
Nacos和Zookeeper对比
查看>>
Nacos在双击startup.cmd启动时提示:Unable to start embedded Tomcat
查看>>
Nacos基础版 从入门到精通
查看>>
Nacos如何实现Raft算法与Raft协议原理详解
查看>>
Nacos安装教程(非常详细)从零基础入门到精通,看完这一篇就够了
查看>>
Nacos实战攻略:从入门到精通,全面掌握服务治理与配置管理!(上)
查看>>
Nacos实战攻略:从入门到精通,全面掌握服务治理与配置管理!(下)
查看>>
Nacos心跳机制实现快速上下线
查看>>
nacos报错com.alibaba.nacos.shaded.io.grpc.StatusRuntimeException: UNAVAILABLE: io exception
查看>>
nacos服务提供和发现及客户端负载均衡配置
查看>>
Nacos服务注册与发现demo
查看>>
Nacos服务注册与发现的2种实现方法!
查看>>
nacos服务注册和发现原理简单实现案例
查看>>
Nacos服务注册总流程(源码分析)
查看>>
nacos服务注册流程
查看>>
Nacos服务部署安装
查看>>
nacos本地可以,上服务器报错
查看>>
Nacos注册Dubbo(2.7.x)以及namespace配置
查看>>
Nacos注册中心有几种调用方式?
查看>>
nacos注册失败,Feign调用失败,feign无法注入成我们的bean对象
查看>>