参考:https://juejin.cn/post/6956079061726658568
https://juejin.cn/post/6844903664042164232

一、集合框架体系

1. 集合框架体系

集合:对象的容器,定义了对多个对象进行操作的常用方法。

集合和数组的区别:
(1)数组长度固定,集合长度不固定
(2)数组可以存储基本类型和引用类型,集合只能存储引用类型
(3)数组只能存储同一类型的元素,集合可以存储不同类型的元素

集合框架体系:
04-集合 - 图1

2. Iterable接口

  1. public interface Iterable<T> {
  2. // 返回一个迭代器
  3. Iterator<T> iterator();
  4. // 实现的默认的for-each方法
  5. default void forEach(Consumer<? super T> action) {
  6. Objects.requireNonNull(action);
  7. for (T t : this) {
  8. action.accept(t);
  9. }
  10. }
  11. default Spliterator<T> spliterator() {
  12. return Spliterators.spliteratorUnknownSize(iterator(), 0);
  13. }
  14. }

派生自Iterable接口的集合,支持for-each循环。

3. Collection接口

表示:代表一组任意类型的对象。
根据集合的属性又可以划分:是否允许重复元素?元素是否有序?

  1. public interface Collection<E> extends Iterable<E> {
  2. int size();
  3. boolean isEmpty();
  4. boolean contains(Object o); // 是否包含某个元素
  5. boolean containsAll(Collection<?> c); // 是否包含另一个集合的全部元素
  6. Iterator<E> iterator(); // 迭代
  7. Object[] toArray();
  8. boolean add(E e); // 增加单个元素
  9. boolean addAll(Collection<? extends E> c); // 增加在另一个集合中的元素
  10. boolean remove(Object o); // 移除单个元素
  11. boolean removeAll(Collection<?> c); // 移除在另一个集合中的元素
  12. boolean retainAll(Collection<?> c); // 保持在另一个集合中的元素
  13. void clear(); // 清空集合
  14. @Override
  15. default Spliterator<E> spliterator() {
  16. return Spliterators.spliterator(this, 0);
  17. }
  18. default Stream<E> stream() {
  19. return StreamSupport.stream(spliterator(), false);
  20. }
  21. default Stream<E> parallelStream() {
  22. return StreamSupport.stream(spliterator(), true);
  23. }
  24. }

二、List接口

List表示一个有序集合,在Collection接口的基础上增加了在指定位置插入元素,在集合中检索元素位置等方法。

  1. public interface List<E> extends Collection<E> {
  2. boolean addAll(int index, Collection<? extends E> c); // 在指定位置插入另一个集合的所有元素
  3. E get(int index); // 获取指定位置的元素
  4. E set(int index, E element); // 设置指定位置的元素值
  5. void add(int index, E element); // 在指定位置插入元素
  6. E remove(int index); // 删除指定位置的元素
  7. int indexOf(Object o); // 查询元素在集合中的位置
  8. int lastIndexOf(Object o); // 查询元素在集合中的最后一个位置
  9. List<E> subList(int fromIndex, int toIndex); // 根据下标获得一个子列表
  10. default void sort(Comparator<? super E> c) // 对列表进行排序
  11. }

1. ArrayList

  • 只基于数组实现,线程不安全
  • 底层维护了一个object类型的数组elementData来存储元素;
  • 如果使用无参构造器,初始大小为0,首次添加元素时扩容为10,后续需要扩容时按照1.5倍扩容。
  • 如果初始化时声明了大小,后续扩容时按照1.5倍扩容。
  • 被transient修饰的属性不参与对象的序列化。
  • capacity表示数组容量,size表示数组中的元素个数。

2. LinkedList

  • 实现了List和Deque接口,底层是双向链表,线程不安全。

3. Vector

  • 可以扩缩容的存储对象的数组,线程安全。

比较

ArrayList LinkedList Vector
是否线程安全
底层数据结构 Object数组 双向循环链表 Object数组
插入、删除操作时间复杂度 插入、删除最后一个元素为O(1),插入、删除元素到指定位置为O(n) 插入、删除元素为O(1) 插入、删除最后一个元素为O(1),插入、删除元素到指定位置为O(n)
是否支持随机访问 实现了RandmoAccess接口,可以根据下标获取元素,支持随机访问 不支持 实现了RandmoAccess接口,可以根据下标获取元素,支持随机访问

三、Set接口

