List接口
实现类:ArrayList LinkedList Vector
ArrayList 基于数组 Object[] 实现的可扩展的集合。查询速度快。需要一块连续的内存存储
创建:默认初始大小为10(可手动设置),
add:每次增加数组元素,需要判断长度是否足够,如不够用,则创建一个长度为当前数组2倍的新数组,并拷贝原数组数据至新数组,数组扩容最影响效率。
LinkedList 底层实现是双向链表,头结点的前驱是尾节点,尾节点的后继是头结点。插入速度快。在内存中随机分布,不需要连续存储。
节点:指向前一个节点的指针+数据元素+指向后一个节点的指针。
遍历:foreach循环遍历效率高
查询:根据下表查询时,判断下标的位置是更靠近左边还是右边,决定查找的顺序。(双向查找)
Vector:与Arraylist相似,但是Vector是线程安全的,ArrayList是线程不安全的。当多个线程同时修改数组时,modCount值(int型)改变,抛出ConcurrentModificationException异常。涉及到结构体发生变化的操作,modCount都会改变
Map接口
实现类:hashMap TreeMap
HashMap:基于哈希表,哈希值可能存在冲突,使用数组+链表(Jdk1.8之后增加了红黑树结构,当链表长度超过8时,将转换成红黑树)的方式实现,链表用于解决冲突。当一组key的哈希值相同,使用equals比较key值,拿到目标值。查找的key,链表过长,影响效率。
为什么设置成8?(概率问题,泊松分布)
HashMap是线程不安全的,因为在并发执行put操作会造成死循环,因为多线程会导致Entry的链表变成环状的数据结构,那Entry的next()节点就会一直不为空,就会产生死循环获取Entry。
多线程扩容
Java7:数据迁移。在数据迁移过程中,有可能会造成链表成环状,这个取next()一直有值,死循环。
Java8:分类迁移。把要迁移的数据分类,迁移后位置不变的和改变的。
key可以存储空值,add操作是,当key==null,table[0] = null
查找:key--得到-->hash值---通过“数组长度”得到-->key的hash值在数组中的下标--迭代该数组元素的链表。
在jdk1.8(包含)之后,数组的初始容量一定是2的指数次幂,第一是为了减少哈希碰撞,第二是如果数组容量为2的指数次幂,计算地址时的模运算优化成与运算结果一致,与运算的计算速度比模运算快(测试1w条数据大概快了30倍)
加载因子设置为0.75? 在时间和空间上做一个折衷。
TreeMap:红黑树,按照key默认由小到大排序,可以自定义排序方式。
ConcurrentHashMap:线程安全的hash表,并发插入操作可能构成循环链表,导致下一次取值时Entry.next()一直不为null,死循环。那么ConcurrentHashMap是如何保证线程安全的,又是如何实现高性能读写的呢?使用volatile保证线程安全,使用分段锁实现高性能读写
Set
HashSet
能够存储空值;不能存储重复值。
向HashSet集合中传入元素时,HashSet会调用该对象的HashCode方法获取Hash值,然后决定存储位置(无序)
LinkedHashSet(HashSet的子类)
能够存储空值;不能存储重复值。
LinkedHashSet是HashSet的子类,不允许重复的值,使用HashCode确定在集合中的位置,使用链表的方式确定位置(有序,按照输入的顺序输出)
TreeSet
不能存储空值,存储null会抛出空指针异常;不能存储重复值,