Java Collection集合源码详解 --- Map
作者:shiwyang
Java版本:1.8
- 深入了解集合源码与数据结构的关系,了解集合内部不同子类的共性与区别
- 一共分为三个部分:Collection Map ConcurrentHashMap
- 这是第二个部分,详细的描述了Map,还介绍了两个工具类,最后是各种类型集合的比较
Map 概述
Map(用 key 来搜索的专家): 使用键值对(key-value)存储,类似于数学上的函数 y=f(x),"x" 代表 key,"y" 代表 value,key 是无序的、不可重复的,value 是无序的、可重复的,每个键最多映射到一个值。- Map接口的常用实现类:HashMap、Hashtable 、Properties
- Map中的Key 不允许重复,key相同时,value替换
- key和value都可以为null
- Key-Value可以是任何类型,常用String类型作为key
Entry< K - V >: Map的原理就是存放K-V键值对,因此Map提供了一个这个接口,用于存放Map中的每一个键值对元素。
EntrySet: 为了方便遍历(提供了 getKey() getValue()),使用这个EntrySet集合,该集合存放的元素的类型Entry。在EntrySet中,定义的类型是Map.Entry 。
HashMap
- 线程不安全
HashMap源码
**entrySet方法:**在HashMap中Set<Map.Entry<K,V>>的Entry对象实际上存放的是HashMap&Node,因为Node 是HashMap中Entry的实现子类(impl)(面向对象中多态的概念)
在执行entrySet后,本质上Set集合还是一个空的,但是在执行 迭代器 的时候,使用了HashMap内的 nextNode() 方法,这个方法可以从 table表 中获取Node元素,而Node元素本身是Entry的子类,可以向上转型;在执行entrySet中的 forEach() 中,也是从table表中获取Node对象。
- 第一步:
public Set<Map.Entry<K,V>> entrySet() {
Set<Map.Entry<K,V>> es;
// 返回空对象
return (es = entrySet) == null ? (entrySet = new EntrySet()) : es;
}
- forEach方法
final class EntrySet extends AbstractSet<Map.Entry<K,V>> {
public final int size() { return size; }
public final void clear() { HashMap.this.clear(); }
// 迭代器
public final Iterator<Map.Entry<K,V>> iterator() {
return new EntryIterator();
}
public final boolean contains(Object o) {
if (!(o instanceof Map.Entry))
return false;
Map.Entry<?,?> e = (Map.Entry<?,?>) o;
Object key = e.getKey();
Node<K,V> candidate = getNode(hash(key), key);
return candidate != null && candidate.equals(e);
}
public final boolean remove(Object o) {
if (o instanceof Map.Entry) {
Map.Entry<?,?> e = (Map.Entry<?,?>) o;
Object key = e.getKey();
Object value = e.getValue();
return removeNode(hash(key), key, value, true, true) != null;
}
return false;
}
public final Spliterator<Map.Entry<K,V>> spliterator() {
return new EntrySpliterator<>(HashMap.this, 0, -1, 0, 0);
}
// forEach方法
public final void forEach(Consumer<? super Map.Entry<K,V>> action) {
Node<K,V>[] tab;
if (action == null)
throw new NullPointerException();
if (size > 0 && (tab = table) != null) {
// 也是从table里面遍历
int mc = modCount;
for (int i = 0; i < tab.length; ++i) {
for (Node<K,V> e = tab[i]; e != null; e = e.next)
action.accept(e);
}
if (modCount != mc)
throw new ConcurrentModificationException();
}
}
}
- 执行迭代器:
final class EntryIterator extends HashIterator
implements Iterator<Map.Entry<K,V>> {
public final Map.Entry<K,V> next() { return nextNode(); }
}
final Node<K,V> nextNode() {
Node<K,V>[] t;
Node<K,V> e = next;
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
if (e == null)
throw new NoSuchElementException();
// 都是从table返回的Node对象
if ((next = (current = e).next) == null && (t = table) != null) {
do {} while (index < t.length && (next = t[index++]) == null);
}
return e;
}
其他的HashMap的源码在前面HashSet里面已经写过了。
HashTable
- HashTable 和HashMap使用方法基本是一样的
- HashTable是线程安全的
- HashTable 的默认大小是 11 扩容阈值也是 0.75 扩容机制是 ( 原大小 * 2 + 1)
- HashTable的key - Value 都不能为null (会抛出异常)
- 常用的实现子类有 Properties 用于获取配置文件
TreeSet&TreeMap
- TreeSet 底层是TreeMap
- TreeSet的保证数据唯一的方法跟HashSet一样,都是利用了Map中的key有不可重复的性质,将元素存入Value中
- 可以通过构造器方法,往TreeSet里面传入比较器,使得TreeSet内的数据按照比较器的方法进行排序。
TreeSet&TreeMap源码
Entry结构
static final class Entry<K,V> implements Map.Entry<K,V> {
K key;
V value;
Entry<K,V> left;
Entry<K,V> right;
Entry<K,V> parent;
boolean color = BLACK;
}
构造函数: 传入比较器的构造函数
public TreeSet(Comparator<? super E> comparator) {
this(new TreeMap<>(comparator));
}
public TreeMap(Comparator<? super K> comparator) {
this.comparator = comparator;
}
put函数: TreeSet的结构跟HashSet很像,关键的不同就是在添加之前,需要使用比较器,确定元素需要添加的位置。
public V put(K key, V value) {
Entry<K,V> t = root;
if (t == null) {
compare(key, key); // type (and possibly null) check
root = new Entry<>(key, value, null);
size = 1;
modCount++;
return null;
}
int cmp;
Entry<K,V> parent;
// 判断是如何比较的:一共有两种方法,一种是传入了comparator对象,一种是对象实现了comparable接口,两种比较的具体比较过程。
// split comparator and comparable paths
// cpr 就是匿名内部类
Comparator<? super K> cpr = comparator;
if (cpr != null) {
do {
parent = t;
// 动态绑定
cmp = cpr.compare(key, t.key);
// 放置位置的逻辑
if (cmp < 0)
t = t.left;
else if (cmp > 0)
t = t.right;
else // 相等,不发生任何事情
return t.setValue(value);
} while (t != null);
}
else {
if (key == null)
throw new NullPointerException();
@SuppressWarnings("unchecked")
Comparable<? super K> k = (Comparable<? super K>) key;
do {
parent = t;
cmp = k.compareTo(t.key);
// 放置位置的逻辑
if (cmp < 0)
t = t.left;
else if (cmp > 0)
t = t.right;
else
return t.setValue(value);
} while (t != null);
}
Entry<K,V> e = new Entry<>(key, value, parent);
if (cmp < 0)
parent.left = e;
else
parent.right = e;
fixAfterInsertion(e);
size++;
modCount++;
return null;
}
技术选型
| 允许重复 | 底层结构 | 增删效率 | 改查效率 |
|---|---|---|---|
| ArrayList | Object [] | 高 | 低 |
| LinkedList | 双向链表 | 低 | 高 |
| 不允许重复 | 底层结构 | 读取顺序 |
|---|---|---|
| HashSet | HashMap | 无序 |
| TreeSet | TreeMap | 排序 |
| LinkedHashSet | HashMap + 双向链表 | 插入取出顺序一致 |
| 键值对 | 底层结构 | 键属性 |
|---|---|---|
| HashMap | 数组 + 单向链表 + 红黑树 | 无序 |
| TreeMap | 二叉树 | 排序 compare |
| HashTable | HashMap + 线程同步 | 无序 |
集合工具类
Java.utils 包下提供了工具类,提供了很多工具方法,方便你对集合进行操作,例如:排序、过滤。
通过学习这个包,既可以丰富了自己对于集合快速操作的手段,也可以通过阅读源码,了解如何操作Java中的数据结构。
提供的工具类主要有两个
- Collections 用于处理 Set、List 和 Map
- Arrays 用于处理数组
Collections 常用方法
排序
- reverse(List list)
- 对指定 List 集合元素进行逆向排序
- shuffle(List list)
- 对 List 集合元素进行随机排序(可以用于抽奖,对一个Set集合(非重复集合)执行shuffle方法之后取抽奖位数的元素)
- sort(List list)
- 根据元素的自然顺序对指定 List 集合的元素按升序进行排序(快速排序)
- sort(List list, Comparator c)
- 根据指定 Comparator 产生的顺序对 List 集合元素进行排序
- swap(List list, int i, int j)
- 将指定 List 集合中的 i 处元素和 j 处元素进行交换
- rotate(List list, int distance)
- 当 distance 为 正数 时,将 list 集合的后 distance 个元素 “整体”移到前面 ;当 distance 为 负数 时,将 list 集合的前 distance 个元素 “整体”移到后面 。该方法不会改变集合的长度
查找、替换
- binarySearch(List list, Object key)
- 使用二分搜索法搜索指定的 List 集合,以获得指定对象在 List 集合中的索引。如果要使该方法可以正常工作,则必须保证 List 中的元素已经处于有序状态
- max(Collection coll)
- 根据元素的自然顺序,返回给定集合中的最大元素
- max(Collection coll, Comparator comp)
- 根据 Comparator 指定的顺序,返回给定集合中的最大元素
- min(Collection coll)
- 根据元素的自然顺序,返回给定集合中的最小元素
- min(Collection coll, Comparator comp)
- 根据 Comparator 指定的顺序,返回给定集合中的最小元素
- fill(List list, Object obj)
- 使用指定元素 obj 替换指定 List 集合中的所有元素
- frequency(Collection c, Object o)
- 返回指定集合中指定元素的出现次数
- indexOfSubList(List source, List target)
- 返回子 List 对象在父 List 对象中第一次出现的位置索引;
- 如果父 List 中没有出现这样的子 List,则返回 -1
- lastIndexOfSubList(List source, List target)
- 返回子 List 对象在父 List 对象中最后一次出现的位置索引;
- 如果父 List 中没有岀现这样的子 List,则返回 -1
- replaceAll(List list, Object oldVal, Object newVal)
- 使用一个新值 newVal 替换 List 对象的所有旧值 oldVal
复制
Arrays 常用方法
sort()
-
排序算法内都有传入比较器的重载方法,这里不重复展示
-
sort(Object[] array)
- 对数组按照升序排序
-
sort(Object[] array, int from, int to)
- 对数组元素指定范围进行排序(排序范围是从元素下标为from,到下标为to-1的元素进行排序)
-
parallelSort(Object[] array)
- 数组元素的并行升序排序 (多线程)
-
parallelSort(Object[] array,int from, int to)
- 指定范围的并行排序
binarySearch()
-
搜索方法都可以指定起始位置 int fromIndex
-
binarySearch(Object[] a, Object key)
- 二分搜索法来搜索指定数组
copyOf()
- copyOf(T[] original, int newLength)
- 将数组中指定长度的数组复制为新的数组
- copyOf(U[] original, int newLength, Class<? extends T[]> newType)
- 将数组中指定长度,指定类型的数组复制为新的数组
- copyOfRange(T[] original, int from, int to)
- 对一个已有的数组进行截取复制,复制出一个左闭右开区间的数组
- copyOfRange(U[] original, int from, int to, Class<? extends T[]> newType)
- 对一个已有的数组进行截取复制,复制出一个左闭右开区间内指定数据类型的数组
fill()
- fill(Object[] array,Object object)
- 可以为数组元素填充相同的值
- fill(Object[] array,int from,int to,Object object)
- 对数组的部分元素填充一个值,从起始位置到结束位置,前闭后开区间
toString()
- toString(Object[] array)
- 返回数组的字符串形式
- deepToString(Object[][] arrays)
- 返回多维数组的字符串形式
spliterator()
- spliterator(T[] array)
- 返回一个并行迭代器
- spliterator(T[] array, int startInclusive, int endExclusive)
- 返回一个并行迭代器,可以指定迭代器中的数组范围