List,Set都是继承自Collection接口
List
- 元素有放入顺序;
- 元素可重复;
- 可以插入多个null元素 ;
- 常用的实现类有 ArrayList、LinkedList 和 Vector。ArrayList 最为流行,它提供了使用索引的随意访问,而 LinkedList 则对于经常需要从 List 中添加或删除元素的场合更为合适。
Set
- 元素无放入顺序(注意:元素虽然无放入顺序,但是元素在set中的位置是有该元素的HashCode决定的,其位置其实是固定的)
- 元素不可重复
- 只允许一个 null 元素;
- TreeSet通过 Comparator 或者 Comparable 维护了一个排序顺序 ;
- Set 接口最流行的几个实现类是 HashSet、LinkedHashSet 以及 TreeSet。最流行的是基于 HashMap 实现的 HashSet;TreeSet 还实现了 SortedSet 接口,因此 TreeSet 是一个根据其 compare() 和 compareTo() 的定义进行排序的有序容器。
HashSet
通过HashMap实现1
2
3
4
5
6
7
8public HashSet() {
map = new HashMap<>();
}
public boolean add(E e) {
// 添加的元素作为 HashMap 的 key
return map.put(e, PRESENT)==null;
}
LinkedHashSet
通过LinkedHashMap实现1
2
3public LinkedHashSet() {
super(16, .75f, true);
}
1 | HashSet(int initialCapacity, float loadFactor, boolean dummy) { |
TreeSet
基于TreeMap实现,构造函数中调用TreeMap的构造函数1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21TreeSet(NavigableMap<E,Object> m) {
this.m = m;
}
public TreeSet() { // 无参数构造函数
this(new TreeMap<E,Object>());
}
public TreeSet(Comparator<? super E> comparator) { // 包含比较器的构造函数
this(new TreeMap<>(comparator));
}
public TreeSet(Collection<? extends E> c) {
this();
addAll(c);
}
public TreeSet(SortedSet<E> s) {
this(s.comparator());
addAll(s);
}
Map
- 不是collection的子接口或者实现类,Map是一个接口;
- TreeMap 也通过 Comparator 或者 Comparable 维护了一个排序顺序。
- Map 里你可以拥有随意个 null 值但最多只能有一个 null 键
- Map 接口最流行的几个实现类是 HashMap、LinkedHashMap、Hashtable 和 TreeMap。(HashMap、TreeMap最常用)
HashMap
1.8的变化
- 引入了红黑树
- 扩容hash的优化,利用扩容后的位置的特性,不需要像JDK1.7的实现那样重新计算hash。
- resize的时候,不想1.7那样需要倒置元素
Java 8系列之重新认识HashMap
Java源码分析:HashMap 1.8 相对于1.7 到底更新了什么?
HashMap原理-1.8
put
1 | final V putVal(int hash, K key, V value, boolean onlyIfAbsent, |
get
get实际上是调用getNode方法:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19final Node<K,V> getNode(int hash, Object key) {
Node<K,V>[] tab; Node<K,V> first, e; int n; K k;
if ((tab = table) != null && (n = tab.length) > 0 &&
(first = tab[(n - 1) & hash]) != null) {
if (first.hash == hash && // always check first node
((k = first.key) == key || (key != null && key.equals(k))))
return first;
if ((e = first.next) != null) {
if (first instanceof TreeNode)// 从红黑树上取
return ((TreeNode<K,V>)first).getTreeNode(hash, key);
do {// 在链表中取
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
return e;
} while ((e = e.next) != null);
}
}
return null;
}
扩容过程
1 | final Node<K,V>[] resize() { |
Java 8系列之重新认识HashMap
HashMap原理-1.8
死锁
LinkedHashMap
LinkedHashMap 能够做到按照插入顺序或者访问顺序进行迭代,这样在我们以后的开发中遇到相似的问题,才能想到用 LinkedHashMap 来解决,否则就算对其内部结构非常了解,不去使用也是没有什么用的。
put
1 | final V putVal(int hash, K key, V value, boolean onlyIfAbsent, |
newNode实际上调用的是子类重写的方法1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18Node<K,V> newNode(int hash, K key, V value, Node<K,V> e) {
LinkedHashMap.Entry<K,V> p =
new LinkedHashMap.Entry<K,V>(hash, key, value, e);
linkNodeLast(p);
return p;
}
// 新节点放入末尾,并且是双向的
private void linkNodeLast(LinkedHashMap.Entry<K,V> p) {
LinkedHashMap.Entry<K,V> last = tail;
tail = p;
if (last == null)
head = p;
else {
p.before = last;
last.after = p;
}
}
afterNodeAccess 和 afterNodeInsertion 也是子类重写的方法1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32void afterNodeAccess(Node<K,V> e) { // 当accessOrder为true时,move node to last
LinkedHashMap.Entry<K,V> last;
if (accessOrder && (last = tail) != e) {
LinkedHashMap.Entry<K,V> p =
(LinkedHashMap.Entry<K,V>)e, b = p.before, a = p.after;
p.after = null;
if (b == null)
head = a;
else
b.after = a;
if (a != null)
a.before = b;
else
last = b;
if (last == null)
head = p;
else {
p.before = last;
last.after = p;
}
tail = p;
++modCount;
}
}
void afterNodeInsertion(boolean evict) { // possibly remove eldest
LinkedHashMap.Entry<K,V> first;
if (evict && (first = head) != null && removeEldestEntry(first)) {
K key = first.key;
removeNode(hash(key), key, null, false, true);
}
}
foreach
遍历时,是通过前后的指针来取出来的,所以是有序的1
2
3
4
5
6
7
8
9
10public void forEach(BiConsumer<? super K, ? super V> action) {
if (action == null)
throw new NullPointerException();
int mc = modCount;
// 根据指针的关系
for (LinkedHashMap.Entry<K,V> e = head; e != null; e = e.after)
action.accept(e.key, e.value);
if (modCount != mc)
throw new ConcurrentModificationException();
}
TreeMap
http://shmilyaw-hotmail-com.iteye.com/blog/1836431
https://www.ibm.com/developerworks/cn/java/j-lo-tree/index.html
彻底看懂 so called 红黑树
使用场景
- 如果你经常会使用索引来对容器中的元素进行访问,那么 List 是你的正确的选择。如果你已经知道索引了的话,那么 List 的实现类比如 ArrayList 可以提供更快速的访问,如果经常添加删除元素的,那么肯定要选择LinkedList。
- 如果你想容器中的元素能够按照它们插入的次序进行有序存储,那么还是 List,因为 List 是一个有序容器,它按照插入顺序进行存储。
- 如果你想保证插入元素的唯一性,也就是你不想有重复值的出现,那么可以选择一个 Set 的实现类,比如 HashSet、LinkedHashSet 或者 TreeSet。所有 Set 的实现类都遵循了统一约束比如唯一性,而且还提供了额外的特性比如 TreeSet 还是一个 SortedSet,所有存储于 TreeSet 中的元素可以使用 Java 里的 Comparator 或者 Comparable 进行排序。LinkedHashSet 也按照元素的插入顺序对它们进行存储。
- 如果你以键和值的形式进行数据存储那么 Map 是你正确的选择。你可以根据你的后续需要从 Hashtable、HashMap、TreeMap 中进行选择。
注:以上源码基于jdk1.8