文章目录
Java 集合总结
Java 有哪些集合,继承关系是怎么样的
平时常用的集合有哪些
- Collection
- List 可以重复添加元素
- ArrayList
- LinkedList
- Set 不能重复添加元素
- HashSet 不接受 null
- TreeSet
- List 可以重复添加元素
- Map 以键值对的形式保存值
- HashMap
- LinkedHashMap
- TreeMap
- Hashtable
- HashMap
List
ArrayList
数组实现,初始容量为 10,不够的时候扩容,扩容就是数组在当前长度基础上增大一倍。添加数据的时候,先判断容量是否够,不够就扩容,扩容完成后再添加。
LinkedList
链表实现。尾插法,即新节点插入到链表尾部。
Set
HashSet
1.8 底层基于 HashMap 实现,HashMap 的 value 都是同一个对象。
TreeSet
1.8 默认是 TreeMap 实现,也可以给构造方法传入一个 NavigableMap(接口)
的实现实例。
Map
HashMap
最常用的一种结构,以数组为基础,数组元素为链表的复合结构。
需要注意的是:
初始容量为 16,默认承载因子为 0.75 ,需要注意的是,在指定容量的时候,如果不是 2 的指数,会计算一个大于指定容量,但是同时又是符合要求里面最小的一个 2 的指数作为容量。
当元素个数大于或等于当前容量 * 承载因子,就会扩容。
1.7 插入数据的时候是头插法,即新的节点在链表头部。1.8 及以后是尾插法,即新的节点在链表尾部。这个修改是为了解决扩容的时候可能会发生的死循环的问题。
1.8 及以后,当同一个链表元素个数大于等于 (8 - 1) 的时候,会变成红黑树来存储,目的是提高效率。红黑树的转变是先比较 key 的 hash 大小,如果相同就尝试比较 key 自身。
无论是扩容,还是转成红黑树,都是先把节点放进去后再做扩容或者转换。
hash 碰撞,是指不同的 key, key 的 hash 和数组大小再次计算后,得出的索引一致。 hash 碰撞越少,HashMap 的效率越高。1.7 1.8 hash 算法有区别。
LinkedHashMap
基于 HashMap 实现的,同时还是一个双向循环链表(比HashMap的节点多了个 before 和 after 节点),在开启排序的情况下,最近使用过的节点(put、get)会放在链表尾部。
这个特性很适合 LRU 算法的实现,大概原理就是 LinkedHashMap 开启排序,然后最近使用的元素都在链表尾部,当链表长度大于指定长度的时候,就从链表头部开始删除,因为链表头部是最近没有使用的。
Hashtable
synchroinzed 实现线程安全,效率低下。
TreeMap
TreeMap 1.8 底层实现是一个红黑树,排序的规则是比较 key 的 “大小”。并且要求 key 的类型是需要可以比较的,并且 key 值不能为 null。
和 HashMap 一样,先插入数据后在重排序。
碰到的常见问题
线程安全问题
由于以上的都不是线程安全的,所以多线程的时候容易出问题,对于安全的线程有以下的几种
- List
- Vector 通过方法添加 synchizoned 关键字实现,效率低下。
- CopyOnWriteArrayList 每次写(添加,删除,修改)的时候,都会生成一个新副本,有频繁的读写会消耗大量内存。
- Set
- CopyOnWriteArraySet 类似 CopyOnWriteArrayList
- Map
- ConcurrentHashMap 分段锁, 1.7 和 1.8 实现有区别
ConcurrentHashMap
分段锁来实现,分段锁可以提高效率,是因为如果不同线程的读写发生在不同的段上,实际上是没有锁竞争的,也就是没有线程被阻塞,所以效率高。
1.7 实现原理是:
ConcurrentHashMap 有一个 segments 数组,这个数组的类型是 Segment,它继承自 ReentrantLock ,也就是说 Segment 是一个锁。
ConcurrentHashMap 插入或者修改数据的时候,会先找到是哪一个 segment , 然后在这个 segment 上面插入或者修改数据,而 segment 插入或者修改数据的时候,会先尝试获取锁,如果获取失败,说明有锁竞争,然后先尝试自旋,自旋超过次后,就开始阻塞线程。因此是线程安全的。
另外需要注意的是获取数据的时候没有加锁,因为是 voliate 变量,可以拿到最新的数据,但是呢,
1.8 实现原理是:
类似 HashMap ,放弃了分段锁,在插入或者修改数据的时候利用 cas 和 synchionzed 来保证线程安全,大概的原理就是先利用 cas 来设置,设置失败的时候说明有线程竞争,这个时候就用 synchionzed 来加锁,保证线程安全。