Iterator Iterable
(Iterator接口 定义迭代器如何实现,Iterable接口 定义得到迭代器)
fail-fast(快速失败迭代器) 与 ConcurrentModificationException
next、remove操作时会比较expectedModCount和modCount是否相等。
//实现iterator后的 定义的 final void checkForComodification() { if (modCount != expectedModCount) throw new ConcurrentModificationException(); } //自身定义的 private void checkForComodification() { if (ArrayList.this.modCount != this.modCount) throw new ConcurrentModificationException(); }
如ArrayList在每次add、remove、clear时 modCount都会相应的增加或减少。
fail-fast 机制是java集合(Collection)中的一种错误机制。当多个线程对同一个集合的内容进行操作时,就可能会产生fail-fast事件。
fail-fast机制,是一种错误检测机制。它只能被用来检测错误,因为JDK并不保证fail-fast机制一定会发生。若在多线程环境下使用fail-fast机制的集合,建议使用“java.util.concurrent包下的类”去取代“java.util包下的类”。
// AbstractList中唯一的属性 // 用来记录List修改的次数:每修改一次(添加/删除等操作),将modCount+1 protected transient int modCount = 0; // 返回List对应迭代器。实际上,是返回Itr对象。 public Iterator<E> iterator() { return new Itr(); } // Itr是Iterator(迭代器)的实现类 private class Itr implements Iterator<E> { int cursor = 0; int lastRet = -1; // 修改数的记录值。 // 每次新建Itr()对象时,都会保存新建该对象时对应的modCount; // 以后每次遍历List中的元素的时候,都会比较expectedModCount和modCount是否相等; // 若不相等,则抛出ConcurrentModificationException异常,产生fail-fast事件。 int expectedModCount = modCount; public boolean hasNext() { return cursor != size(); } public E next() { // 获取下一个元素之前,都会判断“新建Itr对象时保存的modCount”和“当前的modCount”是否相等; // 若不相等,则抛出ConcurrentModificationException异常,产生fail-fast事件。 checkForComodification(); try { E next = get(cursor); lastRet = cursor++; return next; } catch (IndexOutOfBoundsException e) { checkForComodification(); throw new NoSuchElementException(); } }
//每次容量变化时 修改modCount的值 public boolean add(E var1) { this.ensureCapacityInternal(this.size + 1); this.elementData[this.size++] = var1; return true; } private void ensureCapacityInternal(int var1) { this.ensureExplicitCapacity(calculateCapacity(this.elementData, var1)); } private void ensureExplicitCapacity(int var1) { //修改次数加一 ++this.modCount; if (var1 - this.elementData.length > 0) { this.grow(var1); } } 。。。。。。。。
当多线程操作集合时,一个线程访问,另外线程修改了容器,modCount被改变了,就可能产生就会抛出ConcurrentModificationException异常,产生fail-fast事件。
//CopyOnWriteArrayList //每次修改或访问时,加锁,getArray,在setArray。 final Object[] getArray() { return array; } final void setArray(Object[] a) { array = a; } // public Iterator<E> iterator() { return new COWIterator<E>(getArray(), 0); } static final class COWIterator<E> implements ListIterator<E> { /** Snapshot of the array */ //会将每次修改后的容器赋给snapshot,当原始集合数据改变时,snapshot不会改变。 private final Object[] snapshot; /** Index of element to be returned by subsequent call to next. */ private int cursor; private COWIterator(Object[] elements, int initialCursor) { cursor = initialCursor; snapshot = elements; } public boolean hasNext() { return cursor < snapshot.length; } public boolean hasPrevious() { return cursor > 0; } @SuppressWarnings("unchecked") public E next() { if (! hasNext()) throw new NoSuchElementException(); return (E) snapshot[cursor++]; } @SuppressWarnings("unchecked") public E previous() { if (! hasPrevious()) throw new NoSuchElementException(); return (E) snapshot[--cursor]; }
Spliterator
Spliterator是Java 8引入的新接口,顾名思义,Spliterator可以理解为Iterator的Split版本(但用途要丰富很多)。使用Iterator的时候,我们可以顺序地遍历容器中的元素,使用Spliterator的时候,我们可以将元素分割成多份,分别交于不于的线程去遍历,以提高效率。使用 Spliterator 每次可以处理某个元素集合中的一个元素 — 不是从 Spliterator 中获取元素,而是使用 tryAdvance() 或 forEachRemaining() 方法对元素应用操作。但 Spliterator 还可以用于估计其中保存的元素数量,而且还可以像细胞分裂一样变为一分为二。这些新增加的能力让流并行处理代码可以很方便地将工作分布到多个可用线程上完成。
ListIterator
提供了更加丰富的方法。向前向后遍历。
public interface ListIterator<E> extends Iterator<E> { boolean hasNext(); E next(); //前面是否有元素 boolean hasPrevious(); E previous(); int nextIndex(); int previousIndex(); void remove(); void set(E e); void add(E e);
private class Itr implements Iterator<E> { //游标 int cursor; // index of next element to return int lastRet = -1; // index of last element returned; -1 if no such
Collection
集合接口:集合的抽象数据类型,Java 集合框架提供了方便使用的数据结构和算法,可直接使用。
- foreach遍历:
- Iterator遍历:
- stream遍历:https://docs.oracle.com/javase/tutorial/collections/streams/
List接口
有序、可重复,List 的数据结构就是一个序列,存储内容时直接在内存中开辟一块连续的空间,将空间地址与索引对应。
ArrayList---数组实现
LinkedList---双向链表实现
- subList操作时会影响原list,因为把原集合引用给了sublist。
public List<E> subList(int fromIndex, int toIndex) { return (this instanceof RandomAccess ? new RandomAccessSubList<>(this, fromIndex, toIndex) : //操作的是原集合 new SubList<>(this, fromIndex, toIndex)); }
ArrayList扩容机制:
public boolean add(E e) { //当前集合的元素个数size加一 ensureCapacityInternal(size + 1); // Increments modCount!! elementData[size++] = e; return true; } private void ensureCapacityInternal(int minCapacity) { ensureExplicitCapacity(calculateCapacity(elementData, minCapacity)); } private static int calculateCapacity(Object[] elementData, int minCapacity) { //第一次add时,返回数组默认capacity 10 if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) { return Math.max(DEFAULT_CAPACITY, minCapacity); } return minCapacity; } private void ensureExplicitCapacity(int minCapacity) { modCount++; // overflow-conscious code //防止溢出 //第一次add时,初始capacity为10,超过10后 开始扩容 //扩容后,如后续增长到length后,又开始扩容 if (minCapacity - elementData.length > 0) grow(minCapacity); } private void grow(int minCapacity) { // overflow-conscious code int oldCapacity = elementData.length; //在元素个数小于10时,capacity默认10.不会改变,当超过10后, //capacity 在原来的基础上加上 (oldCapacity >> 1 (oldCapacity除以2取整) int newCapacity = oldCapacity + (oldCapacity >> 1); //如果比现在增加的小minCapacity,则把minCapacity赋给newCapacity。 if (newCapacity - minCapacity < 0) newCapacity = minCapacity; //是否已经超过JVM最大允许的capaticy if (newCapacity - MAX_ARRAY_SIZE > 0) newCapacity = hugeCapacity(minCapacity); // minCapacity is usually close to size, so this is a win: //将新的容量拷贝给现在的数组。 elementData = Arrays.copyOf(elementData, newCapacity); } private static int hugeCapacity(int minCapacity) { if (minCapacity < 0) // overflow throw new OutOfMemoryError(); //超过取Integer.MAX_VALUE,反之就是最大的 MAX_ARRAY_SIZE。 return (minCapacity > MAX_ARRAY_SIZE) ? Integer.MAX_VALUE : MAX_ARRAY_SIZE; }
linkedList是双向列表,不需要扩容机制
public boolean add(E e) { linkLast(e); return true; } public void linkLast(E e) { final Node<E> l = last; final Node<E> newNode = new Node<>(l, e, null); last = newNode; if (l == null) first = newNode; else l.next = newNode; size++; modCount++; }
AbstractCollection
对Collection接口的部分实现。
必须实现的两个抽象方法
public abstract Iterator<E> iterator(); public abstract int size();
AbstractList
实现了Iterator接口,以及两个内部子类
private class Itr implements Iterator<E> private class ListItr extends Itr implements ListIterator<E> class RandomAccessSubList<E> extends SubList<E> implements RandomAccess //支持随机访问 class SubList<E> extends AbstractList<E> {
ArrayList
- 容量不固定,想放多少放多少(当然有最大阈值,但一般达不到)
- 有序的(元素输出顺序与输入顺序一致)
- 元素可以为 null
查询效率高:size(), isEmpty(), get(), set() iterator(), ListIterator() 方法的时间复杂度都是 O(1)
新增效率低:add() 添加操作的时间复杂度平均为 O(n)
占用空间更小:对比 LinkedList,不用占用额外空间维护链表结构
Iterator实现
private class Itr implements Iterator<E> { int cursor; // index of next element to return int lastRet = -1; // index of last element returned; -1 if no such int expectedModCount = modCount; Itr() {} public boolean hasNext() { return cursor != size; } @SuppressWarnings("unchecked") public E next() { checkForComodification(); int i = cursor; if (i >= size) throw new NoSuchElementException(); Object[] elementData = ArrayList.this.elementData; if (i >= elementData.length) throw new ConcurrentModificationException(); cursor = i + 1; return (E) elementData[lastRet = i]; } private class ListItr extends Itr implements ListIterator<E> { ListItr(int index) { super(); cursor = index; } public boolean hasPrevious() { return cursor != 0; } public int nextIndex() { return cursor; } public int previousIndex() { return cursor - 1; }
} class ArrayList<E> extends AbstractList<E> implements List<E>, RandomAccess, Cloneable, java.io.Serializable { private static final long serialVersionUID = 8683452581122892189L; //默认 capacity 10 超过后 + capacity>>1进行扩容 private static final int DEFAULT_CAPACITY = 10; private static final Object[] EMPTY_ELEMENTDATA = {}; //初始化时的数组 private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {}; //底层以数组存放元素 transient Object[] elementData; // non-private to simplify nested class access //数组最大长度 超过抛出 OutOfMemoryError private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
AbstractSequentialList
bstractSequentialList 只支持按次序访问,不像 AbstractList 那样支持随机访问。没有实现Iterator.
public abstract ListIterator<E> listIterator(int index);
LinkedList
双向链表实现,插入、删除效率高,只需要改变前后节点指针指向。查询时需要遍历节点。
class LinkedList<E> extends AbstractSequentialList<E> implements List<E>, Deque<E>, Cloneable, java.io.Serializable { transient int size = 0; //头节点 transient Node<E> first; //尾节点 transient Node<E> last; //节点 private static class Node<E> { E item; Node<E> next;//后继节点 Node<E> prev;//前驱节点 Node(Node<E> prev, E element, Node<E> next) { this.item = element; this.next = next; this.prev = prev; } } //查找方法 Node<E> node(int index) { // assert isElementIndex(index); if (index < (size >> 1)) { Node<E> x = first; for (int i = 0; i < index; i++) x = x.next; return x; } else { Node<E> x = last; for (int i = size - 1; i > index; i--) x = x.prev; return x; } }
实现了两个Iterator
//倒叙迭代器 private class DescendingIterator implements Iterator<E> { private final ListItr itr = new ListItr(size()); public boolean hasNext() { return itr.hasPrevious(); } public E next() { return itr.previous(); } public void remove() { itr.remove(); } } // private class ListItr implements ListIterator<E> { private Node<E> lastReturned; private Node<E> next; private int nextIndex; private int expectedModCount = modCount; ListItr(int index) { // assert isPositionIndex(index); next = (index == size) ? null : node(index); nextIndex = index; }
Queue队列
FIFO,尾部添加、头部删除(先进队列的元素先出队列)
Deque
继承Queue的双端队列
Vector
内部数组实现,合ArrayList近似,不过每个操作方法加上了synchronized 线程安全。
public class Vector<E> extends AbstractList<E> implements List<E>, RandomAccess, Cloneable, java.io.Serializable{ protected Object[] elementData; protected int elementCount; protected int capacityIncrement; public Vector(int initialCapacity, int capacityIncrement) { super(); if (initialCapacity < 0) throw new IllegalArgumentException("Illegal Capacity: "+ initialCapacity); this.elementData = new Object[initialCapacity]; this.capacityIncrement = capacityIncrement; } // capacityIncrement 如果有指定则每次加上指定的增长值进行扩容 //无则按原来的2倍进行扩容 private void grow(int minCapacity) { // overflow-conscious code int oldCapacity = elementData.length; int newCapacity = oldCapacity + ((capacityIncrement > 0) ? capacityIncrement : oldCapacity); if (newCapacity - minCapacity < 0) newCapacity = minCapacity; if (newCapacity - MAX_ARRAY_SIZE > 0) newCapacity = hugeCapacity(minCapacity); elementData = Arrays.copyOf(elementData, newCapacity); }
Stack栈
栈是一种线性数据结构,遵从 LIFO(后进先出)的操作顺序,所有操作都是在顶部进行。
继承Vector
class Stack<E> extends Vector<E> { public Stack() { } public E push(E item) { addElement(item); return item; } public synchronized E pop() { E obj; int len = size(); obj = peek(); removeElementAt(len - 1); return obj; } public synchronized E peek() { int len = size(); if (len == 0) throw new EmptyStackException(); return elementAt(len - 1); } public boolean empty() { return size() == 0; } public synchronized int search(Object o) { int i = lastIndexOf(o); if (i >= 0) { return size() - i; } return -1; }
Map
一个 Map 中,任意一个 key 都有唯一确定的 value 与其对应,这个 key-value 的映射就是 map。
- KeySet:KeySet 是一个 Map 中键(key)的集合,以 Set 的形式保存,不允许重复,因此键存储的对象需要重写 equals() 和 hashCode() 方法。
- Values:Values 是一个 Map 中值 (value) 的集合,以 Collection 的形式保存,因此可以重复
- Entry:Entry 是 Map 接口中的内部接口,表示一个键值对的映射。
Hashtable: 线程安全
HashMap: 无序,速度快
TreeMap:有序,效率低
LinkedHashMap:结合 HashMap 和 TreeMap 的有点,有序的同时效率也不错,仅比 HashMap 慢一点
AbstractMap
map的部分实现,实现了两个Entry。
public abstract class AbstractMap<K,V> implements Map<K,V> { transient Set<K> keySet; transient Collection<V> values; public static class SimpleEntry<K,V> implements Entry<K,V>, java.io.Serializable { private static final long serialVersionUID = -8499721149061103585L; private final K key; private V value; public SimpleEntry(K key, V value) { this.key = key; this.value = value; } public SimpleEntry(Entry<? extends K, ? extends V> entry) { this.key = entry.getKey(); this.value = entry.getValue(); } public K getKey() { return key; } public V getValue() { return value; } public V setValue(V value) { V oldValue = this.value; this.value = value; return oldValue; } public boolean equals(Object o) { if (!(o instanceof Map.Entry)) return false; Map.Entry<?,?> e = (Map.Entry<?,?>)o; return eq(key, e.getKey()) && eq(value, e.getValue()); } public int hashCode() { return (key == null ? 0 : key.hashCode()) ^ (value == null ? 0 : value.hashCode()); } public String toString() { return key + "=" + value; } } //不可变的Entry,不支持setValue public static class SimpleImmutableEntry<K,V> implements Entry<K,V>, java.io.Serializable { private static final long serialVersionUID = 7138329143949025153L; private final K key; private final V value; public SimpleImmutableEntry(K key, V value) { this.key = key; this.value = value; } public SimpleImmutableEntry(Entry<? extends K, ? extends V> entry) { this.key = entry.getKey(); this.value = entry.getValue(); } public K getKey() { return key; } public V getValue() { return value; } public V setValue(V value) { throw new UnsupportedOperationException(); } public boolean equals(Object o) { if (!(o instanceof Map.Entry)) return false; Map.Entry<?,?> e = (Map.Entry<?,?>)o; return eq(key, e.getKey()) && eq(value, e.getValue()); } public int hashCode() { return (key == null ? 0 : key.hashCode()) ^ (value == null ? 0 : value.hashCode()); } public String toString() { return key + "=" + value; } }
HashMap
是一个采用哈希表实现的键值对集合,HashMap 的特殊存储结构使得在获取指定元素前需要经过哈希运算,得到目标元素在哈希表中的位置,然后再进行少量比较即可得到元素,这使得 HashMap 的查找效率高。
当发生 哈希冲突(碰撞)的时候,HashMap 采用 拉链法 进行解决。
底层采用数组+链表实现
用哈希函数计算关键字的哈希值(hash value),通过哈希值这个索引就可以找到关键字的存储位置,即桶(bucket)。哈希表不同于二叉树、栈、序列的数据结构一般情况下,在哈希表上的插入、查找、删除等操作的时间复杂度是 O(1)。
查找过程中,关键字的比较次数,取决于产生冲突的多少,产生的冲突少,查找效率就高,产生的冲突多,查找效率就低。因此,影响产生冲突多少的因素,也就是影响查找效率的因素。
影响产生冲突多少有以下三个因素:
- 哈希函数是否均匀;
- 处理冲突的方法;
- 哈希表的加载因子。
哈希表的加载因子和容量决定了在什么时候桶数(存储位置)不够,需要重新哈希。加载因子太大的话桶太多,遍历时效率变低;太大的话频繁 rehash,导致性能降低。所以加载因子的大小需要结合时间和空间效率考虑。
在 HashMap 中的加载因子为 0.75,即四分之三。
- 底层实现是 链表数组,JDK 8 后又加了 红黑树
- 实现了 Map 全部的方法
- key 用 Set 存放,所以想做到 key 不允许重复,key 对应的类需要重写 hashCode 和 equals 方法
- 允许空键和空值(但空键只有一个,且放在第一位)
- 元素是无序的,而且顺序会不定时改变
- 插入、获取的时间复杂度基本是 O(1)(前提是有适当的哈希函数,让元素分布在均匀的位置)
- 遍历整个 Map 需要的时间与 桶(数组) 的长度成正比(因此初始化时 HashMap 的容量不宜太大)
- 两个关键因子:初始容量、加载因子
实现
class HashMap<K,V> extends AbstractMap<K,V> implements Map<K,V>, Cloneable, Serializable { private static final long serialVersionUID = 362498820763181265L; //初始容量16,必须时2的次方 static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16 //默认加载因子的大小:0.75,可不是随便的,结合时间和空间效率考虑得到的 static final float DEFAULT_LOAD_FACTOR = 0.75f; //最大容量: 2^ 30 次方 static final int MAXIMUM_CAPACITY = 1 << 30; //树形阈值:JDK 1.8 新增的,当使用 树 而不是列表来作为桶时使用。必须必 2 大 static final int TREEIFY_THRESHOLD = 8; //非树形阈值:也是 1.8 新增的,扩容时分裂一个树形桶的阈值,要比 TREEIFY_THRESHOLD 小 static final int UNTREEIFY_THRESHOLD = 6; //树形最小容量:桶可能是树的哈希表的最小容量。至少是 TREEIFY_THRESHOLD 的 4 倍,这样能避免扩容时的冲突 static final int MIN_TREEIFY_CAPACITY = 64; //哈希表中的链表数组 transient Node<K,V>[] table; //缓存的 键值对集合 transient Set<Map.Entry<K,V>> entrySet; transient int size; // HashMap 修改的次数,这个变量用来保证 fail-fast 机制 transient int modCount; //阈值,下次需要扩容时的值,等于 容量 * 加载因子(loadFactor) int threshold; //哈希表的加载因子 final float loadFactor; static class Node<K,V> implements Map.Entry<K,V> { final int hash; final K key; V value; Node<K,V> next; Node(int hash, K key, V value, Node<K,V> next) { this.hash = hash; this.key = key; this.value = value; this.next = next; } public final K getKey() { return key; } public final V getValue() { return value; } public final String toString() { return key + "=" + value; } public final int hashCode() { return Objects.hashCode(key) ^ Objects.hashCode(value); } public final V setValue(V newValue) { V oldValue = value; value = newValue; return oldValue; } public final boolean equals(Object o) { if (o == this) return true; if (o instanceof Map.Entry) { //键值相等 Map.Entry<?,?> e = (Map.Entry<?,?>)o; if (Objects.equals(key, e.getKey()) && Objects.equals(value, e.getValue())) return true; } return false; } } /** * @param initialCapacity the initial capacity * @param loadFactor the load factor * @throws IllegalArgumentException if the initial capacity is negative * or the load factor is nonpositive */ public HashMap(int initialCapacity, float loadFactor) { if (initialCapacity < 0) throw new IllegalArgumentException("Illegal initial capacity: " + initialCapacity); if (initialCapacity > MAXIMUM_CAPACITY) initialCapacity = MAXIMUM_CAPACITY; if (loadFactor <= 0 || Float.isNaN(loadFactor)) throw new IllegalArgumentException("Illegal load factor: " + loadFactor); this.loadFactor = loadFactor; this.threshold = tableSizeFor(initialCapacity); } public HashMap(int initialCapacity) { this(initialCapacity, DEFAULT_LOAD_FACTOR); } public HashMap() { this.loadFactor = DEFAULT_LOAD_FACTOR; // all other fields defaulted } public HashMap(Map<? extends K, ? extends V> m) { this.loadFactor = DEFAULT_LOAD_FACTOR; putMapEntries(m, false); } //指定的容量设置阈值 static final int tableSizeFor(int cap) { int n = cap - 1; n |= n >>> 1; n |= n >>> 2; n |= n >>> 4; n |= n >>> 8; n |= n >>> 16; return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1; } //计算hash key static final int hash(Object key) { int h; //由于哈希表的容量都是 2 的 N 次方,在当前,元素的 hashCode() 在很多时候下低位是相同的,这将导致冲突(碰撞), // 因此 1.8 以后做了个移位操作:将元素的 hashCode() 和自己右移 16 位后的结果求异或。 //由于 int 只有 32 位,无符号右移 16 位相当于把高位的一半移到低位: //这样可以避免只靠低位数据来计算哈希时导致的冲突,计算结果由高低位结合决定,可以避免哈希值分布不均匀。而且,采用位运算效率更高。 return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16); } final void putMapEntries(Map<? extends K, ? extends V> m, boolean evict) { int s = m.size(); if (s > 0) { if (table == null) { // pre-size float ft = ((float)s / loadFactor) + 1.0F; int t = ((ft < (float)MAXIMUM_CAPACITY) ? (int)ft : MAXIMUM_CAPACITY); if (t > threshold) threshold = tableSizeFor(t); } //数组不为空,超过进行扩容 else if (s > threshold) resize(); for (Map.Entry<? extends K, ? extends V> e : m.entrySet()) { K key = e.getKey(); V value = e.getValue(); // 先经过 hash() 计算位置,然后复制指定 map 的内容 putVal(hash(key), key, value, false, evict); } } } /** * @param hash 计算得到key的hash * @param key the key * @param value the value to put * @param onlyIfAbsent if true, don't change existing value * @param evict if false, the table is in creation mode. * @return previous value, or null if none * * 1.计算key的hash, * 2.当前哈希表是否为空,新建一个hash表 * 3.如果hash表中没有元素,则新建节点加入。 * 4.hash从第一个元素开始查找hash(key)对应的位置。 * 1.如第一个元素的hash(key)等于添加的,则替换 * 2.第一个元素不是要添加的hash(key),判断是否为TreeNode,调用 putTreeVal() 进行插入 * 3.节点类型不是TreeNode,则遍历哈希表中的链表,找到hash(key)进行替换 * 4.当哈希表的链表大于TREEIFY_THRESHOLD,则treeifyBin()树化 * 5.最后检查是否需要扩容 */ final V putVal(int hash, K key, V value, boolean onlyIfAbsent, boolean evict) { Node<K,V>[] tab; Node<K,V> p; int n, i; if ((tab = table) == null || (n = tab.length) == 0) //如果当前 哈希表内容为空,新建,n 指向最后一个桶的位置,tab 为哈希表另一个引用 n = (tab = resize()).length; if ((p = tab[i = (n - 1) & hash]) == null) //如果要插入的位置没有元素,新建节点并放进去tab tab[i] = newNode(hash, key, value, null); else { Node<K,V> e; K k; if (p.hash == hash && ((k = p.key) == key || (key != null && key.equals(k)))) //如果该位置上已经存在 则替换 value e = p; else if (p instanceof TreeNode) //如果不一样,而且当前采用的 JDK 8 以后的树形节点,调用 putTreeVal 插入 e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value); else { //否则还是从传统的链表数组查找、替换 //遍历链表 for (int binCount = 0; ; ++binCount) { //没有更多了,就把要添加的元素插到后面得了 if ((e = p.next) == null) { p.next = newNode(hash, key, value, null); ///这个桶内链表 大于等于 8,就要树形化 --采用红黑树 if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st treeifyBin(tab, hash); break; } //如果找到要替换的节点,就停止 if (e.hash == hash && ((k = e.key) == key || (key != null && key.equals(k)))) break; p = e; } } //存在就替换节点 if (e != null) { // existing mapping for key V oldValue = e.value; if (!onlyIfAbsent || oldValue == null) e.value = value; afterNodeAccess(e); return oldValue; } } ++modCount; //如果超出阈值,就得扩容 if (++size > threshold) resize(); afterNodeInsertion(evict); return null; } Node<K,V> newNode(int hash, K key, V value, Node<K,V> next) { return new Node<>(hash, key, value, next); } /** * 扩容 开销大,需要迭代所有的元素,rehash、赋值、保留原来的数据结构。 */ final Node<K,V>[] resize() { Node<K,V>[] oldTab = table; int oldCap = (oldTab == null) ? 0 : oldTab.length; int oldThr = threshold; int newCap, newThr = 0; if (oldCap > 0) { if (oldCap >= MAXIMUM_CAPACITY) { threshold = Integer.MAX_VALUE; return oldTab; } else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY && oldCap >= DEFAULT_INITIAL_CAPACITY) //在原来的oldThr 翻倍 //如果oldCap大于等于 16,新的阈值就是旧阈值的两倍 newThr = oldThr << 1; // double threshold } else if (oldThr > 0) // initial capacity was placed in threshold //如果 oldCap 等于0 且 oldThr 大于0, 说明之前创建了哈希表但没有添加元素,初始化容量等于阈值 newCap = oldThr; else { //还没创建哈希表,容量为默认容量,阈值为 默认容量*加载因子 newCap = DEFAULT_INITIAL_CAPACITY; newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY); } //如果新的阈值为 0 ,就得用 新容量*加载因子 重计算一次 if (newThr == 0) { float ft = (float)newCap * loadFactor; newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ? (int)ft : Integer.MAX_VALUE); } //更新阈值 threshold = newThr; //创建 hash表 数组 Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap]; table = newTab; //遍历复制 if (oldTab != null) { for (int j = 0; j < oldCap; ++j) { Node<K,V> e; if ((e = oldTab[j]) != null) { //老节点 null oldTab[j] = null; if (e.next == null) //只有一个节点 需要重新索引(rehash) e.hash & (newCap - 1),等价于 e.hash % newCap newTab[e.hash & (newCap - 1)] = e; else if (e instanceof TreeNode) ///如果旧哈希表中这个位置的桶是树形结构,就要把新哈希表里当前节点也变成树形结构 ((TreeNode<K,V>)e).split(this, newTab, j, oldCap); else { // preserve order //保留旧哈希表中链表的顺序 Node<K,V> loHead = null, loTail = null; Node<K,V> hiHead = null, hiTail = null; Node<K,V> next; do {//循环赋值给新哈希表 next = e.next; if ((e.hash & oldCap) == 0) { if (loTail == null) loHead = e; else loTail.next = e; loTail = e; } else { if (hiTail == null) hiHead = e; else hiTail.next = e; hiTail = e; } } while ((e = next) != null); if (loTail != null) { loTail.next = null; newTab[j] = loHead; } if (hiTail != null) { hiTail.next = null; newTab[j + oldCap] = hiHead; } } } } } return newTab; } //获取value public V get(Object key) { Node<K,V> e; return (e = getNode(hash(key), key)) == null ? null : e.value; } //通过计算过后的hash final Node<K,V> getNode(int hash, Object key) { Node<K,V>[] tab; Node<K,V> first, e; int n; K k; //(n - 1) & hash 计算出hash表的位置---->在从链表中找 //hash表不为null,且[(n - 1) & hash] 表中的第一个节点部位null if ((tab = table) != null && (n = tab.length) > 0 && (first = tab[(n - 1) & hash]) != null) { //第一个节点是不是需要找的节点 是直接返回。 if (first.hash == hash && ((k = first.key) == key || (key != null && key.equals(k)))) return first; //如果不是 遍历所有节点 if ((e = first.next) != null) { if (first instanceof TreeNode) //如果是树形节点,就调用树形节点的 get 方法 树结构查找 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; }
- 不是同步
- iterator都是 fail-fast
- 当 HashMap 中有大量的元素都存放到同一个hash表中时,这时候哈希表数组里只有一个hash表,这个hash表下有一条长长的链表,这个时候 HashMap 就相当于一个单链表,假如单链表有 n 个元素,遍历的时间复杂度就是 O(n),完全失去了它的优势。JDK 1.8 中引用了 红黑树(时间复杂度为 O(logn)) 优化这个问题。
- 为什么哈希表的容量一定要是 2的整数次幂?----------capacity 为 2的整数次幂的话,计算桶的位置 h&(length-1) 就相当于对 length 取模,提升了计算效率;其次,capacity 为 2 的整数次幂的话,为偶数,这样 capacity-1 为奇数,奇数的最后一位是 1,这样便保证了 h&(capacity-1) 的最后一位可能为 0,也可能为 1(这取决于h的值),即与后的结果可能为偶数,也可能为奇数,这样便可以保证散列的均匀性;而如果 capacity 为奇数的话,很明显 capacity-1 为偶数,它的最后一位是 0,这样 h&(capacity-1) 的最后一位肯定为 0,即只能为偶数,这样任何 hash 值都只会被散列到数组的偶数下标位置上,这便浪费了近一半的空间。1.使用减法替代取模,提升计算效率;2.为了使不同 hash 值发生碰撞的概率更小,尽可能促使元素在哈希表中均匀地散列。
- HashMap 允许 key, value 为 null,同时他们都保存在第一个hash表中。
- HashMap 中 equals() 和 hashCode() 有什么作用?-------HashMap 的添加、获取时需要通过 key 的 hashCode() 进行 hash(),然后计算下标 ( n-1 & hash),从而获得要找的同的位置。当发生冲突(碰撞)时,利用 key.equals() 方法去链表或树中去查找对应的节点。
- hash 的实现吗?为什么要这样实现?-------在 JDK 1.8 的实现中,是通过 hashCode() 的高16位异或低16位实现的:(h = k.hashCode()) ^ (h >>> 16)。主要是从速度、功效、质量 来考虑的,这么做可以在桶的 n 比较小的时候,保证高低 bit 都参与到 hash 的计算中,同时位运算不会有太大的开销。
红黑树
jdk1.8之前hashMap的问题: HashMap 的实现是 数组+链表,即使哈希函数取得再好,也很难达到元素百分百均匀分布。当 HashMap 中有大量的元素都存放到同一个桶中时,这个桶下有一条长长的链表,这个时候 HashMap 就相当于一个单链表,假如单链表有 n 个元素,遍历的时间复杂度就是 O(n),完全失去了它的优势。
//当每个hash表中的节点个数大于8 就需要使用红黑树替换链表节点 static final int TREEIFY_THRESHOLD = 8; //树结构拆解为链表 要比 TREEIFY_THRESHOLD 小 static final int UNTREEIFY_THRESHOLD = 6; //树形最小容量: //当哈希表中的容量大于这个值时,hash表才能进行树形化 //否则hash表内元素太多时会扩容,而不是树形化 //为了避免进行扩容、树形化选择的冲突,这个值不能小于 4 * TREEIFY_THRESHOLD static final int MIN_TREEIFY_CAPACITY = 64; //树形化 链表节点 替换成 红黑树节点 /** * 1.根据tab 与MIN_TREEIFY_CAPACITY比较 是扩容还是树形化 * 2.树形化 * 1.遍历原链表中的元素,创建相同个数的树形节点,复制,建立关联 * 2.然后让原链表的第一个元素指向新建的树头结点,替换链表内容为树形内容 */ final void treeifyBin(Node<K,V>[] tab, int hash) { int n, index; Node<K,V> e; //如果当前哈希表为空,或者哈希表中元素的个数小于 进行树形化的阈值(默认为 64),就去新建/扩容 if (tab == null || (n = tab.length) < MIN_TREEIFY_CAPACITY) resize(); else if ((e = tab[index = (n - 1) & hash]) != null) { //如果哈希表中的元素个数超过了 树形化阈值(MIN_TREEIFY_CAPACITY ),则进行树形化 // e 是哈希表中指定位置桶里的链表节点,从第一个开始 // TreeNode<K,V> hd = null;//头节点 TreeNode<K,V> tl = null;//尾节点 do { ///新建一个树形节点,内容和当前链表节点 e 一致 //new TreeNode<>(p.hash, p.key, p.value, next); TreeNode<K,V> p = replacementTreeNode(e, null); if (tl == null) hd = p; else { p.prev = tl; tl.next = p; } tl = p; } while ((e = e.next) != null); //让原链表的第一个元素指向新建的红黑树头结点, // 以后这个链表里的元素就是红黑树而不在链表了 if ((tab[index] = hd) != null) hd.treeify(tab); } } //构造红黑树 final void treeify(Node<K,V>[] tab) { TreeNode<K,V> root = null; for (TreeNode<K,V> x = this, next; x != null; x = next) { next = (TreeNode<K,V>)x.next; x.left = x.right = null; //第一次进入循环,确定头结点,为黑色 if (root == null) { x.parent = null; x.red = false; root = x; } //后面进入循环走的逻辑,x 指向树中的某个节点 else { K k = x.key; int h = x.hash; Class<?> kc = null; for (TreeNode<K,V> p = root;;) { int dir, ph; K pk = p.key; if ((ph = p.hash) > h) dir = -1; else if (ph < h) dir = 1; else if ((kc == null && (kc = comparableClassFor(k)) == null) || (dir = compareComparables(kc, k, pk)) == 0) dir = tieBreakOrder(k, pk); TreeNode<K,V> xp = p; if ((p = (dir <= 0) ? p.left : p.right) == null) { x.parent = xp; if (dir <= 0) xp.left = x; else xp.right = x; root = balanceInsertion(root, x); break; } } } } moveRootToFront(tab, root); } final TreeNode<K,V> getTreeNode(int h, Object k) { return ((parent != null) ? root() : this).find(h, k, null); } /** * 使用的公式是 (n - 1) &hash)得到头节点 如果头节点是红黑树节点,就调用红黑树节点的 getTreeNode() 方法--》find * 红黑树查找 * 折半查找,效率很高。 */ final TreeNode<K,V> find(int h, Object k, Class<?> kc) { TreeNode<K,V> p = this; do { int ph, dir; K pk; TreeNode<K,V> pl = p.left, pr = p.right, q; if ((ph = p.hash) > h) p = pl; else if (ph < h) p = pr; else if ((pk = p.key) == k || (k != null && k.equals(pk))) return p; else if (pl == null) p = pr; else if (pr == null) p = pl; else if ((kc != null || (kc = comparableClassFor(k)) != null) && (dir = compareComparables(kc, k, pk)) != 0) p = (dir < 0) ? pl : pr; else if ((q = pr.find(h, k, kc)) != null) return q; else p = pl; } while (p != null); return null; } /** * 扩容时,如果当前桶中元素结构是红黑树,并且元素个数小于链表还原阈值 UNTREEIFY_THRESHOLD (默认为 6), * 就会把桶中的树形结构缩小或者直接还原(切分)为链表结构 */ final void split(java.util.HashMap<K,V> map, Node<K,V>[] tab, int index, int bit) { TreeNode<K,V> b = this; // Relink into lo and hi lists, preserving order TreeNode<K,V> loHead = null, loTail = null; TreeNode<K,V> hiHead = null, hiTail = null; int lc = 0, hc = 0; for (TreeNode<K,V> e = b, next; e != null; e = next) { next = (TreeNode<K,V>)e.next; e.next = null; if ((e.hash & bit) == 0) { if ((e.prev = loTail) == null) loHead = e; else loTail.next = e; loTail = e; ++lc; } else { if ((e.prev = hiTail) == null) hiHead = e; else hiTail.next = e; hiTail = e; ++hc; } } if (loHead != null) { if (lc <= UNTREEIFY_THRESHOLD) tab[index] = loHead.untreeify(map); else { tab[index] = loHead; if (hiHead != null) // (else is already treeified) loHead.treeify(tab); } } if (hiHead != null) { if (hc <= UNTREEIFY_THRESHOLD) tab[index + bit] = hiHead.untreeify(map); else { tab[index + bit] = hiHead; if (loHead != null) hiHead.treeify(tab); } } } /** * 红黑树节点 */ static final class TreeNode<K,V> extends LinkedHashMap.Entry<K,V> { TreeNode<K,V> parent; // red-black tree links 父节点 TreeNode<K,V> left; //左节点 TreeNode<K,V> right; //右节点 TreeNode<K,V> prev; // 前节点 boolean red; TreeNode(int hash, K key, V val, Node<K,V> next) { super(hash, key, val, next); }
- 添加时,当tab中链表个数超过 8 时会转换成红黑树;
- 删除、扩容时,如果tab中结构为红黑树,并且树中元素个数太少(小于UNTREEIFY_THRESHOLD)的话,会进行修剪或者直接还原成链表结构;
- 查找时即使哈希函数不优,大量元素集中在一个tab中,由于有红黑树结构,性能也不会差。