Set表示不包含重复元素的集合,最多只有一个null元素。Set接口和Collection接口相比,没有提供更多的方法。

  1. public interface Set<E> extends Collection<E> {
  2. int size();
  3. boolean isEmpty();
  4. boolean contains(Object o);
  5. Iterator<E> iterator();
  6. Object[] toArray();
  7. boolean add(E e);
  8. boolean remove(Object o);
  9. boolean containsAll(Collection<?> c);
  10. boolean addAll(Collection<? extends E> c);
  11. boolean retainAll(Collection<?> c);
  12. boolean removeAll(Collection<?> c);
  13. void clear();
  14. @Override
  15. default Spliterator<E> spliterator() {
  16. return Spliterators.spliterator(this, Spliterator.DISTINCT);
  17. }
  18. }

HashSet

  • 底层实现基于HashMap,使用HashMap的key存储元素,基于默认的装载因子0.75,也可以自定义装载因子。

LinkedHashSet

  • LinkedHashSet也实现了Set接口,而且是HashSet的子类。
  • 不同之处在于:LinkedHashSet的底层基于LinkedHashMap,而HashSet使用的是HashMap。

    TreeSet

    TreeSet实现了SortedSet接口 ```java

public interface SortedSet extends Set { Comparator<? super E> comparator(); SortedSet subSet(E fromElement, E toElement); SortedSet headSet(E toElement); SortedSet tailSet(E fromElement); E first(); E last(); @Override default Spliterator spliterator() { return new Spliterators.IteratorSpliterator(this, Spliterator.DISTINCT | Spliterator.SORTED | Spliterator.ORDERED) { @Override public Comparator<? super E> getComparator() { return SortedSet.this.comparator(); } }; } }

  1. | | HashSet | LinkedHashSet | TreeSete |
  2. | --- | --- | --- | --- |
  3. | 特点 | 无序、唯一 | 无序、唯一 | 有序、唯一 |
  4. | 是否线程安全 | | | |
  5. | 底层结构 | HashMap | LinkedHashMap | 红黑树 |
  6. ---
  7. <a name="XgiTr"></a>
  8. # 四、Map接口
  9. 一个Map表示将key映射到value的接口,其中key不能重复,每个key可以映射到最多一个value。<br />Map接口提供了三种集合视角,分别是:只包含keySet,只包含value的集合,和包含key-value映射的Set
  10. ```java
  11. public interface Map<K, V> {
  12. int size();
  13. boolean isEmpty();
  14. boolean containsKey(Object key);
  15. boolean containsValue(Object value);
  16. V get(Object key);
  17. V put(K key, V value);
  18. V remove(Object key);
  19. void clear();
  20. void putAll(Map<? extends K, ? extends V> m);
  21. Set<K> keySet();
  22. Collection<V> values();
  23. Set<Map.Entry<K, V>> entrySet();
  24. default V getOrDefault(Object key, V defaultValue);
  25. default V putIfAbsent(K key, V value);
  26. default boolean replace(K key, V oldValue, V newValue);
  27. default boolean remove(Object key, Object value);
  28. default void forEach(BiConsumer<? super K, ? super V> action);
  29. }

1. HashMap

1.1 层次关系

  1. public class HashMap<K,V> extends AbstractMap<K,V> implements Map<K,V>, Cloneable, Serializable {}

HashMap类继承了抽象类AbstractMap,实现了Map接口。

1.2 基本思想

JDK1.8之后,HashMap的底层实现基于数组+链表+红黑树。
image.png

1.3 类静态属性

  1. // 默认的初始哈希表容量
  2. static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16
  3. // 哈希表容量的允许的最大值
  4. static final int MAXIMUM_CAPACITY = 1 << 30;
  5. // 默认的装载因子
  6. static final float DEFAULT_LOAD_FACTOR = 0.75f;
  7. // 从链表进化为红黑树的所需的最少节点个数
  8. static final int TREEIFY_THRESHOLD = 8;
  9. // 从红黑树退化为链表的节点个数
  10. static final int UNTREEIFY_THRESHOLD = 6;
  11. // 从链表转进化为红黑树所需的最小的哈希表容量
  12. static final int MIN_TREEIFY_CAPACITY = 64;

1.4 基本结构

