1、容器
Java中的容器主要有Collection和Map,它们都是顶层接口,都位于java.util包下,实际使用地容器都是基于这两个接口延伸出来的。
拓展(数据结构)
java中的容器其实就是数据结构在java中的实现
常用数据结构
- 线性表
连续存储,就是数组 - 链表
分散存储,每个元素为头节点+内容+尾节点 - 队列
双向通,两头开口,一边进一边出
先进先出 - 栈
单向通,一边开口,哪边进哪边出
先进后出 - 树
二叉树、红黑树
一个父节点,两个子节点,树形展开
想要详细了解java的数据结构可以参考
(小声bb:这个文章是要钱的,可以去网上再找找有没有其他的归纳的很好的)
2、Collection接口
Collection的继承树
Collection接口的类图
拓展:如何在java中查看继承体系
选中需要查看的类名(或接口名),按Ctrl+T,就会显示如下信息
(一)List
是一种已知顺序的有序数据集,可容纳重复的元素。
下面介绍几种常见List
(1)ArrayList(底层数组)
ArrayList是基于动态数组实现的非线程安全的集合(简单理解:所谓动态数组就是数组扩容)
ArrayList实现了List接口,常用操作有add、remove、get、size
其内部的数据采用数组进行存储
因此随机读写的速度很快,但删除、添加等操作相对会消耗比较多的时间,因为会有相关的一系列节点移动
ArrayList内部以数组的形式存储数据,默认的是一个空数组,当添加数据后,会扩充为一个长度为10的数组
(2)Vector(底层数组)
Vector类实现了可扩展的对象数组
像数组一样,它包含可以使用整数索引访问的组件
但是,Vector的大小可以根据需要增长或缩小,以适应在创建Vector之后添加和删除项目
Vector是同步的(线程安全)。 如果不需要线程安全的实现,建议使用ArrayList代替Vector
(3)LinkedList(底层链表)
LinkedList是基于链表实现的非线程安全的集合
双链表实现了List和Deque接口。
请注意,此实现不同步(不安全) 如果多个线程同时访问链接列表,并且至少有一个线程在结构上修改列表,则必须在外部进行同步。
该类定义代源码(可以看出实现了List与Queue,其中Queue的实现是通过实现Deque):
public class LinkedList<E>
extends AbstractSequentialList<E>
implements List<E>, Deque<E>, Cloneable, java.io.Serializable
{
(4)CopyOnWriteArrayList
这个类解决了ArrayList多线程读写不安全的问题,这个类可以多线程一边读一边写
实现原理:每次对数组集合的修改会new一个新的数组集合,对新的修改完,再覆盖掉旧的,
在这期间如果有另外的线程需要访问会访问原数组(注意不可以同时修改,线程加锁了的,只限于写的同时读)
该类定义代源码(可以看出实现了List集合):
public class CopyOnWriteArrayList<E>
implements List<E>, RandomAccess, Cloneable, java.io.Serializable
下面是别人解析的官方解释(看不懂的就按我上面的理解吧):
CopyOnWriteArrayList<E>类是ArrayList<E>的线程安全版本
一句话很清楚的说明了CopyOnWriteArrayList<E>的作用,注释中同样指出了实现了原理:
任何改变数组的操作都是在内部数组的一个新的拷贝上进行的。
这样的实现对于写来说代价很大,但如果多线程环境下遍历的操作,读操作远远多于写操作来说就比较高效了。
因此应用的场景就是:在多线程环境下一个读操作远远多于写操作的列表。
线程安全最简单的做法就是同步,那我们从源码的角度来看下ConpyOnWriteArrayList<E>相对于直接在ArrayList<E>上进行操作的同步比较起来的改进之处:写操作在可重入锁的保护下在内部数组的拷贝上进行,操作完成后将内部数组换成最新的数组。这样读操作就不许要进行同步,避免了直接同步ArrayList<E>时对读操作带来的开销
CopyOnWriteArrayList的实现原理
在使用CopyOnWriteArrayList之前,我们先阅读其源码了解下它是如何实现的。
以下代码是向CopyOnWriteArrayList中add方法的实现(向CopyOnWriteArrayList埋添加元素),
可以发现在添加的时候是需要加锁的,则多线程写的时候会Copy出N个副本出来。
(二)Set
是一种数学意义上的集合,最大的特点是不可容纳重复元素。
一个set集合不能包含重复的元素,set实现Collection并增加了hashCode和equals方法
set不允许存储值相同的元素,原因为在取值的时候set依靠的是元素本身取值
由于set提供了hashCode和Equals方法,所以set支持比较
set允许存储一个null值,并且不为空
在存储数据时应该满足:s1.hashCode = s2.hashCode 并且 s1.equals(s2)
set主要实现HashSet,TreeSet,LinkedHashSet
一般不怎么用,了解一下
(三)Queue
队列在java中的实现,与经典数据结构的队列一致。
Queue主要实现LinkedList、PriorityQueue(优先级队列 )
一般不怎么用,了解一下
3、Map接口
下面是Map接口的类图:
(1)HashMap:即哈希表,与经典数据结构中哈希表一致,主要用于快速查找
(2)HashTable:与HashMap功能基本一致,不过是线程安全的,但是是锁一整个
(3)SortedMap:经过排序的Map
(4)ConcurrentMap:并发Map,主要用于多线程并发环境下,只锁其中访问的一个元素
4、Java中常用的三大容器(List、Set和Map)区别
List | Set | Map |
可以存储重复元素 | 不可以存储重复元素 | 键值对方式存储,存储的键不能重复 |
基于数组或者链表实现 | 基于Map实现 | 基于哈希表或者红黑树实现 |
面试题:
1、集合和数组的区别
- 数组长度固定,如果要增删元素特别麻烦
(注意不要说集合的长度是可以改变的,实现集合的类不同,也不一定) - 集合封装了很多方法,用起来比较方便
- 集合只能存放引用类型(基本类型会自动拆装箱为其包装类),数组都可以存放
- 集合中可以装不同的类型,数组中只能装相同的类型
2、集合体系的理解
就这个图吧,这个图理解很全了
3、Collection 与 Collections 的区别
首先Collection和Collections是两个完全不同的范畴。正如下图,Collection是所有集合的root接口,而Collections仅仅是一个工具类,它提供了很多静态的工具方法来操作一个集合。
Collection为集合的接口
Collections为集合的工具类:用来实现功能的,提供静态工具方法操作集合
一般后面加s的都是工具类
常用的Collections方法
- 随机列表的方法
static void shuffle(List<?> list) 使用默认的随机源随机排列指定的列表
static void shuffle(List<?> list, Random rnd) 使用指定的随机源随机排列指定的列表
4、ArrayList与LinkedList有什么区别
他们的区别体现了数组与链表两种存储结构的区别
- ArrayList基于动态数组实现的非线程安全的集合;LinkedList基于链表实现的非线程安全的集合
- 对于随机index访问的get和set方法,一般ArrayList的速度要优于LinkedList
因为ArrayList直接通过数组下标直接找到元素;LinkedList要移动指针遍历每个元素直到找到为止 - 新增和删除元素,一般LinkedList的速度要优于ArrayList
因为ArrayList在新增和删除元素时,可能扩容和复制数组
LinkedList实例化对象需要时间外,只需要修改指针即可 - LinkedList集合不支持 高效的随机随机访问(RandomAccess)
- ArrayList的空间浪费主要体现在在list列表的结尾预留一定的容量空间,而LinkedList的空间花费则体现在它的每一个元素都需要消耗相当的空间
5、ArrayList与Vector有什么区别
- 线程安全:ArrayList是线程不安全的
Vector是线程安全的 - ArrayList默认长度为10,里面底层是数组的扩容,每次扩容原来长度的1.5倍
Vector底层也是数组,但是会扩容到原来长度的2倍 - Vector一般不用了,因为效率低
6、HashMap与HashTable有什么区别
- HashMap是线程不安全的
HashTable是线程安全的 - HashMap允许null作为key
HashTable不允许 - HashTable被淘汰
7、列举几个线程安全的集合
Vetor
HashTable
ConcurrentHashMap
CopyOnWriteArrayList
8、List与Set的区别
List可重复,Set不可重复
注意:有序无序不作为他们的区别
9、谈一谈CopyOnWriteArrayList
实现了读写分离,即:一个线程在进来写的时候(增删改),另外一个线程也可以进来读(查)
10、说一说HashMap的存储原理,为什么他的效率最高
理解为Python中的字典类型(学过Python的可以这么理解)
包含键值对,通过键来找值
通过键算出hashcode编码,通过这个编码找到值所在的存储位置
11、如何优化HashMap
- 在初始化时指定长度,HashMap在元素容量达到3/4时会扩容
一旦扩容,之前的元素都要全部打乱,效率极低 - HashMap中元素存放的位置和Key的HashCode有关,也和HashMap的长度有关
- 尽量使用字符串作为Key,字符串的HashCode有缓存,不需要再重新再算HashCode
- Key一定要保证Key的HashCode方法和equsls方法一致,避免出现链表
12、谈一谈ConcurrentHashMap
线程安全,但是效率相对于HashTable更高
HashTable是锁整个map元素,ConcurrentHashMap是只锁我们访问的那一个元素
13、如何基于ArrayList自己写一个线程安全的集合
通过继承安全集合