1. List
特点
- 有序
- 允许多个null
1.1 ArrayList
ArrayList是一个可变对象数组, 默认构造创建了一个空数组,默认初始容量为10
add()
先让modCount(结构性修改次数)自增再调用add(E e, Object[] elementData, int s)
保证方法字节码大小小于35,使得在循环调用时的速度变快
可以发现 grow()
在数组长度满时扩容
这里的grow()
当老数组长度大于0并且,不为空时是扩容数组
,新数组的长度为老数组长的1.5倍(右移1位是除以2)
当为空时是初始化数组
,初始长度为默认(10)与输入取最大值
remove()
-
remove 索引
-
remove 对象
found:{}是可以用break跳出循环语句块的循环
先遍历数组找到对象的索引,在调用fastRemove()
方法 -
fastRemove()
如果是末尾位置直接设为null,
不是末尾位置,利用数组复制的方式,将索引之后的元素左移
sort()
sort
的实现调用了Arrays.sort(),并利用modCount判断了并发修改异常
contains()
contains的本质就是遍历数组,会根据空与非空进行不同的判断
subList()
可以看到subList
方法是构造了一个静态内部类的对象
forEach()
ArrayList的forEach
遍历就是最常规的for循环数组遍历
1.2 Vector
可以看到Vector
的构造需要初始长度(默认10),与 溢出时的增量(默认0)
add()
可以发现,Vector的add
是通过加锁来保证线程安全的,这种做法是不推荐的,我们应该使用CopyOnWriteArrayList
Vector
的grow
方法也与ArrayList不同,当没有给出默认增量时,默认增量是原数组的长度,所以默认扩容长度是2倍
remove()
Vector
的大部分方法与ArrayList
一致,但是通过加锁来保证线程安全
1.3 LinkedList
第一个构造函数初始化了一个空链表,第二个调用了addAll
方法,将参数集合添加到链表中
add
- add(E e)
add()
调用了linkLast(尾插)
,在其中初始化了一个Node
节点,将last指向Node
,调整first和next
这里我们可以看出,LinkedList
实现了一个双向链表
- add(int index, E element)
如果插入位置与链表长度相等则直接尾插,如果不等,则在索引位置前插入
通过遍历链表的方式找到索引处的节点并返回(会根据索引位置判断,是从头遍历还是从末尾遍历)
remove
默认的remove
会删除链表的第一个节点
最后的函数是将删除的节点至空,以便及时的垃圾回收
get
与set
这两个方法都采用遍历的方式来更改或者获取节点 可以看出 链表在查找的时间复杂度高
forEach()
链表的forEach
采用了迭代器的遍历方式
2. Map<K, V>
特点
- 不能包含重复的键; 每个键最多可以映射一个值
- HashMap只允许一条记录的键为NULL,TreeMap不允许键为NULL
- 没有继承Collection接口,无序
2.1 HashMap<K,V>
jdk1.8以上的 HashMap
由数组+链表(红黑树) 组成
桶(数组)的长度一定为2的整数次幂,手动设置的也会扩大到2的整数次幂,每次扩容重新哈希也是如此
默认构造了一个初始容量16 装载因子为0.75的HashMap
,当然也可以手动提供 容量和装载因子
tableSizeFor
会根据初始容量计算出扩容阈值threshold
(2的次方) 例如初始容量100 阈值为2的次方数 128
直到这里,HashMap
还没有初始化数组
put(k,v)
这里利用hash()
计算了key的hash值,并放到putVal
中
hash()
的取值要结合数组槽位(n - 1) & hash
来分析
^
按位异或运算,只要位不同结果为1,不然结果为0;
>>>
无符号右移:右边补0
将h无符号右移16为相当于将高区16位移动到了低区的16位,再与原hashcode做异或运算,可以将高低位二进制特征混合起来
从上文可知高区的16位与原hashcode相比没有发生变化,低区的16位发生了变化
我们可知通过上面(h = key.hashCode()) ^ (h >>> 16)进行运算可以把高区与低区的二进制特征混合到低区,那么为什么要这么做呢?
我们都知道重新计算出的新哈希值在后面将会参与hashmap中数组槽位的计算,计算公式:(n - 1) & hash,假如这时数组槽位有16个,则槽位计算如下:
高区的16位很有可能会被数组槽位数的二进制码锁屏蔽,如果我们不做刚才移位异或运算,那么在计算槽位时将丢失高区特征
也许你可能会说,即使丢失了高区特征不同hashcode也可以计算出不同的槽位来,但是细想当两个哈希码很接近时,那么这高区的一点点差异就可能导致一次哈希碰撞,所以这也是将性能做到极致的一种体现
可以发现,HashMap中的数组由Node<K,V>
组成
可以看到Node
的数据结构,其中有key
与value
与hash
和指向下一个节点的指针
从上面可以看到 jdk1.8后的hashmap采用了尾插法
下面看看resize()
是如何扩容的
get()
调用getNode()
获取节点,如果没找到返回null
remove()
这里调用了removeNode()
删除指定节点,删除成功返回节点的值,失败返回null
forEach()
HashMap的forEach遍历为 遍历数组节点(数组中有链表或红黑树则时间复杂度增加)的方式
2.2 LinkedHashMap<K,V>
Map接口的哈希表和链表实现,具有可预测的迭代顺序。
此实现与HashMap不同之处在于它额外维护了一个贯穿其所有条目的双向链表
。 使得数据变得有序
此链表定义迭代排序,通常是键插入映射的顺序 ( 插入顺序 )。
请注意,如果将键重新插入Map,则不会影响插入顺序。
默认构造的初始大小为16 默认负载因子为0.75
除了默认构造,也可以手动设置容量,负载因子和排序模式accsessOrder
put(k,v)
LinkedHashMap
的put方法与HashMap
的put大致相同,但是改变了初始化新节点的方式,初始化完的节点会被连接到链表尾部
LinkedHashMap中的节点为Entry
,它继承了HashMap中的Node
get()
LinkedHashMap
重新实现了get
方法 利用HashMap的getNode
获取节点的值,如果此时accessOrder
为true,则将访问的节点置于链表末尾
remove()
forEach()
可以看到LinkedHashMap的遍历方式为链表的遍历的方式,是有序的
2.3 TreeMap
TreeMap
是通过红黑树实现的,其中的comparator
会在插入数据时进行排序,如果未传入comparator
则会进行自然排序,所以他是有序的
TreeMap
是有Entry
节点组成的,每次操作完节点后都要重新平衡树
当Key
为无法比较的对象时,构造函数必须传入比较器
put()
get()
这里getEntry
寻找指定节点,当未找到会返回null
当传入构造器用传入的构造器遍历比较寻找,未传入用自然排序比较寻找
remove()
先调用getEntry
遍历找到要删除的节点再调用deleteEntry
删除
forEach()
采用了迭代遍历的方式对树进行遍历,当modCount
不统一时会抛出并发修改异常
3. Set
3.1 HashSet
没有重复元素的无序集合
默认构造,构造了一个默认容量为16,负载因子为0.75的HashMap
可以看到,HashSet
的本质就是一个只有Key
,值为Object
的HashMap
同样,hashSet也是可以自己设置负载因子及容量的
add()
add
直接调用了HashMap的put
,传了键与一个空对象
remove()
remove
直接调用了HashMap的remove
forEach()
遍历方式为迭代器遍历
3.2 TreeSet
TreeSet
是一个有序集
里面维护了一个可排序Map
add()
remove()
forEach()
遍历方式为迭代器遍历
4. Queue
4.1 PriorityQueue
PriorityQueue中的数据必须是有序的,或者是可排序的
PriorityQueue不允许添加null
默认构造 声明了一个容量为11 不带比较器的优先队列
它的其他构造方法还能指定容量,指定比较器,或者用其他的集合构造它
add()
,offer()
这两个方法都是入队操作,在判断是否需要扩容后队尾入队
从上面可以看到,当长度低于64时,扩容为2n+2
,当大于等于64时 扩容1.5倍
插入函数对是否有比较器进行了区分,有比较器是用比较器,无比较器用Comparable
比较
remove()
remove先遍历找出节点位置再调用removeAt()删除
removeAt
在删除最后一个元素时直接将对应元素置为null,其他时候将最小的元素移动到根,再对其进行删除操作
forEach()
遍历采用了for循环遍历
5. 视图
5.1 不可修改视图
Xxx.of(" “,” ")
用这个方法可以将对象组成集合,这个方法组成的集合是小集合,是不可修改的名,如果想让他们可以修改可以将其传入对应集合的构造器中
Arrays.asList() 实现的集合也与此类似
subXxx(E from,E to)
左开右闭,返回大于等于from到to所有元素构成的子集
Collections.unmodifiableXxx()
返回一个对应的接口对象,但是所有的更改器方法已经重新定义为抛出异常
5.2 同步视图
Collections.synchronizedXxx()
可以返回一个线程安全的结合对象 确保安全的方式为加synchronized
锁
5.3 检查型视图
Collections.checkedXxx()
可以检查集合的泛型与所给的泛型是否一致,这个错误在add时无法被找到,可以用这个检查
5.4 方法
排序 sort()
集合类库的排序比快速排序慢 需要传入可排序集合对象 或者传入对象与比较器
removeof replaceAll
这两个方法可以传入lamda表达式对集合进行操作
集合转数组 toArray
这个方法有双重职责,不仅要填充一个已有的数组(如果它足够长),还要创建一个新数组
必须
xxx.toArray(new xx[0])