链表节点Node

  1. static class Node<K,V> implements Map.Entry<K,V> {
  2. final int hash; // key的hash值,
  3. final K key; // key
  4. V value; // value
  5. Node<K,V> next; // next Node
  6. Node(int hash, K key, V value, Node<K,V> next) {
  7. this.hash = hash;
  8. this.key = key;
  9. this.value = value;
  10. this.next = next;
  11. }
  12. public final K getKey() { return key; }
  13. public final V getValue() { return value; }
  14. public final String toString() { return key + "=" + value; }
  15. public final int hashCode() {
  16. return Objects.hashCode(key) ^ Objects.hashCode(value);
  17. }
  18. public final V setValue(V newValue) {
  19. V oldValue = value;
  20. value = newValue;
  21. return oldValue;
  22. }
  23. public final boolean equals(Object o) {
  24. if (o == this)
  25. return true;
  26. if (o instanceof Map.Entry) {
  27. Map.Entry<?,?> e = (Map.Entry<?,?>)o;
  28. if (Objects.equals(key, e.getKey()) &&
  29. Objects.equals(value, e.getValue()))
  30. return true;
  31. }
  32. return false;
  33. }
  34. }

hash函数

  1. static final int hash(Object key) {
  2. int h;
  3. // 如果key为null,hash值为0,否则:key的hashCode结果的高16位与低16位做异或运算。
  4. return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
  5. }

1.5 对象属性

  1. transient Node<K,V>[] table;
  2. transient Set<Map.Entry<K,V>> entrySet;
  3. transient int size;
  4. transient int modCount;
  5. int threshold;
  6. final float loadFactor;

1.6 核心方法分析

1.6.1 初始化

指定初始容量和加载因子

  1. // 第一种初始化:指定数组初始容量和加载因子
  2. public HashMap(int initialCapacity, float loadFactor) {
  3. if (initialCapacity < 0)
  4. throw new IllegalArgumentException("Illegal initial capacity: " + initialCapacity);
  5. if (initialCapacity > MAXIMUM_CAPACITY)
  6. initialCapacity = MAXIMUM_CAPACITY;
  7. if (loadFactor <= 0 || Float.isNaN(loadFactor))
  8. throw new IllegalArgumentException("Illegal load factor: " + loadFactor);
  9. this.loadFactor = loadFactor;
  10. this.threshold = tableSizeFor(initialCapacity);// ?
  11. }
  12. // tableSizeFor方法:对于给定的capacity,返回不小于它的2的幂次的新的capacity
  13. // 比如:给定5-8会返回8,给定9-16会返回16,给定17-32会返回32。
  14. static final int tableSizeFor(int cap) {
  15. int n = cap - 1;
  16. n |= n >>> 1;
  17. n |= n >>> 2;
  18. n |= n >>> 4;
  19. n |= n >>> 8;
  20. n |= n >>> 16;
  21. return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1;
  22. }
  23. // 方法二:指定数组初始容量,使用默认装载因子,调用方法一构造一个空的HashMap
  24. public HashMap(int initialCapacity) {
  25. this(initialCapacity, DEFAULT_LOAD_FACTOR);
  26. }
  27. // 方法三:无参构造,使用默认的初始容量16和加载因子0.75构造一个空的HashMap
  28. public HashMap() {
  29. this.loadFactor = DEFAULT_LOAD_FACTOR; // all other fields defaulted
  30. }

使用已有的map进行构造

  1. // 方法四:基于Map构造,先使用方法三构造,在将Map保存到HashMap中
  2. public HashMap(Map<? extends K, ? extends V> m) {
  3. this.loadFactor = DEFAULT_LOAD_FACTOR;
  4. putMapEntries(m, false);
  5. }
  6. // putMapEntries方法:批量插入元素
  7. final void putMapEntries(Map<? extends K, ? extends V> m, boolean evict) {
  8. int s = m.size();
  9. if (s > 0) {
  10. if (table == null) { // pre-size
  11. float ft = ((float)s / loadFactor) + 1.0F;
  12. int t = ((ft < (float)MAXIMUM_CAPACITY) ?
  13. (int)ft : MAXIMUM_CAPACITY);
  14. if (t > threshold)
  15. threshold = tableSizeFor(t);
  16. }
  17. else if (s > threshold) // 如果m元素数量大于阈值,需要扩容
  18. resize();
  19. // 遍历m,插入HashMap
  20. for (Map.Entry<? extends K, ? extends V> e : m.entrySet()) {
  21. K key = e.getKey();
  22. V value = e.getValue();
  23. putVal(hash(key), key, value, false, evict);
  24. }
  25. }
  26. }

