在编程语言中,我们必然少不了存储数据的容器,虽然我们有数组,但是数组是连续的开辟处一块连续的内存空间,我们的数据过大会无法存储完,数据量小,会浪费空间,所以我们需要使用集合存储数据,而在Java中list和set是我们最常用的集合,它们都属于Collection接口的子接口,那么我们下面分析其共同点,和其对应的数据结构。
1.list接口:
首先list接口,作为单列集合(只存储值),它允许值重复(值可以不唯一)。它的实现类有Arraylist,linkedList,Vector
从线程安全的角度,我们逐个分析:
线程不安全:
ArrayList:
1.数据结构:Object数组,观察源码可知,其底层逻辑还是以Object,数组存放数据
2.扩容方式:
首先我们需要知道,集合比数组的优势之一就是其内存储空间可变化,那么它是如何变化的,而且它的数据结构是一个Object数组,但数组并没有完成初始化,又在什么时候完成初始化。且容量不足时又如何扩容呢。它们都与其内部定义的成员变量(默认数组),以及无参,有参构造方法,add()方法有关,下面我们先观察源码(扩容的源码没有展示)
内部定义的成员变量与数组:
无参构造方法 源码:
有参构造方法 源码:
add()方法:
结论:无参构造方法:观察源码可知,它的构造方法是在内部将默认的数组初始化,但长度为0,当我们调用add()方法添加第一个元素时,它就会调用内部的参数,将数组的长度扩容为10,当容量不足时,就会以现数组容量的1.5倍进行扩容。
有参构造方法:观察源码可知,其有参构造方法是根据参数,将内部数组初始化长度(适用于预估数据量创建),当容量不足时,我们的数组容量也是以现数组容量的1.5倍进行扩容
使用场景:相对来说,其内部存储空间连续,格局下标获取元素,适合查找,遍历,
不适合插入,删除,当删除和插入到中间或者头部元素时,其需要复制数组内容,再删除或插入,消耗性能。包含调用add的重载方法,根据下标插入等
LinkedList:
1.数据结构:观察源码可知,其内部为一条双向的链表
2.扩容方式:观察源码可知,当我们调用add方法添加元素时,其就会动态的添加一个节点,链表动态的扩容,并且如果是add()方法,或者 addLast()方法添加到链表尾部(尾插法),如果是addFirst()方法就是动态添加到头部(头插法)。
此类为我们提供了不同参数的add方法,也可根据参数,动态的将元素添加到任意位置
使用场景:相对来说插入,删除效率高,查找效率低,但是查找头元素,尾元素效率极高。
线程安全:Vector,Stack
Vector:
1.数据结构:
无参构造,和有参构造方法
结论:观察源码可知,其内部也是数组
2.扩容方式:
观察上述源码,我们可知
无参构造,或单个参数有参构造:其内部数组初始容量为10,当容量不足时,我们按数组现有容量的两倍进行扩容。
两个参数有参构造:
参数一:设定了数组初始容量的值,参数二:规定了数组容量扩容值。
3.线程安全: 观察其内部的多个方法,我们可知,其内部不同的方法都加上了synchronized,锁所以保证线程安全。
Stack:观察源码可知,Stack类继承自Vector类,Stack(栈)本身就是一种数据存储结构,其最大特点就是先进后出FILO。
最后,list接口的实现类包含了多种,还有CopyOnWriterArrayList等,在此就不逐一介绍了。