
Collection
- List:ArrayList、Vector、LinkedList
- set:HashSet,LinkedHashSet、TreeSet
- (Queue接口)
Map
- HashMap
- HashTable
- LinkedHashMap
- ConcurentHashMap
- TreeMap
1.ArrayList/Vector
ArrayList:
1.容量不够扩容为原来的1.5倍(elementData = Arrays.copyOf(elementData, newCapacity);)
2.结构本身由动态数组实现不能序列化(由transient修饰),因此序列化时是对里面的每个元素遍历序列化。
Vector:
1.容量不够扩容为原来的2倍,Vector 底层数据结构和 ArrayList 类似,也是一个动态数组存放数据。不过是在 add() 方法的时候使用 synchronized 进行同步写数据,但是开销较大,所以 Vector 是一个同步容器并不是一个并发容器。
(同步并发:https://blog.youkuaiyun.com/XM_no_homework/article/details/103888289)
2.LinkedList
LinkedList 底层是基于双向链表实现的
LinkedList 插入,删除都是移动指针效率很高。
查找需要进行遍历查询,效率较低。
3.HashMap
(HashMap面试题:https://blog.youkuaiyun.com/suifeng629/article/details/82179996)

JDK 1.7:HashMap 底层是基于数组+链表实现的。
JDK 1.8:HashMap 底层是基于数组+链表+红黑树实现的(链表到达一定阀值链表变红黑树)。
其中有两个重要的参数:
- 容量
- 负载因子
容量的默认大小是 16,负载因子是 0.75,当 HashMap 的 size > 16*0.75 时就会发生扩容(容量和负载因子都可以自由调整)。
hashMap是非线程安全的,表现在两种情况下:
1. 扩容:https://zhuanlan.zhihu.com/p/42703011
t1线程对map进行扩容,此时t2线程来读取数据,原本要读取位置为2的元素,扩容后此元素位置未必是2,则出现读取错误数据。
2.hash碰撞
两个线程添加元素发生hash碰撞,都要将此元素添加到链表的头部,则会发生数据被覆盖。
红黑树:http://www.tianxiaobo.com/2018/01/11/%E7%BA%A2%E9%BB%91%E6%A0%91%E8%AF%A6%E7%BB%86%E5%88%86%E6%9E%90/
hash冲突:
由于数组的长度有限,所以难免会出现不同的 Key 通过运算得到的 index 相同,这种情况可以利用链表来解决,HashMap 会在 table[index]处形成链表,采用头插法将数据插入到链表中。链表中存储的是键值对
put()和get():
get 和 put 类似,也是将传入的 Key 计算出 index ,如果该位置上是一个链表就需要遍历整个链表,通过 key.equals(k) 来找到对应的元素。
在 JDK1.8 中对 HashMap 进行了优化: 当 hash 碰撞之后写入链表的长度超过了阈值(默认为8)并且 table 的长度不小于64(否则扩容一次)时,链表将会转换为红黑树。
假设 hash 冲突非常严重,一个数组后面接了很长的链表,此时重新的时间复杂度就是 O(n) 。
如果是红黑树,时间复杂度就是 O(logn) 。
大大提高了查询效率
4.HashTable
HashTable与HashMap
1、同步性:**Hashtable是线程安全的,也就是说是同步的,**而HashMap是线程序不安全的,不是同步的。
2、HashMap允许存在一个为null的key,多个为null的value 。
3、hashtable的key和value都不允许为null。
5.TreeMap
TreeMap实现SortMap接口,能够把它保存的记录根据键排序,默认是按键值的升序排序(自然顺序),也可以指定排序的比较器,当用Iterator遍历TreeMap时,得到的记录是排过序的。不允许key值为空,非同步的;
同样的值的map,顺序不同,equals时,true,说明,treeMap在equals()时是整理了顺序了的。
6. LinkedHashMap
LinkedHashMap 基于 HashMap, 但具有顺序(插入的顺序),来解决有排序需求的场景。
它的底层是继承于 HashMap 实现的,由一个双向链表所构成。
LinkedHashMap 的排序方式有两种:
根据写入顺序排序。
根据访问顺序排序。
其中根据访问顺序排序时,每次 get 都会将访问的值移动到链表末尾,这样重复操作就能得到一个按照访问顺序排序的链表。
LinkedHashMap 其实就是对 HashMap 进行了拓展,使用了双向链表来保证了顺序性
7.ConcurrentHashMap
结构图:

ConcurrentHashMap由 Segment 数组、HashEntry 组成,和 HashMap 一样,仍然是数组加链表。
对Segment(分段)加锁(继承于Reentrantlock) 先访问segment再访问HashEntry
/**
* Segment 数组,存放数据时首先需要定位到具体的 Segment 中。
*/
final Segment<K,V>[] segments;
transient Set<K> keySet;
transient Set<Map.Entry<K,V>> entrySet;
put()
put()加分段锁,先是通过 key 定位到 Segment,之后在对应的 Segment 中进行具体的 put
虽然 HashEntry 中的 value 是用 volatile 关键词修饰的,但是并不能保证并发的原子性,所以 put 操作时仍然需要加锁处理。
put()过程:
- 将当前 Segment 中的 table 通过 key 的 hashcode 定位到 HashEntry。
- 遍历该 HashEntry,如果不为空则判断传入的 key 和当前遍历的 key 是否相等,相等则覆盖旧的 value。
- 不为空则需要新建一个 HashEntry 并加入到 Segment 中,同时会先判断是否需要扩容。
- 最后会解除在 1 中所获取当前 Segment 的锁
get()
get()不加锁,只需要将 Key 通过 Hash 之后定位到具体的 Segment ,再通过一次 Hash 定位到具体的元素上。
由于 HashEntry 中的 value 属性是用 volatile 关键词修饰的,保证了内存可见性,所以每次获取时都是最新值。
ConcurrentHashMap 的 get 方法是非常高效的,因为整个过程都不需要加锁
JDK1.8中抛弃了原有的 Segment 分段锁,而采用了 CAS + synchronized 来保证并发安全性。
8. HashSet
HashSet 是一个不允许存储重复元素的集合,内部由HashMap实现
HashSet成员变量:
private transient HashMap<E,Object> map;
// Dummy value to associate with an Object in the backing Map
private static final Object PRESENT = new Object();
其中key为set中添加的值,value为 (private static final Object PRESENT = new Object();)
当有重复的值写入到 HashSet 时,value 会被覆盖,但 key 不会受到影响,这样就保证了 HashSet 中只能存放不重复的元素。
9.遍历
在类集中提供了以下四种的常见输出方式:
1)Iterator:迭代输出,是使用最多的输出方式。
2)ListIterator:是Iterator的子接口,专门用于输出List中的内容。
3)foreach输出:JDK1.5之后提供的新功能,可以输出数组或集合。
4)for循环
代码示例如下:
for的形式:for(int i=0;i<arr.size();i++){…}
foreach的形式: for(int i:arr){…}
iterator的形式:
Iterator it = arr.iterator();
while(it.hasNext()){ object o =it.next(); …}
1541

被折叠的 条评论
为什么被折叠?