1.6.2 元素插入put

  1. public V put(K key, V value) {
  2. return putVal(hash(key), key, value, false, true);
  3. }
  4. final V putVal(int hash, K key, V value, boolean onlyIfAbsent, boolean evict) {
  5. Node<K,V>[] tab; // 复制当前的哈希表
  6. Node<K,V> p; // key对应的数组下标所在的首个节点元素
  7. int n; // 哈希表容量,数组长度
  8. int i; // key对应的数组下标
  9. // 步骤一:预计算:如果原来的哈希表为空,新哈希表的长度为扩容后的长度
  10. if ((tab = table) == null || (n = tab.length) == 0)
  11. n = (tab = resize()).length;
  12. // 分支1:如果key的哈希值对应的数组下标所在的元素为空,直接构建新的节点插入即可
  13. if ((p = tab[i = (n - 1) & hash]) == null)
  14. tab[i] = newNode(hash, key, value, null);
  15. else { // 分支2:如果key对应的数组下标存储不为空
  16. Node<K,V> e; // 根据传入的key找到的节点
  17. K k; // 首个节点元素的key
  18. // 分支2.1:如果传入的key与首个节点元素的key相等,则首个节点元素即为找到的节点
  19. if (p.hash == hash && ((k = p.key) == key || (key != null && key.equals(k))))
  20. e = p;
  21. else if (p instanceof TreeNode) // 分支2.2:如果是红黑树,使用相应的方法找到节点
  22. e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
  23. else { // 分支2.3:头结点不是要找的节点&链表结构:遍历链表
  24. for (int binCount = 0; ; ++binCount) {
  25. // 分支2.3.1:如果遍历完链表都没有找到,在链表末尾插入新的节点
  26. if ((e = p.next) == null) {
  27. p.next = newNode(hash, key, value, null);
  28. // 如果插入新的结点后,当前链表长度大于树化的阈值,对链表进行树化转变为红黑树结构
  29. if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
  30. treeifyBin(tab, hash);
  31. break;
  32. }
  33. // 分支2.3.2:如果遍历链表过程中找到了对应结点,中断遍历,此时结点e为找到的节点
  34. if (e.hash == hash && ((k = e.key) == key || (key != null && key.equals(k))))
  35. break;
  36. p = e; // 通过移动p指针来遍历链表
  37. }
  38. }
  39. // 找到结点之后的赋值操作
  40. // 分支2.3.1插入了新的结点同时进行了赋值,其余分支都是修改了原来的结点值
  41. if (e != null) { // existing mapping for key
  42. V oldValue = e.value;
  43. if (!onlyIfAbsent || oldValue == null)
  44. e.value = value;
  45. afterNodeAccess(e);
  46. return oldValue; // 对于修改了原有结点值的情况,返回原来的值,且修改次数modCount和哈希表元素数量size都不变
  47. }
  48. }
  49. // 只有对于分支2.3.1的情况,有新的结点插入,修改次数加一,元素个数加一,可能需要扩容,且原来的值为null。
  50. ++modCount;
  51. if (++size > threshold)
  52. resize();
  53. afterNodeInsertion(evict);
  54. return null;
  55. }

1.6.3 哈希表扩容resize

  1. final Node<K, V>[] resize () {
  2. Node<K, V>[] oldTab = table; // 保存原来的哈希表
  3. int oldCap = (oldTab == null) ? 0 : oldTab.length; // 原来的哈希表容量
  4. int oldThr = threshold; // 原来的哈希表扩容阈值
  5. int newCap, newThr = 0; // 初始化新哈希表的容量和扩容阈值为0
  6. // 步骤一:计算新的哈希表的容量和扩容阈值
  7. // 1.1 如果原来的哈希表不为空
  8. if (oldCap > 0) {
  9. // 1.1.1 如果原来的哈希表的容量已经不小于允许的最大容量,则无法进行扩容
  10. if (oldCap >= MAXIMUM_CAPACITY) {
  11. threshold = Integer.MAX_VALUE;
  12. return oldTab;
  13. } // 1.1.2 新哈希表容量翻倍,扩容阈值翻倍
  14. else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY && oldCap >= DEFAULT_INITIAL_CAPACITY)
  15. newThr = oldThr << 1; // double threshold
  16. } // 1.2 如果旧哈希表为空但是其扩容阈值大于0(?),则新哈希表的容量等于就哈希表的扩容阈值。
  17. else if (oldThr > 0) // initial capacity was placed in threshold
  18. newCap = oldThr;
  19. else { // 1.3 旧哈希表为空&&扩容阈值为0(旧哈希表使用无参构造且没有执行其他任何操作),使用默认值
  20. newCap = DEFAULT_INITIAL_CAPACITY;
  21. newThr = (int) (DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);
  22. }
  23. // 步骤二:扩容
  24. // 如果新的扩容阈值为0(?),重新根据容量和装载因子计算
  25. if (newThr == 0) {
  26. float ft = (float) newCap * loadFactor;
  27. newThr = (newCap < MAXIMUM_CAPACITY && ft < (float) MAXIMUM_CAPACITY ? (int) ft : Integer.MAX_VALUE);
  28. }
  29. threshold = newThr;
  30. @SuppressWarnings({"rawtypes", "unchecked"})
  31. Node<K, V>[] newTab = (Node<K, V>[]) new Node[newCap]; // 初始化空的新的哈希表
  32. table = newTab;
  33. // 如果旧的哈希表不为空,需要将其搬运过来
  34. if (oldTab != null) {
  35. for (int j = 0; j < oldCap; ++j) { // 遍历旧哈希表中的每个桶
  36. Node<K, V> e; //保存结点的临时变量
  37. if ((e = oldTab[j]) != null) { // 如果桶的第一个结点不为空
  38. oldTab[j] = null; // 删除旧哈希表的结点便于JVM的GC
  39. if (e.next == null) // 情况一:如果当前的桶只有一个节点
  40. newTab[e.hash & (newCap - 1)] = e;
  41. else if (e instanceof TreeNode) // 情况二:如果当前的桶是树节点(红黑树)
  42. ((TreeNode<K, V>) e).split(this, newTab, j, oldCap);
  43. else { // 情况三:如果当前的桶是链表,将链表拆分为两个新链表,插入到新哈希表的对应桶中
  44. Node<K, V> loHead = null, loTail = null; // 旧哈希表当前bin的链表拆分后的头结点和尾结点(低位)
  45. Node<K, V> hiHead = null, hiTail = null; // 旧哈希表当前bin的链表拆分后的头结点和尾结点(高位)
  46. Node<K, V> next;
  47. // 步骤1:对于链表中的每个结点,判断在新哈希表中的位置,据此构建两个链表
  48. do {
  49. next = e.next;
  50. if ((e.hash & oldCap) == 0) { // 与旧哈希表中的位置相等
  51. if (loTail == null) // 用head指向第一个结点
  52. loHead = e;
  53. else
  54. loTail.next = e;
  55. loTail = e; // tail始终指向最后一个结点
  56. } else { // ((e.hash & oldCap) == 1),新的位置=旧的位置+旧哈希表的容量
  57. if (hiTail == null)
  58. hiHead = e;
  59. else
  60. hiTail.next = e;
  61. hiTail = e;
  62. }
  63. } while ((e = next) != null);
  64. // 步骤2:将两个链表分别插入到新哈希表中去
  65. if (loTail != null) {
  66. loTail.next = null; // 对于新哈希表中的链表,需要使其尾结点的next指向null
  67. newTab[j] = loHead;
  68. }
  69. if (hiTail != null) {
  70. hiTail.next = null;
  71. newTab[j + oldCap] = hiHead;
  72. }
  73. }
  74. }
  75. }
  76. }
  77. return newTab;
  78. }

1.6.4 元素查询get

  1. // 给定key,获取在哈希表中的值
  2. public V get (Object key){
  3. Node<K, V> e;
  4. return (e = getNode(hash(key), key)) == null ? null : e.value;
  5. }
  6. // hash函数:给定key,使用key的hashCode方法结果的高16位和低16位做异或运算得到
  7. static final int hash (Object key){
  8. int h;
  9. return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
  10. }
  11. final Node<K, V> getNode (int hash, Object key){
  12. Node<K, V>[] tab;
  13. Node<K, V> first, e;
  14. int n;
  15. K k;
  16. // 哈希表存在且不为空,key所在的bin不为空
  17. if ((tab = table) != null && (n = tab.length) > 0 && (first = tab[(n - 1) & hash]) != null) {
  18. // 如果第一个结点即为要找的结点,直接返回
  19. if (first.hash == hash && ((k = first.key) == key || (key != null && key.equals(k))))
  20. return first;
  21. if ((e = first.next) != null) {
  22. if (first instanceof TreeNode) // 如果bin为红黑树结构,调用相应的方法
  23. return ((TreeNode<K, V>) first).getTreeNode(hash, key);
  24. // 如果为链表结构:遍历所有结点,一一比较
  25. do {
  26. if (e.hash == hash && ((k = e.key) == key || (key != null && key.equals(k))))
  27. return e;
  28. } while ((e = e.next) != null);
  29. }
  30. }
  31. return null;
  32. }

1.6.5 元素删除remove

  1. // 给定key,删除哈希表中的对应结点,返回删除的结点值
  2. public V remove (Object key){
  3. Node<K, V> e;
  4. return (e = removeNode(hash(key), key, null, false, true)) == null ? null : e.value;
  5. }
  6. final Node<K, V> removeNode (int hash, Object key, Object value,boolean matchValue, boolean movable) {
  7. Node<K, V>[] tab;
  8. Node<K, V> p;
  9. int n, index;
  10. // 哈希表存在且不为空,key所在的bin不为空
  11. if ((tab = table) != null && (n = tab.length) > 0 && (p = tab[index = (n - 1) & hash]) != null) {
  12. Node<K, V> node = null, e;
  13. K k;
  14. V v;
  15. // 步骤一:找到待删除的结点
  16. // 情况一:如果第一个结点即为要找的结点
  17. if (p.hash == hash && ((k = p.key) == key || (key != null && key.equals(k))))
  18. node = p;
  19. else if ((e = p.next) != null) {
  20. if (p instanceof TreeNode) // 情况二:如果为红黑树,调用相应的方法找到待删除的结点
  21. node = ((TreeNode<K, V>) p).getTreeNode(hash, key);
  22. else { // 情况三:如果为链表,遍历找到待删除的结点
  23. do {
  24. if (e.hash == hash &&
  25. ((k = e.key) == key ||
  26. (key != null && key.equals(k)))) {
  27. node = e;
  28. break;
  29. }
  30. p = e;
  31. } while ((e = e.next) != null);
  32. }
  33. }
  34. // 步骤二:删除对应结点
  35. if (node != null && (!matchValue || (v = node.value) == value || (value != null && value.equals(v)))) {
  36. if (node instanceof TreeNode) // 待删节点为树节点
  37. ((TreeNode<K, V>) node).removeTreeNode(this, tab, movable);
  38. else if (node == p) // 待删节点为头结点
  39. tab[index] = node.next;
  40. else // 待删节点为链表的非头结点
  41. p.next = node.next;
  42. ++modCount;
  43. --size;
  44. afterNodeRemoval(node);
  45. return node;
  46. }
  47. }
  48. return null;
  49. }

1.6.6 元素更新replace

  1. // 将哈希表中的键等于key、值等于oldValue的结点的值更新为newValue
  2. // 返回是否更新成功(即输入的key:oldValue是否存在)
  3. public boolean replace (K key, V oldValue, V newValue){
  4. Node<K, V> e;
  5. V v;
  6. if ((e = getNode(hash(key), key)) != null && ((v = e.value) == oldValue || (v != null && v.equals(oldValue)))) {
  7. e.value = newValue;
  8. afterNodeAccess(e);
  9. return true;
  10. }
  11. return false;
  12. }
  13. // 将哈希表中的键等于key的结点(不论原来的value是什么)的值更新为value
  14. // 返回key对应的原来的value
  15. public V replace (K key, V value){
  16. Node<K, V> e;
  17. if ((e = getNode(hash(key), key)) != null) {
  18. V oldValue = e.value;
  19. e.value = value;
  20. afterNodeAccess(e);
  21. return oldValue;
  22. }
  23. return null;
  24. }

1.7 总结

2个重要的参数,capacity和装载因子。
capacity是哈希表中桶的数量,装载因子衡量了哈希表中元素个数与容量的关系,当元素个数/容量大于装载因子的时候,哈希表就要扩容。
扩容意味着增加桶的数量,对原有的元素重新作哈希得到存储位置。

2. LinkedHashMap

3. HashTable

HashTable虽然也实现了Map接口,但是它继承了DIctionary抽象类,而HashMap继承了AbstractMap抽象类。
HastTable是线程安全的。

  1. public class Hashtable<K,V> extends Dictionary<K,V> implements Map<K,V>, Cloneable, java.io.Serializable {}


Dictionary接口

  1. public abstract class Dictionary<K,V> {
  2. public Dictionary() {}
  3. public abstract int size();
  4. public abstract boolean isEmpty();
  5. public abstract Enumeration<K> keys();
  6. public abstract Enumeration<V> elements();
  7. public abstract V get(Object key);
  8. public abstract V put(K key, V value);
  9. public abstract V remove(Object key);
  10. }

4. ConcurrentHashMap

和HashTable一样,都是线程安全的,区别在于实现线程安全的方式不同。

底层数据结构不同:
JDK1.7的 ConcurrentHashMap 底层采用 分段的数组+链表 实现,JDK1.8 采用的数据结构跟HashMap1.8的结构一样,数组+链表/红黑二叉树。
Hashtable 和 JDK1.8 之前的 HashMap 的底层数据结构类似都是采用 数组+链表 的形式,数组是 HashMap 的主体,链表则是主要为了解决哈希冲突而存在的。

实现线程安全的方式不同:
在JDK1.7的时候,ConcurrentHashMap(分段锁) 对整个桶数组进行了分割分段(Segment),每一把锁只锁容器其中一部分数据,多线程访问容器里不同数据段的数据,就不会存在锁竞争,提高并发访问率。(默认分配16个Segment,比Hashtable效率提高16倍。) 到了 JDK1.8 的时候已经摒弃了Segment的概念,而是直接用 Node 数组+链表+红黑树的数据结构来实现,并发控制使用 synchronized 和 CAS 来操作。(JDK1.6以后 对 synchronized锁做了很多优化) 整个看起来就像是优化过且线程安全的 HashMap,虽然在JDK1.8中还能看到 Segment 的数据结构,但是已经简化了属性,只是为了兼容旧版本;
Hashtable(同一把锁) :使用 synchronized 来保证线程安全,效率非常低下。当一个线程访问同步方法时,其他线程也访问同步方法,可能会进入阻塞或轮询状态,如使用 put 添加元素,另一个线程不能使用 put 添加元素,也不能使用 get,竞争会越来越激烈效率越低。

image.png
JDK1.7的ConcurrentHashMap
image.png
JDK1.8的ConcurrentHashMap
image.png

JDK1.7(上面有示意图)

首先将数据分为一段一段的存储,然后给每一段数据配一把锁,当一个线程占用锁访问其中一个段数据时,其他段的数据也能被其他线程访问。
ConcurrentHashMap 是由 Segment 数组结构和 HahEntry 数组结构组成
Segment 实现了 ReentrantLock,所以 Segment 是一种可重入锁,扮演锁的角色。HashEntry 用于存储键值对数据。
static class Segment extends ReentrantLock implements Serializable { } 复制代码
一个 ConcurrentHashMap 里包含一个 Segment 数组。Segment 的结构和HashMap类似,是一种数组和链表结构,一个 Segment 包含一个 HashEntry 数组,每个 HashEntry 是一个链表结构的元素,每个 Segment 守护着一个HashEntry数组里的元素,当对 HashEntry 数组的数据进行修改时,必须首先获得对应的 Segment的锁。

JDK1.8 (上面有示意图)

ConcurrentHashMap取消了Segment分段锁,采用CAS和synchronized来保证并发安全。数据结构跟HashMap1.8的结构类似,数组+链表/红黑二叉树。
synchronized只锁定当前链表或红黑二叉树的首节点,这样只要hash不冲突,就不会产生并发,效率又提升N倍。

5. 总结

HashMap LinkedHashMap HashTable ConcurrentHashMap
是否线程安全 非线程安全 非线程安全 线程安全 线程安全
对null的支持 允许一个null作为key,多个null作为value 不允许null作为key或value
初始化 (1)创建时如果不指定容量,默认初始值为16,之后每次扩充为原来的2倍(2)创建时指定了容量初始值,会扩充为2的幂次方大小 (1)创建时如果不指定容量,默认初始值为11,之后每次扩充为2n+1
(2)创建时指定了初始值,则使用指定的值进行初始化
底层实现 JDK1.8之前使用数组+链表,JDK1.8之后使用数组+链表+红黑树,当链表长度大于阈值时,将链表转化为红黑树。 数组+链表+红黑树
数组-》双向链表
数组+链表