diff --git a/35/target/ArrayListDemo.class b/35/target/ArrayListDemo.class new file mode 100644 index 0000000..98f8536 Binary files /dev/null and b/35/target/ArrayListDemo.class differ diff --git a/35/target/HashMapDemo$1.class b/35/target/HashMapDemo$1.class new file mode 100644 index 0000000..0ead949 Binary files /dev/null and b/35/target/HashMapDemo$1.class differ diff --git a/35/target/HashMapDemo.class b/35/target/HashMapDemo.class new file mode 100644 index 0000000..f97b55e Binary files /dev/null and b/35/target/HashMapDemo.class differ diff --git a/35/target/LinkedListDemo.class b/35/target/LinkedListDemo.class new file mode 100644 index 0000000..e2aa6f6 Binary files /dev/null and b/35/target/LinkedListDemo.class differ diff --git "a/week_01/04/ArrayList \346\272\220\347\240\201\345\210\206\346\236\220.md" "b/week_01/04/ArrayList \346\272\220\347\240\201\345\210\206\346\236\220.md" new file mode 100644 index 0000000..7ecd5c9 --- /dev/null +++ "b/week_01/04/ArrayList \346\272\220\347\240\201\345\210\206\346\236\220.md" @@ -0,0 +1,447 @@ +# ArrayList 源码分析 + + + +## 继承体系 + +- AbstractList +- List +- RandomAccess +- Cloneable +- java.io.Serializable + +> List 接口,是一种有顺序的集合,sequence (序列,顺序) +> +> index +> +> typically Duplicate elements (Unlike sets) + + + +> ArrayList Resiable-array, permits alll elements including null. +> +> roughly equivalent to Vector (synchronized) +> +> size,isEmpty,get,set,iterator,listIterator -> constant time +> +> add -> amortized(均摊) constant time +> +> other operations -> linear time (roughly) +> +> capacity >= list.size() ; capacity group automatically + + + +## 构造方法 + +1. 默认空构造 + + ​ + + ```java + /** + * Constructs an empty list with an initial capacity of ten. + * private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {}; + * 为什么说是10呢,可能是因为添加第一个元素时会扩展成10 + * Any empty ArrayList with elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA + * will be expanded to DEFAULT_CAPACITY when the first element is added + */ + public ArrayList() { + this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA; + } + ``` + +2. 传入 initialCapacity的 构造 + + ```java + /** + * Constructs an empty list with the specified initial capacity. + * 当传入 初始容量为0的构造时,为什么和上面的空构造不用同一个 DEFAULTCAPACITY_EMPTY_ELEMENTDATA + * 这俩是为了什么设计而存在的呢? + * 在 DEFAULTCAPACITY_EMPTY_ELEMENTDATA 上的注释写了 + * ----------------------------- + * Shared empty array instance used for default sized empty instances. We + * distinguish this from EMPTY_ELEMENTDATA to know how much to inflate when + * first element is added. + * ----------------------------- + * 为什么存在呢? + * 大致意思就是如果你构造函数里传入了0,证明你这个故意为之,就是为了减少空间的使用, + * 默认构造呢 就是会使用默认的10容量 + * @param initialCapacity the initial capacity of the list + * @throws IllegalArgumentException if the specified initial capacity + * is negative + */ + public ArrayList(int initialCapacity) { + if (initialCapacity > 0) { + this.elementData = new Object[initialCapacity]; + } else if (initialCapacity == 0) { + this.elementData = EMPTY_ELEMENTDATA; + } else { + throw new IllegalArgumentException("Illegal Capacity: "+ + initialCapacity); + } + } + ``` + +3. 传入集合的构造 + + ```java + /** + * Constructs a list containing the elements of the specified + * collection, in the order they are returned by the collection's + * iterator. + * + * @param c the collection whose elements are to be placed into this list + * @throws NullPointerException if the specified collection is null + */ + public ArrayList(Collection c) { + elementData = c.toArray(); + if ((size = elementData.length) != 0) { + // defend against c.toArray (incorrectly) not returning Object[] + // (see e.g. https://bugs.openjdk.java.net/browse/JDK-6260652) + if (elementData.getClass() != Object[].class) + elementData = Arrays.copyOf(elementData, size, Object[].class); + } else { + // replace with empty array. + this.elementData = EMPTY_ELEMENTDATA; + } + } + ``` + + ```java + @HotSpotIntrinsicCandidate + public static T[] copyOf(U[] original, int newLength, Class newType) { + @SuppressWarnings("unchecked") + // 相同类型的元素 + T[] copy = ((Object)newType == (Object)Object[].class) + ? (T[]) new Object[newLength] + : (T[]) Array.newInstance(newType.getComponentType(), newLength); + System.arraycopy(original, 0, copy, 0, + Math.min(original.length, newLength)); + return copy; + } + ``` + + ## + +## Constant Time Methods + +1. size + + ```java + /** + * Returns the number of elements in this list. + * 返回list中的元素数量 + * @return the number of elements in this list + */ + public int size() { + return size; + } + ``` + +2. isEmpty + + ```java + /** + * Returns {@code true} if this list contains no elements. + * 如果list中不包含元素返回 true + * @return {@code true} if this list contains no elements + */ + public boolean isEmpty() { + return size == 0; + } + ``` + +3. get + + ```java + /** + * Returns the element at the specified position in this list. + * 返回list在指定位置的元素 + * @param index index of the element to return + * @return the element at the specified position in this list + * @throws IndexOutOfBoundsException {@inheritDoc} + */ + public E get(int index) { + Objects.checkIndex(index, size); + return elementData(index); + } + ``` + +4. set + + ```java + /** + * Replaces the element at the specified position in this list with + * the specified element. + * 替换指定位置元素 + * @param index index of the element to replace + * @param element element to be stored at the specified position + * @return the element previously at the specified position + * @throws IndexOutOfBoundsException {@inheritDoc} + */ + public E set(int index, E element) { + Objects.checkIndex(index, size); + E oldValue = elementData(index); + elementData[index] = element; + return oldValue; + } + ``` + +5. iterator + + ```java + /** + * Returns an iterator over the elements in this list in proper sequence. + * 返回此list的遍历器(以正确的顺序) + *

The returned iterator is fail-fast. + * + * @return an iterator over the elements in this list in proper sequence + */ + public Iterator iterator() { + // 具体实现后续再看 + return new Itr(); + } + ``` + +6. listIterator + + ```java + /** + * Returns a list iterator over the elements in this list (in proper + * sequence), starting at the specified position in the list. + * The specified index indicates the first element that would be + * returned by an initial call to {@link ListIterator#next next}. + * An initial call to {@link ListIterator#previous previous} would + * return the element with the specified index minus one. + * + *

The returned list iterator is fail-fast. + * + * @throws IndexOutOfBoundsException {@inheritDoc} + */ + public ListIterator listIterator(int index) { + rangeCheckForAdd(index); + return new ListItr(index); + } + ``` + + + +## 主要看 + +1. add + + ```java + /** + * Appends the specified element to the end of this list. + * 在列表中结尾增加指定元素 + * @param e element to be appended to this list + * @return {@code true} (as specified by {@link Collection#add}) + */ + public boolean add(E e) { + // 修改次数 + 1 + modCount++; + add(e, elementData, size); + return true; + } + + /** + * 此方法使方法字节码小于35,使得在C1编译循环中区分开来 + * This helper method split out from add(E) to keep method + * bytecode size under 35 (the -XX:MaxInlineSize default value), + * which helps when add(E) is called in a C1-compiled loop. + */ + private void add(E e, Object[] elementData, int s) { + if (s == elementData.length) + // 由于元素已满,所以调用grow函数 + elementData = grow(); + elementData[s] = e; + size = s + 1; + } + + + private Object[] grow() { + return grow(size + 1); + } + /** + * 增加容量确保集合可以存储传入的 minCapacity 数量 + * Increases the capacity to ensure that it can hold at least the + * number of elements specified by the minimum capacity argument. + * + * @param minCapacity the desired minimum capacity + * @throws OutOfMemoryError if minCapacity is less than zero + */ + private Object[] grow(int minCapacity) { + return elementData = Arrays.copyOf(elementData, + newCapacity(minCapacity)); + } + + + /** + * 返回 至少大于给定的 minCapacity大小,如果 1.5倍能够满足就返回。 + * 除非给定MAX_ARRAY_SIZE不然不会返回这个值 + * Returns a capacity at least as large as the given minimum capacity. + * Returns the current capacity increased by 50% if that suffices. + * Will not return a capacity greater than MAX_ARRAY_SIZE unless + * the given minimum capacity is greater than MAX_ARRAY_SIZE. + * + * @param minCapacity the desired minimum capacity + * @throws OutOfMemoryError if minCapacity is less than zero + */ + private int newCapacity(int minCapacity) { + // overflow-conscious code + int oldCapacity = elementData.length; + // 1.5倍 + int newCapacity = oldCapacity + (oldCapacity >> 1); + if (newCapacity - minCapacity <= 0) { + if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) + return Math.max(DEFAULT_CAPACITY, minCapacity); + if (minCapacity < 0) // overflow + // 溢出考虑 + throw new OutOfMemoryError(); + return minCapacity; + } + return (newCapacity - MAX_ARRAY_SIZE <= 0) + ? newCapacity + : hugeCapacity(minCapacity); + } + + private static int hugeCapacity(int minCapacity) { + if (minCapacity < 0) // overflow + throw new OutOfMemoryError(); + return (minCapacity > MAX_ARRAY_SIZE) + ? Integer.MAX_VALUE + : MAX_ARRAY_SIZE; + } + + /** + * 将元素插入list中指定位置。交换此时指定位置的现有元素,后移后置元素 + * Inserts the specified element at the specified position in this + * list. Shifts the element currently at that position (if any) and + * any subsequent elements to the right (adds one to their indices). + * + * @param index index at which the specified element is to be inserted + * @param element element to be inserted + * @throws IndexOutOfBoundsException {@inheritDoc} + */ + public void add(int index, E element) { + // 检查是否越界 + rangeCheckForAdd(index); + // 修改次数+1 + modCount++; + final int s; + Object[] elementData; + if ((s = size) == (elementData = this.elementData).length) + elementData = grow(); + // 调用系统数组拷贝 + System.arraycopy(elementData, index, + elementData, index + 1, + s - index); + elementData[index] = element; + size = s + 1; + } + + /** + * A version of rangeCheck used by add and addAll. + */ + private void rangeCheckForAdd(int index) { + if (index > size || index < 0) + throw new IndexOutOfBoundsException(outOfBoundsMsg(index)); + } + ``` + +2. addAll + + ```java + /** + * 将所有集合中元素加入此list的最后 + * Appends all of the elements in the specified collection to the end of + * this list, in the order that they are returned by the + * specified collection's Iterator. The behavior of this operation is + * undefined if the specified collection is modified while the operation + * is in progress. (This implies that the behavior of this call is + * undefined if the specified collection is this list, and this + * list is nonempty.) + * + * @param c collection containing elements to be added to this list + * @return {@code true} if this list changed as a result of the call + * @throws NullPointerException if the specified collection is null + */ + public boolean addAll(Collection c) { + // 转成数组 + Object[] a = c.toArray(); + // 修改数量 + 1 + modCount++; + // 增加的数量 还有空指针的风险 + int numNew = a.length; + if (numNew == 0) + return false; + Object[] elementData; + // 增加的位置 + final int s; + if (numNew > (elementData = this.elementData).length - (s = size)) + elementData = grow(s + numNew); + // 将集合c中的元素拷贝到最后 + System.arraycopy(a, 0, elementData, s, numNew); + size = s + numNew; + return true; + } + ``` + +3. remove + + ```java + /** + * 移除list中第一个出现的元素(如果存在的话),如果list不包含元素,那这个就不会改变。更正式来说就是移除低索 + * 引的元素 满足Objects.equals(o, get(i))的元素存在。 + * Removes the first occurrence of the specified element from this list, + * if it is present. If the list does not contain the element, it is + * unchanged. More formally, removes the element with the lowest index + * {@code i} such that + * {@code Objects.equals(o, get(i))} + * (if such an element exists). Returns {@code true} if this list + * contained the specified element (or equivalently, if this list + * changed as a result of the call). + * + * @param o element to be removed from this list, if present + * @return {@code true} if this list contained the specified element + */ + public boolean remove(Object o) { + final Object[] es = elementData; + final int size = this.size; + int i = 0; + // 代码块 + found: { + // 区分为空 + if (o == null) { + for (; i < size; i++) + if (es[i] == null) + break found; + // 不为空的 + } else { + for (; i < size; i++) + if (o.equals(es[i])) + break found; + } + return false; + } + fastRemove(es, i); + return true; + } + + /** + * 基础的移除方法 跳过界限检查不返回移除的元素 + * Private remove method that skips bounds checking and does not + * return the value removed. + */ + private void fastRemove(Object[] es, int i) { + modCount++; + final int newSize; + if ((newSize = size - 1) > i) + System.arraycopy(es, i + 1, es, i, newSize - i); + es[size = newSize] = null; + } + + ``` + + \ No newline at end of file diff --git "a/week_01/04/HashMap\346\272\220\347\240\201\345\210\206\346\236\220.md" "b/week_01/04/HashMap\346\272\220\347\240\201\345\210\206\346\236\220.md" new file mode 100644 index 0000000..048ec8e --- /dev/null +++ "b/week_01/04/HashMap\346\272\220\347\240\201\345\210\206\346\236\220.md" @@ -0,0 +1,1130 @@ +# HashMap + + + +## 继承体系 + +### 继承 + +​ AbstractMap + +### 实现 + +1. Map +2. Cloneable +3. Serializable + +## 主要属性 + +```java +/** + * 默认初始容量16 + * The default initial capacity - MUST be a power of two. + */ +static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16 + +/** + * 最大的容量 + * The maximum capacity, used if a higher value is implicitly specified + * by either of the constructors with arguments. + * MUST be a power of two <= 1<<30. + */ +static final int MAXIMUM_CAPACITY = 1 << 30; + +/** + * 默认加载因子 + * The load factor used when none specified in constructor. + */ +static final float DEFAULT_LOAD_FACTOR = 0.75f; + +/** + * 使用树的桶数量阈值 + * The bin count threshold for using a tree rather than list for a + * bin. Bins are converted to trees when adding an element to a + * bin with at least this many nodes. The value must be greater + * than 2 and should be at least 8 to mesh with assumptions in + * tree removal about conversion back to plain bins upon + * shrinkage. + */ +static final int TREEIFY_THRESHOLD = 8; + +/** + * 树退化成链表的阈值 + * The bin count threshold for untreeifying a (split) bin during a + * resize operation. Should be less than TREEIFY_THRESHOLD, and at + * most 6 to mesh with shrinkage detection under removal. + */ +static final int UNTREEIFY_THRESHOLD = 6; + +/** + * The smallest table capacity for which bins may be treeified. + * (Otherwise the table is resized if too many nodes in a bin.) + * Should be at least 4 * TREEIFY_THRESHOLD to avoid conflicts + * between resizing and treeification thresholds. + */ +static final int MIN_TREEIFY_CAPACITY = 64; +``` + + + +```java +/** + * 桶(表),第一次使用时才会初始化,必要时扩容 + * The table, initialized on first use, and resized as + * necessary. When allocated, length is always a power of two. + * (We also tolerate length zero in some operations to allow + * bootstrapping mechanics that are currently not needed.) + */ +transient Node[] table; + +/** + * 缓存键值对的entrySet + * Holds cached entrySet(). Note that AbstractMap fields are used + * for keySet() and values(). + */ +transient Set> entrySet; + +/** + * 键值对的数量 + * The number of key-value mappings contained in this map. + */ +transient int size; + +/** + * 修改次数 + * The number of times this HashMap has been structurally modified + * Structural modifications are those that change the number of mappings in + * the HashMap or otherwise modify its internal structure (e.g., + * rehash). This field is used to make iterators on Collection-views of + * the HashMap fail-fast. (See ConcurrentModificationException). + */ +transient int modCount; + +/** + * 下一个要重新resize的大小 + * The next size value at which to resize (capacity * load factor). + * + * @serial + */ +// (The javadoc description is true upon serialization. +// Additionally, if the table array has not been allocated, this +// field holds the initial array capacity, or zero signifying +// DEFAULT_INITIAL_CAPACITY.) +int threshold; + +/** + * 加载因子 + * The load factor for the hash table. + * + * @serial + */ +final float loadFactor; +``` + +## 构造函数 + +1. 空构造 + + ```java + /** + * 默认值 16 load factor .75 + * Constructs an empty HashMap with the default initial capacity + * (16) and the default load factor (0.75). + */ + public HashMap() { + this.loadFactor = DEFAULT_LOAD_FACTOR; // all other fields defaulted + } + ``` + +2. 传入Map的构造 + + ```java + /** + * 构造足够承载目标的容量 + * Constructs a new HashMap with the same mappings as the + * specified Map. The HashMap is created with + * default load factor (0.75) and an initial capacity sufficient to + * hold the mappings in the specified Map. + * + * @param m the map whose mappings are to be placed in this map + * @throws NullPointerException if the specified map is null + */ + public HashMap(Map m) { + this.loadFactor = DEFAULT_LOAD_FACTOR; + putMapEntries(m, false); + } + + /** + * Implements Map.putAll and Map constructor. + * + * @param m the map + * @param evict false when initially constructing this map, else + * true (relayed to method afterNodeInsertion). + */ + final void putMapEntries(Map m, boolean evict) { + int s = m.size(); + if (s > 0) { + if (table == null) { // pre-size + // 除以加载因子 + 1 防止因加入全部元素而要扩容影响性能 + float ft = ((float)s / loadFactor) + 1.0F; + // 防止大于 MAXIMUM_CAPACITY + int t = ((ft < (float)MAXIMUM_CAPACITY) ? + (int)ft : MAXIMUM_CAPACITY); + // 修改threshold值 + if (t > threshold) + threshold = tableSizeFor(t); + } + else if (s > threshold) + // 查看如下 + resize(); + for (Map.Entry e : m.entrySet()) { + K key = e.getKey(); + V value = e.getValue(); + // 进行存值操作 + putVal(hash(key), key, value, false, evict); + } + } + } + ``` + + ```java + /** + * 初始化或将table双倍。如果是初始化,通过初始化容量threshold来分配, + * 然而由于我们使用了二次幂的扩容方式,元素可能存储在原索引位置,或者移动到二次幂的新表中 + * Initializes or doubles table size. If null, allocates in + * accord with initial capacity target held in field threshold. + * Otherwise, because we are using power-of-two expansion, the + * elements from each bin must either stay at same index, or move + * with a power of two offset in the new table. + * + * @return the table + */ + final Node[] resize() { + // 旧的表 + Node[] 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; + } + // 旧容量 >= DEFAULT_INITIAL_CAPACITY && 新容量 = 旧容量 * 2 < MAXIMUM_CAPACITY + else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY && + oldCap >= DEFAULT_INITIAL_CAPACITY) + // 这种就是翻倍的情况 + newThr = oldThr << 1; // double threshold + } + else if (oldThr > 0) // initial capacity was placed in threshold + newCap = oldThr; + else { // zero initial threshold signifies using defaults + // 初始默认值 + newCap = DEFAULT_INITIAL_CAPACITY; + newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY); + } + if (newThr == 0) { + float ft = (float)newCap * loadFactor; + newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ? + (int)ft : Integer.MAX_VALUE); + } + threshold = newThr; + @SuppressWarnings({"rawtypes","unchecked"}) + Node[] newTab = (Node[])new Node[newCap]; + table = newTab; + if (oldTab != null) { + for (int j = 0; j < oldCap; ++j) { + Node e; + if ((e = oldTab[j]) != null) { + // gc help + oldTab[j] = null; + if (e.next == null) + newTab[e.hash & (newCap - 1)] = e; + else if (e instanceof TreeNode) + ((TreeNode)e).split(this, newTab, j, oldCap); + else { // preserve order + Node loHead = null, loTail = null; + Node hiHead = null, hiTail = null; + Node 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; + } + ``` + + ```java + /** + * 实现Map.put 和 相关方法 + * Implements Map.put and related methods. + * + * @param hash hash for key + * @param key the key + * @param value the value to put + * @param onlyIfAbsent if true, don't change existing value 如果为true 不需要改变已存在的值 + * @param evict if false, the table is in creation mode. 如果为false ,表是在创建模式 + * @return previous value, or null if none + */ + final V putVal(int hash, K key, V value, boolean onlyIfAbsent, + boolean evict) { + Node[] tab; Node p; int n, i; + // 容器没有初始化的情况下 需要调用resize + if ((tab = table) == null || (n = tab.length) == 0) + n = (tab = resize()).length; + // 如果该位置为空的情况直接赋值 即 桶的位置上为空 + if ((p = tab[i = (n - 1) & hash]) == null) + tab[i] = newNode(hash, key, value, null); + else { + // 此位置已存在桶 + Node e; K k; + if (p.hash == hash && + ((k = p.key) == key || (key != null && key.equals(k)))) + e = p; + else if (p instanceof TreeNode) + // 如果已经是树了,那么调用TreeNode#putTreeVal + e = ((TreeNode)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; + } + } + // 找到相同key的元素 + 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; + } + ``` + + ```java + /** + * Replaces all linked nodes in bin at index for given hash unless + * table is too small, in which case resizes instead. + */ + final void treeifyBin(Node[] tab, int hash) { + int n, index; Node e; + if (tab == null || (n = tab.length) < MIN_TREEIFY_CAPACITY) + resize(); + else if ((e = tab[index = (n - 1) & hash]) != null) { + TreeNode hd = null, tl = null; + // 把所有节点转化为树节点 + do { + TreeNode 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); + } + } + ``` + +3. + + + +## 内部类 + +### 简单看 + + + +### 仔细看 + +1. Node + + ```java + /** + * 基础hash桶节点 + * Basic hash bin node, used for most entries. (See below for + * TreeNode subclass, and in LinkedHashMap for its Entry subclass.) + */ + static class Node implements Map.Entry { + final int hash; + final K key; + V value; + // 单向链表 + Node next; + + Node(int hash, K key, V value, Node 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; + } + } + ``` + + + +2. TreeNode + + ```java + /** + * 树桶,继承了 LinkedHashMap.Entry,所以即可以作为常规或链式节点 + * Entry for Tree bins. Extends LinkedHashMap.Entry (which in turn + * extends Node) so can be used as extension of either regular or + * linked node. + */ + static final class TreeNode extends LinkedHashMap.Entry { + // 父亲节点 + TreeNode parent; // red-black tree links + // 左节点 + TreeNode left; + // 右节点 + TreeNode right; + TreeNode prev; // needed to unlink next upon deletion + boolean red; + // 构造方法 + TreeNode(int hash, K key, V val, Node next) { + super(hash, key, val, next); + } + + /** + * Returns root of tree containing this node. + * 树的根 + */ + final TreeNode root() { + for (TreeNode r = this, p;;) { + if ((p = r.parent) == null) + return r; + r = p; + } + } + + /** + * 确保给的根是头结点,将Root移到最前面 + * Ensures that the given root is the first node of its bin. + */ + static void moveRootToFront(Node[] tab, TreeNode root) { + int n; + if (root != null && tab != null && (n = tab.length) > 0) { + int index = (n - 1) & root.hash; + TreeNode first = (TreeNode)tab[index]; + if (root != first) { + Node rn; + tab[index] = root; + TreeNode rp = root.prev; + if ((rn = root.next) != null) + ((TreeNode)rn).prev = rp; + if (rp != null) + rp.next = rn; + if (first != null) + first.prev = root; + root.next = first; + root.prev = null; + } + assert checkInvariants(root); + } + } + + /** + * 找到给定hash值和key的Treenode + * Finds the node starting at root p with the given hash and key. + * The kc argument caches comparableClassFor(key) upon first use + * comparing keys. + */ + final TreeNode find(int h, Object k, Class kc) { + TreeNode p = this; + do { + int ph, dir; K pk; + TreeNode 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; + } + + /** + * Calls find for root node. + */ + final TreeNode getTreeNode(int h, Object k) { + return ((parent != null) ? root() : this).find(h, k, null); + } + + /** + * Tie-breaking utility for ordering insertions when equal + * hashCodes and non-comparable. We don't require a total + * order, just a consistent insertion rule to maintain + * equivalence across rebalancings. Tie-breaking further than + * necessary simplifies testing a bit. + */ + static int tieBreakOrder(Object a, Object b) { + int d; + if (a == null || b == null || + (d = a.getClass().getName(). + compareTo(b.getClass().getName())) == 0) + d = (System.identityHashCode(a) <= System.identityHashCode(b) ? + -1 : 1); + return d; + } + + /** + * Forms tree of the nodes linked from this node. + */ + final void treeify(Node[] tab) { + TreeNode root = null; + for (TreeNode x = this, next; x != null; x = next) { + next = (TreeNode)x.next; + x.left = x.right = null; + if (root == null) { + x.parent = null; + x.red = false; + root = x; + } + else { + K k = x.key; + int h = x.hash; + Class kc = null; + for (TreeNode 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 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); + } + + /** + * Returns a list of non-TreeNodes replacing those linked from + * this node. + */ + final Node untreeify(HashMap map) { + Node hd = null, tl = null; + for (Node q = this; q != null; q = q.next) { + Node p = map.replacementNode(q, null); + if (tl == null) + hd = p; + else + tl.next = p; + tl = p; + } + return hd; + } + + /** + * Tree version of putVal. + */ + final TreeNode putTreeVal(HashMap map, Node[] tab, + int h, K k, V v) { + Class kc = null; + boolean searched = false; + TreeNode root = (parent != null) ? root() : this; + for (TreeNode p = root;;) { + int dir, ph; K pk; + if ((ph = p.hash) > h) + dir = -1; + else if (ph < h) + dir = 1; + else if ((pk = p.key) == k || (k != null && k.equals(pk))) + return p; + else if ((kc == null && + (kc = comparableClassFor(k)) == null) || + (dir = compareComparables(kc, k, pk)) == 0) { + if (!searched) { + TreeNode q, ch; + searched = true; + if (((ch = p.left) != null && + (q = ch.find(h, k, kc)) != null) || + ((ch = p.right) != null && + (q = ch.find(h, k, kc)) != null)) + return q; + } + dir = tieBreakOrder(k, pk); + } + + TreeNode xp = p; + if ((p = (dir <= 0) ? p.left : p.right) == null) { + Node xpn = xp.next; + TreeNode x = map.newTreeNode(h, k, v, xpn); + if (dir <= 0) + xp.left = x; + else + xp.right = x; + xp.next = x; + x.parent = x.prev = xp; + if (xpn != null) + ((TreeNode)xpn).prev = x; + moveRootToFront(tab, balanceInsertion(root, x)); + return null; + } + } + } + + /** + * Removes the given node, that must be present before this call. + * This is messier than typical red-black deletion code because we + * cannot swap the contents of an interior node with a leaf + * successor that is pinned by "next" pointers that are accessible + * independently during traversal. So instead we swap the tree + * linkages. If the current tree appears to have too few nodes, + * the bin is converted back to a plain bin. (The test triggers + * somewhere between 2 and 6 nodes, depending on tree structure). + */ + final void removeTreeNode(HashMap map, Node[] tab, + boolean movable) { + int n; + if (tab == null || (n = tab.length) == 0) + return; + int index = (n - 1) & hash; + TreeNode first = (TreeNode)tab[index], root = first, rl; + TreeNode succ = (TreeNode)next, pred = prev; + if (pred == null) + tab[index] = first = succ; + else + pred.next = succ; + if (succ != null) + succ.prev = pred; + if (first == null) + return; + if (root.parent != null) + root = root.root(); + if (root == null + || (movable + && (root.right == null + || (rl = root.left) == null + || rl.left == null))) { + tab[index] = first.untreeify(map); // too small + return; + } + TreeNode p = this, pl = left, pr = right, replacement; + if (pl != null && pr != null) { + TreeNode s = pr, sl; + while ((sl = s.left) != null) // find successor + s = sl; + boolean c = s.red; s.red = p.red; p.red = c; // swap colors + TreeNode sr = s.right; + TreeNode pp = p.parent; + if (s == pr) { // p was s's direct parent + p.parent = s; + s.right = p; + } + else { + TreeNode sp = s.parent; + if ((p.parent = sp) != null) { + if (s == sp.left) + sp.left = p; + else + sp.right = p; + } + if ((s.right = pr) != null) + pr.parent = s; + } + p.left = null; + if ((p.right = sr) != null) + sr.parent = p; + if ((s.left = pl) != null) + pl.parent = s; + if ((s.parent = pp) == null) + root = s; + else if (p == pp.left) + pp.left = s; + else + pp.right = s; + if (sr != null) + replacement = sr; + else + replacement = p; + } + else if (pl != null) + replacement = pl; + else if (pr != null) + replacement = pr; + else + replacement = p; + if (replacement != p) { + TreeNode pp = replacement.parent = p.parent; + if (pp == null) + root = replacement; + else if (p == pp.left) + pp.left = replacement; + else + pp.right = replacement; + p.left = p.right = p.parent = null; + } + + TreeNode r = p.red ? root : balanceDeletion(root, replacement); + + if (replacement == p) { // detach + TreeNode pp = p.parent; + p.parent = null; + if (pp != null) { + if (p == pp.left) + pp.left = null; + else if (p == pp.right) + pp.right = null; + } + } + if (movable) + moveRootToFront(tab, r); + } + + /** + * Splits nodes in a tree bin into lower and upper tree bins, + * or untreeifies if now too small. Called only from resize; + * see above discussion about split bits and indices. + * + * @param map the map + * @param tab the table for recording bin heads + * @param index the index of the table being split + * @param bit the bit of hash to split on + */ + final void split(HashMap map, Node[] tab, int index, int bit) { + TreeNode b = this; + // Relink into lo and hi lists, preserving order + TreeNode loHead = null, loTail = null; + TreeNode hiHead = null, hiTail = null; + int lc = 0, hc = 0; + for (TreeNode e = b, next; e != null; e = next) { + next = (TreeNode)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); + } + } + } + + /* ------------------------------------------------------------ */ + // Red-black tree methods, all adapted from CLR + // 左旋 + static TreeNode rotateLeft(TreeNode root, + TreeNode p) { + TreeNode r, pp, rl; + if (p != null && (r = p.right) != null) { + if ((rl = p.right = r.left) != null) + rl.parent = p; + if ((pp = r.parent = p.parent) == null) + (root = r).red = false; + else if (pp.left == p) + pp.left = r; + else + pp.right = r; + r.left = p; + p.parent = r; + } + return root; + } + // 右旋 + static TreeNode rotateRight(TreeNode root, + TreeNode p) { + TreeNode l, pp, lr; + if (p != null && (l = p.left) != null) { + if ((lr = p.left = l.right) != null) + lr.parent = p; + if ((pp = l.parent = p.parent) == null) + (root = l).red = false; + else if (pp.right == p) + pp.right = l; + else + pp.left = l; + l.right = p; + p.parent = l; + } + return root; + } + // 增加平衡 + static TreeNode balanceInsertion(TreeNode root, + TreeNode x) { + x.red = true; + for (TreeNode xp, xpp, xppl, xppr;;) { + if ((xp = x.parent) == null) { + x.red = false; + return x; + } + else if (!xp.red || (xpp = xp.parent) == null) + return root; + if (xp == (xppl = xpp.left)) { + if ((xppr = xpp.right) != null && xppr.red) { + xppr.red = false; + xp.red = false; + xpp.red = true; + x = xpp; + } + else { + if (x == xp.right) { + root = rotateLeft(root, x = xp); + xpp = (xp = x.parent) == null ? null : xp.parent; + } + if (xp != null) { + xp.red = false; + if (xpp != null) { + xpp.red = true; + root = rotateRight(root, xpp); + } + } + } + } + else { + if (xppl != null && xppl.red) { + xppl.red = false; + xp.red = false; + xpp.red = true; + x = xpp; + } + else { + if (x == xp.left) { + root = rotateRight(root, x = xp); + xpp = (xp = x.parent) == null ? null : xp.parent; + } + if (xp != null) { + xp.red = false; + if (xpp != null) { + xpp.red = true; + root = rotateLeft(root, xpp); + } + } + } + } + } + } + // 删除平衡 + static TreeNode balanceDeletion(TreeNode root, + TreeNode x) { + for (TreeNode xp, xpl, xpr;;) { + if (x == null || x == root) + return root; + else if ((xp = x.parent) == null) { + x.red = false; + return x; + } + else if (x.red) { + x.red = false; + return root; + } + else if ((xpl = xp.left) == x) { + if ((xpr = xp.right) != null && xpr.red) { + xpr.red = false; + xp.red = true; + root = rotateLeft(root, xp); + xpr = (xp = x.parent) == null ? null : xp.right; + } + if (xpr == null) + x = xp; + else { + TreeNode sl = xpr.left, sr = xpr.right; + if ((sr == null || !sr.red) && + (sl == null || !sl.red)) { + xpr.red = true; + x = xp; + } + else { + if (sr == null || !sr.red) { + if (sl != null) + sl.red = false; + xpr.red = true; + root = rotateRight(root, xpr); + xpr = (xp = x.parent) == null ? + null : xp.right; + } + if (xpr != null) { + xpr.red = (xp == null) ? false : xp.red; + if ((sr = xpr.right) != null) + sr.red = false; + } + if (xp != null) { + xp.red = false; + root = rotateLeft(root, xp); + } + x = root; + } + } + } + else { // symmetric + if (xpl != null && xpl.red) { + xpl.red = false; + xp.red = true; + root = rotateRight(root, xp); + xpl = (xp = x.parent) == null ? null : xp.left; + } + if (xpl == null) + x = xp; + else { + TreeNode sl = xpl.left, sr = xpl.right; + if ((sl == null || !sl.red) && + (sr == null || !sr.red)) { + xpl.red = true; + x = xp; + } + else { + if (sl == null || !sl.red) { + if (sr != null) + sr.red = false; + xpl.red = true; + root = rotateLeft(root, xpl); + xpl = (xp = x.parent) == null ? + null : xp.left; + } + if (xpl != null) { + xpl.red = (xp == null) ? false : xp.red; + if ((sl = xpl.left) != null) + sl.red = false; + } + if (xp != null) { + xp.red = false; + root = rotateRight(root, xp); + } + x = root; + } + } + } + } + } + + /** + * Recursive invariant check + */ + static boolean checkInvariants(TreeNode t) { + TreeNode tp = t.parent, tl = t.left, tr = t.right, + tb = t.prev, tn = (TreeNode)t.next; + if (tb != null && tb.next != t) + return false; + if (tn != null && tn.prev != t) + return false; + if (tp != null && t != tp.left && t != tp.right) + return false; + if (tl != null && (tl.parent != t || tl.hash > t.hash)) + return false; + if (tr != null && (tr.parent != t || tr.hash < t.hash)) + return false; + if (t.red && tl != null && tl.red && tr != null && tr.red) + return false; + if (tl != null && !checkInvariants(tl)) + return false; + if (tr != null && !checkInvariants(tr)) + return false; + return true; + } + } + ``` + + + +## 主要方法 + +### 简单看 + +```java +/** + * 返回指定key映射的值,如果不存在此Key 就返回null + * Returns the value to which the specified key is mapped, + * or {@code null} if this map contains no mapping for the key. + * + *

More formally, if this map contains a mapping from a key + * {@code k} to a value {@code v} such that {@code (key==null ? k==null : + * key.equals(k))}, then this method returns {@code v}; otherwise + * it returns {@code null}. (There can be at most one such mapping.) + * + *

A return value of {@code null} does not necessarily + * indicate that the map contains no mapping for the key; it's also + * possible that the map explicitly maps the key to {@code null}. + * The {@link #containsKey containsKey} operation may be used to + * distinguish these two cases. + * + * @see #put(Object, Object) + */ +public V get(Object key) { + Node e; + return (e = getNode(hash(key), key)) == null ? null : e.value; +} + +/** + * Implements Map.get and related methods. + * + * @param hash hash for key + * @param key the key + * @return the node, or null if none + */ +final Node getNode(int hash, Object key) { + Node[] tab; Node first, e; int n; K k; + if ((tab = table) != null && (n = tab.length) > 0 && + (first = tab[(n - 1) & hash]) != null) { + // 为什么要check头节点 因为 map一开始并不做初始化? + 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)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; +} +``` + + + +```java +/** + * 在这个map中将键值关联,如果map之前存在此key,老的值会被替代 + * Associates the specified value with the specified key in this map. + * If the map previously contained a mapping for the key, the old + * value is replaced. + * + * @param key key with which the specified value is to be associated + * @param value value to be associated with the specified key + * @return the previous value associated with key, or + * null if there was no mapping for key. + * (A null return can also indicate that the map + * previously associated null with key.) + */ +public V put(K key, V value) { + // 此方法上面有分析 + return putVal(hash(key), key, value, false, true); +} +``` + +### 仔细读 + diff --git "a/week_01/04/LinkedList\346\272\220\347\240\201\345\210\206\346\236\220.md" "b/week_01/04/LinkedList\346\272\220\347\240\201\345\210\206\346\236\220.md" new file mode 100644 index 0000000..c0164af --- /dev/null +++ "b/week_01/04/LinkedList\346\272\220\347\240\201\345\210\206\346\236\220.md" @@ -0,0 +1,401 @@ +# LinkedList + +> ``` +> * Doubly-linked list implementation of the {@code List} and {@code Deque} +> * interfaces. Implements all optional list operations, and permits all +> * elements (including {@code null}). +> ``` +> +> 双向链表实现了List接口和Deque,实现了所有的list操作,允许包含空的所有元素。 +> +> ** not synchronized ** -> Collections#synchronizedList 方法调用 +> +> 实现了 List Deque + + + + + +## 主要属性 + +```java +// 元素的个数 +transient int size = 0; + +/** + * 指向第一个节点 + * Pointer to first node. + * 什么意思? + * Invariant: (first == null && last == null) || + * (first.prev == null && first.item != null) + */ +transient Node first; + +/** + * 指向最后一个节点 + * Pointer to last node. + * Invariant: (first == null && last == null) || + * (last.next == null && last.item != null) + */ +transient Node last; +``` + + + +## 构造方法 + +1. LinkedList() + + ```java + /** + * 构造空的集合 + * Constructs an empty list. + */ + public LinkedList() { + } + ``` + +2. LinkedList(Collection c) + + ```java + /** + * 构造一个list集合包含传入的集合的所有元素,他们的顺序按照 集合c 所iterator的返回顺序 + * Constructs a list containing the elements of the specified + * collection, in the order they are returned by the collection's + * iterator. + * + * @param c the collection whose elements are to be placed into this list + * @throws NullPointerException if the specified collection is null + */ + public LinkedList(Collection c) { + this(); + // 将集合加到此list中 后续再看 + addAll(c); + } + ``` + + + + + + + +## 方法 + +### 简单看 + +1. 查 + + ```java + /** + * 返回第一个元素 O1 + * Returns the first element in this list. + * + * @return the first element in this list + * @throws NoSuchElementException if this list is empty + */ + public E getFirst() { + final Node f = first; + if (f == null) + throw new NoSuchElementException(); + return f.item; + } + + /** + * 返回最后一个元素 O1 + * Returns the last element in this list. + * + * @return the last element in this list + * @throws NoSuchElementException if this list is empty + */ + public E getLast() { + final Node l = last; + if (l == null) + throw new NoSuchElementException(); + return l.item; + } + + ``` + +2. 删 + + ```java + /** + * 移出并返回第一元素 + * Removes and returns the first element from this list. + * + * @return the first element from this list + * @throws NoSuchElementException if this list is empty + */ + public E removeFirst() { + final Node f = first; + if (f == null) + throw new NoSuchElementException(); + return unlinkFirst(f); + } + + /** + * 移出并返回最后一个元素 + * Removes and returns the last element from this list. + * + * @return the last element from this list + * @throws NoSuchElementException if this list is empty + */ + public E removeLast() { + final Node l = last; + if (l == null) + throw new NoSuchElementException(); + return unlinkLast(l); + } + ``` + + + +3. + + + +### 细看 + +1. addAll + + ```java + /** + * 将传入的集合c全部加入到此集合list中,他们的顺序是按照collection c iterator的顺序。 + * 当传入的collection c 在操作在进行中时,被修改了。此操作的表现 undefined + * Appends all of the elements in the specified collection to the end of + * this list, in the order that they are returned by the specified + * collection's iterator. The behavior of this operation is undefined if + * the specified collection is modified while the operation is in + * progress. (Note that this will occur if the specified collection is + * this list, and it's nonempty.) + * + * @param c collection containing elements to be added to this list + * @return {@code true} if this list changed as a result of the call + * @throws NullPointerException if the specified collection is null + */ + public boolean addAll(Collection c) { + return addAll(size, c); + } + + /** + * 将传入的集合的所有元素加入到此list中(从指定位置开始),将当前位置交换,右边的往后移。 + * 新增的元素会以集合的 iterator的函数顺序 + * Inserts all of the elements in the specified collection into this + * list, starting at the specified position. Shifts the element + * currently at that position (if any) and any subsequent elements to + * the right (increases their indices). The new elements will appear + * in the list in the order that they are returned by the + * specified collection's iterator. + * + * @param index index at which to insert the first element + * from the specified collection + * @param c collection containing elements to be added to this list + * @return {@code true} if this list changed as a result of the call + * @throws IndexOutOfBoundsException {@inheritDoc} + * @throws NullPointerException if the specified collection is null + */ + public boolean addAll(int index, Collection c) { + // 检查索引是否越界 + checkPositionIndex(index); + + Object[] a = c.toArray(); + int numNew = a.length; + if (numNew == 0) + return false; + // pred 要加入的前一个,succ 要插入的元素的位置 + Node pred, succ; + if (index == size) { + succ = null; + pred = last; + } else { + succ = node(index); + pred = succ.prev; + } + // 遍历要加入集合的元素数组 + for (Object o : a) { + // 将元素强转成对应的类型 + @SuppressWarnings("unchecked") E e = (E) o; + Node newNode = new Node<>(pred, e, null); + if (pred == null) + // 前者为空 将此作为链首 + first = newNode; + else + // 否则就将前者的后节点指向他 + pred.next = newNode; + // pred 使用newNode + pred = newNode; + } + + if (succ == null) { + //  插入的位置为空的情况 证明最后一个就是加进去的最后一个即pred + last = pred; + } else { + // 不为空仅需将俩相连 + pred.next = succ; + succ.prev = pred; + } + + size += numNew; + modCount++; + return true; + } + + /** + * 返回在指定索引位置的节点 + * Returns the (non-null) Node at the specified element index. + */ + Node node(int index) { + // assert isElementIndex(index); + // 在前一半从前往后找 + if (index < (size >> 1)) { + Node x = first; + for (int i = 0; i < index; i++) + x = x.next; + return x; + } else { + // 在后一半从后往前找 + Node x = last; + for (int i = size - 1; i > index; i--) + x = x.prev; + return x; + } + } + ``` + +2. add + + ```java + /** + * 插入具体元素在指定的位置 移动此位置的元素和右边的元素 + * Inserts the specified element at the specified position in this list. + * Shifts the element currently at that position (if any) and any + * subsequent elements to the right (adds one to their indices). + * + * @param index index at which the specified element is to be inserted + * @param element element to be inserted + * @throws IndexOutOfBoundsException {@inheritDoc} + */ + public void add(int index, E element) { + checkPositionIndex(index); + + if (index == size) + linkLast(element); + else + linkBefore(element, node(index)); + } + ``` + +3. unlinkFirst, unlinkLast + + ```java + /** + * 移出并返回非空的第一节点f,删除头 + * Unlinks non-null first node f. + */ + private E unlinkFirst(Node f) { + // assert f == first && f != null; + final E element = f.item; + final Node next = f.next; + f.item = null; + f.next = null; // help GC + first = next; + if (next == null) + // 如果下一个为空 证明这条链就是无元素的 + last = null; + else + // 将头结点 prev至成空 + next.prev = null; + size--; + modCount++; + return element; + } + + /** + * 移出并返回非空的最后一个节点,删除尾 + * Unlinks non-null last node l. + */ + private E unlinkLast(Node l) { + // assert l == last && l != null; + final E element = l.item; + final Node prev = l.prev; + l.item = null; + l.prev = null; // help GC + last = prev; + if (prev == null) + first = null; + else + prev.next = null; + size--; + modCount++; + return element; + } + ``` + +4. linkFirst, linkLast + + ```java + /** + * 将元素作为头结点连接 + * Links e as first element. + */ + private void linkFirst(E e) { + // 头结点取出作为 新增节点的next节点 + final Node f = first; + final Node newNode = new Node<>(null, e, f); + // 将头重置成新节点 + first = newNode; + if (f == null) + // 如果头结点为空 那么尾节点也是这个新节点 + last = newNode; + else + // f的prev指向现有节点 + f.prev = newNode; + size++; + modCount++; + } + + /** + * Links e as last element. + */ + void linkLast(E e) { + // 找到之前最后一个节点 + final Node l = last; + // 组装出现有节点 + final Node newNode = new Node<>(l, e, null); + last = newNode; + if (l == null) + // 之前的最后一个节点为空 证明这条链无数据 + first = newNode; + else + // 最后一个节点的 + l.next = newNode; + size++; + modCount++; + } + ``` + +5. linkBefore + + ```java + /** + * 将元素e 插入到succ之前 + * Inserts element e before non-null Node succ. + */ + void linkBefore(E e, Node succ) { + // assert succ != null; + // succ之前的位置 + final Node pred = succ.prev; + // pred -> newNode(e) -> succ + final Node newNode = new Node<>(pred, e, succ); + succ.prev = newNode; + if (pred == null) + // 之前的最后一个节点为空 证明这条链无数据 + first = newNode; + else + pred.next = newNode; + size++; + modCount++; + } + ``` + diff --git a/week_01/07/ArrayList-007.md b/week_01/07/ArrayList-007.md new file mode 100644 index 0000000..c512c70 --- /dev/null +++ b/week_01/07/ArrayList-007.md @@ -0,0 +1,148 @@ +ArrayList源码解析 +前言:源码都是基于JDK1.8。ArrayList是我们开发中比较常用的一个集合类,底层是基于数组实现的,现在就来看一看里面是怎么实现的 +1.首先看一下定义的成员变量 + int DEFAULT_CAPACITY = 10; //默认初始化数组的大小 + Object[] EMPTY_ELEMENTDATA = {}; //空数组对象 + Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {}; //默认大小空数组对象,跟上面变量的区别在于,新建一个集合对象时,没有指定大小,就用这个对象 + transient Object[] elementData; //集合CRUD时操作的数组,不可序列化 + int size; //集合的大小 + int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8; //最大的数组大小,为啥要-8,我也没搞懂 +2.成员变量之后来看一下几个构造方法 + //带容量参数的构造方法 + public ArrayList(int initialCapacity) { + if (initialCapacity > 0) { //容量大于0,直接新建一个Object数组 + this.elementData = new Object[initialCapacity]; + } else if (initialCapacity == 0) { //容量等于0,直接用之前声明的空数组对象 + this.elementData = EMPTY_ELEMENTDATA; + } else { //如果小于0,参数是不合法的,抛出异常 + throw new IllegalArgumentException("Illegal Capacity: "+ initialCapacity); + } + } + //不带参数的构造方法 + public ArrayList() { + this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA; //默认大小空数组对象 + } + //带集合参数的构造方法 + public ArrayList(Collection c) { + elementData = c.toArray(); //先把集合转换为数组,下面会分析toArray()这个方法 + if ((size = elementData.length) != 0) { //把集合的大小赋值给size,如果传入集合的长度不为0,再进去判断数组的Class对象是不是Object[].class + // c.toArray might (incorrectly) not return Object[] (see 6260652) + if (elementData.getClass() != Object[].class) //如果不是Object[].class,那就得复制整个数组里面的元素 + elementData = Arrays.copyOf(elementData, size, Object[].class); + } else { //如果集合大小等于0(不可能小于0,因为小于0都会抛异常),赋值为空数组对象 + // replace with empty array. + this.elementData = EMPTY_ELEMENTDATA; + } + } +3.构造方法说完之后,再来看几个重要的方法,其它方法就不一一分析了 + //首先看一下add方法,有两个add方法,一个是向数组添加元素,一个是向数组指定位置添加元素 + public boolean add(E e) { + ensureCapacityInternal(size + 1); // Increments modCount!! + elementData[size++] = e; + return true; + } + //这个方法是确保数组的容量,让新加的元素能加到数组中去 + private void ensureCapacityInternal(int minCapacity) { + ensureExplicitCapacity(calculateCapacity(elementData, minCapacity)); + } + private void ensureExplicitCapacity(int minCapacity) { + modCount++; + // overflow-conscious code + if (minCapacity - elementData.length > 0) //如果最小容量大于数组的大小,扩容数组 + grow(minCapacity); + } + //扩容方法 + private void grow(int minCapacity) { + // overflow-conscious code + int oldCapacity = elementData.length; + int newCapacity = oldCapacity + (oldCapacity >> 1); //先把数组扩容1.5倍 + if (newCapacity - minCapacity < 0) //如果扩容后的大小还小于minCapacity,那就直接把大小改成minCapacity + newCapacity = minCapacity; + if (newCapacity - MAX_ARRAY_SIZE > 0) //如果扩容后大小大于最大数组大小,看minCapacity大小是否大于最大数组大小,如果大于返回Integer.MAX_VALUE,否则返回MAX_ARRAY_SIZE + 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(); + return (minCapacity > MAX_ARRAY_SIZE) ? //这个地方返回Integer.MAX_VALUE,和上面最大数组大小,会不会有问题? + Integer.MAX_VALUE : + MAX_ARRAY_SIZE; + } + //计算容量大小 + private static int calculateCapacity(Object[] elementData, int minCapacity) { + if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) { //如果数组为空,取默认大小和最小容量大小中的最大值 + return Math.max(DEFAULT_CAPACITY, minCapacity); + } //否则返回最小容量大小 + return minCapacity; + } + + //在指定位置插入数据 + public void add(int index, E element) { + rangeCheckForAdd(index); //判断添加元素的位置是否越界 + ensureCapacityInternal(size + 1); // Increments modCount!! //跟上面的方法一样,判断是否要扩容,如果要扩容,扩容后返回新数组 + System.arraycopy(elementData, index, elementData, index + 1, size - index); //在指定位置插入元素后,把指定位置后的元素全部向后移一位 + elementData[index] = element; + size++; + } + + //批量增加的方法 + public boolean addAll(int index, Collection c) { + rangeCheckForAdd(index); //判断添加元素的位置是否越界 + Object[] a = c.toArray(); //把要添加的集合转换为Object[] + int numNew = a.length; + //判断是否需要扩容,如果需要的话,大小为size + numNew + ensureCapacityInternal(size + numNew); // Increments modCount + int numMoved = size - index; + if (numMoved > 0) //判断原数组里面的元素是否需要移动,如果需要,移动到index + numNew,为什么是这个长度呢?因为index + numNew的长度要放批量新加的集合 + System.arraycopy(elementData, index, elementData, index + numNew, numMoved); + //把新加的集合添加到数组中 + System.arraycopy(a, 0, elementData, index, numNew); + size += numNew; //把size变成添加集合后的大小 + return numNew != 0; + } + + //get方法 + public E get(int index) { + rangeCheck(index); //判断元素下标是否大于size + return elementData(index); //从数组中取元素返回 + } + + //删除方法,这里有两个,一个是根据下标删除,并返回删除的元素,时间复杂度为O(1)。还有一个是根据元素删除,返回是否删除成功,需要循环数组中的元素,最好时间复杂度为O(1),最坏时间复杂度为O(n),平均时间复杂度为O(n) + public E remove(int index) { + rangeCheck(index); //判断元素下标是否大于size + modCount++; + E oldValue = elementData(index); //取出要删除的元素,最后返回 + int numMoved = size - index - 1; //得到要移动元素的长度 + if (numMoved > 0) //如果元素大于0,移动数组 + System.arraycopy(elementData, index+1, elementData, index, numMoved); + elementData[--size] = null; // clear to let GC do its work //把最后一个元素置为空 + return oldValue; + } + + //截取集合 + public List subList(int fromIndex, int toIndex) { + subListRangeCheck(fromIndex, toIndex, size); //检查边界,是否合法 + return new SubList(this, 0, fromIndex, toIndex); //每次截取集合,会返回一个新的SubList,这个新的SubList又实现了集合中的大部分方法。这里就不贴代码了,太长了 + } + + //1.8新增的循环集合方法 + @Override + public void forEach(Consumer action) { + Objects.requireNonNull(action); //判断传的函数式对象是否为空 + final int expectedModCount = modCount; //操作次数 + @SuppressWarnings("unchecked") + final E[] elementData = (E[]) this.elementData; //底层数组 + final int size = this.size; //数组大小 + for (int i=0; modCount == expectedModCount && i < size; i++) { //这里多一个判断是因为在集合循环的时候,不能去增、删、改里面的元素 + action.accept(elementData[i]); + } + if (modCount != expectedModCount) { //如果有增、删、改操作的话,就抛异常 + throw new ConcurrentModificationException(); + } + } + +4.总结:ArrayList就分析到这了,如有错误请指正,或者有建议也欢迎提出来一起讨论 + + diff --git a/week_01/07/HashMap-007.md b/week_01/07/HashMap-007.md new file mode 100644 index 0000000..966a88e --- /dev/null +++ b/week_01/07/HashMap-007.md @@ -0,0 +1,218 @@ +HashMap源码分析 +1.关于HashMap有几个前提先说清楚,要不然后面看代码的时候也是懵的(以JDK1.8为例来说明) + (1).HashMap继承(extends)自 AbstractMap抽象(abstract)类 实现(implements)了Map,Cloneable, Serializable三个接口 + (2).DEFAULT_INITIAL_CAPACITY = 1 << 4;初始容量为1*2*2*2*2=16(左移1位就是乘以2,左移4位就是乘以2^4)。MAXIMUM_CAPACITY = 1 << 30;最大容量为(2^30) + (3).DEFAULT_LOAD_FACTOR = 0.75f;初始加载因子,容量*加载因子=阀值,如果添加的元素大于这个阀值,就两倍扩容 + (4).TREEIFY_THRESHOLD = 8;这个变量我的理解为,如果一个Key对应的Hash表中的元素超过8个,就转换为树(这里转换为红黑树),在后面还有一个值决定是不是转换为树 + (5).UNTREEIFY_THRESHOLD = 6;如果一个Key对应的Hash表中的元素小于6个,如果元素结构是红黑树的话,就转换为单链表结构 + (6).MIN_TREEIFY_CAPACITY = 64;这个变量在treeifyBin这个方法中,只有HashMap中的元素大于64才会去真正的转换为红黑树 + (7).Node为HashMap中没有树型化时的类型,TreeNode为树型化后的类型(1.8之前没有转红黑树的操作,直接是用的Entry的单链表) + +2.把有关前提说完后,开始来介绍里面两个有代表性的方法put(K,V);get(K)。为了能更清楚的说明,我就直接贴代码,然后写注释 + (1)put(K,V)方法: + + public V put(K key, V value) { + //调用putVal方法 + return putVal(hash(key), key, value, false, true); + } + + 来看看putVal方法 + hash把key求hash值;key和value分别是键值对,onlyIfAbsent如果里的值已存在,则不去覆盖原来的值,evict我也没太看懂这是干嘛的,影响不大 + + final V putVal(int hash, K key, V value, boolean onlyIfAbsent, boolean evict) { + Node[] tab; Node p; int n, i; + if ((tab = table) == null || (n = tab.length) == 0) + //如果第一次调用put方法,table为空,调用resize()方法,一个初始化和扩容的方法,后面再分析具体的实现 + n = (tab = resize()).length; + if ((p = tab[i = (n - 1) & hash]) == null) + //在指定tab数组的位置没有值的话,把新加的值添加到数组指定位置(相当于存Key的位置没有值) + tab[i] = newNode(hash, key, value, null); + else { + //在指定tab数组的位置已经存在值了,那么把新加的值存到已存在的Key对应的值最后 + Node e; K k; + if (p.hash == hash && + ((k = p.key) == key || (key != null && key.equals(k)))) + //如果新添加的值和已存在的Key相同,让e = p; + e = p; + else if (p instanceof TreeNode) + //如果添加的元素是红黑树,则调用TreeNode里面相关的put方法。这里我就不去详情说了,太复杂了,我也没看懂 + e = ((TreeNode)p).putTreeVal(this, tab, hash, key, value); + else { + //Key对应的节点数量 + for (int binCount = 0; ; ++binCount) { + if ((e = p.next) == null) { + //如果是最后一个,把新添加的元素加到最后一个元素后面 + p.next = newNode(hash, key, value, null); + //如果节点元素大于TREEIFY_THRESHOLD-1这个值,调用转红黑树的方法 + 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 + //如果存在相同的元素,onlyIfAbsent这个值为true,就不去覆盖原值 + V oldValue = e.value; + if (!onlyIfAbsent || oldValue == null) + e.value = value; + afterNodeAccess(e); + return oldValue; + } + } + //修改次数加1 + ++modCount; + //size>阀值就会触发扩容,增加两倍 + if (++size > threshold) + resize(); + afterNodeInsertion(evict); + return null; + } + + (2)resize()方法: + final Node[] resize() { + Node[] oldTab = table; + //得到原来tab的容量 + int oldCap = (oldTab == null) ? 0 : oldTab.length; + //原来的阀值 + int oldThr = threshold; + int newCap, newThr = 0; + if (oldCap > 0) { + //如果原容量不为0 + if (oldCap >= MAXIMUM_CAPACITY) { + //原容量大于最大容量,把阀值设置为int能表示的最大值 + //不再继续扩容,把原tab返回 + threshold = Integer.MAX_VALUE; + return oldTab; + } + else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY && + oldCap >= DEFAULT_INITIAL_CAPACITY) + //如果新容量扩容2倍后,小于最大容量,且原容量大于初始化容量 + //阀值扩容2倍 + newThr = oldThr << 1; // double threshold + } + else if (oldThr > 0) // initial capacity was placed in threshold + newCap = oldThr; + else { // zero initial threshold signifies using defaults + //如果阀值和容量都为0,都取默认值 + newCap = DEFAULT_INITIAL_CAPACITY; + newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY); + } + if (newThr == 0) { + //如果阀值没有设置,则为新容量*加载因子 + float ft = (float)newCap * loadFactor; + newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ? + (int)ft : Integer.MAX_VALUE); + } + //把新的阀值赋值给阀值变量 + threshold = newThr; + //新建一个newCap长度的Node[]数组 + @SuppressWarnings({"rawtypes","unchecked"}) + Node[] newTab = (Node[])new Node[newCap]; + //把新tab赋值给table + table = newTab; + //如果原tab有值,扩容之后把原来的元素都放到新的tab数组中去 + if (oldTab != null) { + for (int j = 0; j < oldCap; ++j) { + Node e; + if ((e = oldTab[j]) != null) { + oldTab[j] = null; + if (e.next == null) + newTab[e.hash & (newCap - 1)] = e; + else if (e instanceof TreeNode) + ((TreeNode)e).split(this, newTab, j, oldCap); + else { // preserve order + Node loHead = null, loTail = null; + Node hiHead = null, hiTail = null; + Node 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; + } + (3)treeifyBin()这个方法再提一下,注意第二行(n = tab.length) < MIN_TREEIFY_CAPACITY这个条件,只有tab的元素大于等于64的时候,才真正的转红黑树 + final void treeifyBin(Node[] tab, int hash) { + int n, index; Node e; + if (tab == null || (n = tab.length) < MIN_TREEIFY_CAPACITY) + resize(); + else if ((e = tab[index = (n - 1) & hash]) != null) { + TreeNode hd = null, tl = null; + do { + TreeNode 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); + } + } + + (4)get(key)方法: + public V get(Object key) { + Node e; + //调用了getNode(hash,key)方法 + return (e = getNode(hash(key), key)) == null ? null : e.value; + } + (5)getNode(hash,key)方法: + final Node getNode(int hash, Object key) { + Node[] tab; Node first, e; int n; K k; + if ((tab = table) != null && (n = tab.length) > 0 && + (first = tab[(n - 1) & hash]) != null) { + //数组不为空,长度不为0,在数组中能找到key对应的元素,则继续 + if (first.hash == hash && // always check first node + ((k = first.key) == key || (key != null && key.equals(k)))) + //如果找到的元素刚好是key对应的这个元素,直接返回 + return first; + if ((e = first.next) != null) { + //如果第一个元素后面还有元素 + if (first instanceof TreeNode) + //如果第一个元素是红黑树,则去对应的TreeNode方法中去找 + return ((TreeNode)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; + } + +3.HashMap的源码就分析到这,如果有不准确的地方,欢迎指出,如果有问题的话,也欢迎随时一起探讨 diff --git a/week_01/08/ArrayList-008.md b/week_01/08/ArrayList-008.md new file mode 100644 index 0000000..90a5697 --- /dev/null +++ b/week_01/08/ArrayList-008.md @@ -0,0 +1,284 @@ +# 读源码--ArrayList + +1. ## 继承结构 + + 1. ### 继承类 + + - #### AbstractList + + 2. ### 实现接口 + + - List + - RandomAcces--可随机访问 + - Cloneable--可拷贝 + - java.io.Serializable--可序列化 + +2. ## 属性和方法 + + 1. ### 属性 + + - #### 默认空间(static):DEFAULT_CAPACITY--10 + + - #### 初始化数组(static) + + - ##### EMPTY_ELEMENTDATA: + + - ##### DEFAULTCAPACITY_EMPTY_ELEMENTDATA + + - #### 瞬态对象:elementData + + 2. ### 常用方法 + + 1. #### 构造器 + + - ArrayList(int size) + - ArrayList(Collections c):任意集合转ArrayList。底层实现为数组 + + 2. #### trimToSize():去除空值生成新的集合 + + 3. #### int size() + + 4. #### isEmpty() + + 5. #### contains(Object o) + + 6. #### indexOf(Object o) + + - 若为null,则返回第一个null所在的索引 + - 无则返回-1 + + 7. #### lastIndexOf(Object o) + + 8. #### toArray():转数组 + + 9. #### clear():将数组所有元素置空,便于GC回收 + +3. ## 扩容(调试) + + 1. #### 添加元素add + + 2. #### 最小容量minCapacity(添加元素后的数组长度)与数组容量element.length + + - ##### 初始化时为均为0 + + - ##### 首次添加单个元素后为element.length变为10 + + ``` + private static int calculateCapacity(Object[] elementData, int minCapacity) { + // 首次增加元素容量扩展为默认容量10 + if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) { + return Math.max(DEFAULT_CAPACITY, minCapacity); + } + return minCapacity; + } + ``` + + ``` + private void ensureCapacityInternal(int minCapacity) { + ensureExplicitCapacity(calculateCapacity(elementData, minCapacity)); + } + ``` + + ``` + // 精确扩容 + private void ensureExplicitCapacity(int minCapacity) { + modCount++; + + // overflow-conscious code + if (minCapacity - elementData.length > 0) + grow(minCapacity); + } + ``` + + - ##### 后面超过element.length后依次1.5增长 + + ``` + // 真正执行扩容的方法grow + private void grow(int minCapacity) { + // overflow-conscious code + int oldCapacity = elementData.length; + int newCapacity = oldCapacity + (oldCapacity >> 1); + if (newCapacity - minCapacity < 0) + newCapacity = minCapacity; + 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); + } + ``` + + + + - ###### 注:非1.5倍扩容的情况 + + - ##### add()首次添加单个元素扩容至10 + + - ##### addAll()批量增加后的数组长度大于扩容1.5倍容量时,直接扩容至数组长度 + + ``` + public static void main(String[] args) { + ArrayList list = new ArrayList(); + ArrayList list1 = new ArrayList(); + int count = 10; + int count1 = 8; + for (int i = 0; i < count1; i++) { + list1.add(i); + } + for (int i = 0; i < count; i++) { + list.add(i); + System.out.println(i+":"+list); + } + // 当增加的数组长度大于1.5倍容量的扩容情况 + list.addAll(list1); + } + ``` + +4. ## 增删改查 + + 1. ### 通用方法 + + - #### System.arraycopy()方法 + + ``` + public static native void arraycopy(Object src, int srcPos, + Object dest, int destPos, + int length); + ``` + + 2. ### 增 + + - ##### 末尾增加:boolean add(E e) + + - ##### 指定位置增加 + + ``` + public void add(int index, E element) { + rangeCheckForAdd(index); + + ensureCapacityInternal(size + 1); // Increments modCount!! + // 后续元素依次往后移,添加缓慢,删除同理 + System.arraycopy(elementData, index, elementData, index + 1, + size - index); + elementData[index] = element; + size++; + } + ``` + + - ##### 批量添加 + + ``` + public boolean addAll(Collection c) { + Object[] a = c.toArray(); + int numNew = a.length; + // 此时的容量(size + numNew)可能超过扩容后1.5倍,则扩容后的容量为(size + numNew) + ensureCapacityInternal(size + numNew); // Increments modCount + System.arraycopy(a, 0, elementData, size, numNew); + size += numNew; + return numNew != 0; + } + ``` + + ``` + private void grow(int minCapacity) { + // overflow-conscious code + int oldCapacity = elementData.length; + int newCapacity = oldCapacity + (oldCapacity >> 1); + + // 需求容量minCapacity大于扩容后的容量newCapacity + if (newCapacity - minCapacity < 0) + newCapacity = minCapacity; + 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); + } + ``` + + - ##### 指定位置批量添加 + + 3. ### 删除 + + - ##### 指定下标 + + ``` + public E remove(int index) { + rangeCheck(index); + + modCount++; + E oldValue = elementData(index); + // 下标为index的元素,实际是数组的第index+1个元素 + int numMoved = size - index - 1; + if (numMoved > 0) + System.arraycopy(elementData, index+1, elementData, index, + numMoved); + elementData[--size] = null; // clear to let GC do its work + + return oldValue; + } + ``` + + - ##### 指定对象 + + ``` + public boolean remove(Object o) { + if (o == null) { + for (int index = 0; index < size; index++) + if (elementData[index] == null) { + fastRemove(index); + return true; + } + } else { + for (int index = 0; index < size; index++) + if (o.equals(elementData[index])) { + fastRemove(index); + return true; + } + } + return false; + } + ``` + + - ##### 指定下标范围批量移除 + + 4. ### 改 + + 1. #### set(int index, E element):替换指定位置的元素 + + 5. ### 查 + + 1. #### get(int index) + + 2. #### 迭代方法 + + - ##### ListIterator listIterator(int index):类似String的subString + + - ##### ListIterator listIterator() + + 3. #### List subList(int fromIndex, int toIndex): + + - ##### 返回的是ArrayList的内部类--SubList + + - ##### 该SubList无法转换为ArrayList,只是ArrayList的一个视图 + + - ###### 对父子类做的非结构性修改,都会影响到彼此 + + - ###### 对子List做结构性修改,操作会反映到父List上 + + - ###### 对父List做结构性修改,会抛出异常ConcurrentModificationException + + - 若需要对subList进行修改,有不想动原list,那么可以创建subList的一个拷贝 + + ``` + subList = Lists.newArrayList(subList); + list.stream().skip(strart).limit(end).collect(Collectors.toList()); + ``` + + + + 4. + + - + + + + \ No newline at end of file diff --git a/week_01/08/ArrayList-008.xmind b/week_01/08/ArrayList-008.xmind new file mode 100644 index 0000000..8d522a9 Binary files /dev/null and b/week_01/08/ArrayList-008.xmind differ diff --git a/week_01/08/HashMap-008.md b/week_01/08/HashMap-008.md new file mode 100644 index 0000000..3370da4 --- /dev/null +++ b/week_01/08/HashMap-008.md @@ -0,0 +1,341 @@ +# 读源码--HashMap + +1. ## 继承体系 + + 1. ### 继承抽象类AbstractHashMap + + 2. ### 实现接口List,Cloneable,Serializable + +2. ## 常规属性与方法 + + 1. ### 重要静态属性 + + ``` + // 默认初始化容量为16 + static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16 + // 最大容量2的30次方 + static final int MAXIMUM_CAPACITY = 1 << 30; + // 默认负载因子0.75 + static final float DEFAULT_LOAD_FACTOR = 0.75f; + // 转红黑树阈值8 + static final int TREEIFY_THRESHOLD = 8; + // 转链表阈值6 + static final int UNTREEIFY_THRESHOLD = 6; + // 转树的最小容量64,哈希表的容量小于64,会先进行扩容;不能小于4*TREEIFY_THRESHOLD + static final int MIN_TREEIFY_CAPACITY = 64; + ``` + + 2. ### 内部类Node(final?) + + 3. ### 方法 + + 1. #### hash方法:将key进行hash重算,让key分别更均匀 + + ``` + static final int hash(Object key) { + int h; + return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16); + } + ``` + + + + 2. #### comparableClassFor(Object o):判断该对象是否实现Comparable接口 + + 3. #### compareComparables(Class kc, Object k, Object x):k实现Comparable接口,若x为kc类,比较k与x + + 4. #### tableSizeFor(int cap):得到大于或等于给定cap的最小二次幂,如cap为15,16,返回的都是16.(位运算技巧牛逼) + + 4. ### 成员属性 + + ``` + // + transient Node[] table; + transient Set> entrySet; + transient int size; + transient int modCount; + int threshold; //下次扩容的阈值 + final float loadFactor;//负载因子 + ``` + + + + 5. ### 构造器 + + 1. #### HashMap(int initialCapacity, float loadFactor) + + 2. #### HashMap(int initialCapacity) + + 3. #### HashMap() + + 4. #### public HashMap(Map m) + + 6. ### 常规方法 + + 1. #### size() + + 2. #### isEmpty() + + 3. #### containKey(Object key) + + 4. #### containKey(Object value) + + 5. #### clear():清空map + +3. ## 底层方法 + + 1. ### putMapEntries方法: + + ``` + final void putMapEntries(Map 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); + } + // 当map大小大于当前阈值时,扩容 + else if (s > threshold) + resize(); + // 赋值 + for (Map.Entry e : m.entrySet()) { + K key = e.getKey(); + V value = e.getValue(); + putVal(hash(key), key, value, false, evict); + } + } + } + ``` + + + + 2. ### putVal方法:赋值 + + ``` + final V putVal(int hash, K key, V value, boolean onlyIfAbsent, + boolean evict) { + Node[] tab; Node p; int n, i; + // 判断tab是否为null + if ((tab = table) == null || (n = tab.length) == 0) + n = (tab = resize()).length; + // 若对应索引下tab值为空,则直接插入 + if ((p = tab[i = (n - 1) & hash]) == null) + tab[i] = newNode(hash, key, value, null); + // 若存在,说明存在相同hash值。1:key值相同,说明存在该key了 2:key值不同,hash冲突 + else { + Node e; K k; + // key值相同 + if (p.hash == hash && + ((k = p.key) == key || (key != null && key.equals(k)))) + e = p; + // key值不同 + // p为树节点 + else if (p instanceof TreeNode) + e = ((TreeNode)p).putTreeVal(this, tab, hash, key, value); + // p为链表 + else { + for (int binCount = 0; ; ++binCount) { + // 插入到链表尾部 + if ((e = p.next) == null) { + p.next = newNode(hash, key, value, null); + // 超过转树的阈值,转为树节点,-1是因为binCount从0开始 + 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; + } + ``` + + ![](E:\Project\JavaStudy\week_01\08\HashMap-put执行流程示意图.jpg) + + 3. ### reSize():扩容 + + ``` + final Node[] resize() { + Node[] 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) + newThr = oldThr << 1; // double threshold + } + else if (oldThr > 0) // initial capacity was placed in threshold + newCap = oldThr; + else { // zero initial threshold signifies using defaults + newCap = DEFAULT_INITIAL_CAPACITY; + newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY); + } + if (newThr == 0) { + float ft = (float)newCap * loadFactor; + newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ? + (int)ft : Integer.MAX_VALUE); + } + threshold = newThr; + @SuppressWarnings({"rawtypes","unchecked"}) + Node[] newTab = (Node[])new Node[newCap]; + table = newTab; + if (oldTab != null) { + for (int j = 0; j < oldCap; ++j) { + Node e; + if ((e = oldTab[j]) != null) { + oldTab[j] = null; + if (e.next == null) + newTab[e.hash & (newCap - 1)] = e; + else if (e instanceof TreeNode) + ((TreeNode)e).split(this, newTab, j, oldCap); + else { // preserve order + Node loHead = null, loTail = null; + Node hiHead = null, hiTail = null; + Node 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; + } + ``` + + 4. ### treeifyBin():转树(树结构不懂,待学习) + + ``` + final void treeifyBin(Node[] tab, int hash) { + int n, index; Node e; + // 桶大小小于最小转树容量,先扩容 + if (tab == null || (n = tab.length) < MIN_TREEIFY_CAPACITY) + resize(); + else if ((e = tab[index = (n - 1) & hash]) != null) { + TreeNode hd = null, tl = null; + do { + TreeNode 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); + } + } + ``` + + + +4. ## 增删改查 + + 1. ### 查: + + 1. #### get(Object key) + + 2. #### getNode(int hash, Object key) + + ``` + final Node getNode(int hash, Object key) { + Node[] tab; Node 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; + // 当存在hash冲突时,进行链表查询 + if ((e = first.next) != null) { + // 若为树,则进行树节点查询(链表长度超过8会转为红黑树) + if (first instanceof TreeNode) + return ((TreeNode)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; + } + ``` + + + + 2. ### 增 + + 1. #### put(K key,V value):增或改 + + 2. #### putAll(Map m):增加map + + 3. ### 删 + + 1. #### remove(Object key) + + 2. #### remove(K key,V value): + + 4. ### 改 + + 1. #### put(K key,V value): + + 2. #### replace(K key, V oldValue, V newValue) + + 5. #### 遍历 + + 1. #### Set keySet() :将map中所有键放入Set中,通过遍历Set达到遍历map键的目的 + + 2. #### Collections values():获取map中的所有值 + + 3. #### Set> entrySet():遍历map键和值 \ No newline at end of file diff --git a/week_01/08/HashMap-008.xmind b/week_01/08/HashMap-008.xmind new file mode 100644 index 0000000..d31c054 Binary files /dev/null and b/week_01/08/HashMap-008.xmind differ diff --git "a/week_01/08/HashMap-put\346\211\247\350\241\214\346\265\201\347\250\213\347\244\272\346\204\217\345\233\276.jpg" "b/week_01/08/HashMap-put\346\211\247\350\241\214\346\265\201\347\250\213\347\244\272\346\204\217\345\233\276.jpg" new file mode 100644 index 0000000..e5e4a2e Binary files /dev/null and "b/week_01/08/HashMap-put\346\211\247\350\241\214\346\265\201\347\250\213\347\244\272\346\204\217\345\233\276.jpg" differ diff --git a/week_01/08/LinkedList-008.md b/week_01/08/LinkedList-008.md new file mode 100644 index 0000000..e007585 --- /dev/null +++ b/week_01/08/LinkedList-008.md @@ -0,0 +1,239 @@ +# 读源码--LinkedList + +1. ## 继承体系 + + 1. ### 示意图 + + ### + + 2. ### 继承分析 + + 1. #### 继承AbstractSequentialList + + 2. #### 实现 + + - RandomAcces--可随机访问 + - Cloneable--可拷贝 + - java.io.Serializable--可序列化 + +2. ## 属性和方法 + + 1. ### 属性 + + ``` + transient int size = 0; // 大小 + transient Node first; // 首节点 + transient Node last; // 末节点 + ``` + + 2. ### 构造器 + + ``` + public LinkedList() { + } + + public LinkedList(Collection c) { + this(); + addAll(c); + } + ``` + + 3. ### 节点方法(非环链表首节点的prev为null,尾节点的next为null) + + 1. 设置元素为首节点 + + ``` + private void linkFirst(E e) { + final Node f = first; + final Node newNode = new Node<>(null, e, f); + first = newNode; + // 判断原链表是否为空 + // 为空则把last也置为新节点 + // 否则把原首节点的prev置为新节点 + if (f == null) + last = newNode; + else + f.prev = newNode; + size++; + modCount++; + } + ``` + + 2. 设置元素为末节点 + + ``` + void linkLast(E e) { + final Node l = last; + final Node newNode = new Node<>(l, e, null); + last = newNode; + // 判断原链表是否为空 + // 为空则把first也置为新节点 + // 否则把原首节点的prev置为新节点 + if (l == null) + first = newNode; + else + l.next = newNode; + size++; + modCount++; + } + ``` + + 3. 插入 + + ``` + void linkBefore(E e, Node succ) { + // assert succ != null; + // 获取首节点的上个节点 + final Node pred = succ.prev; + final Node newNode = new Node<>(pred, e, succ); + succ.prev = newNode; + //若上个节点为空,则新节点为首节点 + if (pred == null) + first = newNode; + else + pred.next = newNode; + size++; + modCount++; + } + ``` + + 4. 删除首节点 + + private E unlinkFirst(Node f) { + // assert f == first && f != null; + final E element = f.item; + final Node next = f.next; + f.item = null; + f.next = null; // help GC + first = next; + //只有一个节点的情况下 + if (next == null) // 下个节点为空 + last = null; + else + next.prev = null; + size--; + modCount++; + return element; + } + + 5. 删除末节点 + + ``` + private E unlinkLast(Node l) { + // assert l == last && l != null; + final E element = l.item; + final Node prev = l.prev; + l.item = null; + l.prev = null; // help GC + last = prev; + //只有一个节点的情况下 + if (prev == null) + first = null; + else + prev.next = null; + size--; + modCount++; + return element; + } + ``` + + 6. 删除任意节点 + + ``` + E unlink(Node x) { + // assert x != null; + final E element = x.item; + final Node next = x.next; + final Node prev = x.prev; + + if (prev == null) { + first = next; + } else { + prev.next = next; + x.prev = null; + } + + if (next == null) { + last = prev; + } else { + next.prev = prev; + x.next = null; + } + + x.item = null; + size--; + modCount++; + return element; + } + ``` + + 7. 查询 + + - getFirst() + + - getLast() + + - Node node(int index) + + ``` + Node node(int index) { + // assert isElementIndex(index); + + if (index < (size >> 1)) { + Node x = first; + for (int i = 0; i < index; i++) + x = x.next; + return x; + } else { + Node x = last; + for (int i = size - 1; i > index; i--) + x = x.prev; + return x; + } + } + ``` + + + + 4. ### 常用方法 + + 1. contains(Object o) + 2. size() + 3. clear() + +3. ## 增删改查 + + 1. ### 增 + + - void addFirst(E e):首端添加 + - void addLast(E e):末端添加 + - boolean add():同addLast(),区别是该方法有返回值 + - boolean (Collection c):添加集合 + - boolean (int index, Collection c):指定位置添加集合 + - void add(int index, E element) :指定位置添加元素 + + 2. ### 删 + + - boolean remove(Object o) :删除linkedList中值为o的节点 + - E remove(int index) + + 3. ### 改 + + - E set(int index, E element) + + 4. ### 查 + + - E get(int index) :查询下标为index的节点的值 + - boolean isElementIndex(int index): + - boolean isPositionIndex(int index): + - int indexOf(Object o) + - int lastIndexOf(Object o) + + 5. ### 迭代器 + + 1. ListIterator listIterator(int index) + + + +### + diff --git a/week_01/08/LinkedList-008.xmind b/week_01/08/LinkedList-008.xmind new file mode 100644 index 0000000..d9dde51 Binary files /dev/null and b/week_01/08/LinkedList-008.xmind differ diff --git "a/week_01/08/LinkedList\347\273\247\346\211\277\344\275\223\347\263\273.jpg" "b/week_01/08/LinkedList\347\273\247\346\211\277\344\275\223\347\263\273.jpg" new file mode 100644 index 0000000..5cc9ddb Binary files /dev/null and "b/week_01/08/LinkedList\347\273\247\346\211\277\344\275\223\347\263\273.jpg" differ diff --git a/week_01/08/README.md b/week_01/08/README.md index 5bb53d0..b122c48 100644 --- a/week_01/08/README.md +++ b/week_01/08/README.md @@ -6,8 +6,8 @@ ### 一、学习周期(2个月) -| 时间 | 内容 | -| :------------------------------- | ------- | +| 时间 | 内容 | +| :------------------------------- | ------- | | 第一周 (2019/12/09-2019/12.15) | jdk || | 第二周 (2019/12/16-2019/12.22) | jdk || | 第三周 (2019/12/23-2019/12.29) | jdk || @@ -32,10 +32,11 @@ #### 3、review5名其他的学习笔记或学习总结 ​ 在项目的`Pull requests`可以看到其他人的Pull requests记录,并进行review。 - + ###三、源码提交流程 + - 先将[xxx]仓库 `fork` 到自己的 GitHub 账号下。 - 将 `fork` 后的仓库 `clone` 到本地,然后在本地新建、修改自己的源码学习笔记,**注意:** 仅允许在和自己编号对应的目录下新建或修改自己的源码学习笔记。完成后,将相关修改部分 `push` 到自己的 GitHub 远程仓库。 - 当完成本周作业,提交 `Pull Request`申请给[xxx]仓库,Pull 作业时,必须备注自己的编号和提交第几周的作业,如`007-week 02`,是指编号为`007`的成员提交的`第二周`的源码学习笔记。 @@ -44,4 +45,8 @@ -ps:任何学习上的问题可以发布issure求助,其他同学有时间就帮忙看看哈。 \ No newline at end of file +ps:任何学习上的问题可以发布issure求助,其他同学有时间就帮忙看看哈。 + + + +# \ No newline at end of file diff --git a/week_01/09/ArrayList-009.md b/week_01/09/ArrayList-009.md new file mode 100644 index 0000000..206ac3c --- /dev/null +++ b/week_01/09/ArrayList-009.md @@ -0,0 +1,215 @@ +##关于ArrayList小笔记 + + +###1、认识ArrayList + +1)ArrayList就是动态数组 + + ```java +public class ArrayList extends AbstractList + implements List, RandomAccess, Cloneable, java.io.Serializable + ``` + +2)实现了List,提供了基础的添加、删除等操作; + +3)实现Cloneable; + +4)实现java.io.Serializable,可被序列化; + +5)实现RandomAccess,可随机访问。 + +###2、属性概览 + +```java +/** + * 默认初始化大小. + */ + private static final int DEFAULT_CAPACITY = 10; + /** + * 空数组 当创建实例数量为空时使用. + */ + private static final Object[] EMPTY_ELEMENTDATA = {}; + /** + * 存储元素的数组. + */ + transient Object[] elementData;// non-private to simplify nested class access + /** + * 数组大小. + */ + private int size; + +``` + +###3、构造器 +```java +/** + * 创建指定大小数组 + * @param 数组大小 + */ +public ArrayList(int initialCapacity) { + super(); + // 如果传入大小小于0,抛出异常 + if (initialCapacity < 0) + throw new IllegalArgumentException("Illegal Capacity: "+ + initialCapacity); + // 创建指定容量大小新数组 + this.elementData = new Object[initialCapacity]; +} + +/** + * 默认构造器为空数组,使用时按默认大小 + */ + public ArrayList() { + super(); + this.elementData = EMPTY_ELEMENTDATA; +} + +/** + * 将传入的集合转成数组 + */ + public ArrayList(Collection c) { + elementData = c.toArray(); + size = elementData.length; + // c.toArray might (incorrectly) not return Object[] (see 6260652) + //see 6260652为bug编号 https://bugs.java.com/bugdatabase/view_bug.do?bug_id=6260652 + if (elementData.getClass() != Object[].class) + elementData = Arrays.copyOf(elementData, size, Object[].class); +} + +``` +ps:针对最后一类创建数组方式为什么返回不一定是一个对象,测试如下: + +```java +//第一种方式 +List test1 = new ArrayList( ); +test1.add("123"); +System.out.println(test1.toArray()); +//返回结果:[Ljava.lang.Object;@a74868d + +//第二种方式 +List test2 = Arrays.asList("123"); +System.out.println(test2.toArray()); +//返回结果:[Ljava.lang.String;@12c8a2c0 +``` + +### 4、add +1)add(E e) + +```java +/** + * 添加数组,从末尾开始添加 + */ +public boolean add(E e) { + //检查是否需要扩容 + ensureCapacityInternal(size + 1); // Increments modCount!! + //将元素添加到数组最后一位 + elementData[size++] = e; + return true; +} + +private void ensureCapacityInternal(int minCapacity) { + //如果是空数组,则初始化默认大小10 + if (elementData == EMPTY_ELEMENTDATA) { + minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity); + } + ensureExplicitCapacity(minCapacity); +} + +private void ensureExplicitCapacity(int minCapacity) { + //用于统计list被修改的次数 + modCount++; + // overflow-conscious code + if (minCapacity - elementData.length > 0) + //扩容 + grow(minCapacity); +} + +private void grow(int minCapacity) { + // overflow-conscious code + int oldCapacity = elementData.length; + //扩容1.5倍 + int newCapacity = oldCapacity + (oldCapacity >> 1); + //如果新容量小于需要最小容量,则以需要容量为准 + if (newCapacity - minCapacity < 0) + newCapacity = minCapacity; + //如果新容量大于最大容量,则以最大容量为准 + 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); +} +``` +2)add(int index, E element) + +``` java +/** + * 添加元素到指定位置 + */ +public void add(int index, E element) { + //检查是否越界 + rangeCheckForAdd(index); + //检查是否需要扩容 + ensureCapacityInternal(size + 1); // Increments modCount!! + //将数组index之后的元素往后移动一位 + System.arraycopy(elementData, index, elementData, index + 1, + size - index); + //再将index设置为需要添加的元素 + elementData[index] = element; + //数组长度+1 + size++; +} +``` + +###5、get + +``` java +/** + * 获得指定位置该元素 + */ +public E get(int index) { + ///检查是否越界 + rangeCheck(index); + //返回元素 + return elementData(index); +} + + +private void rangeCheck(int index) { + if (index < 0 || index >= this.size) + throw new IndexOutOfBoundsException(outOfBoundsMsg(index)); +} + +private void checkForComodification() { + //保证操作为同一list(考虑并发情况下) + if (ArrayList.this.modCount != this.modCount) + throw new ConcurrentModificationException(); +} + +``` + +###6、remove + +``` java +/** + * 删除指定位置元素 + */ +public E remove(int index) { + //检查是否越界 + rangeCheck(index); + + modCount++; + //获取index位置元素 + E oldValue = elementData(index); + //如果index不是最后一位,则把index之后的元素往前挪一位 + int numMoved = size - index - 1; + if (numMoved > 0) + System.arraycopy(elementData, index+1, elementData, index, + numMoved); + //// 将最后一个元素删除,帮助GC + elementData[--size] = null; // clear to let GC do its work + //返回删除旧值 + return oldValue; +} + +``` diff --git a/week_01/09/HashMap-009.md b/week_01/09/HashMap-009.md new file mode 100644 index 0000000..d7c70fe --- /dev/null +++ b/week_01/09/HashMap-009.md @@ -0,0 +1,445 @@ +##关于HashMap + +###1、认识HashMap +1)采用key/value存储结构,每个key对应唯一的value,查询和修改的速度都很快 + +```java +public class HashMap extends AbstractMap + implements Map, Cloneable, Serializable { +``` +2)实现Cloneable + +3)实现Serializable ,可序列化 + +4)继承AbstractMap,实现Map接口,可实现Map功能。 + + +###2、属性 + +```java +/** + * 默认初始容量16 + */ +static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16 + +/** + * 最大的容量为2的30次方 + */ +static final int MAXIMUM_CAPACITY = 1 << 30; + +/** + * 默认装载因子 + */ +static final float DEFAULT_LOAD_FACTOR = 0.75f; + +/** + * 当一个桶中的元素个数大于等于8时进行树化 + */ +static final int TREEIFY_THRESHOLD = 8; + +/** + * 当一个桶中的元素个数小于等于6时把树转化为链表 + */ +static final int UNTREEIFY_THRESHOLD = 6; + +/** + * 当桶的个数达到64的时候才进行树化 + */ +static final int MIN_TREEIFY_CAPACITY = 64; + +/** + * 数组,桶(bucket) + */ +transient Node[] table; + +/** + * 作为entrySet()的缓存 + */ +transient Set> entrySet; + +/** + * 元素的数量 + */ +transient int size; + +/** + * 修改次数,用于在迭代的时候执行快速失败策略 + */ +transient int modCount; + +/** + * 当桶的使用数量达到多少时进行扩容,threshold = capacity * loadFactor + */ +int threshold; + +/** + * 装载因子 + */ +final float loadFactor; + +``` +###3、Node类 + +```java +/** + * 典型的单链表节点,其中,hash用来存储key计算得来的hash值。 + */ +static class Node implements Map.Entry { + final int hash; + final K key; + V value; + Node next; +``` + +###4、TreeNode类 + +```java +/** + * 典型的树型节点,其中,prev是链表中的节点,用于在删除元素的时候可以快速找到它的前置节点。 + */ +static final class TreeNode extends LinkedHashMap.Entry { + TreeNode parent; // red-black tree links + TreeNode left; + TreeNode right; + TreeNode prev; // needed to unlink next upon deletion + boolean red; + +``` +###5、构造方法 + +1) HashMap(int initialCapacity, float loadFactor) + +```java + +/** + * 创建一个hashmap + * @param initialCapacity 初始化容量大小 + * @param loadFactor 默认装载因子 + * @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); +} + +/** + * Returns a power of two size for the given target capacity. + */ +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; +} + + +``` +2) HashMap(int initialCapacity) + +```java +/** + * 创建一个hashmap + * 传入初始化容量大小,装载因子为默认值 + */ +public HashMap(int initialCapacity) { + this(initialCapacity, DEFAULT_LOAD_FACTOR); +} +``` + +3)HashMap() + +``` java +/** + * 创建一个默认值hashmap + */ +public HashMap() { + this.loadFactor = DEFAULT_LOAD_FACTOR; // all other fields defaulted +} +``` + +###6、put方法 + +``` java +/** + * hashmap 添加新元素 + */ +public V put(K key, V value) { + //调用hash(key)计算出key的hash值 + return putVal(hash(key), key, value, false, true); +} + +static final int hash(Object key) { + int h; + //解释(h = key.hashCode()) ^ (h >>> 16): + //调用key的hashCode(),且让高16位与整个hash异或,这样做是为了使计算出的hash更分散 + return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16); +} + +final V putVal(int hash, K key, V value, boolean onlyIfAbsent, + boolean evict) { + Node[] tab; Node p; int n, i; + //如果桶中元素为空 + if ((tab = table) == null || (n = tab.length) == 0) + //调用resize初始化 + n = (tab = resize()).length; + //(n - 1) & hash 计算元素在哪个桶中 + //如果这个桶中还没有元素,则把这个元素放在桶中的第一个位置 + if ((p = tab[i = (n - 1) & hash]) == null) + tab[i] = newNode(hash, key, value, null); + else { //如果桶中存在元素 + Node e; K k; + // 如果桶中第一个元素的key与待插入元素的key相同,保存到e中用于后续修改value值 + if (p.hash == hash && + ((k = p.key) == key || (key != null && key.equals(k)))) + e = p; + else if (p instanceof TreeNode) + // 如果第一个元素是树节点,则调用树节点的putTreeVal插入元素 + e = ((TreeNode)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); + if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st + treeifyBin(tab, hash); + break; + } + + // 如果待插入的key在链表中找到了,则退出循环 + if (e.hash == hash && + ((k = e.key) == key || (key != null && key.equals(k)))) + break; + p = e; + } + } + + // 如果找到了对应key的元素 + if (e != null) { // existing mapping for key + // 记录下旧值 + V oldValue = e.value; + // 判断是否需要替换旧值 + if (!onlyIfAbsent || oldValue == null) + e.value = value; + afterNodeAccess(e); + //返回旧值 + return oldValue; + } + } + + ++modCount; + + // 元素数量加1,判断是否需要扩容。 + if (++size > threshold) + resize(); + afterNodeInsertion(evict); + //如果没有找到,则返回null + return null; +} +``` +####ps resize方法 + +``` java +/** + * 扩容方法 + */ +final Node[] resize() { + Node[] 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; + } + + // 如果旧容量的两倍小于最大容量并且旧容量大于默认初始容量(16),则容量扩大为两部,扩容门槛也扩大为两倍 + else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY && + oldCap >= DEFAULT_INITIAL_CAPACITY) + newThr = oldThr << 1; // double threshold + } + else if (oldThr > 0) + // 如果旧容量为0且旧扩容门槛大于0,则把新容量赋值为旧门槛 + newCap = oldThr; + else { + // 如果旧容量旧扩容门槛都是0,说明还未初始化过,则初始化容量为默认容量,扩容门槛为默认容量*默认装载因子 + 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; + // 新建一个新容量的数组 + @SuppressWarnings({"rawtypes","unchecked"}) + Node[] newTab = (Node[])new Node[newCap]; + // 把桶赋值为新数组 + table = newTab; + // 如果旧数组不为空,则搬移元素 + if (oldTab != null) { + for (int j = 0; j < oldCap; ++j) { + Node e; + if ((e = oldTab[j]) != null) { + oldTab[j] = null; + if (e.next == null) + newTab[e.hash & (newCap - 1)] = e; + else if (e instanceof TreeNode) + ((TreeNode)e).split(this, newTab, j, oldCap); + else { // preserve order + Node loHead = null, loTail = null; + Node hiHead = null, hiTail = null; + Node 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; +} + +``` + +###7、get方法 + +``` java +/** + * 获取某一键值 + */ +public V get(Object key) { + Node e; + return (e = getNode(hash(key), key)) == null ? null : e.value; +} + +final Node getNode(int hash, Object key) { + Node[] tab; Node first, e; int n; K k; + // 如果桶的数量大于0并且待查找的key所在的桶的第一个元素不为空 + 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)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; +} +``` +###8、remove方法 + +```java +/** + * 删除某一键值 + */ +public V remove(Object key) { + Node e; + return (e = removeNode(hash(key), key, null, false, true)) == null ? + null : e.value; +} + +final Node removeNode(int hash, Object key, Object value, + boolean matchValue, boolean movable) { + Node[] tab; Node p; int n, index; + // 如果桶的数量大于0且待删除的元素所在的桶的第一个元素不为空 + if ((tab = table) != null && (n = tab.length) > 0 && + (p = tab[index = (n - 1) & hash]) != null) { + Node node = null, e; K k; V v; + // 如果第一个元素正好就是要找的元素,赋值给node变量后续删除使用 + if (p.hash == hash && + ((k = p.key) == key || (key != null && key.equals(k)))) + node = p; + else if ((e = p.next) != null) { + // 如果第一个元素是树节点,则以树的方式查找节点 + if (p instanceof TreeNode) + node = ((TreeNode)p).getTreeNode(hash, key); + else { + // 否则遍历整个链表查找元素 + do { + if (e.hash == hash && + ((k = e.key) == key || + (key != null && key.equals(k)))) { + node = e; + break; + } + p = e; + } while ((e = e.next) != null); + } + } + + // 如果找到了元素,则看参数是否需要匹配value值,如果不需要匹配直接删除,如果需要匹配则看value值是否与传入的value相等 + if (node != null && (!matchValue || (v = node.value) == value || + (value != null && value.equals(v)))) { + // 如果是树节点,调用树的删除方法(以node调用的,是删除自己) + if (node instanceof TreeNode) + ((TreeNode)node).removeTreeNode(this, tab, movable); + // 如果待删除的元素是第一个元素,则把第二个元素移到第一的位置 + else if (node == p) + tab[index] = node.next; + //删除node节点 + else + p.next = node.next; + ++modCount; + --size; + //删除节点后处理 + afterNodeRemoval(node); + return node; + } + } + return null; +} +``` + \ No newline at end of file diff --git a/week_01/1/README.md b/week_01/1/README.md deleted file mode 100644 index 5bb53d0..0000000 --- a/week_01/1/README.md +++ /dev/null @@ -1,47 +0,0 @@ -## 源码刻意学习小组 - -[TOC] - - - -### 一、学习周期(2个月) - -| 时间 | 内容 | -| :------------------------------- | ------- | -| 第一周 (2019/12/09-2019/12.15) | jdk || -| 第二周 (2019/12/16-2019/12.22) | jdk || -| 第三周 (2019/12/23-2019/12.29) | jdk || -| 第四周 (2019/12/30-2020/01/05) | jdk || -| 第五周 (2020/01/06-2020/01/12) | Spring || -| 第六周 (2020/01/13-2020/01/19) | Spring || -| 第七周 (2020/01/20-2020/01/26) | MyBatis || -| 第八周 (2020/01/27-2020/02/02) | MyBatis || - - - -### 二、作业 - -#### 1、源码学习笔记(必做) - -​ 至少提交2个类的源码分析笔记 - -#### 2、本周学习总结(可选) - -​ 学习总结直接在GitHub的issue上发布即可。 - -#### 3、review5名其他的学习笔记或学习总结 - -​ 在项目的`Pull requests`可以看到其他人的Pull requests记录,并进行review。 - - - -###三、源码提交流程 -- 先将[xxx]仓库 `fork` 到自己的 GitHub 账号下。 -- 将 `fork` 后的仓库 `clone` 到本地,然后在本地新建、修改自己的源码学习笔记,**注意:** 仅允许在和自己编号对应的目录下新建或修改自己的源码学习笔记。完成后,将相关修改部分 `push` 到自己的 GitHub 远程仓库。 -- 当完成本周作业,提交 `Pull Request`申请给[xxx]仓库,Pull 作业时,必须备注自己的编号和提交第几周的作业,如`007-week 02`,是指编号为`007`的成员提交的`第二周`的源码学习笔记。 -- 源码学习笔记的命名规则:**`内容标题-编号`**,比如学号为 `007` 的成员完成`ArrayList`类后,请将源码学习笔记名保存为 `ArrayList-007 `。(内容标题自定义) -- 务必按照Pull Request的备注形式和作业文件的命名进行提交,这样方便统计。 - - - -ps:任何学习上的问题可以发布issure求助,其他同学有时间就帮忙看看哈。 \ No newline at end of file diff --git a/week_01/11/ArrayList.md b/week_01/11/ArrayList.md new file mode 100644 index 0000000..89489a1 --- /dev/null +++ b/week_01/11/ArrayList.md @@ -0,0 +1,59 @@ +ava.util.ArrayList ǷdzҪһ࣬ڴй㷺ʹãEʾͣArrayListһࡣ ArrayList൱C++ vectorڴ洢鲻ͬһȹ̶ArrayListijǶ̬ģƣԴ洢Ķ󣬵ֻܴ洢󣬲ܴ洢ԭint + +import java.util.ArrayList; public class TestArrayList { public static void main(String[] args) { // Create a list to store cities ArrayList cityList = new ArrayList(); + + // Add some cities in the list + cityList.add("London"); + // cityList now contains [London] + + cityList.add("Denver"); + // cityList now contains [London, Denver] + + cityList.add("Paris"); + // cityList now contains [London, Denver, Paris] + + cityList.add("Miami"); + // cityList now contains [London, Denver, Paris, Miami] + + cityList.add("Seoul"); + // Contains [London, Denver, Paris, Miami, Seoul] + + cityList.add("Tokyo"); + // Contains [London, Denver, Paris, Miami, Seoul, Tokyo] + + System.out.println("List size? " + cityList.size()); // 6 + System.out.println("Is Miami in the list? " + cityList.contains("Miami")); // true + System.out.println("The location of Denver in the list? " + cityList.indexOf("Denver")); // 1 listУ-1 + System.out.println("Is the list empty? " + cityList.isEmpty()); // Print false + + // Insert a new city at index 2 + cityList.add(2, "Xian"); + // Contains [London, Denver, Xian, Paris, Miami, Seoul, Tokyo] + + // Remove a city from the list + cityList.remove("Miami"); + // Contains [London, Denver, Xian, Paris, Seoul, Tokyo] + + // Remove a city at index 1 + cityList.remove(1); + // Contains [London, Xian, Paris, Seoul, Tokyo] + + // Display the contents in the list + System.out.println(cityList.toString()); + + // Display the contents in the list in reverse order + for (int i = cityList.size() - 1; i >= 0; i--) + System.out.print(cityList.get(i) + " "); + System.out.println(); + + // Create a list to store two circles + ArrayList list = new ArrayList(); + + // Add two circles + list.add(new CircleFromSimpleGeometricObject(2)); + list.add(new CircleFromSimpleGeometricObject(3)); + + // Display the area of the first circle in the list + System.out.println("The area of the circle? " + list.get(0).getArea()); +} +} \ No newline at end of file diff --git a/week_01/11/HashMap.md b/week_01/11/HashMap.md new file mode 100644 index 0000000..1e99d18 --- /dev/null +++ b/week_01/11/HashMap.md @@ -0,0 +1,48 @@ +HashMapJDK1.8֮ǰʵַʽ +,JDK1.8HashMap˵ײŻ,Ϊ ++ʵ,ҪĿ߲Чʡ + +1.̳йϵ + +public class HashMap extends AbstractMap implements Map, Cloneable, Serializable + +2.&췽 //޶ֵ ڵ8ʱתΪ洢 static final int TREEIFY_THRESHOLD = 8; //ڵС6ʱתΪ洢 static final int UNTREEIFY_THRESHOLD = 6; //СΪ 64 static final int MIN_TREEIFY_CAPACITY = 64; //HashMapʼС static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16 //HashMap static final int MAXIMUM_CAPACITY = 1 << 30; //ĬϴС static final float DEFAULT_LOAD_FACTOR = 0.75f; //NodeMap.Entryӿڵʵ //ڴ˴洢ݵNode2 //ÿһNodeʶһ transient Node[] table; //HashMapС,HashMapļֵԵĶ transient int size; //HashMapıĴ transient int modCount; //һHashMapݵĴС int threshold; //洢ӵij final float loadFactor; + +//ĬϵĹ캯 public HashMap() { this.loadFactor = DEFAULT_LOAD_FACTOR; // all other fields defaulted } //ָС public HashMap(int initialCapacity) { this(initialCapacity, DEFAULT_LOAD_FACTOR); } //ָС͸ӴС public HashMap(int initialCapacity, float loadFactor) { //ָСС0,׳IllegalArgumentException쳣 if (initialCapacity < 0) throw new IllegalArgumentException("Illegal initial capacity: " + initialCapacity); //жָСǷHashMap if (initialCapacity > MAXIMUM_CAPACITY) initialCapacity = MAXIMUM_CAPACITY; //ָĸӲС0ΪNullж׳IllegalArgumentException쳣 if (loadFactor <= 0 || Float.isNaN(loadFactor)) throw new IllegalArgumentException("Illegal load factor: " + loadFactor); + + this.loadFactor = loadFactor; + // áHashMapֵHashMapд洢ݵﵽthresholdʱҪHashMapӱ + this.threshold = tableSizeFor(initialCapacity); +} +//һMap,MapԪMap.EntryȫӽHashMapʵ +public HashMap(Map m) { + this.loadFactor = DEFAULT_LOAD_FACTOR; + //˹췽ҪʵMap.putAll() + putMapEntries(m, false); +} +3.Nodeʵ //ʵMap.Entryӿ static class Node implements Map.Entry { final int hash; final K key; V value; Node next; //캯 Node(int hash, K key, V value, Node 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; + } + //equalsԶԱ + 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; + } +} \ No newline at end of file diff --git a/week_01/12/ArrayList-012.md b/week_01/12/ArrayList-012.md new file mode 100644 index 0000000..6e0ee0c --- /dev/null +++ b/week_01/12/ArrayList-012.md @@ -0,0 +1,214 @@ +#### 问题 +elementData为什么加transient? +自动扩容是如何进行的? +modCount作用是什么? + +#### 简介 +线性表之一,基于数组,支持动态扩容 +#### 继承体系 +![image](https://raw.githubusercontent.com/sljie1988/image/master/jdkSourceStudy/ArrayListInheritSystem.png?token=AKPZOCKK54HH5325AXJNB2K56XVGI) +#### 源码解析 + +##### 属性 +``` +// 默认容量 +private static final int DEFAULT_CAPACITY = 10; +// 空数组,如果传入的容量为0时使用 +private static final Object[] EMPTY_ELEMENTDATA = {}; +// 空数组,传传入容量时使用,添加第一个元素的时候会重新初始为默认容量大小 +private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {}; +// 存储元素的数组 +transient Object[] elementData; +// 集合中元素的个数 +private int size; +``` +##### 构造方法 +``` +public ArrayList(int initialCapacity); +// 初始化为DEFAULT空数组,添加第一个元素时扩容为默认大小,10 +public ArrayList(); +// 使用拷贝把传入集合的元素拷贝到elementData数组中 +public ArrayList(Collection c); +``` +##### 主要方法 +###### boolean add(E e) +``` +public boolean add(E e) { + ensureCapacityInternal(size + 1); // Increments modCount!! + elementData[size++] = e; + return true; +} + +// 增加第一个元素时设定默认容量10 +private void ensureCapacityInternal(int minCapacity) { + if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) { + minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity); + } + ensureExplicitCapacity(minCapacity); +} + +// 容量不够时扩容 +private void ensureExplicitCapacity(int minCapacity) { + modCount++; + if (minCapacity - elementData.length > 0) + grow(minCapacity); +} + +private void grow(int minCapacity) { + int oldCapacity = elementData.length; + // 1.5倍扩容 + int newCapacity = oldCapacity + (oldCapacity >> 1); + if (newCapacity - minCapacity < 0) + newCapacity = minCapacity; + // 最大容量,2的31次方-1 + if (newCapacity - MAX_ARRAY_SIZE > 0) + newCapacity = hugeCapacity(minCapacity); + elementData = Arrays.copyOf(elementData, newCapacity); +} +``` +###### void add(int index, E element) +``` +public void add(int index, E element) { + // 角标越界检查 + rangeCheckForAdd(index); + ensureCapacityInternal(size + 1); + // index+1处复制起始位置为index,长度为size-index的数据 + System.arraycopy(elementData, index, elementData, index + 1, + size - index); + elementData[index] = element; + size++; +} +``` +###### E remove(int index) +``` +public E remove(int index) { + rangeCheck(index); + + modCount++; + E oldValue = elementData(index); + + int numMoved = size - index - 1; + if (numMoved > 0) + // index处复制起始位置为index+1,长度为size - index - 1的数据 + System.arraycopy(elementData, index+1, elementData, index, + numMoved); + // 尾元素置空,size-1 + elementData[--size] = null; // clear to let GC do its work + + return oldValue; +} +``` +###### boolean remove(Object o) +``` +public boolean remove(Object o) { + if (o == null) { + for (int index = 0; index < size; index++) + if (elementData[index] == null) { + fastRemove(index); + return true; + } + } else { + for (int index = 0; index < size; index++) + if (o.equals(elementData[index])) { + fastRemove(index); + return true; + } + } + return false; +} + +private void fastRemove(int index) { + // 无需检查越界 + modCount++; + int numMoved = size - index - 1; + if (numMoved > 0) + System.arraycopy(elementData, index+1, elementData, index, + numMoved); + elementData[--size] = null; // clear to let GC do its work +} +``` +###### voic clear() +``` +public void clear() { + modCount++; + + // clear to let GC do its work + for (int i = 0; i < size; i++) + elementData[i] = null; + + size = 0; +} +``` +###### E set(int index, E element) +``` +public E set(int index, E element) { + rangeCheck(index); + + E oldValue = elementData(index); + elementData[index] = element; + return oldValue; +} +``` +###### boolean addAll(Collection c) +尾部添加集合 +###### boolean addAll(int index, Collection c) +###### void removeRange(int fromIndex, int toIndex) +###### boolean removeAll(Collection c) +``` +// 移除集合中包含参数集合中的数据 +public boolean removeAll(Collection c) { + Objects.requireNonNull(c); + return batchRemove(c, false); +} +``` +###### boolean retainAll(Collection c) +``` +// 保留集合中包含参数集合中的数据 +public boolean retainAll(Collection c) { + Objects.requireNonNull(c); + return batchRemove(c, true); +} +``` +``` +private boolean batchRemove(Collection c, boolean complement) { + final Object[] elementData = this.elementData; + int r = 0, w = 0; + boolean modified = false; + try { + for (; r < size; r++) + if (c.contains(elementData[r]) == complement) + elementData[w++] = elementData[r]; + } finally { + // Preserve behavioral compatibility with AbstractCollection, + // even if c.contains() throws. + if (r != size) { + System.arraycopy(elementData, r, + elementData, w, + size - r); + w += size - r; + } + if (w != size) { + // clear to let GC do its work + for (int i = w; i < size; i++) + elementData[i] = null; + modCount += size - w; + size = w; + modified = true; + } + } + return modified; +} +``` +###### void writeObject(java.io.ObjectOutputStream s) +###### void readObject(java.io.ObjectInputStream s) +###### ListIterator listIterator(int index) +###### List subList(int fromIndex, int toIndex) +###### void sort(Comparator c) + +#### 总结 +默认容量为10,以1.5倍容量扩容 +Collection.toArray()转换后不一定全是Object[] + +#### 延伸 +bug网址: +https://bugs.java.com/bugdatabase/view_bug.do?bug_id=6260652 \ No newline at end of file diff --git a/week_01/15/HashMap-015.md b/week_01/15/HashMap-015.md new file mode 100644 index 0000000..8c85042 --- /dev/null +++ b/week_01/15/HashMap-015.md @@ -0,0 +1,485 @@ +### [Java 8] HashMap + +`HashMap` 是哈希表的基本实现,不是线程安全的。`HashMap` 底层主要存储是一个数组 `table`,数组中每个元素称为一个桶。将 `key` 通过哈希函数得 `key.hashCode()` 到哈希值 `hash`,再将 `hash` 按照桶的个数(即数组长度)取模得到该 `key` 所映射的桶(即数组的索引)。 + +因此从 `key` 到桶的映射过程可能会碰撞,即不同的 `key` 可能会映射到同一个桶,因此桶内需要能存多个键值对。桶默认使用链表存储多个键值对。 + +如果碰撞过多会严重影响 `HashMap` 的性能,本来算个 `hash` 在取个模再比较个 `key` 三部曲就完事的工作,在碰撞时第三步要遍历链表挨个比较键值找到要查找的 `key`,这样 *O(1)* 的时间复杂度退化为 *O(K)*,其中K为桶内键值对数。 + +因此为了减小碰撞带来的性能退化,有两种策略分别针对不同的场景: +1. 假设哈希函数分布还是不错的,但因为桶数量太少了,广泛分布的 `hash` 被压缩到少量的桶中不碰撞才怪。既然这样,就扩容桶的数量,原先映射到一个桶就可能分散到不同的桶。比如桶的数量为4,`hash` 为3和7的 `key` 都会映射到索引为3的桶,但把桶扩容到8后,两者就分别映射到索引为3和7的桶;当然等键值对数增长到桶的数量再扩容有点晚了,肯定已经发生一些碰撞了,试想多牛逼的哈希函数配上多么契合的场景才能保证一个桶只落一个键值对呢。因此需要一个阈值,键值对数超过阈值就扩容。 + +2. 假设哈希函数实现得比较烂,一堆不同的 `key` 通过哈希函数计算后都是差不多的 `hash`,桶再多又有毛用,全TMD映射到少量的桶中,桶里链表又特别长。既然这样,那就提高键值对多的桶内查找 `key` 的效率,用红黑树替换链表,将 *O(K)* 补救到 *O(logK)* + + +#### 常量及实例变量 +``` java +// 默认桶的数量,必须是2的整数次幂 +static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16 + +// 桶的最大数量 +static final int MAXIMUM_CAPACITY = 1 << 30; + +// 默认负载因子 +static final float DEFAULT_LOAD_FACTOR = 0.75f; + +// 桶内链表转化为红黑树的键值对数量阈值 +static final int TREEIFY_THRESHOLD = 8; + +// 桶内红黑树转化为链表的键值对数量阈值 +static final int UNTREEIFY_THRESHOLD = 6; + +// 当桶的数量没有达到这个阈值时,桶内链表不会转化为红黑树 +static final int MIN_TREEIFY_CAPACITY = 64; + +// 桶数组 +transient Node[] table; + +// 键值对总数 +transient int size; + +// HashMap修改次数(确切地说是结构变更次数,不包括修改已存在key的value),其实相当于当前集合快照版本,用于迭代器遍历时检查集合是否被修改 +transient int modCount; + +// 桶数组扩容阈值 +int threshold; + +// 负载因子 +final float loadFactor; +``` +负载因子 `loadFacotr` 是桶数组相对扩容阈值,是一个相对于桶数量的比例(可以大于1),因此绝对阈值 `threshold` 就是桶的数量 `table.length` 乘以负载因子 `loadFactor` 。当键值对总数 `size` 达到 `threshold` 时,触发 `resize` 方法进行桶数组 `table` 扩容。 因此 `loadFactor` 可以理解为 `HashMap` 时间和空间的权衡 + +`loadFactor` 是 `HashMap` 初始化时可以指定的,如果未指定则默认为 `DEFAULT_LOAD_FACTOR` + +桶的数量即 `table` 的大小也是 `HashMap` 初始化时可以指定的,如果未指定则默认为 `DEFAULT_INITIAL_CAPACITY` + +`table` 也不是无限扩容的,最多支持 `MAXIMUM_CAPACITY` 个桶 + +`table` 的长度必须是2的整数次幂,这是为了取模运算更高效,即hash对2的整数次幂n取模可以用骚操作位运算 `hash & (n - 1)` 。这是也是为什么默认值 `DEFAULT_INITIAL_CAPACITY` 及最大值 `MAXIMUM_CAPACITY` 也要求是2的整数次幂 + +`table` 的元素是 `HashMap.Node` 类型,`HashMap.Node` 是默认的链表节点,`HashMap.TreeNode` 是红黑树节点,继承了 `HashMap.Node` 。既然链表有头树有根,`table` 中就只引用一个头/根节点即可。 + +当某个桶内键值对数量超过 `TREEIFY_THRESHOLD` 时将触发 `treeifyBin` 方法将这个桶的链表转化为红黑树,当然这有个大前提,就是当前桶数量不少于 `MIN_TREEIFY_CAPACITY` ,因为桶很少的时候冲突的可能性就是非常高,这时就因为某个链太长就转为红黑树太鲁莽了,怎么也得先多弄几个桶看看是桶太少还是哈希函数太烂 + +由于 `HashMap.TreeNode` 空间几乎是 `HashMap.Node` 的2倍,因此在性能提升不大的情况下链表没必要转化为红黑树,另外对于良好实现分布均匀的哈希函数,冲突的概率很小,对应于这种情况就应该只有极低的概率链表转化红黑树。对于服从常数为0.5的泊松分布的哈希函数,8个 `key` 落到同一个桶中的概率只有0.00000006,因此将 `TREEIFY_THRESHOLD` 设为8可以满足上面的论断 + +如果某个桶已经转化为红黑树,`resize` 后原先桶里的键值对可能落到不同的桶中,即触发 `split`方法,`split` 后树节点可能很少了,浪费多一倍的空间没什么必要了,可以转化回链表,即触发 `untreeify` 。如果没有这个操作,多次 `resize` 和 `split` 后链表可能有很多的桶只有很少的节点,但却使用红黑树结构。`split` 后一个桶内红黑树节点降到多少转化回链表受阈值 `UNTREEIFY_THRESHOLD` 控制 + + +#### 哈希 +``` java +static final int hash(Object key) { + int h; + // HashMap允许key为null,对应的hash为0 + // 由于hash之后要 &(table.length-1)确定桶索引,只有比table.length唯一的1的位低的位保留下来,高位信息都被过滤掉了 + // 对于哈希函数分布不均的情况这里挣扎了下,将hash低16位和高16位做异或,这样高位信息也会反应在低位中,降低某些哈希函数实现只在高位变化从而碰撞的概率,当然这里也只是用开销很小的位操作,因为分布平均的哈希函数不需要挣扎,而实现比较烂的哈希函数有转化红黑树兜底 + return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16); +} +``` + + +#### 构造函数及相关辅助方法 +``` java +public HashMap(int initialCapacity, float loadFactor) { + if (initialCapacity < 0) + throw new IllegalArgumentException("Illegal initial capacity: " + + initialCapacity); + // 桶数量不超过MAXIMUM_CAPACITY + if (initialCapacity > MAXIMUM_CAPACITY) + initialCapacity = MAXIMUM_CAPACITY; + if (loadFactor <= 0 || Float.isNaN(loadFactor)) + throw new IllegalArgumentException("Illegal load factor: " + + loadFactor); + this.loadFactor = loadFactor; + // 调用tableSizeFor得到是桶数量,这里却赋值给了threshold,这是一个构造时的临时处理,因为table是延迟初始化的,并且没有专门的字段存储桶容量,因此先扔给threshold存着,具体等第一次resize初始化table时再将真正的扩容阈值赋给threshold + 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 m) { + this.loadFactor = DEFAULT_LOAD_FACTOR; + putMapEntries(m, false); +} + +// 计算大于等于cap的最小的2的整数幂 +static final int tableSizeFor(int cap) { + // -1是专门针对cap正好就是2的整数幂这种情况 + int n = cap - 1; + // 将n最高1位右边的位都设置为1 + n |= n >>> 1; + n |= n >>> 2; + n |= n >>> 4; + n |= n >>> 8; + n |= n >>> 16; + // 再+1正好就是2的整数幂 + return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1; +} + +final void putMapEntries(Map m, boolean evict) { + // 获取传进来的键值对数量 + int s = m.size(); + if (s > 0) { + // 判断table是否初始化 + if (table == null) { // pre-size + // 用键值对数量除以负载因子倒推桶容量,加上1预防浮点数计算误差,这里还不是真正的桶容量,因为还未向上取最小2的整数幂 + float ft = ((float)s / loadFactor) + 1.0F; + // 桶容量不超过MAXIMUM_CAPACITY + int t = ((ft < (float)MAXIMUM_CAPACITY) ? + (int)ft : MAXIMUM_CAPACITY); + // table没初始化时threshold暂存桶容量,或者threshold为0表示使用默认桶数量,无论哪种情况,这里将通过键值对反推的桶容量取向上最小的2的整数幂赋给threshold,在第一次resize时用来初始化table + if (t > threshold) + threshold = tableSizeFor(t); + } + else if (s > threshold) + // table已经初始化了,但发现键值对数量超过扩容阈值了,那就赶紧先resize,不要等到putVal再resize + resize(); + // 遍历将每个键值对增加到该HashMap中 + for (Map.Entry e : m.entrySet()) { + K key = e.getKey(); + V value = e.getValue(); + putVal(hash(key), key, value, false, evict); + } + } +} +``` + + +#### 桶容量 +``` java +// 这里恰好和构造函数呼应,体现了table延迟初始化前后桶容量是如何保存的 +final int capacity() { + // 1. table若已初始化,当然table.length就是桶容量 + // 2. 若table未初始化,且threshold大于0,对应HashMap前两个构造函数,参数指定了桶容量,暂存在threshold中 + // 3. 若table未初始化,且threshold等于0,对应HashMap第三个构造函数(无参),没有指定桶容量则使用默认桶容量 + return (table != null) ? table.length : + (threshold > 0) ? threshold : + DEFAULT_INITIAL_CAPACITY; +} +``` + + +#### 扩容 +``` java +final Node[] resize() { + Node[] oldTab = table; + int oldCap = (oldTab == null) ? 0 : oldTab.length; + int oldThr = threshold; + int newCap, newThr = 0; + if (oldCap > 0) { + // oldCap大于0,说明table已经初始化 + if (oldCap >= MAXIMUM_CAPACITY) { + // 桶容量已经达到MAXIMUM_CAPACITY了,再也扩不动了 + // 将threshold设置为Integer.MAX_VALUE,再也不触发resize + threshold = Integer.MAX_VALUE; + return oldTab; + } + // 桶容量扩容一倍 + else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY && + oldCap >= DEFAULT_INITIAL_CAPACITY) + // 只有扩容后容量未达到MAXIMUM_CAPACITY并且扩容前容量不低于DEFAULT_INITIAL_CAPACITY时才将threshold增大一倍 + // 第一个条件类似上面的分支,扩容后如果桶容量达到MAXIMUM_CAPACITY,那么threshold就应该设置为Integer.MAX_VALUE而不是傻傻地增大一倍,这个操作在下面newThr==0的分支中处理 + // 第二个条件是因为当桶容量很小的时候,threshold移位操作带来的小数点上的误差影响非常大,应该由扩容后的桶容量乘以负载因子重新计算,这同样交给下面newThr==0的分支中计算 + newThr = oldThr << 1; // double threshold + } + else if (oldThr > 0) // initial capacity was placed in threshold + // table未初始化但threshold大于0,说明指定容量构造后第一次resize,threshold暂存的就是table初始化的容量,这里正式移交给桶容量变量,threshold本身则由下面newThr==0的分支中计算 + newCap = oldThr; + else { // zero initial threshold signifies using defaults + // table未初始化且threshold等于0,说明无参构造后第一次resize,桶容量使用默认容量,threshold直接由默认容量乘以负载因子计算 + newCap = DEFAULT_INITIAL_CAPACITY; + newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY); + } + if (newThr == 0) { + // 计算扩容阈值 + float ft = (float)newCap * loadFactor; + // 如果桶容量达到MAXIMUM_CAPACITY或扩容阈值达到MAXIMUM_CAPACITY,直接将threshold设置为Integer.MAX_VALUE,否则将计算好的阈值赋给threshold + // 之所以还要判断计算的阈值是否达到MAXIMUM_CAPACITY是因为loadFactor是可能大于1的 + newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ? + (int)ft : Integer.MAX_VALUE); + } + threshold = newThr; + @SuppressWarnings({"rawtypes","unchecked"}) + // 用扩容容量构造新table + Node[] newTab = (Node[])new Node[newCap]; + table = newTab; + // 如果table不是第一次初始化,则需要将旧table的键值对迁移到新table中,键值对可能在新table中落到另外一个桶中 + if (oldTab != null) { + for (int j = 0; j < oldCap; ++j) { + Node e; + if ((e = oldTab[j]) != null) { + // 移除旧table中引用在这次循环后尽早回收 + oldTab[j] = null; + // e.next为null说明该桶内只有一个节点 + if (e.next == null) + // 直接将这个节点移到新table中hash对应的索引即可 + newTab[e.hash & (newCap - 1)] = e; + // e.next不为null说明桶内有多个节点,可能是链表也可能是红黑树 + else if (e instanceof TreeNode) + // 该桶是红黑树 + // 很有可能拆成两棵分别迁移到不同的桶 + ((TreeNode)e).split(this, newTab, j, oldCap); + else { // preserve order + // 该桶是链表 + Node loHead = null, loTail = null; + Node hiHead = null, hiTail = null; + Node next; + do { + next = e.next; + // 由于oldCap是2的整数幂,只有唯一的1,e.hash&oldCap得到e.hash相应这一位的信息。如果结果为0,则说明 e.hash % newCap < oldCap,则扩容前后该节点落到相同索引的桶(低索引半区);但如果结果为1,则说明 oldCap <= e.hash % newCap < newCap,扩容后该节点将落在新扩展的高索引半区并且与扩容前桶索引(低索引半区)相差oldCap + 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; +} +``` + +#### 键值对总数 +``` java +public int size() { + return size; +} + +public boolean isEmpty() { + return size == 0; +} +``` + + +#### 增/改键值对 +``` java +public V put(K key, V value) { + return putVal(hash(key), key, value, false, true); +} + +final V putVal(int hash, K key, V value, boolean onlyIfAbsent, + boolean evict) { + Node[] tab; Node p; int n, i; + // 判断table是否初始化 + if ((tab = table) == null || (n = tab.length) == 0) + // 触发resize初始化table + n = (tab = resize()).length; + // 判断所属桶是否有节点 + if ((p = tab[i = (n - 1) & hash]) == null) + // 没有节点则直接创建链表头节点 + tab[i] = newNode(hash, key, value, null); + else { + // 该桶已经有节点 + Node e; K k; + // 判断头/根节点是否是要找的key + if (p.hash == hash && + ((k = p.key) == key || (key != null && key.equals(k)))) + e = p; + // 判断是否是红黑树根节点 + else if (p instanceof TreeNode) + e = ((TreeNode)p).putTreeVal(this, tab, hash, key, value); + else { + // 链表有多个节点且头节点不是要找的key,则向下遍历链表 + for (int binCount = 0; ; ++binCount) { + if ((e = p.next) == null) { + // 遍历了一圈没有找到key,则说明需要新增一个键值对 + p.next = newNode(hash, key, value, null); + // 判断该桶内链表节点数量是否达到转化红黑树阈值TREEIFY_THRESHOLD,这里用TREEIFY_THRESHOLD - 1是因为头节点已经在循环前遍历过了 + if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st + // 达到转化红黑树的阈值,转化红黑树 + treeifyBin(tab, hash); + break; + } + // 判断该节点是否是要找的key + if (e.hash == hash && + ((k = e.key) == key || (key != null && key.equals(k)))) + break; + p = e; + } + } + // 如果找到了key,则说明是一个值更新操作 + if (e != null) { // existing mapping for key + V oldValue = e.value; + if (!onlyIfAbsent || oldValue == null) + e.value = value; + afterNodeAccess(e); + return oldValue; + } + } + ++modCount; + // 递增键值对总数size,判断结果是否超过扩容阈值threshold + if (++size > threshold) + // 超过threshold,调用resize扩容 + resize(); + afterNodeInsertion(evict); + return null; +} + +public void putAll(Map m) { + putMapEntries(m, true); +} +``` + + +#### 转化红黑树 +``` java +final void treeifyBin(Node[] tab, int hash) { + int n, index; Node e; + // 如果桶容量小于MIN_TREEIFY_CAPACITY,说明桶数量还太少,则扩容而不是转化红黑树 + if (tab == null || (n = tab.length) < MIN_TREEIFY_CAPACITY) + resize(); + else if ((e = tab[index = (n - 1) & hash]) != null) { + TreeNode hd = null, tl = null; + do { + TreeNode 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); + } +} +``` + + +#### 查找 +``` java +public V get(Object key) { + Node e; + return (e = getNode(hash(key), key)) == null ? null : e.value; +} + +final Node getNode(int hash, Object key) { + Node[] tab; Node first, e; int n; K k; + // 判断table是否初始化以及hash对应的桶是否有节点 + if ((tab = table) != null && (n = tab.length) > 0 && + (first = tab[(n - 1) & hash]) != null) { + // 判断头/根节点是否是要找的key + if (first.hash == hash && // always check first node + ((k = first.key) == key || (key != null && key.equals(k)))) + return first; + // 如果头/根节点不是要找的key,且桶内还有其他节点 + if ((e = first.next) != null) { + // 判断是否是红黑树,如是则在红黑树内查找key + if (first instanceof TreeNode) + return ((TreeNode)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); + } + } + // 没找到key + return null; +} +``` + + +#### 删除 +``` java +public V remove(Object key) { + Node e; + return (e = removeNode(hash(key), key, null, false, true)) == null ? + null : e.value; +} + +final Node removeNode(int hash, Object key, Object value, + boolean matchValue, boolean movable) { + Node[] tab; Node p; int n, index; + // 与getNode类似,查找key的节点 + if ((tab = table) != null && (n = tab.length) > 0 && + (p = tab[index = (n - 1) & hash]) != null) { + Node node = null, e; K k; V v; + if (p.hash == hash && + ((k = p.key) == key || (key != null && key.equals(k)))) + node = p; + else if ((e = p.next) != null) { + if (p instanceof TreeNode) + node = ((TreeNode)p).getTreeNode(hash, key); + else { + do { + if (e.hash == hash && + ((k = e.key) == key || + (key != null && key.equals(k)))) { + node = e; + break; + } + p = e; + } while ((e = e.next) != null); + } + } + // 判断节点是否找到且满足值匹配条件(如果开启值匹配选项) + if (node != null && (!matchValue || (v = node.value) == value || (value != null && value.equals(v)))) { + // 判断是否是红黑树节点 + if (node instanceof TreeNode) + // 移除红黑树节点 + ((TreeNode)node).removeTreeNode(this, tab, movable); + // 判断是否是链表头节点 + else if (node == p) + // 无论是下一个节点还是null,都直接赋给桶 + tab[index] = node.next; + else + // 父节点next直接指向子节点,及删除链表当前节点 + p.next = node.next; + ++modCount; + // 递减键值对总数 + --size; + afterNodeRemoval(node); + return node; + } + } + // 没找到key + return null; +} + +public void clear() { + Node[] tab; + modCount++; + if ((tab = table) != null && size > 0) { + size = 0; + // 清除所有桶对头/根节点的引用 + for (int i = 0; i < tab.length; ++i) + tab[i] = null; + } +} +``` diff --git "a/week_01/16/ArrayList\346\272\220\347\240\201\345\210\206\346\236\220_016.pdf" "b/week_01/16/ArrayList\346\272\220\347\240\201\345\210\206\346\236\220_016.pdf" new file mode 100644 index 0000000..a6029e3 Binary files /dev/null and "b/week_01/16/ArrayList\346\272\220\347\240\201\345\210\206\346\236\220_016.pdf" differ diff --git "a/week_01/16/LinkedList\346\272\220\347\240\201\345\210\206\346\236\220_016.pdf" "b/week_01/16/LinkedList\346\272\220\347\240\201\345\210\206\346\236\220_016.pdf" new file mode 100644 index 0000000..81126d0 Binary files /dev/null and "b/week_01/16/LinkedList\346\272\220\347\240\201\345\210\206\346\236\220_016.pdf" differ diff --git a/week_01/17/List/ArrayList-17 b/week_01/17/List/ArrayList-17 new file mode 100644 index 0000000..f1f1c98 --- /dev/null +++ b/week_01/17/List/ArrayList-17 @@ -0,0 +1,48 @@ +##ArrayList + + ##接口实现 + List, + 标记接口 RandomAccess, --可快速访问,即访问速度为1次。 + 标记接口 Cloneable, ----可克隆。 + 标记接口 java.io.Serializable--可序列化。 + + + ##底层实现 + 1.ArrayList底层是数组,可以根据Size动态增长。 + ##扩容 + 1.ArrayList的默认长度为10。 + 2.每一次扩容为当前长度的50%。 + 依据 int newCapacity = oldCapacity + (oldCapacity >> 1); + 3.copy,新起一个数组,再将原始数据放到新的数组里去。 + ##遍历 + 1.因为实现 RandomAccess 该接口,则 for循环的速度比Iterator迭代器快,出处RandomAccess文档。 + 2.关于For,ForEach 循环。 + for在遍历数组结构的数据快。 + forEach在遍历链表数据比较快。 + ##线程安全 + 1.否 + ##属性 + private static final long serialVersionUID = 8683452581122892189L; + //默认长度 + private static final int DEFAULT_CAPACITY = 10; + //空数组 + private static final Object[] EMPTY_ELEMENTDATA = {}; + //transient 不会被序列化(如实现Externalizable,并且手动序列化了)该修饰符将没有作用 + transient Object[] elementData; + //构造一个空集合的时候,返回该属性 + private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {}; + + ##构造方法 + 1.public ArrayList() { + this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA; + } + 2.ArrayList(int i){ + i==返回 EMPTY_ELEMENTDATA。 + i>0返回一个i长度的Object的数组 + + } + 3.ArrayList(Collection c){ + c.长度为0 返回空数组。 + c.长度不为0 返回一个C长度的Object[]数组。 + + } \ No newline at end of file diff --git a/week_01/17/List/HashMap-17 b/week_01/17/List/HashMap-17 new file mode 100644 index 0000000..5c7b0ba --- /dev/null +++ b/week_01/17/List/HashMap-17 @@ -0,0 +1,102 @@ +##HashMap + + ##接口 + 1.Map + 2.Cloneable --可拷贝 + 3.Serializable-可序列化 + ##属性 + //默认长度16, 必须是2的幂 + static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; + + static final int MAXIMUM_CAPACITY = 1 << 30; + //扩容因子(数据量到75%,则扩容一次) + static final float DEFAULT_LOAD_FACTOR = 0.75f; + //变成树的一个阀值 + static final int TREEIFY_THRESHOLD = 8; + //取消树的一个阀值 + static final int UNTREEIFY_THRESHOLD = 6; + + static final int MIN_TREEIFY_CAPACITY = 64; + ##构造函数 + 1.public HashMap() { + this.loadFactor = DEFAULT_LOAD_FACTOR; // all other fields defaulted + } + 则当前Map 长度16,扩容因子75% + 2.HashMap(int i){ + HashMap(int initialCapacity, float loadFactor) + } + + 3.public HashMap(Map m) { + this.loadFactor = DEFAULT_LOAD_FACTOR; + putMapEntries(m, false); + } + + + 4.方法 + /** + * Implements Map.put and related methods. + * + * @param hash hash for key + * @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 + */ + final V putVal(int hash, K key, V value, boolean onlyIfAbsent, + boolean evict) { + Node[] tab; + Node p; + int n, i; + //如果表是空的则从表新计算 + if ((tab = table) == null || (n = tab.length) == 0) + //获取表新计算的长度 + n = (tab = resize()).length; + //没有Hahs碰撞,直接在该角标插入新值 + if ((p = tab[i = (n - 1) & hash]) == null) + //新增一个桶到指定表中 + tab[i] = newNode(hash, key, value, null); + //有哈希碰撞 + else { + Node e; K k; + //如果在原来的找到了一个相同的桶,则把之前的桶赋值过来 + if (p.hash == hash && + ((k = p.key) == key || (key != null && key.equals(k)))) + e = p; + //看下 P 是不是是个树,拷贝一个树 + else if (p instanceof TreeNode) + e = ((TreeNode)p).putTreeVal(this, tab, hash, key, value); + //如果不是一个树 + else { + for (int binCount = 0; ; ++binCount) { + //该节点后面没有值,当前Hash只有这一个值 + if ((e = p.next) == null) { + //新建一个节点赋值给表当前的位置 + p.next = newNode(hash, key, value, null); + 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; + } + } + //记录该Map修改的次数 + ++modCount; + if (++size > threshold) + resize(); + afterNodeInsertion(evict); + return null; + } + ##HashMap源码还需要看,请各位大佬,指导一下,第一次看,懵逼中。。。。。。。。。。。。。。。 \ No newline at end of file diff --git a/week_01/18/ArrayList.md b/week_01/18/ArrayList.md new file mode 100644 index 0000000..2dc8ab1 --- /dev/null +++ b/week_01/18/ArrayList.md @@ -0,0 +1,90 @@ +# ArrayList + +## ArrayList的基础特点 + 1. Resizable-array + 2. permits all elements, including null + 3. unsynchronized vs Vector + 4. adding n elements requires O(n) time + 5. it is always at least as large as the list size. As elements are added to an ArrayList, its capacity grows automatically.* + +- ArrayList 的类关系? +## Capacity +1. ArrayList 的几种构造函数对 ArrayList容量的影响? 传入集合的构造函数的实现? + 1. 默认构造函数 +![](ArrayList/BF00ECE6-732E-444A-ACB1-A6A8FBF0E713.png) + 2. 构造函数,带初始容器initialCapacity参数 +![](ArrayList/1B8F3C92-DE88-42D8-98DC-C0D48ABB192B.png) +- 通过上面的比较,`DEFAULTCAPACITY_EMPTY_ELEMENTDATA ` 和`EMPTY_ELEMENTDATA` ,**前者知道扩容的容量是10** +![](ArrayList/4A579666-1860-4B86-8921-2749B3F8F839.png) + +2. 几种控制ArrayList 容量的方法? + - 如何缩容?这个方法可以直观的说明 ::size<= capacity:: ,将容量缩减至当前的size 大小 +![](ArrayList/18F2F986-FECA-4F67-A7C1-554722D8C9D9.png) + - 如何扩容?(第一种:目标容量从外部传入,即调用方指定目标容量) +![](ArrayList/A5418EEA-107A-40D1-AD2F-C180951E180F.png) + - 如何扩容?(第二种:目标容量从外部传入,即调用方指定目标容量) +![](ArrayList/76CEBE20-CAB1-4A53-A9F2-6C20D90A08B0.png) + +## modCount +1. modCount的表达的意义(什么是 structurally modified)?用途和如何使用? + - 意义 +![](ArrayList/7B84EAAB-ADA6-47FD-A836-6BC69AA7B3E3.png) + - 用途(什么是 fail-fast behavior)和如何使用(关键看子类方法是否要提供 fail-fast 功能) +![](ArrayList/3236957C-AEE5-41BF-A7C3-E79542C095F2.png) + + +2. ArrayList 中哪些方法会导致集合structurally modified(需要modCount++)? +> sort,replaceAll等其他方法同样同样分析 + - 在增加元素的时候modCount++,因为都要调扩容方法,modCount++写在扩容方法里 +![](ArrayList/3F7854B9-884A-49EF-A2D3-6EA5E9F506D4.png) + - 除了增删改,sort方法为什么也会导致(structurally modified.需要modCount++) ? + - 为什么校验 modCount 值? + - sort排序的过程也要迭代元素,比如最后**modCount**发生了改变,你的排序是不准确的,要抛出并发修改异常 + - 这里 sort 用的是**归并排序** +![](ArrayList/E2F335A9-19FF-4CF4-84C7-559019C8BF6D.png) + - 排序虽然不会增删元素,但是可能会将(2,3,1)—>(1,2,3),集合发生了结构性改变;而且,这时候如果有其它方法校验集合的第一个元素就发现了2变成了1,如果不让 modCount++.其它方法就没办法发现这种改变,造成不可预期的错误 + + - 注意:对增删来讲没增删一个元素 modCount++,所以批量删除方法这样写的 +![](ArrayList/260DD573-E696-4AA4-8F9E-C61A5C4FCB20.png) + + +## batchRemove方法 +1. removeAll(Collection c),retainAll(Collection c) +怎么实现的? +![](ArrayList/FE975389-C228-445D-A53F-BD5AF5290B1E.png) +- contains(Object o) 什么时候抛出异常 +![](ArrayList/C2139442-9F3B-44BE-A66E-17FCDAF013EE.png) + +## ArrayList 的迭代器ListItr,Itr和Iterator +1. ListItr,ListIterator.Itr和Iterator之间的关系? +![](ArrayList/D061D159-5CD3-40A5-AA1E-97E39EADD00D.png) +![](ArrayList/6C380A25-F767-4D36-87DF-FB60DE1B89F5.png) +2. ListItr迭代器拥有的3个属性的含义? + 1. int cursor**; *// index of next element to return* + 2. int lastRet**= -1; *// index of last element returned; -1 if no such* + 3. int expectedModCount= modCount; (校验容器是否被并发修改) +3. 迭代器的构造函数的作用? +![](ArrayList/92C4B78A-AFD7-4FA8-8FED-A2016BB83935.png) + +4. ListItr迭代器在父类 Itr 迭代器的基础上新增的 previous()方法实现?新增 add,set 方法的特点,以及和 ArrayList中的 add,set 的区别? +> 关键点是怎么维护cursor,lastRet两个字段 + - add是加在 cursor 指向的索引位置,因为不返回任何元素,**lastRet= -1**;但是还可以接着加元素,所以**cursor+1** + - set是更新lastRet位置的元素,并返回旧值 +![](ArrayList/BCA8000C-ABFC-4361-8B4E-927C709A5989.png) + + +## SubList +1. 为什么要在 ArrayList 里搞一个内部类数据结构 SubList? + - 这里面主要是视图思想,同一个数据源可以提供多个不同的视图对象(提供需要的数据,隐藏不需要的数据,安全又方便) + - 感觉在 JDK8后这些就被流取代了,Stream流更适合做这些, SubList毕竟还是数据结构 +![](ArrayList/9AFEEE04-BE12-4CB2-9A8F-7EE1570E1660.png) + + +## Java8引入函数式接口带来的改变 + 1. [[Spliterator]] <-----待补充 + +1. `transient Object[] elementData;` +的作用?为什么用 transient 修饰? +2. fail-fast behavior of an iterator? fail-fast机制带来的问题? + + diff --git a/week_01/18/ArrayList/18F2F986-FECA-4F67-A7C1-554722D8C9D9.png b/week_01/18/ArrayList/18F2F986-FECA-4F67-A7C1-554722D8C9D9.png new file mode 100644 index 0000000..7f709f0 Binary files /dev/null and b/week_01/18/ArrayList/18F2F986-FECA-4F67-A7C1-554722D8C9D9.png differ diff --git a/week_01/18/ArrayList/1B8F3C92-DE88-42D8-98DC-C0D48ABB192B.png b/week_01/18/ArrayList/1B8F3C92-DE88-42D8-98DC-C0D48ABB192B.png new file mode 100644 index 0000000..c844d54 Binary files /dev/null and b/week_01/18/ArrayList/1B8F3C92-DE88-42D8-98DC-C0D48ABB192B.png differ diff --git a/week_01/18/ArrayList/260DD573-E696-4AA4-8F9E-C61A5C4FCB20.png b/week_01/18/ArrayList/260DD573-E696-4AA4-8F9E-C61A5C4FCB20.png new file mode 100644 index 0000000..e8c7924 Binary files /dev/null and b/week_01/18/ArrayList/260DD573-E696-4AA4-8F9E-C61A5C4FCB20.png differ diff --git a/week_01/18/ArrayList/3236957C-AEE5-41BF-A7C3-E79542C095F2.png b/week_01/18/ArrayList/3236957C-AEE5-41BF-A7C3-E79542C095F2.png new file mode 100644 index 0000000..7992b4e Binary files /dev/null and b/week_01/18/ArrayList/3236957C-AEE5-41BF-A7C3-E79542C095F2.png differ diff --git a/week_01/18/ArrayList/3F7854B9-884A-49EF-A2D3-6EA5E9F506D4.png b/week_01/18/ArrayList/3F7854B9-884A-49EF-A2D3-6EA5E9F506D4.png new file mode 100644 index 0000000..783e71f Binary files /dev/null and b/week_01/18/ArrayList/3F7854B9-884A-49EF-A2D3-6EA5E9F506D4.png differ diff --git a/week_01/18/ArrayList/4A579666-1860-4B86-8921-2749B3F8F839.png b/week_01/18/ArrayList/4A579666-1860-4B86-8921-2749B3F8F839.png new file mode 100644 index 0000000..b963ea5 Binary files /dev/null and b/week_01/18/ArrayList/4A579666-1860-4B86-8921-2749B3F8F839.png differ diff --git a/week_01/18/ArrayList/6C380A25-F767-4D36-87DF-FB60DE1B89F5.png b/week_01/18/ArrayList/6C380A25-F767-4D36-87DF-FB60DE1B89F5.png new file mode 100644 index 0000000..6f2f658 Binary files /dev/null and b/week_01/18/ArrayList/6C380A25-F767-4D36-87DF-FB60DE1B89F5.png differ diff --git a/week_01/18/ArrayList/76CEBE20-CAB1-4A53-A9F2-6C20D90A08B0.png b/week_01/18/ArrayList/76CEBE20-CAB1-4A53-A9F2-6C20D90A08B0.png new file mode 100644 index 0000000..cc543a5 Binary files /dev/null and b/week_01/18/ArrayList/76CEBE20-CAB1-4A53-A9F2-6C20D90A08B0.png differ diff --git a/week_01/18/ArrayList/7B84EAAB-ADA6-47FD-A836-6BC69AA7B3E3.png b/week_01/18/ArrayList/7B84EAAB-ADA6-47FD-A836-6BC69AA7B3E3.png new file mode 100644 index 0000000..d7d998d Binary files /dev/null and b/week_01/18/ArrayList/7B84EAAB-ADA6-47FD-A836-6BC69AA7B3E3.png differ diff --git a/week_01/18/ArrayList/92C4B78A-AFD7-4FA8-8FED-A2016BB83935.png b/week_01/18/ArrayList/92C4B78A-AFD7-4FA8-8FED-A2016BB83935.png new file mode 100644 index 0000000..60dd8a7 Binary files /dev/null and b/week_01/18/ArrayList/92C4B78A-AFD7-4FA8-8FED-A2016BB83935.png differ diff --git a/week_01/18/ArrayList/9AFEEE04-BE12-4CB2-9A8F-7EE1570E1660.png b/week_01/18/ArrayList/9AFEEE04-BE12-4CB2-9A8F-7EE1570E1660.png new file mode 100644 index 0000000..aca6c47 Binary files /dev/null and b/week_01/18/ArrayList/9AFEEE04-BE12-4CB2-9A8F-7EE1570E1660.png differ diff --git a/week_01/18/ArrayList/A5418EEA-107A-40D1-AD2F-C180951E180F.png b/week_01/18/ArrayList/A5418EEA-107A-40D1-AD2F-C180951E180F.png new file mode 100644 index 0000000..d37765c Binary files /dev/null and b/week_01/18/ArrayList/A5418EEA-107A-40D1-AD2F-C180951E180F.png differ diff --git a/week_01/18/ArrayList/BCA8000C-ABFC-4361-8B4E-927C709A5989.png b/week_01/18/ArrayList/BCA8000C-ABFC-4361-8B4E-927C709A5989.png new file mode 100644 index 0000000..b046f52 Binary files /dev/null and b/week_01/18/ArrayList/BCA8000C-ABFC-4361-8B4E-927C709A5989.png differ diff --git a/week_01/18/ArrayList/BF00ECE6-732E-444A-ACB1-A6A8FBF0E713.png b/week_01/18/ArrayList/BF00ECE6-732E-444A-ACB1-A6A8FBF0E713.png new file mode 100644 index 0000000..dd7c7e8 Binary files /dev/null and b/week_01/18/ArrayList/BF00ECE6-732E-444A-ACB1-A6A8FBF0E713.png differ diff --git a/week_01/18/ArrayList/C2139442-9F3B-44BE-A66E-17FCDAF013EE.png b/week_01/18/ArrayList/C2139442-9F3B-44BE-A66E-17FCDAF013EE.png new file mode 100644 index 0000000..aea89af Binary files /dev/null and b/week_01/18/ArrayList/C2139442-9F3B-44BE-A66E-17FCDAF013EE.png differ diff --git a/week_01/18/ArrayList/D061D159-5CD3-40A5-AA1E-97E39EADD00D.png b/week_01/18/ArrayList/D061D159-5CD3-40A5-AA1E-97E39EADD00D.png new file mode 100644 index 0000000..889a377 Binary files /dev/null and b/week_01/18/ArrayList/D061D159-5CD3-40A5-AA1E-97E39EADD00D.png differ diff --git a/week_01/18/ArrayList/E2F335A9-19FF-4CF4-84C7-559019C8BF6D.png b/week_01/18/ArrayList/E2F335A9-19FF-4CF4-84C7-559019C8BF6D.png new file mode 100644 index 0000000..0f49c18 Binary files /dev/null and b/week_01/18/ArrayList/E2F335A9-19FF-4CF4-84C7-559019C8BF6D.png differ diff --git a/week_01/18/ArrayList/FE975389-C228-445D-A53F-BD5AF5290B1E.png b/week_01/18/ArrayList/FE975389-C228-445D-A53F-BD5AF5290B1E.png new file mode 100644 index 0000000..df81cd0 Binary files /dev/null and b/week_01/18/ArrayList/FE975389-C228-445D-A53F-BD5AF5290B1E.png differ diff --git a/week_01/18/HashMap.md b/week_01/18/HashMap.md new file mode 100644 index 0000000..8b07828 --- /dev/null +++ b/week_01/18/HashMap.md @@ -0,0 +1,125 @@ +# HashMap +[HashMap源码之resize方法 - 简书](https://www.jianshu.com/p/4fc089ca25dd) +[JDK 1.8 中 HashMap 扩容 - 算法网](http://ddrv.cn/a/234367) +[hash()、tableSizeFor()()](https://blog.csdn.net/fan2012huan/article/details/51097331) +## HashMap的基础特点 + 1. permits null values and the null key + 2. unsynchronized and permits nulls (vs Hashtable) + 3. makes no guarantees as to the order of the map + 4. **initial capacity** and **load factor**affect its performance + 5. not synchronized + +## 基本数据结构和接口 + 1. 什么是 Map 中的 Entry接口,Entry 表达的含义支持什么操作以及如何获取一个Map 的引用? +![](HashMap/url.jpg) +- 获取集合视图,有了集合就可以使用集合的迭代器迭代 +![](HashMap/C95BFC29-5513-4702-A388-8B42B595D2F3.png) + + 2. HashMap 中的静态内部类Node的数据结构是怎样的? + - 递归的数据结构(链表) +![](HashMap/A5B79ABC-3BAA-4572-9314-8A24ACBA054A.png) + 3. HashMap 中的内部类KeySet的数据结构是怎样的,和其它AbstractCollection 的实现类相比较提供了什么特点,这些特点来自于哪里? + - 继承 AbstractSet抽象类—> 继承自AbstractCollection抽线累 + - KeySet对AbstractCollection中的抽象方法提供了实现(AbstractSet没有实现剩下的方法),并添加了额外的方法Spliterator spliterator() + - 总结KeySet的特点来自于这几方面 + 1. AbstractSet提供的hashCode,equals,removeAll实现方法 + 2. KeySet自己提供的获取并发迭代器Spliterator的方法 + 3. KeySet实现的AbstractCollection中的抽象方法,比如获取迭代器实现,返回的是KeyIterator迭代器,remove,forEachss方法也是由 KeySet 自己实现,其它剩下的抽线方法实现size,clear,contains 直接调用的外部类 HashMap 的实现(这也是内部类的好处,持有外部类的引用) +![](HashMap/DB3DDD54-51DE-4F54-92EB-750865A314AE.png) + 4. HashMap中的内部类数据结构,除了 KeySet 这一种 HashMap 返回的集合视图对象,还有Values,EntrySet这两种内部类数据结构(集合视图对象),它们之间的实现有哪些异同? + - 这3种集合视图对象能获取的迭代器不同,迭代的细节不同 + - 当然 remove()移除,contains()校验包含 的对象也不同 +![](HashMap/60B179F9-4E4E-4F18-912E-BAE73438408E.png) + 5. HashMap中抽象类HashIterator的数据结构是怎样的,各成员变量的含义?它提供的实现和特点? + - HashIterator提供了 HashMap 中3种具体的迭代器的一些共性(作为它们的父类) + - **next将表示第一个非空桶中的第一个结点,index将表示下一个桶**。 +![](HashMap/BE66A21F-8ECA-4D54-9DA6-2B6E56411FE5.png) + - HashIterator 中nextNode()方法的实现? +![](HashMap/8DF53F3E-ED04-4887-9040-8A3E5EFF4CA6.png) + + 6. KeyIterator,ValueIterator,EntryIterator3种迭代器数据结构是怎样的? + - 从这三个迭代器来看,HashMap可以提供三种集合视图对象,而3种不同的迭代器在迭代时返回 Node不同的部分 +![](HashMap/A112D80B-06CB-4CA3-B2D3-F858D345F9E9.png) + + 7. HashMapSpliterator,KeySpliterator,ValueSpliterator,EntrySpliterator 待补充 + + + +## 关键的属性(成员变量) + 1. 什么是 HashMap 的loadFactor,initialCapacity,threshold?他们的作用和互相关系? + - threshold: HashMap进行扩容的阈值,它的值等于 HashMap 的容量乘以负载因子,The next size value at which to resize (capacity *load factor). +![](HashMap/url.png) + + 2. 什么是 HashMap中的transient Node[] table? + - The table, initialized on first use, and resized as necessary. When allocated, length is always a power of two. + - **HashMap的底层实现仍是数组,只是数组的每一项都是一条链表(初始化时)** +![](HashMap/url%202.jpg) + 3. HashMap 中 TREEIFY_THRESHOLD ,UNTREEIFY_THRESHOLD和MIN_TREEIFY_CAPACITY的作用? +![](HashMap/27063A0A-F3D8-43B7-A6C4-62941FC103D7.png) +![](HashMap/3561298E-96B7-4F6D-BE2A-00BB2471F72F.png) + +## 关键方法 +### hash()、tableSizeFor()方法 +1. 为什么要有HashMap的hash()方法,难道不能直接使用KV中K原有的hash值吗?在HashMap的put、get操作时为什么不能直接使用K中原有的hash值? + - 注意:0101这个第四位就是由高位>>>16产生的,这样高位就参与计算了,减少了碰撞 +![](HashMap/CDE9997E-6505-4FEF-B302-3F2BA6D16EFE.png) +![](HashMap/FDAB1F01-DF08-4EAB-A8ED-CACE9A01BC77.png) +2. HashMap 中tableSizeFor()方法的作用和算法实现的过程? + - 作用: 在实例化HashMap实例时,如果给定了initialCapacity,由于HashMap的capacity都是2的幂,因此这个方法用于找到大于等于initialCapacity的最小的2的幂(initialCapacity如果就是2的幂,则返回的还是这个数)。 +![](HashMap/0E7306FA-D012-43E3-8FD2-C2C08745249F.png) + + +### resize方法 +1. 哪两种情况(具体是3种情况)会调用resize()方法,resize()后的容量分别表现怎样? +![](HashMap/39323DC7-EDBC-4B72-80B1-9BB52A7D51D2.png) + +2. HashMap 中resize 时候,如何完成oldTab中Node迁移到table(newTab)中去可能遇到哪三种情况,分别如何实现的? + +![](HashMap/58F19611-BA2C-4F60-AAB4-96A7B7AD63A2.png) +- 链表扩容示意图 +![](HashMap/519be432-d93c-11e4-85bb-dff0a03af9d3.png) +![](HashMap/17145701-8f7c6a1b2062212a.png) +![](HashMap/070E9364-3CDA-4685-9DAC-609FC3DCBE8B.png) +![](HashMap/zA3ENz.png) +![](HashMap/qn34qsnq0r4249psn0q5s604o3po08op.jpg.gif) + +### put 方法 + 1. HashMap 中 put 方法的执行逻辑? + 2. HashMap 中 put 方法中的如何插入数组的索引位置对应存在红黑树了,*红黑树的插入操作是怎样的,即putTreeVal方法执行过程? + + +## 获取 HashMap 内部的数据结构的实例 + + + +## HashCode 方法 +## HashMap的性能 + 1. 影响HashMap的性能的两个因素? + 2. 为什么the default load factor 设置为 (.75)? + 3. HashMap如何扩容? + + +## TREEIFY(树化) + 1. 树化相关的属性? + 2. TREEIFY和UNTREEIFY的时机和过程? + + + +## fail-fast机制和安全性 +通过 ABC 三个线程具体说明(ABC 之间相互依赖的情况) + 1. the fail-fast behavior of iterators should be used only to detect bugs + + + +## 其它 + - 为什么需要 Node[] tab,不直接访问table + + + + + + +![](HashMap/2ead182893de70e85e71ef3dbd0c5b36.jpg.png) +## 1.7vs1.8 + - 1.7,1.8HashMap 扩容的对比? + - 待补充 \ No newline at end of file diff --git a/week_01/18/HashMap/070E9364-3CDA-4685-9DAC-609FC3DCBE8B.png b/week_01/18/HashMap/070E9364-3CDA-4685-9DAC-609FC3DCBE8B.png new file mode 100644 index 0000000..2dd2d03 Binary files /dev/null and b/week_01/18/HashMap/070E9364-3CDA-4685-9DAC-609FC3DCBE8B.png differ diff --git a/week_01/18/HashMap/0E7306FA-D012-43E3-8FD2-C2C08745249F.png b/week_01/18/HashMap/0E7306FA-D012-43E3-8FD2-C2C08745249F.png new file mode 100644 index 0000000..8832357 Binary files /dev/null and b/week_01/18/HashMap/0E7306FA-D012-43E3-8FD2-C2C08745249F.png differ diff --git a/week_01/18/HashMap/17145701-8f7c6a1b2062212a.png b/week_01/18/HashMap/17145701-8f7c6a1b2062212a.png new file mode 100644 index 0000000..35a4757 Binary files /dev/null and b/week_01/18/HashMap/17145701-8f7c6a1b2062212a.png differ diff --git a/week_01/18/HashMap/27063A0A-F3D8-43B7-A6C4-62941FC103D7.png b/week_01/18/HashMap/27063A0A-F3D8-43B7-A6C4-62941FC103D7.png new file mode 100644 index 0000000..0bfa388 Binary files /dev/null and b/week_01/18/HashMap/27063A0A-F3D8-43B7-A6C4-62941FC103D7.png differ diff --git a/week_01/18/HashMap/2ead182893de70e85e71ef3dbd0c5b36.jpg.png b/week_01/18/HashMap/2ead182893de70e85e71ef3dbd0c5b36.jpg.png new file mode 100644 index 0000000..b1673bb Binary files /dev/null and b/week_01/18/HashMap/2ead182893de70e85e71ef3dbd0c5b36.jpg.png differ diff --git a/week_01/18/HashMap/3561298E-96B7-4F6D-BE2A-00BB2471F72F.png b/week_01/18/HashMap/3561298E-96B7-4F6D-BE2A-00BB2471F72F.png new file mode 100644 index 0000000..ddfc9d7 Binary files /dev/null and b/week_01/18/HashMap/3561298E-96B7-4F6D-BE2A-00BB2471F72F.png differ diff --git a/week_01/18/HashMap/39323DC7-EDBC-4B72-80B1-9BB52A7D51D2.png b/week_01/18/HashMap/39323DC7-EDBC-4B72-80B1-9BB52A7D51D2.png new file mode 100644 index 0000000..db02c97 Binary files /dev/null and b/week_01/18/HashMap/39323DC7-EDBC-4B72-80B1-9BB52A7D51D2.png differ diff --git a/week_01/18/HashMap/519be432-d93c-11e4-85bb-dff0a03af9d3.png b/week_01/18/HashMap/519be432-d93c-11e4-85bb-dff0a03af9d3.png new file mode 100644 index 0000000..2a9b2e4 Binary files /dev/null and b/week_01/18/HashMap/519be432-d93c-11e4-85bb-dff0a03af9d3.png differ diff --git a/week_01/18/HashMap/58F19611-BA2C-4F60-AAB4-96A7B7AD63A2.png b/week_01/18/HashMap/58F19611-BA2C-4F60-AAB4-96A7B7AD63A2.png new file mode 100644 index 0000000..0a0278c Binary files /dev/null and b/week_01/18/HashMap/58F19611-BA2C-4F60-AAB4-96A7B7AD63A2.png differ diff --git a/week_01/18/HashMap/60B179F9-4E4E-4F18-912E-BAE73438408E.png b/week_01/18/HashMap/60B179F9-4E4E-4F18-912E-BAE73438408E.png new file mode 100644 index 0000000..b6b142d Binary files /dev/null and b/week_01/18/HashMap/60B179F9-4E4E-4F18-912E-BAE73438408E.png differ diff --git a/week_01/18/HashMap/8DF53F3E-ED04-4887-9040-8A3E5EFF4CA6.png b/week_01/18/HashMap/8DF53F3E-ED04-4887-9040-8A3E5EFF4CA6.png new file mode 100644 index 0000000..be6a79d Binary files /dev/null and b/week_01/18/HashMap/8DF53F3E-ED04-4887-9040-8A3E5EFF4CA6.png differ diff --git a/week_01/18/HashMap/A112D80B-06CB-4CA3-B2D3-F858D345F9E9.png b/week_01/18/HashMap/A112D80B-06CB-4CA3-B2D3-F858D345F9E9.png new file mode 100644 index 0000000..3e3f0b1 Binary files /dev/null and b/week_01/18/HashMap/A112D80B-06CB-4CA3-B2D3-F858D345F9E9.png differ diff --git a/week_01/18/HashMap/A5B79ABC-3BAA-4572-9314-8A24ACBA054A.png b/week_01/18/HashMap/A5B79ABC-3BAA-4572-9314-8A24ACBA054A.png new file mode 100644 index 0000000..29f9741 Binary files /dev/null and b/week_01/18/HashMap/A5B79ABC-3BAA-4572-9314-8A24ACBA054A.png differ diff --git a/week_01/18/HashMap/BE66A21F-8ECA-4D54-9DA6-2B6E56411FE5.png b/week_01/18/HashMap/BE66A21F-8ECA-4D54-9DA6-2B6E56411FE5.png new file mode 100644 index 0000000..324d7da Binary files /dev/null and b/week_01/18/HashMap/BE66A21F-8ECA-4D54-9DA6-2B6E56411FE5.png differ diff --git a/week_01/18/HashMap/C95BFC29-5513-4702-A388-8B42B595D2F3.png b/week_01/18/HashMap/C95BFC29-5513-4702-A388-8B42B595D2F3.png new file mode 100644 index 0000000..57d6ac6 Binary files /dev/null and b/week_01/18/HashMap/C95BFC29-5513-4702-A388-8B42B595D2F3.png differ diff --git a/week_01/18/HashMap/CDE9997E-6505-4FEF-B302-3F2BA6D16EFE.png b/week_01/18/HashMap/CDE9997E-6505-4FEF-B302-3F2BA6D16EFE.png new file mode 100644 index 0000000..16b0f93 Binary files /dev/null and b/week_01/18/HashMap/CDE9997E-6505-4FEF-B302-3F2BA6D16EFE.png differ diff --git a/week_01/18/HashMap/DB3DDD54-51DE-4F54-92EB-750865A314AE.png b/week_01/18/HashMap/DB3DDD54-51DE-4F54-92EB-750865A314AE.png new file mode 100644 index 0000000..7d27533 Binary files /dev/null and b/week_01/18/HashMap/DB3DDD54-51DE-4F54-92EB-750865A314AE.png differ diff --git a/week_01/18/HashMap/FDAB1F01-DF08-4EAB-A8ED-CACE9A01BC77.png b/week_01/18/HashMap/FDAB1F01-DF08-4EAB-A8ED-CACE9A01BC77.png new file mode 100644 index 0000000..d9bc3f2 Binary files /dev/null and b/week_01/18/HashMap/FDAB1F01-DF08-4EAB-A8ED-CACE9A01BC77.png differ diff --git a/week_01/18/HashMap/qn34qsnq0r4249psn0q5s604o3po08op.jpg.gif b/week_01/18/HashMap/qn34qsnq0r4249psn0q5s604o3po08op.jpg.gif new file mode 100644 index 0000000..bb4ab69 Binary files /dev/null and b/week_01/18/HashMap/qn34qsnq0r4249psn0q5s604o3po08op.jpg.gif differ diff --git a/week_01/18/HashMap/url 2.jpg b/week_01/18/HashMap/url 2.jpg new file mode 100644 index 0000000..8c68fdc Binary files /dev/null and b/week_01/18/HashMap/url 2.jpg differ diff --git a/week_01/18/HashMap/url.jpg b/week_01/18/HashMap/url.jpg new file mode 100644 index 0000000..51d5fa9 Binary files /dev/null and b/week_01/18/HashMap/url.jpg differ diff --git a/week_01/18/HashMap/url.png b/week_01/18/HashMap/url.png new file mode 100644 index 0000000..ab648dd Binary files /dev/null and b/week_01/18/HashMap/url.png differ diff --git a/week_01/18/HashMap/zA3ENz.png b/week_01/18/HashMap/zA3ENz.png new file mode 100644 index 0000000..561148b Binary files /dev/null and b/week_01/18/HashMap/zA3ENz.png differ diff --git a/week_01/20/ArrayList.md b/week_01/20/ArrayList.md new file mode 100644 index 0000000..cb8ff47 --- /dev/null +++ b/week_01/20/ArrayList.md @@ -0,0 +1,54 @@ ++ 定义 +```java +public class ArrayList extends AbstractList + implements List, RandomAccess, Cloneable, java.io.Serializable +``` ++ 实际上是一个动态数组,容量可以动态的增长,其继承了AbstractList +```java +//如果是无参构造方法创建对象的话,ArrayList的初始化长度为10,这是一个静态常量 +private static final int DEFAULT_CAPACITY = 10; +​ +//MPTY_ELEMENTDATA实际上是一个空的对象数组 + private static final Object[] EMPTY_ELEMENTDATA = {}; +​ +//保存ArrayList数据的对象数组缓冲区 elementData的初始容量为10,大小会根据ArrayList容量的增长而动态的增长。 + private transient Object[] elementData; +//集合的长度 + private int size; +``` ++ add方法 +```java +/** + * Appends the specified element to the end of this list. + */ +//增加元素到集合的最后 +public boolean add(E e) { +ensureCapacityInternal(size + 1); // Increments modCount!! +//因为++运算符的特点 先使用后运算 这里实际上是 +//elementData[size] = e +//size+1 +elementData[size++] = e; + return true; +} +``` ++ 扩容 ++ (1)检查是否需要扩容; ++ (2)如果elementData等于DEFAULTCAPACITY_EMPTY_ELEMENTDATA则初始化容量大小为DEFAULT_CAPACITY; ++ (3)新容量是老容量的1.5倍(oldCapacity + (oldCapacity >> 1)),如果加了这么多容量发现比需要的容量还小,则以需要的容量为准; ++ (4)创建新容量的数组并把老数组拷贝到新数组; +```java +private void grow(int minCapacity) { + // overflow-conscious code + int oldCapacity = elementData.length; + //新的容量是在原有的容量基础上+50% 右移一位就是二分之一 + int newCapacity = oldCapacity + (oldCapacity >> 1); + //如果新容量小于最小容量,按照最小容量进行扩容 + if (newCapacity - minCapacity < 0) + newCapacity = minCapacity; + if (newCapacity - MAX_ARRAY_SIZE > 0) + newCapacity = hugeCapacity(minCapacity); + // minCapacity is usually close to size, so this is a win: + //这里是重点 调用工具类Arrays的copyOf扩容 + elementData = Arrays.copyOf(elementData, newCapacity); +} +``` \ No newline at end of file diff --git a/week_01/20/HashMap.md b/week_01/20/HashMap.md new file mode 100644 index 0000000..64e658c --- /dev/null +++ b/week_01/20/HashMap.md @@ -0,0 +1,173 @@ +#### 简介 ++ HashMap采用key/value存储结构,每个key对应唯一的value,查询和修改的速度都很快,能达到O(1)的平均时间复杂度。它是非线程安全的,且不保证元素存储的顺序。 ++ HashMap实现了Cloneable,可以被克隆。 ++ HashMap实现了Serializable,可以被序列化。 ++ HashMap继承自AbstractMap,实现了Map接口,具有Map的所有功能。 +#### 存储结构 ++ ![](https://mmbiz.qpic.cn/mmbiz_png/C91PV9BDK3ybgqMRZDOdr5w7uDsHFrhqTibA17Zqqibm0Nwe5d0nxB2q3nDLSertDuNJfvT7F6kicSs7k9O5CpvKA/640?wx_fmt=png&tp=webp&wxfrom=5&wx_lazy=1&wx_co=1) ++ 数组 + 链表 + 红黑树(O(1)、O(k)、O(logk)) + +#### 源码解析 ++ 成员变量 + ```java + //默认的初始容量,必须是2的幂。 + static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; + //最大容量(必须是2的幂且小于2的30次方,传入容量过大将被这个值替换) + static final int MAXIMUM_CAPACITY = 1 << 30; + //默认装载因子,默认值为0.75,如果实际元素所占容量占分配容量的75%时就要扩容了。如果填充比很大,说明利用的空间很多,但是查找的效率很低,因为链表的长度很大(当然最新版本使用了红黑树后会改进很多),HashMap本来是以空间换时间,所以填充比没必要太大。但是填充比太小又会导致空间浪费。如果关注内存,填充比可以稍大,如果主要关注查找性能,填充比可以稍小。 + static final float _LOAD_FACTOR = 0.75f; + + //一个桶的树化阈值 + //当桶中元素个数超过这个值时,需要使用红黑树节点替换链表节点 + //这个值必须为 8,要不然频繁转换效率也不高 + static final int TREEIFY_THRESHOLD = 8; + + //一个树的链表还原阈值 + //当扩容时,桶中元素个数小于这个值,就会把树形的桶元素 还原(切分)为链表结构 + //这个值应该比上面那个小,至少为 6,避免频繁转换 + static final int UNTREEIFY_THRESHOLD = 6; + + //哈希表的最小树形化容量 + //当哈希表中的容量大于这个值时,表中的桶才能进行树形化 + //否则桶内元素太多时会扩容,而不是树形化 + //为了避免进行扩容、树形化选择的冲突,这个值不能小于 4 * TREEIFY_THRESHOLD + static final int MIN_TREEIFY_CAPACITY = 64; + + //存储数据的Entry数组,长度是2的幂。 + transient Entry[] table; + // + transient Set> entrySet; + //map中保存的键值对的数量 + transient int size; + //需要调整大小的极限值(容量*装载因子) + int threshold; + //装载因子 + final float loadFactor; + //map结构被改变的次数 + transient volatile int modCount; + ``` ++ 计算阀值 + ```java + static final int tableSizeFor(int cap) { + //经过下面的 或 和位移 运算, n最终各位都是1。 + int n = cap - 1; + n |= n >>> 1; + n |= n >>> 2; + n |= n >>> 4; + n |= n >>> 8; + n |= n >>> 16; + //判断n是否越界,返回 2的n次方作为 table(哈希桶)的阈值 + return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1; + } + ``` ++ 扩容 + ```java + final Node[] resize() { + //oldTab 为当前表的哈希桶 + Node[] oldTab = table; + //当前哈希桶的容量 length + int oldCap = (oldTab == null) ? 0 : oldTab.length; + //当前的阈值 + int oldThr = threshold; + //初始化新的容量和阈值为0 + int newCap, newThr = 0; + //如果当前容量大于0 + if (oldCap > 0) { + //如果当前容量已经到达上限 + if (oldCap >= MAXIMUM_CAPACITY) { + //则设置阈值是2的31次方-1 + threshold = Integer.MAX_VALUE; + //同时返回当前的哈希桶,不再扩容 + return oldTab; + }//否则新的容量为旧的容量的两倍。 + else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY && + oldCap >= DEFAULT_INITIAL_CAPACITY) + //如果旧的容量大于等于默认初始容量16 + //那么新的阈值也等于旧的阈值的两倍 + newThr = oldThr << 1; // double threshold + } + //如果当前表是空的,但是有阈值。代表是初始化时指定了容量、阈值的情况 + else if (oldThr > 0) + newCap = oldThr;//那么新表的容量就等于旧的阈值 + else { + //如果当前表是空的,而且也没有阈值。代表是初始化时没有任何容量/阈值参数的情况 + newCap = DEFAULT_INITIAL_CAPACITY;//此时新表的容量为默认的容量 16 + //新的阈值为默认容量16 * 默认加载因子0.75f = 12 + newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY); + } + if (newThr == 0) { + //如果新的阈值是0,对应的是 当前表是空的,但是有阈值的情况 + float ft = (float)newCap * loadFactor;//根据新表容量 和 加载因子 求出新的阈值 + //进行越界修复 + newThr = (newCap < MAXIMUM_CAPACITY && ft <(float)MAXIMUM_CAPACITY ? (int)ft : Integer.MAX_VALUE); + } + //更新阈值 + threshold = newThr; + @SuppressWarnings({"rawtypes","unchecked"}) + //根据新的容量 构建新的哈希桶 + Node[] newTab = (Node[])new Node[newCap]; + //更新哈希桶引用 + table = newTab; + //如果以前的哈希桶中有元素 + //下面开始将当前哈希桶中的所有节点转移到新的哈希桶中 + if (oldTab != null) { + //遍历老的哈希桶 + for (int j = 0; j < oldCap; ++j) { + //取出当前的节点 e + Node e; + //如果当前桶中有元素,则将链表赋值给e + if ((e = oldTab[j]) != null) { + //将原哈希桶置空以便GC + oldTab[j] = null; + //如果当前链表中就一个元素,(没有发生哈希碰撞) + if (e.next == null) + //直接将这个元素放置在新的哈希桶里。 + //注意这里取下标 是用 哈希值 与 桶的长度-1 。 由于桶的长度是2的n次方,这么做其实是等于 一个模运算。但是效率更高 + newTab[e.hash & (newCap - 1)] = e; + //如果发生过哈希碰撞 ,而且是节点数超过8个,转化成了红黑树 + else if (e instanceof TreeNode) + ((TreeNode)e).split(this, newTab, j, oldCap); + //如果发生过哈希碰撞,节点数小于8个。则要根据链表上每个节点的哈希值,依次放入新哈希桶对应下标位置。 + else { + //因为扩容是容量翻倍,所以原链表上的每个节点,现在可能存放在原来的下标,即low位,或者扩容后的下标,即high位。high位=low位+原哈希桶容量 + //低位链表的头结点、尾节点 + Node loHead = null, loTail = null; + //高位链表的头节点、尾节点 + Node hiHead = null, hiTail = null; + Node next;//临时节点 存放e的下一个节点 + do { + next = e.next; +   //利用位运算代替常规运算:利用哈希值与旧的容量,可以得到哈希值去模后,是大于等于oldCap还是小于oldCap,等于0代表小于oldCap,应该存放在低位,否则存放在高位 + 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); + //将低位链表存放在原index处 + if (loTail != null) { + loTail.next = null; + newTab[j] = loHead; + } + //将高位链表存放在新index处 + if (hiTail != null) { + hiTail.next = null; + newTab[j + oldCap] = hiHead; + } + } + } + } + } + return newTab; + } + ``` \ No newline at end of file diff --git a/week_01/22/ArrayList-22.java b/week_01/22/ArrayList-22.java new file mode 100644 index 0000000..f3d2d1e --- /dev/null +++ b/week_01/22/ArrayList-22.java @@ -0,0 +1,329 @@ +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.io.Serializable; +import java.lang.reflect.Array; +import java.util.AbstractList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Iterator; +import java.util.List; +import java.util.RandomAccess; + +/** + * 【源码链接】 + * Source for java.util.ArrayList: + * http://developer.classpath.org/doc/java/util/ArrayList-source.html + * + * */ + +/** + * 【简介】 + * ArrayList底层使用的是数组来实现List接口,提供了所有可选的List操作并且允许null值。 + * 元素的随机访问是常数时间O(1),在列表中间添加或者删除元素的时间复杂度是O(n)的。 + * 每个List都有一个容量,当达到最大容量时会自动增加自身的容量。 + * 我们可以通过ensureCapacity和trimToSize来确保容量大小,避免重新分配或浪费内存。 + * + * ArrayList不是synchronized的,如果需要多线程访问,可以这样做: + * List list = Collections.synchronizedList(new ArrayList(...)) + * + * 以下就主要方法进行解析说明: + * **/ + + public class ArrayList extends AbstractList implements List,RandomAccess,Cloneable,Serializable{ + + private static final long serialVersionUID = 8683452581122892189L; + + //新建ArrayList的默认容量大小 + private static final int DEFAULT_CAPACITY = 10; + + //ArrayList的元素个数 + private int size; + + //存储数据的数组 + private transient E[] data; + + //根据容量大小来构建ArrayList + public ArrayList(int capacity){ + if(capacity < 0){ + throw new IllegalArgumentException(); + } + data = (E[]) new Object[capacity]; + } + + //默认容量大小来构建ArrayList + public ArrayList(){ + this(DEFAULT_CAPACITY); + } + + //根据给定元素来构建ArrayList + public ArrayList(Collectionc){ + this((int) (c.size() * 1.1f)); + addAll(c); + } + + //修改size使得等于ArrayList实际大小 + public void trimToSize(){ + if(size != data.length){ + E[] newData = (E[]) new Object[size]; + System.arraycopy(data, 0, newData, 0, size); + data = newData; + } + } + + //如果ArrayList容量不足以存储元素,则自动扩展到length*2 + public void ensureCapacity(int minCapacity){ + int current = data.length; + if(minCapacity > current){ + E[] newData = (E[]) new Object[Math.max(current*2, minCapacity)]; + System.arraycopy(data, 0, newData, 0, size); + data = newData; + } + } + + //返回List的元素个数 + public int size(){ + return size; + } + + //判断List是否为空 + public boolean isEmpty(){ + return size == 0; + } + + //判断element是否在ArrayList中 + public boolean contains(Object e){ + return indexOf(e) != -1; + } + + //判断element在ArrayList中首次出现的最低位置索引,否则返回-1 + public int indexOf(Object e){ + for(int i = 0; i < size; i++){ + if(e.equals(data[i])){ + return i; + } + } + return -1; + } + + //判断element在ArrayList中首次出现的最高位置索引,否则返回-1 + public int lastIndexOf(Object e){ + for(int i = size-1; i > 0; i--){ + if(e.equals(data[i])){ + return i; + } + } + return -1; + } + + //ArrayList的浅拷贝 + public Object clone(){ + ArrayList clone = null; + try{ + clone = (ArrayList) super.clone(); + clone.data = (E[]) data.clone(); + }catch(CloneNotSupportedException e){ + + } + return clone; + } + + //返回一个独立的数组,存储ArrayList的所有元素 + public Object[] toArray(){ + E[] array = (E[]) new Object[size]; + System.arraycopy(data, 0, array, 0, size); + return array; + } + + //返回一个运行时传入数组类型的独立数组,存储ArrayList的所有元素 + //如果存储数组的size太小,则扩展为目标类型T的大小 + public T[] toArray(T[] a){ + if(a.length < size){ + a = (T[]) Array.newInstance(a.getClass().getComponentType(), size); + }else if(a.length > size){ + a[size] = null; + } + System.arraycopy(data, 0, a, 0, size); + return a; + } + + //检查索引是否在可能的元素范围内 + private void checkBoundInclusive(int index) { + if(index > size){ + throw new IndexOutOfBoundsException("Index:" + index + ",Size:" + size ); + } + } + + //检查索引是否在现有元素的范围内。 + private void checkBoundExclusive(int index) { + if(index >= size){ + throw new IndexOutOfBoundsException("Index:" + index + ",Size:" + size ); + } + } + + //检索用户提供的索引处的元素 + public E get(int index){ + checkBoundExclusive(index); + return data[index]; + } + + //给特定下标元素进行赋值,返回以前位于指定索引处的元素 + public E set(int index, E e){ + checkBoundExclusive(index); + E result = data[index]; + data[index] = e; + return result; + } + + //在ArrayList的尾部添加元素:如果已满,则size+1; + //modCount字段表示list结构上被修改的次数. + public boolean add(E e){ + modCount++; + if(size == data.length){ + ensureCapacity(size + 1); + } + data[size++] = e; + return true; + } + + //根据索引下标位置添加元素:如果已满,则size+1; + //如果插入位置不是尾部,将index后面元素往后移动一位,再插入元素于index + public void add(int index , Collection c) { + checkBoundExclusive(index); + modCount++; + if (size == data.length) { + ensureCapacity(size + 1); + } + if (index != size) { + System.arraycopy(data, index, data, index + 1, size - index); + } + data[index] = c; + size++; + } + + //根据索引下标位置移除元素 + public E remove(int index){ + checkBoundExclusive(index); + E r = data[index]; + modCount++; + if(index != --size){ + System.arraycopy(data, index, data, index + 1, size - index); + } + data[size] = null; + return r; + } + + //清空ArrayList + public void clear(){ + if(size > 0 ){ + modCount++; + Arrays.fill(data, 0, size, null); + size = 0 ; + } + } + + //将提供的集合中的每个元素添加到此列表 + public boolean addAll(Collectionc){ + return addAll(size, c); + } + + //将提供的集合中的每个元素添加到此列表index开始的位置:先将index后面元素移动csize个位置,然后插入 + public boolean addAll(int index,Collectionc){ + checkBoundExclusive(index); + Iterator itr = c.iterator(); + int csize = c.size(); + + modCount++; + if(csize+size > data.length){ + ensureCapacity(size + csize); + } + //移动原列表元素 + int end = index + csize; + if(size > 0 && index != size){ + System.arraycopy(data, index, data, end, size - index); + } + size += csize; + //添加新元素 + for(;index < end;index++){ + data[index] = itr.next(); + } + return csize>0; + } + + //移除在某个范围间隔的列表元素:将toIndex后面的元素往前移动(size - toIndex)位 + protected void removeRange(int fromIndex, int toIndex){ + int change = toIndex - fromIndex; + if(change > 0){ + modCount++; + System.arraycopy(data, toIndex, data, fromIndex, size - toIndex); + size -= change; + } + else if(change < 0){ + throw new IndexOutOfBoundsException(); + } + } + + //从此列表中删除给定集合中包含的所有元素 + //判断元素存在,如【a,b,c,d】中存在【b】,返回下标i=1,将【c,d】前移获得【a,c,d】 + boolean removeAllInternal(Collectionc){ + int i,j; + for(i=0;ic){ + int i,j; + for(i=0;i extends AbstractSequentialList implements List,Deque,Cloneable,Serializable{ + + private static final long serialVersionUID = 876323262645176354L; + + //LinkedList第一个元素 + transient Entry first; + + //LinkedList最后一个元素 + transient Entry last; + + //LinkedList的长度 + transient int size = 0; + + //新建内部类来表示列表中的项,包含单个元素。 + private static final class Entry{ + //列表元素 + T data; + //后继指针 + Entry next; + //前继指针 + Entry previous; + + Entry(T data){ + this.data = data; + } + } + + //获取LinkedList位置下标为n的元素,顺序or倒序 + Entry getEntry(int n){ + Entry e; + if(n < size/2){ + e = first; + while(n-- > 0){ + e = e.next; + } + }else{ + e = last; + while(++n < size){ + e = e.previous; + } + } + return e; + } + + //从列表中删除条目。这将调整大小并适当处理“first”和“last” + //modCount字段表示list结构上被修改的次数. + void removeEntry(Entry e){ + modCount++; + size--; + if(size == 0){ + first = last = null; + }else { + if(e == first){ + first = e.next; + e.next.previous = null; + }else if(e == last){ + last = e.previous; + e.previous.next = null; + }else{ + e.next.previous = e.previous; + e.previous.next = e.next; + } + } + } + + //检查索引是否在可能的元素范围内 + private void checkBoundsInclusive(int index){ + if(index < 0 || index > size){ + throw new IndexOutOfBoundsException("Index:"+index+",Size:"+size); + } + } + + //检查索引是否在现有元素的范围内。 + private void checkBoundsExclusive(int index){ + if(index < 0 || index >= size){ + throw new IndexOutOfBoundsException("Index:"+index+",Size:"+size); + } + } + + //创建一个空的LinkedList + public LinkedList(){ + + } + + //根据给定元素创建一个LinkedList + public LinkedList(Collection c){ + addAll(c); + } + + //返回LinkedList第一个元素 + public T getFirst(){ + if(size ==0){ + throw new NoSuchElementException(); + } + return first.data; + } + + //返回LinkedList最后一个元素 + public T getLast(){ + if(size == 0){ + throw new NoSuchElementException(); + } + return last.data; + } + + //移除并返回LinkedList第一个元素 + public T removeFirst(){ + if(size == 0){ + throw new NoSuchElementException(); + } + modCount++; + size--; + T r = first.data; + + if(first.next != null){ + first.next.previous = null; + }else{ + last = null; + } + + first = first.next; + return r; + } + + //移除并返回LinkedList最后一个元素 + public T removeLast(){ + if(size == 0){ + throw new NoSuchElementException(); + } + modCount++; + size--; + T r = last.data; + + if(last.previous != null){ + last.previous.next = null; + }else{ + first = null; + } + + last = last.previous; + return r; + } + + //在LinkedList首部插入元素 + public void addFirst(T o){ + Entry e = new Entry<>(o); + modCount++; + if(size == 0){ + first = last = e; + }else{ + e.next = first; + first.previous = e; + first = e; + } + size++; + } + + //在LinkedList尾部插入元素 + public void addLast(T o){ + addLastEntry(new Entry(o)); + } + private void addLastEntry(Entry e) { + modCount++; + if(size ==0){ + first = last = e; + }else{ + e.previous = last; + last.next = e; + last = e; + } + size++; + } + + //如果列表包含给定的对象,则返回true + public boolean contains(Object o){ + Entry e = first; + while(e != null){ + if(o.equals(e.data)){ + return true; + } + e = e.next; + } + return false; + } + + //返回LinkedList的大小 + public int size(){ + return size; + } + + //在LinkedList尾部添加元素 + public boolean add(T o){ + addLastEntry(new Entry(o)); + return true; + } + + //删除列表中与给定对象匹配的最低索引处的项 + public boolean remove(Object o){ + Entry e = first; + while(e != null){ + if(o.equals(e.data)){ + removeEntry(e); + return true; + } + e = e.next; + } + return false; + } + + //按迭代顺序将集合的元素追加到此列表的末尾 + public boolean addAll(Collection c){ + return addAll(size,c); + } + + //在此列表的给定索引处按迭代顺序插入集合的元素 + public boolean addAll(int index,Collection c){ + checkBoundsInclusive(index); + int csize = c.size(); + if(csize == 0){ + return false; + } + Iterator itr = c.iterator(); + + Entry after = null; + Entry before = null; + if(index != size){ + after = getEntry(index); + before = after.previous; + }else{ + before = last; + } + + //创建第一个新条目。我们还没有设置从“before”到第一个条目的链接, + Entry e = new Entry(itr.next()); + e.previous = before; + Entry prev = e; + Entry firstNew = e; + + //创建并链接所有剩余条目。 + for(int pos = 1;pos < csize; pos++){ + e = new Entry(itr.next()); + e.previous = prev; + prev.next = e; + prev = e; + } + + //将新的条目链链接到列表中。 + modCount++; + size += csize; + prev.next = after; + if(after != null){ + after.previous = e; + }else{ + last = e; + } + if(before != null){ + before.next = firstNew; + }else{ + first = firstNew; + } + return true; + } + + //清空LinkedList + public void clear(){ + if(size > 0 ){ + modCount++; + first = null; + last = null; + size =0; + } + } + + //获取元素的下标 + public T get(int index){ + checkBoundsExclusive(index); + return getEntry(index).data; + } + + //替换列表中给定位置的元素。 + public T set(int index,T o){ + checkBoundsExclusive(index); + Entry e = getEntry(index); + T old = e.data; + e.data = o; + return old; + } + + //在列表中d 给定位置插入元素。 + public void add(int index ,T o){ + checkBoundsInclusive(index); + Entry e = new Entry(o); + if(index after = getEntry(index); + e.next = after; + e.previous = after.previous; + if(after.previous == null){ + first = e; + }else{ + after.previous.next = e; + } + after.previous = e; + size ++; + }else{ + addLastEntry(e); + } + } + + //从列表中删除位于给定位置的元素。 + public T remove(int index){ + checkBoundsExclusive(index); + Entry e = getEntry(index); + removeEntry(e); + return e.data; + } + + //返回元素位于列表中的第一个索引,或-1 + public int indexOf(Object o){ + int index = 0; + Entry e = first; + while(e != null){ + if(o.equals(e.data)){ + return index; + } + index ++; + e = e.next; + } + return -1; + } + + //返回元素位于列表中的最后一个索引,或-1。 + public int lastIndexOf(Object o){ + int index = size -1; + Entry e = last; + while(e != null){ + if(o.equals(e.data)){ + return index; + } + index --; + e = e.previous; + } + return -1; + } + + //从给定的索引开始,获取此列表上的ListIterator。此方法返回的ListIterator支持add、remove和set方法。 + public ListIterator listIterator(int index){ + checkBoundsInclusive(index); + return new LinkedListItr(index); + } + //列表上的列表迭代器。这个类跟踪它在列表中的位置以及它所处的两个列表项。 + private final class LinkedListItr implements ListIterator{ + + private int konwnMod = modCount; + private Entry next; + private Entry previous; + private Entry lastReturned; + private int position; + + //初始化迭代器 + public LinkedListItr(int index) { + if(index == size){ + next = null; + previous = (Entry) last; + }else{ + next = (Entry)getEntry(index); + previous = next.previous; + } + position = index; + } + + //检查迭代器的一致性。 + private void checkMod(){ + if(konwnMod != modCount) + throw new ConcurrentModificationException(); + } + + //返回下一个元素的下标 + public int nextIndex(){ + return position; + } + + //返回前一个元素的下标 + public int previousIndex(){ + return position-1; + } + + //如果通过下一个元素存在更多元素,则返回true。 + public boolean hasNext(){ + return (next != null); + } + + //如果先前存在更多元素,则返回true。 + public boolean hasPrevious(){ + return (previous != null); + } + + //返回下一个元素 + public I next(){ + checkMod(); + if(next == null) + throw new NoSuchElementException(); + position++; + lastReturned = previous = next; + next = lastReturned.next; + return lastReturned.data; + } + + //返回前一个元素 + public I previous(){ + checkMod(); + if(previous == null) + throw new NoSuchElementException(); + position--; + lastReturned = next = previous; + previous = lastReturned.previous; + return lastReturned.data; + } + + //从列表中删除最近返回的元素 + public void remove(){ + checkMod(); + if(lastReturned == null){ + throw new IllegalStateException(); + } + if(lastReturned == previous){ + position--; + } + + next = lastReturned.next; + previous = lastReturned.previous; + removeEntry((Entry)lastReturned); + + konwnMod++; + lastReturned = null; + } + + //在上一个和下一个之间添加元素,然后前进到下一个。 + public void add(I o){ + checkMod(); + modCount++; + konwnMod++; + size++; + position++; + Entry e = new Entry(o); + e.previous = previous; + e.next = next; + + if(previous != null) + previous.next = e; + else + first = (Entry)e; + + if(next != null){ + next.previous = e; + }else{ + last = (Entry) e; + } + previous = e; + lastReturned = null; + } + + //更改最近返回的元素的内容。 + public void set(I o){ + checkMod(); + if(lastReturned == null){ + throw new IllegalStateException(); + } + lastReturned.data = o; + } + } + + + + //LinkedList的浅拷贝 + public Object clone(){ + LinkedList copy = null; + try{ + copy = (LinkedList) super.clone(); + }catch(CloneNotSupportedException ex){ + + } + copy.clear(); + copy.addAll(this); + return copy; + } + + //返回按顺序包含列表元素的数组。 + public Object[] toArray(){ + Object[] array = new Object[size]; + Entry e = first; + for(int i=0;i S[] toArray(S[] a){ + if(a.length < size){ + a = (S[])Array.newInstance(a.getClass().getComponentType(), size); + }else if(a.length > size){ + a[size] = null; + } + Entry e = first; + for(int i=0;i e = first; + while(e != null){ + s.writeObject(e.data); + e = e.next; + } + } + + //从给定流反序列化此对象。 + private void readObject(ObjectInputStream s) throws IOException,ClassNotFoundException{ + s.defaultReadObject(); + int i = s.readInt(); + while(--i >= 0){ + addLastEntry(new Entry((T) s.readObject())); + } + } + + //按相反的顺序获取此列表上的迭代器。 + public Iterator descendIterator(){ + return new Iterator() { + private int konwnMod = modCount; + private Entry next =last; + private Entry lastReturned; + private int position = size() - 1; + //检查迭代过程中从其他地方对列表所做的修改。 + private void checkMod(){ + if(konwnMod != modCount) + throw new ConcurrentModificationException(); + } + public boolean hasNext(){ + return next != null; + } + public T next(){ + checkMod(); + if(next == null) + throw new NoSuchElementException(); + --position; + lastReturned = next; + next = lastReturned.previous; + return lastReturned.data; + } + public void remove(){ + checkMod(); + if(lastReturned == null) + throw new IllegalStateException(); + removeEntry(lastReturned); + lastReturned = null; + ++konwnMod; + } + }; + } + + //在列表的前面插入指定的元素。 + public boolean offerFirst(T value){ + addFirst(value); + return true; + } + + //在列表的后面插入指定的元素。 + public boolean offerLast(T value){ + return add(value); + } + + //返回列表的第一个元素而不删除 + public T peekFirst(){ + return peek(); + } + + //返回列表的最后一个元素而不删除 + public T peekLast(){ + if(size == 0){ + return null; + } + return getLast(); + } + + //删除并返回列表的第一个元素 + public T pollFirst(){ + return poll(); + } + + //删除并返回列表的最后一个元素 + public T pollLast(){ + if(size == 0){ + return null; + } + return removeLast(); + } + + //通过移除并返回列表中的第一个元素,从堆栈中弹出一个元素。 + public T pop(){ + return removeFirst(); + } + + //通过将元素添加到列表的前面,将其推送到堆栈上。 + public void push(T value) { + addFirst(value); + } + + //从头到尾遍历列表时,从列表中删除指定元素的第一个匹配项。 + public boolean removeFirstOccurrence(Object o){ + return remove(o); + } + + //从头到尾遍历列表时,从列表中删除指定元素的最后一个匹配项。 + public boolean removeLastOccurrence(Object o){ + Entry e = last; + while(e!=null){ + if(o.equals(e.data)){ + removeEntry(e); + return true; + } + e = e.previous; + } + return false; + } + } \ No newline at end of file diff --git a/week_01/23/ArrayList-23.md b/week_01/23/ArrayList-23.md new file mode 100644 index 0000000..c776162 --- /dev/null +++ b/week_01/23/ArrayList-23.md @@ -0,0 +1,196 @@ +### ArrayList简介 + +```java +public class ArrayList extends AbstractList + implements List, RandomAccess, Cloneable, java.io.Serializable +``` + +  ArrayList 的底层是数组队列,相当于动态数组。与 Java 中的数组相比,它的容量能动态增长。在添加大量元素前,应用程序可以使用`ensureCapacity`操作来增加 ArrayList 实例的容量。这可以减少递增式再分配的数量。 + +它继承于 **AbstractList**,实现了 **List**, **RandomAccess**, **Cloneable**, **java.io.Serializable** 这些接口。 + +  ArrayList 继承了AbstractList,实现了List。它是一个数组队列,提供了相关的添加、删除、修改、遍历等功能。 + +  ArrayList 实现了**RandomAccess 接口**, RandomAccess 是一个标志接口,表明实现这个这个接口的 List 集合是支持**快速随机访问**的。在 ArrayList 中,我们即可以通过元素的序号快速获取元素对象,这就是快速随机访问。 + +  ArrayList 实现了**Cloneable 接口**,即覆盖了函数 clone(),**能被克隆**。 + +  ArrayList 实现**java.io.Serializable 接口**,这意味着ArrayList**支持序列化**,**能通过序列化去传输**。 + +  和 Vector 不同,**ArrayList 中的操作不是线程安全的**!所以,建议在单线程中才使用 ArrayList,而在多线程中可以选择 Vector 或者 CopyOnWriteArrayList。 + +### ArrayList属性 + +```java + private static final long serialVersionUID = 8683452581122892189L; + + /** + * 默认初始容量大小 + */ + private static final int DEFAULT_CAPACITY = 10; + + /** + * 空数组(用于空实例)。 + */ + private static final Object[] EMPTY_ELEMENTDATA = {}; + + //用于默认大小空实例的共享空数组实例。 + //我们把它从EMPTY_ELEMENTDATA数组中区分出来,以知道在添加第一个元素时容量需要增加多少。 + private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {}; + + /** + * 保存ArrayList数据的数组 + */ + transient Object[] elementData; // non-private to simplify nested class access + + /** + * ArrayList 所包含的元素个数 + */ + private int size; + +``` + + + +### ArrayList源码分析 + +#### System.arraycopy()和Arrays.copyOf()方法 + +  下面add(int index, E element)方法就很巧妙的用到了arraycopy()方法让数组自己复制自己实现让index开始之后的所有成员后移一个位置: + +```java + /** + * 在此列表中的指定位置插入指定的元素。 + *先调用 rangeCheckForAdd 对index进行界限检查;然后调用 ensureCapacityInternal 方法保证capacity足够大; + *再将从index开始之后的所有成员后移一个位置;将element插入index位置;最后size加1。 + */ + public void add(int index, E element) { + rangeCheckForAdd(index); + + ensureCapacityInternal(size + 1); // Increments modCount!! + //arraycopy()方法实现数组自己复制自己 + //elementData:源数组;index:源数组中的起始位置;elementData:目标数组;index + 1:目标数组中的起始位置; size - index:要复制的数组元素的数量; + System.arraycopy(elementData, index, elementData, index + 1, size - index); + elementData[index] = element; + size++; + } +``` + +又如toArray()方法中用到了copyOf()方法 + +```java + /** + *以正确的顺序(从第一个到最后一个元素)返回一个包含此列表中所有元素的数组。 + *返回的数组将是“安全的”,因为该列表不保留对它的引用。 (换句话说,这个方法必须分配一个新的数组)。 + */ + public Object[] toArray() { + //elementData:要复制的数组;size:要复制的长度 + return Arrays.copyOf(elementData, size); + } + + @SuppressWarnings("unchecked") + public static T[] copyOf(T[] original, int newLength) { + return (T[]) copyOf(original, newLength, original.getClass()); + } + + + public static T[] copyOf(U[] original, int newLength, Class newType) { + @SuppressWarnings("unchecked") + T[] copy = ((Object)newType == (Object)Object[].class) + ? (T[]) new Object[newLength] + : (T[]) Array.newInstance(newType.getComponentType(), newLength); + System.arraycopy(original, 0, copy, 0, + Math.min(original.length, newLength)); + return copy; + } + +``` + +##### 两者联系与区别 + +**联系:** 看源代码可以发现`copyOf()`内部调用了`System.arraycopy()`方法 **区别:** + +1. arraycopy()需要目标数组,将原数组拷贝到你自己定义的数组里,而且可以选择拷贝的起点和长度以及放入新数组中的位置 +2. copyOf()是系统自动在内部新建一个数组,并返回该数组。 + +#### ArrayList 核心扩容技术 + +```java +//下面是ArrayList的扩容机制 +//ArrayList的扩容机制提高了性能,如果每次只扩充一个, +//那么频繁的插入会导致频繁的拷贝,降低性能,而ArrayList的扩容机制避免了这种情况。 + /** + * 如有必要,增加此ArrayList实例的容量,以确保它至少能容纳元素的数量 + * @param minCapacity 所需的最小容量 + */ + public void ensureCapacity(int minCapacity) { + int minExpand = (elementData != DEFAULTCAPACITY_EMPTY_ELEMENTDATA) + // any size if not default element table + ? 0 + // larger than default for default empty table. It's already + // supposed to be at default size. + : DEFAULT_CAPACITY; + + if (minCapacity > minExpand) { + ensureExplicitCapacity(minCapacity); + } + } + //得到最小扩容量 + private void ensureCapacityInternal(int minCapacity) { + if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) { + // 获取默认的容量和传入参数的较大值 + minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity); + } + + ensureExplicitCapacity(minCapacity); + } + //判断是否需要扩容,上面两个方法都要调用 + private void ensureExplicitCapacity(int minCapacity) { + modCount++; + + // 如果说minCapacity也就是所需的最小容量大于保存ArrayList数据的数组的长度的话,就需要调用grow(minCapacity)方法扩容。 + //这个minCapacity到底为多少呢?举个例子在添加元素(add)方法中这个minCapacity的大小就为现在数组的长度加1 + if (minCapacity - elementData.length > 0) + //调用grow方法进行扩容,调用此方法代表已经开始扩容了 + grow(minCapacity); + } + /** + * ArrayList扩容的核心方法。 + */ + private void grow(int minCapacity) { + //elementData为保存ArrayList数据的数组 + ///elementData.length求数组长度elementData.size是求数组中的元素个数 + // oldCapacity为旧容量,newCapacity为新容量 + int oldCapacity = elementData.length; + //将oldCapacity 右移一位,其效果相当于oldCapacity /2, + //我们知道位运算的速度远远快于整除运算,整句运算式的结果就是将新容量更新为旧容量的1.5倍, + int newCapacity = oldCapacity + (oldCapacity >> 1); + //然后检查新容量是否大于最小需要容量,若还是小于最小需要容量,那么就把最小需要容量当作数组的新容量, + if (newCapacity - minCapacity < 0) + newCapacity = minCapacity; + //再检查新容量是否超出了ArrayList所定义的最大容量, + //若超出了,则调用hugeCapacity()来比较minCapacity和 MAX_ARRAY_SIZE, + //如果minCapacity大于MAX_ARRAY_SIZE,则新容量则为Interger.MAX_VALUE,否则,新容量大小则为 MAX_ARRAY_SIZE。 + 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); + } + +``` + +  **移位运算符**  **简介**:移位运算符就是在二进制的基础上对数字进行平移。按照平移的方向和填充数字的规则分为三种:<<(左移)、>>(带符号右移)和>>>(无符号右移)。   **作用**:**对于大数据的2进制运算,位移运算符比那些普通运算符的运算要快很多,因为程序仅仅移动一下而已,不去计算,这样提高了效率,节省了资源**  比如这里:int newCapacity = oldCapacity + (oldCapacity >> 1); 右移一位相当于除2,右移n位相当于除以 2 的 n 次方。这里 oldCapacity 明显右移了1位所以相当于oldCapacity /2。 + +#### 内部类 + +```java + (1)private class Itr implements Iterator + (2)private class ListItr extends Itr implements ListIterator + (3)private class SubList extends AbstractList implements RandomAccess + (4)static final class ArrayListSpliterator implements Spliterator +``` + +  ArrayList有四个内部类,其中的**Itr是实现了Iterator接口**,同时重写了里面的**hasNext()**, **next()**, **remove()** 等方法;其中的**ListItr** 继承 **Itr**,实现了**ListIterator接口**,同时重写了**hasPrevious()**, **nextIndex()**, **previousIndex()**, **previous()**, **set(E e)**, **add(E e)** 等方法,ListIterator在Iterator的基础上增加了添加对象,修改对象,逆向遍历等方法。 + +### + diff --git a/week_01/23/HashMap-23.md b/week_01/23/HashMap-23.md new file mode 100644 index 0000000..5b5dee3 --- /dev/null +++ b/week_01/23/HashMap-23.md @@ -0,0 +1,464 @@ + + +HashMap 主要用来存放键值对,它基于哈希表的Map接口实现,是常用的Java集合之一。 + +JDK1.8 之前 HashMap 由 数组+链表 组成的,数组是 HashMap 的主体,链表则是主要为了解决哈希冲突而存在的(“拉链法”解决冲突).JDK1.8 以后在解决哈希冲突时有了较大的变化,当链表长度大于阈值(默认为 8)时,将链表转化为红黑树(将链表转换成红黑树前会判断,如果当前数组的长度小于 64,那么会选择先进行数组扩容,而不是转换为红黑树)。 + +## 底层数据结构分析 + +### JDK1.8之前 + +JDK1.8 之前 HashMap 底层是 **数组和链表** 结合在一起使用也就是 **链表散列**。**HashMap 通过 key 的 hashCode 经过扰动函数处理过后得到 hash 值,然后通过 `(n - 1) & hash` 判断当前元素存放的位置(这里的 n 指的是数组的长度),如果当前位置存在元素的话,就判断该元素与要存入的元素的 hash 值以及 key 是否相同,如果相同的话,直接覆盖,不相同就通过拉链法解决冲突。** + +**所谓扰动函数指的就是 HashMap 的 hash 方法。使用 hash 方法也就是扰动函数是为了防止一些实现比较差的 hashCode() 方法 换句话说使用扰动函数之后可以减少碰撞。** + +**JDK 1.8 HashMap 的 hash 方法源码:** + +JDK 1.8 的 hash方法 相比于 JDK 1.7 hash 方法更加简化,但是原理不变。 + +```java + static final int hash(Object key) { + int h; + // key.hashCode():返回散列值也就是hashcode + // ^ :按位异或 + // >>>:无符号右移,忽略符号位,空位都以0补齐 + return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16); + } +``` + +对比一下 JDK1.7的 HashMap 的 hash 方法源码. + +``` +static int hash(int h) { + // This function ensures that hashCodes that differ only by + // constant multiples at each bit position have a bounded + // number of collisions (approximately 8 at default load factor). + + h ^= (h >>> 20) ^ (h >>> 12); + return h ^ (h >>> 7) ^ (h >>> 4); +} +``` + +相比于 JDK1.8 的 hash 方法 ,JDK 1.7 的 hash 方法的性能会稍差一点点,因为毕竟扰动了 4 次。 + +所谓 **“拉链法”** 就是:将链表和数组相结合。也就是说创建一个链表数组,数组中每一格就是一个链表。若遇到哈希冲突,则将冲突的值加到链表中即可。 + +### JDK1.8之后 + +相比于之前的版本,jdk1.8在解决哈希冲突时有了较大的变化,当链表长度大于阈值(默认为8)时,将链表转化为红黑树。 + + + +**类的属性:** + +```java +public class HashMap extends AbstractMap implements Map, Cloneable, Serializable { + // 序列号 + private static final long serialVersionUID = 362498820763181265L; + // 默认的初始容量是16 + static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; + // 最大容量 + static final int MAXIMUM_CAPACITY = 1 << 30; + // 默认的填充因子 + static final float DEFAULT_LOAD_FACTOR = 0.75f; + // 当桶(bucket)上的结点数大于这个值时会转成红黑树 + static final int TREEIFY_THRESHOLD = 8; + // 当桶(bucket)上的结点数小于这个值时树转链表 + static final int UNTREEIFY_THRESHOLD = 6; + // 桶中结构转化为红黑树对应的table的最小大小 + static final int MIN_TREEIFY_CAPACITY = 64; + // 存储元素的数组,总是2的幂次倍 + transient Node[] table; + // 存放具体元素的集 + transient Set> entrySet; + // 存放元素的个数,注意这个不等于数组的长度。 + transient int size; + // 每次扩容和更改map结构的计数器 + transient int modCount; + // 临界值 当实际大小(容量*填充因子)超过临界值时,会进行扩容 + int threshold; + // 加载因子 + final float loadFactor; +} +``` + +- **threshold** + + **threshold = capacity \* loadFactor**,**当Size>=threshold**的时候,那么就要考虑对数组的扩增了,也就是说,这个的意思就是 **衡量数组是否需要扩增的一个标准**。 + +**Node节点类源码:** + +```java +// 继承自 Map.Entry +static class Node implements Map.Entry { + final int hash;// 哈希值,存放元素到hashmap中时用来与其他元素hash值比较 + final K key;//键 + V value;//值 + // 指向下一个节点 + Node next; + Node(int hash, K key, V value, Node 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; } + // 重写hashCode()方法 + public final int hashCode() { + return Objects.hashCode(key) ^ Objects.hashCode(value); + } + + public final V setValue(V newValue) { + V oldValue = value; + value = newValue; + return oldValue; + } + // 重写 equals() 方法 + 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; + } +} +``` + +**树节点类源码:** + +```java +static final class TreeNode extends LinkedHashMap.Entry { + TreeNode parent; // 父 + TreeNode left; // 左 + TreeNode right; // 右 + TreeNode prev; // needed to unlink next upon deletion + boolean red; // 判断颜色 + TreeNode(int hash, K key, V val, Node next) { + super(hash, key, val, next); + } + // 返回根节点 + final TreeNode root() { + for (TreeNode r = this, p;;) { + if ((p = r.parent) == null) + return r; + r = p; + } +``` + +## HashMap源码分析 + +### 构造方法 + +HashMap 中有四个构造方法,它们分别如下: + +```java + // 默认构造函数。 + public HashMap() { + this.loadFactor = DEFAULT_LOAD_FACTOR; // all other fields defaulted + } + + // 包含另一个“Map”的构造函数 + public HashMap(Map m) { + this.loadFactor = DEFAULT_LOAD_FACTOR; + putMapEntries(m, false);//下面会分析到这个方法 + } + + // 指定“容量大小”的构造函数 + public HashMap(int initialCapacity) { + this(initialCapacity, DEFAULT_LOAD_FACTOR); + } + + // 指定“容量大小”和“加载因子”的构造函数 + 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); + } + + static final int tableSizeFor(int cap) { + //// 扩容门槛为传入的初始容量往上取最近的2的n次方 + 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; + } + +``` + +**putMapEntries方法:** + +```java +final void putMapEntries(Map m, boolean evict) { + int s = m.size(); + if (s > 0) { + // 判断table是否已经初始化 + if (table == null) { // pre-size + // 未初始化,s为m的实际元素个数 + float ft = ((float)s / loadFactor) + 1.0F; + int t = ((ft < (float)MAXIMUM_CAPACITY) ? + (int)ft : MAXIMUM_CAPACITY); + // 计算得到的t大于阈值,则初始化阈值 + if (t > threshold) + threshold = tableSizeFor(t); + } + // 已初始化,并且m元素个数大于阈值,进行扩容处理 + else if (s > threshold) + resize(); + // 将m中的所有元素添加至HashMap中 + for (Map.Entry e : m.entrySet()) { + K key = e.getKey(); + V value = e.getValue(); + putVal(hash(key), key, value, false, evict); + } + } +} + +``` + +### put方法 + +**对putVal方法添加元素的分析如下:** + +- ①如果定位到的数组位置没有元素 就直接插入。 +- ②如果定位到的数组位置有元素就和要插入的key比较,如果key相同就直接覆盖,如果key不相同,就判断p是否是一个树节点,如果是就调用`e = ((TreeNode)p).putTreeVal(this, tab, hash, key, value)`将元素添加进入。如果不是就遍历链表插入(插入的是链表尾部)。 + +```java +public V put(K key, V value) { + return putVal(hash(key), key, value, false, true); +} + +final V putVal(int hash, K key, V value, boolean onlyIfAbsent, + boolean evict) { + Node[] tab; Node p; int n, i; + // table未初始化或者长度为0,进行扩容 + if ((tab = table) == null || (n = tab.length) == 0) + n = (tab = resize()).length; + // (n - 1) & hash 确定元素存放在哪个桶中,桶为空,新生成结点放入桶中(此时,这个结点是放在数组中) + if ((p = tab[i = (n - 1) & hash]) == null) + tab[i] = newNode(hash, key, value, null); + // 桶中已经存在元素 + else { + Node e; K k; + // 比较桶中第一个元素(数组中的结点)的hash值相等,key相等 + if (p.hash == hash && + ((k = p.key) == key || (key != null && key.equals(k)))) + // 将第一个元素赋值给e,用e来记录 + e = p; + // hash值不相等,即key不相等;为红黑树结点 + else if (p instanceof TreeNode) + // 放入树中 + e = ((TreeNode)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); + // 结点数量达到阈值,转化为红黑树 + if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st + treeifyBin(tab, hash); + // 跳出循环 + break; + } + // 判断链表中结点的key值与插入的元素的key值是否相等 + if (e.hash == hash && + ((k = e.key) == key || (key != null && key.equals(k)))) + // 相等,跳出循环 + break; + // 用于遍历桶中的链表,与前面的e = p.next组合,可以遍历链表 + p = e; + } + } + // 表示在桶中找到key值、hash值与插入元素相等的结点 + if (e != null) { + // 记录e的value + V oldValue = e.value; + // onlyIfAbsent为false或者旧值为null + if (!onlyIfAbsent || oldValue == null) + //用新值替换旧值 + e.value = value; + // 访问后回调 + afterNodeAccess(e); + // 返回旧值 + return oldValue; + } + } + // 结构性修改 + ++modCount; + // 实际大小大于阈值则扩容 + if (++size > threshold) + resize(); + // 插入后回调 + afterNodeInsertion(evict); + return null; +} +``` + + JDK1.7 put方法的代码** + +**put方法** + +- ①如果定位到的数组位置没有元素 就直接插入。 +- ②如果定位到的数组位置有元素,遍历以这个元素为头结点的链表,依次和插入的key比较,如果key相同就直接覆盖,不同就采用头插法插入元素。 + +```java +public V put(K key, V value) + if (table == EMPTY_TABLE) { + inflateTable(threshold); +} + if (key == null) + return putForNullKey(value); + int hash = hash(key); + int i = indexFor(hash, table.length); + for (Entry e = table[i]; e != null; e = e.next) { // 先遍历 + Object k; + if (e.hash == hash && ((k = e.key) == key || key.equals(k))) { + V oldValue = e.value; + e.value = value; + e.recordAccess(this); + return oldValue; + } + } + + modCount++; + addEntry(hash, key, value, i); // 再插入 + return null; +} +``` + +### get方法 + +```java +public V get(Object key) { + Node e; + return (e = getNode(hash(key), key)) == null ? null : e.value; +} + +final Node getNode(int hash, Object key) { + Node[] tab; Node 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) { + // 在树中get + if (first instanceof TreeNode) + return ((TreeNode)first).getTreeNode(hash, key); + // 在链表中get + do { + if (e.hash == hash && + ((k = e.key) == key || (key != null && key.equals(k)))) + return e; + } while ((e = e.next) != null); + } + } + return null; +} +``` + +### resize方法 + +```java +final Node[] resize() { + Node[] 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; + } + // 没超过最大值,就扩充为原来的2倍 + else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY && oldCap >= DEFAULT_INITIAL_CAPACITY) + newThr = oldThr << 1; // double threshold + } + else if (oldThr > 0) // initial capacity was placed in threshold + newCap = oldThr; + else { + // signifies using defaults + newCap = DEFAULT_INITIAL_CAPACITY; + newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY); + } + // 计算新的resize上限 + if (newThr == 0) { + float ft = (float)newCap * loadFactor; + newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ? (int)ft : Integer.MAX_VALUE); + } + threshold = newThr; + @SuppressWarnings({"rawtypes","unchecked"}) + Node[] newTab = (Node[])new Node[newCap]; + table = newTab; + if (oldTab != null) { + // 把每个bucket都移动到新的buckets中 + for (int j = 0; j < oldCap; ++j) { + Node e; + if ((e = oldTab[j]) != null) { + oldTab[j] = null; + if (e.next == null) + newTab[e.hash & (newCap - 1)] = e; + else if (e instanceof TreeNode) + ((TreeNode)e).split(this, newTab, j, oldCap); + else { + Node loHead = null, loTail = null; + Node hiHead = null, hiTail = null; + Node next; + do { + next = e.next; + // 原索引 + if ((e.hash & oldCap) == 0) { + if (loTail == null) + loHead = e; + else + loTail.next = e; + loTail = e; + } + // 原索引+oldCap + else { + if (hiTail == null) + hiHead = e; + else + hiTail.next = e; + hiTail = e; + } + } while ((e = next) != null); + // 原索引放到bucket里 + if (loTail != null) { + loTail.next = null; + newTab[j] = loHead; + } + // 原索引+oldCap放到bucket里 + if (hiTail != null) { + hiTail.next = null; + newTab[j + oldCap] = hiHead; + } + } + } + } + } + return newTab; +} +``` + + + diff --git a/week_01/23/LinkedList-23.md b/week_01/23/LinkedList-23.md new file mode 100644 index 0000000..c289b51 --- /dev/null +++ b/week_01/23/LinkedList-23.md @@ -0,0 +1,435 @@ +## 简介 + +LinkedList是一个实现了List接口和Deque接口的双端链表。 LinkedList底层的链表结构使它支持高效的插入和删除操作,另外它实现了Deque接口,使得LinkedList类也具有队列的特性; LinkedList不是线程安全的 + +## 内部结构分析 + +```java +private static class Node { + E item;//节点值 + Node next;//后继节点 + Node prev;//前驱节点 + + Node(Node prev, E element, Node next) { + this.item = element; + this.next = next; + this.prev = prev; + } + } +``` + +这个类就代表双端链表的节点Node。这个类有三个属性,分别是前驱节点,本节点的值,后继结点。 + +## LinkedList源码分析 + +### 构造方法 + +**空构造方法:** + +```java + public LinkedList() { + } +``` + +**用已有的集合创建链表的构造方法:** + +```java + public LinkedList(Collection c) { + this(); + addAll(c); + } +``` + +### add方法 + +**add(E e)** 方法:将元素添加到链表尾部 + +```java +public boolean add(E e) { + linkLast(e);//这里就只调用了这一个方法 + return true; + } + /** + * 链接使e作为最后一个元素。 + */ + void linkLast(E e) { + final Node l = last; + final Node newNode = new Node<>(l, e, null); + last = newNode;//新建节点 + if (l == null) + first = newNode; + else + l.next = newNode;//指向后继元素也就是指向下一个元素 + size++; + modCount++; + } +``` + +**add(int index,E e)**:在指定位置添加元素 + +```java +public void add(int index, E element) { + checkPositionIndex(index); //检查索引是否处于[0-size]之间 + + if (index == size)//添加在链表尾部 + linkLast(element); + else//添加在链表中间 + linkBefore(element, node(index)); + } + + Node node(int index) { + // assert isElementIndex(index); + + if (index < (size >> 1)) { + Node x = first; + for (int i = 0; i < index; i++) + x = x.next; + return x; + } else { + Node x = last; + for (int i = size - 1; i > index; i--) + x = x.prev; + return x; + } + } + + void linkBefore(E e, Node succ) { + // assert succ != null; + final Node pred = succ.prev; + final Node newNode = new Node<>(pred, e, succ); + succ.prev = newNode; + if (pred == null) + first = newNode; + else + pred.next = newNode; + size++; + modCount++; + } +``` + +linkBefore方法需要给定两个参数,一个插入节点的值,一个指定的node,所以我们又调用了Node(index)去找到index对应的node。 + +**addAll(Collection c ):将集合插入到链表尾部** + +```java +public boolean addAll(Collection c) { + return addAll(size, c); + } +``` + +**addAll(int index, Collection c):** 将集合从指定位置开始插入 + +```java +public boolean addAll(int index, Collection c) { + //1:检查index范围是否在size之内 + checkPositionIndex(index); + + //2:toArray()方法把集合的数据存到对象数组中 + Object[] a = c.toArray(); + int numNew = a.length; + if (numNew == 0) + return false; + + //3:得到插入位置的前驱节点和后继节点 + Node pred, succ; + //如果插入位置为尾部,前驱节点为last,后继节点为null + if (index == size) { + succ = null; + pred = last; + } + //否则,调用node()方法得到后继节点,再得到前驱节点 + else { + succ = node(index); + pred = succ.prev; + } + + // 4:遍历数据将数据插入 + for (Object o : a) { + @SuppressWarnings("unchecked") E e = (E) o; + //创建新节点 + Node newNode = new Node<>(pred, e, null); + //如果插入位置在链表头部 + if (pred == null) + first = newNode; + else + pred.next = newNode; + pred = newNode; + } + + //如果插入位置在尾部,重置last节点 + if (succ == null) { + last = pred; + } + //否则,将插入的链表与先前链表连接起来 + else { + pred.next = succ; + succ.prev = pred; + } + + size += numNew; + modCount++; + return true; + } +``` + +**addFirst(E e):** 将元素添加到链表头部 + +```java + public void addFirst(E e) { + linkFirst(e); + } +private void linkFirst(E e) { + final Node f = first; + final Node newNode = new Node<>(null, e, f);//新建节点,以头节点为后继节点 + first = newNode; + //如果链表为空,last节点也指向该节点 + if (f == null) + last = newNode; + //否则,将头节点的前驱指针指向新节点,也就是指向前一个元素 + else + f.prev = newNode; + size++; + modCount++; + } +``` + +**addLast(E e):** 将元素添加到链表尾部 + +```java +public void addLast(E e) { + linkLast(e); + } +``` + +### 根据位置取数据的方法 + +**get(int index):** 根据指定索引返回数据 + +```java +public E get(int index) { + //检查index范围是否在size之内 + checkElementIndex(index); + //调用Node(index)去找到index对应的node然后返回它的值 + return node(index).item; + } +``` + +**获取头节点(index=0)数据方法:** + +```java +public E getFirst() { + final Node f = first; + if (f == null) + throw new NoSuchElementException(); + return f.item; + } +public E element() { + return getFirst(); + } +public E peek() { + final Node f = first; + return (f == null) ? null : f.item; + } + +public E peekFirst() { + final Node f = first; + return (f == null) ? null : f.item; + } +``` + +**区别:** getFirst(),element(),peek(),peekFirst() 这四个获取头结点方法的区别在于对链表为空时的处理,是抛出异常还是返回null,其中**getFirst()** 和**element()** 方法将会在链表为空时,抛出异常 + +element()方法的内部就是使用getFirst()实现的。它们会在链表为空时,抛出NoSuchElementException +**获取尾节点(index=-1)数据方法:** + +```java + public E getLast() { + final Node l = last; + if (l == null) + throw new NoSuchElementException(); + return l.item; + } + public E peekLast() { + final Node l = last; + return (l == null) ? null : l.item; + } +``` + +**两者区别:** **getLast()** 方法在链表为空时,会抛出**NoSuchElementException**,而**peekLast()** 则不会,只是会返回 **null**。 + +### 根据对象得到索引的方法 + +**int indexOf(Object o):** 从头遍历找 + +```java +public int indexOf(Object o) { + int index = 0; + if (o == null) { + //从头遍历 + for (Node x = first; x != null; x = x.next) { + if (x.item == null) + return index; + index++; + } + } else { + //从头遍历 + for (Node x = first; x != null; x = x.next) { + if (o.equals(x.item)) + return index; + index++; + } + } + return -1; + } +``` + +**int lastIndexOf(Object o):** 从尾遍历找 + +```java +public int lastIndexOf(Object o) { + int index = size; + if (o == null) { + //从尾遍历 + for (Node x = last; x != null; x = x.prev) { + index--; + if (x.item == null) + return index; + } + } else { + //从尾遍历 + for (Node x = last; x != null; x = x.prev) { + index--; + if (o.equals(x.item)) + return index; + } + } + return -1; + } +``` + +### 检查链表是否包含某对象的方法: + +**contains(Object o):** 检查对象o是否存在于链表中 + +```java + public boolean contains(Object o) { + return indexOf(o) != -1; + } +``` + +### 删除方法 + +**remove()** ,**removeFirst(),pop():** 删除头节点 + +```java +public E poll() { + final Node f = first; + return (f == null) ? null : unlinkFirst(f); + } + +public E pop() { + return removeFirst(); + } +public E remove() { + return removeFirst(); + } +public E removeFirst() { + final Node f = first; + if (f == null) + throw new NoSuchElementException(); + return unlinkFirst(f); + } +``` + +**removeLast(),pollLast():** 删除尾节点 + +```java +public E removeLast() { + final Node l = last; + if (l == null) + throw new NoSuchElementException(); + return unlinkLast(l); + } +public E pollLast() { + final Node l = last; + return (l == null) ? null : unlinkLast(l); + } +``` + +**remove(Object o):** 删除指定元素 + +```java +public boolean remove(Object o) { + //如果删除对象为null + if (o == null) { + //从头开始遍历 + for (Node x = first; x != null; x = x.next) { + //找到元素 + if (x.item == null) { + //从链表中移除找到的元素 + unlink(x); + return true; + } + } + } else { + //从头开始遍历 + for (Node x = first; x != null; x = x.next) { + //找到元素 + if (o.equals(x.item)) { + //从链表中移除找到的元素 + unlink(x); + return true; + } + } + } + return false; + } +``` + + + +unlink(Node x) 方法: + +```java +E unlink(Node x) { + // assert x != null; + final E element = x.item; + final Node next = x.next;//得到后继节点 + final Node prev = x.prev;//得到前驱节点 + + //删除前驱指针 + if (prev == null) { + first = next;//如果删除的节点是头节点,令头节点指向该节点的后继节点 + } else { + prev.next = next;//将前驱节点的后继节点指向后继节点 + x.prev = null; + } + + //删除后继指针 + if (next == null) { + last = prev;//如果删除的节点是尾节点,令尾节点指向该节点的前驱节点 + } else { + next.prev = prev; + x.next = null; + } + + x.item = null; + size--; + modCount++; + return element; + } +``` + +**remove(int index)**:删除指定位置的元素 + +```java +public E remove(int index) { + //检查index范围 + checkElementIndex(index); + //将节点删除 + return unlink(node(index)); + } +``` + + + diff --git a/week_01/25/ArrayList_25.md b/week_01/25/ArrayList_25.md new file mode 100644 index 0000000..ac334f7 --- /dev/null +++ b/week_01/25/ArrayList_25.md @@ -0,0 +1,420 @@ +在分析动态数组ArrayList的源码之前先对其底层数据结构——数组进行分析 +# 数组 +数组是一种最基本的数据结构,采用了一组连续的内存空间按顺序存储对象或基本数据类型。其访问的时间复杂度为O(1),插入以及删除由于涉及到元素的移动时间复杂度为O(n),在Java中声明一个数组需要事先指定数组的大小,那么就可能会造成空间浪费以及扩容问题,为了解决这些问题,动态数组ArrayList就诞生了。 + +# ArrayList +```java +public class ArrayList extends AbstractList + implements List, RandomAccess, Cloneable, java.io.Serializable +``` +(1)实现了RandomAccess接口,标识其具备随机访问功能 +(2)实现了List,提供了List基础的增,删,改,查等操作 +(3)实现了Cloneable,可以被克隆 +(4)实现了Serializable,可以被序列化 + +### 属性 +```java + /** + * 默认的容量大小为10 + * Lazy-load: 只有在ArrayList真正添加元素的时候才会通过DEFAULT_CAPACITY创建数组, + */ + private static final int DEFAULT_CAPACITY = 10; + + /** + * 当指定ArrayList容量为0的时候, 底层使用的空数组 + */ + private static final Object[] EMPTY_ELEMENTDATA = {}; + + /** + * 调用默认构造函数ArrayList()时,使用这个空数组作为elementData + * 与EMPTY_ELEMENTDATA的区别在于: DEFAULTCAPACITY_EMPTY_ELEMENTDATA在第一次添加元素时会变为长度为DEFAULT_CAPACITY的数组 + */ + private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {}; + + /** + * 真正存储元素的数组 + * 标记了transient,此字段不会被序列化 + */ + transient Object[] elementData; // non-private to simplify nested class access + + /** + * 容器中元素的个数 + */ + private int size; +``` + +### 构造函数 +```java + /** + * 1. 指定容器大小初始化ArrayList + */ + public ArrayList(int initialCapacity) { + //若传入初始Capacity > 0, 那么就根据Capacity创建对应长度的Object数组 + if (initialCapacity > 0) { + this.elementData = new Object[initialCapacity]; + //若传入初始Capacity = 0, 那么就将EMPTY_ELEMENTDATA这个空数组赋给elementData + } else if (initialCapacity == 0) { + this.elementData = EMPTY_ELEMENTDATA; + //若传入初始Capacity < 0, throw Exception + } else { + throw new IllegalArgumentException("Illegal Capacity: "+ + initialCapacity); + } + } + + /** + * 2. 默认构造函数 + */ + public ArrayList() { + //将DEFAULTCAPACITY_EMPTY_ELEMENTDATA这个空数组赋给elementData + this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA; + } + + /** + * 3. 通过传入一个集合初始化ArrayList + */ + public ArrayList(Collection c) { + //把传入的集合元素转换为数组拷贝给elementData + elementData = c.toArray(); + //检查拷贝之后, elementData的长度是否为0 + if ((size = elementData.length) != 0) { + // c.toArray或许不能正确地转换为Object数组 + if (elementData.getClass() != Object[].class) + //若没有正确转换为Object[], 重新拷贝成Object[].class类型再赋给elementData + elementData = Arrays.copyOf(elementData, size, Object[].class); + } else { + // 若拷贝之后elementData长度为0, 那么直接将EMPTY_ELEMENTDATA赋给elementData + this.elementData = EMPTY_ELEMENTDATA; + } + } +``` + +针对c.toArray不能正确转换为Object数组,可以参考如下实例: +```java + String[] str = new String[]{"wang", "han", "lin"}; + List asListTemp = Arrays.asList("wang", "han", "lin"); + //asListTemp.remove(0); // UnsupportedOperationException + //str[0] = "wang1998"; // 那么temp.get(0)也会随之被修改 + List temp = new ArrayList<>(asListTemp); +``` +这是因为asListTemp仍然是Arrays的内部类对象,asList()并没有实现对集合的修改,这里只是转换了接口,但后台数据仍然是String数组。因此在遇到```if (elementData.getClass() != Object[].class)```这段代码时必须要通过Arrays.copyOf进行赋值。 +顺便一提,使用Arrays.asList()转换的集合并不能对其进行修改相关的操作,这类操作会抛出UnsupportedOperationException异常,但是在原数组之上进行修改是可行的。这里体现的是适配器模式,只是转换接口,后台数据不变。 + + +### add(E e) 添加元素到数组末尾 +(1)明确添加元素后数组所需要的最小长度minCapacity +(2)如果minCapacity > elementData.length,需要进行数组扩容 +(3)在末尾处添加元素 +(4)添加成功return true + +```java + /** + * 添加元素到数组末尾, 时间复杂度为O(1) + */ + public boolean add(E e) { + //确保ArrayList有足够的Capacity添加新的元素 + ensureCapacityInternal(size + 1); + //把元素插入到elementData的末尾元素之后 + elementData[size++] = e; + return true; + } + + private void ensureCapacityInternal(int minCapacity) { + ensureExplicitCapacity(calculateCapacity(elementData, minCapacity)); + } + + private static int calculateCapacity(Object[] elementData, int minCapacity) { + //如果当前ArrayList处于刚初始化的状态, 就返回默认长度10 + if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) { + return Math.max(DEFAULT_CAPACITY, minCapacity); + } + //如果ArrayList已经处于正在使用的状态(已经进行了添加操作), 直接返回传入的minCapacity + return minCapacity; + } + + + private void ensureExplicitCapacity(int minCapacity) { + modCount++;//modCount用于记录操作ArrayList的次数 + + // 如果elementData的长度小于添加元素所需要的minCapacity, 需要对原数组进行扩容操作 + if (minCapacity - elementData.length > 0) + grow(minCapacity); + } + + private void grow(int minCapacity) { + //原数组长度 + int oldCapacity = elementData.length; + //新数组的长度 = 原数组长度的1.5倍 + int newCapacity = oldCapacity + (oldCapacity >> 1); + //如果新数组的长度依然小于minCapacity, 那么以minCapacity为准进行数组扩容 + if (newCapacity - minCapacity < 0) + newCapacity = minCapacity; + //若新数组长度已经超过ArrayList规定的最大长度, 则使用最大长度 + if (newCapacity - MAX_ARRAY_SIZE > 0) + newCapacity = hugeCapacity(minCapacity); + //底层是根据newCapacity创建了一个新的数组, 然后在这个新数组之上添加原数组的元素 + //将新数组赋值给elementData + elementData = Arrays.copyOf(elementData, newCapacity); + } +``` + +### add(int index, E element) 在指定位置添加元素 +(1)检查index是否越界 +(2)检查是否需要扩容 +(3)右移index以及后续所有元素 +(4)index位置插入新元素 +(5)size++ +```java + /** + * 在指定位置插入元素, 并将原本在该位置的元素以及右边的所有元素向右移动一位 + */ + public void add(int index, E element) { + //检查index是否越界 + rangeCheckForAdd(index); + //确保ArrayList有足够的Capacity添加新的元素 + ensureCapacityInternal(size + 1); + //移动原本在该位置的元素以及右边的所有元素 + System.arraycopy(elementData, index, elementData, index + 1, + size - index); + //在index位置插入元素 + elementData[index] = element; + size++; + } + + /** + * 确保index合法 + */ + private void rangeCheckForAdd(int index) { + if (index > size || index < 0) + throw new IndexOutOfBoundsException(outOfBoundsMsg(index)); + } +``` + +### addAll(Collection c) 在末尾追加集合c中的所有元素 +(1)将集合c转换为Object数组 +(2)检查是否需要扩容 +(3)将集合c中的元素全部拷贝到原数组末尾 +```java + public boolean addAll(Collection c) { + //将集合c转换为Object[] + Object[] a = c.toArray(); + int numNew = a.length; + //检查是否需要扩容 + ensureCapacityInternal(size + numNew); // Increments modCount + //将集合c中的元素全部拷贝到原数组末尾 + System.arraycopy(a, 0, elementData, size, numNew); + size += numNew; + //若c不为空return true, else return false + return numNew != 0; + } +``` + +### addAll(int index, Collection c) 指定位置添加集合c的所有元素 +(1)检查index是否越界 +(2)将集合c转换为Object数组 +(3)检查是否需要扩容 +(4)移动index以及右边的所有元素,挪出空间存放集合c的所有元素 +(5)index位置添加集合c的所有元素 +```java + public boolean addAll(int index, Collection c) { + //检查index是否合法 + rangeCheckForAdd(index); + //这里的几个步骤都和addAll(Collection c)相同 + Object[] a = c.toArray(); + int numNew = a.length; + ensureCapacityInternal(size + numNew); // Increments modCount + //需要移动的元素个数 + int numMoved = size - index; + //若需要移动的元素个数大于0, 那么需要进行元素移动操作 + if (numMoved > 0) + //右移index以及右边的所有元素, 挪出空间用于存放集合c的元素 + System.arraycopy(elementData, index, elementData, index + numNew, + numMoved); + //在index位置将集合c的所有元素拷贝至elementData + System.arraycopy(a, 0, elementData, index, numNew); + size += numNew; + return numNew != 0; + } +``` + +### get(int index) 获取指定位置上的元素 +(1)检查index是否越界 +(2)返回index位置上的元素 +```java + public E get(int index) { + rangeCheck(index); + return elementData(index); + } + + /** + * 这里只检查是否越上界, 如果越下界会抛出ArrayIndexOutOfBoundsException + */ + private void rangeCheck(int index) { + if (index >= size) + throw new IndexOutOfBoundsException(outOfBoundsMsg(index)); + } + + E elementData(int index) { + return (E) elementData[index]; + } +``` + +### remove(int index) 删除指定位置上的元素并返回 +(1)检查index是否越界 +(2)获取index位置上的元素 +(3)若index不指向末尾元素,index后面的元素前移一位 +(4)末尾元素置为null,便于GC回收 +(5)返回删除元素 +```java + public E remove(int index) { + //检查index是否越界 + rangeCheck(index); + + modCount++; + //获取index位置上的元素 + E oldValue = elementData(index); + //如果index并不指向最后一个元素, 那么就前移index后面的元素 + int numMoved = size - index - 1; + if (numMoved > 0) + System.arraycopy(elementData, index+1, elementData, index, + numMoved); + //elementData最后一位置为null, 消除引用便于GC回收 + elementData[--size] = null; + return oldValue; + } +``` + +### remove(Object o) 删除指定元素值 +(1)遍历整个数组找到指定元素 +(2)fastRemove +```java + /** + 删除数组中第一次出现的指定元素, 删除成功return true, else return false + */ + public boolean remove(Object o) { + //遍历整个数组, 删除第一个出现的null元素 (这里使用"=="进行比较) + if (o == null) { + for (int index = 0; index < size; index++) + if (elementData[index] == null) { + fastRemove(index); + return true; + } + } else {//遍历整个数组, 删除第一个出现的指定元素 (这里使用"equals()"进行比较) + for (int index = 0; index < size; index++) + if (o.equals(elementData[index])) { + fastRemove(index); + return true; + } + } + return false; + } + + /* + * 删除指定位置的元素 + * 与remove()相比, 该方法略去了越界检查以及返回删除元素, 其他步骤都是一样的 + * 之所以这样做是因为, 省去了不必要的检查操作, 提升了性能 + */ + private void fastRemove(int index) { + modCount++; + int numMoved = size - index - 1; + if (numMoved > 0) + System.arraycopy(elementData, index+1, elementData, index, + numMoved); + elementData[--size] = null; // clear to let GC do its work + } +``` + +### retainAll(Collection c) 求与集合c的交集部分 +(1)集合c检查是否为空 +(2)遍历element,保留与集合c相交的部分,删除不相交的部分(写指针之后的所有元素置为空) +(3)若成功批量删除return true,else return false +```java + public boolean retainAll(Collection c) { + //要求集合c不为空 + Objects.requireNonNull(c); + //调用批量删除方法, 此时boolean complement = true, 表示删除不包含在c中的元素 + return batchRemove(c, true); + } + + /** + * 批量删除元素 + * complement = true: 删除c中不包含的元素 + * complement = false: 删除c中包含的元素 + */ + private boolean batchRemove(Collection c, boolean complement) { + final Object[] elementData = this.elementData; + //初始化读指针r, 写指针w + int r = 0, w = 0; + //是否成功修改的返回值, 默认false + boolean modified = false; + try { + //遍历整个数组, 如果集合c中包含读指针指向的元素, 且complement = ture, 就将读指针指向的元素放在写指针指向的位置, 这样就留下了集合c与ArrayList相交的部分, 删除了不相交的元素 + //如果集合c中不包含读指针指向的元素, 且complement = false, 就将读指针指向的元素放在写指针指向的位置, 这样就留下了集合c与ArrayList不相交的部分, 删除了相交的元素 + //整个操作都在原数组上完成, 不需要额外空间 + //这个操作有点类似LeetCode上移动零那道题, 比较典型的双指针解法 + for (; r < size; r++) + if (c.contains(elementData[r]) == complement) + elementData[w++] = elementData[r]; + } finally { + //正常情况下, 读指针最后一定是指向size的, 除非c.contains()抛出了异常 + if (r != size) { + //若c.contains抛出异常, 则把未读元素都拷贝到写指针之后 + System.arraycopy(elementData, r, + elementData, w, + size - r); + //写指针指向相应的位置 + w += size - r; + } + //若写指针不指向末尾元素, 那么写指针后序的元素都需要置为空 + if (w != size) { + // clear to let GC do its work + for (int i = w; i < size; i++) + elementData[i] = null; + modCount += size - w; + //此时删除后的size等于写指针所指的位置 + size = w; + //标识完成了整个批量删除 + modified = true; + } + } + return modified; + } +``` + +### removeAll(Collection c) 求与集合c的差集 +```java + /** + * 保留当前集合中不与c相交的元素 + */ + public boolean removeAll(Collection c) { + Objects.requireNonNull(c); + //同样调用了批量删除方法, complement = false意味着删除包含在c中的元素 + return batchRemove(c, false); + } +``` + +# 总结 +(1)ArrayList底层采用Object数组进行数据存储,每次添加时都会检查是否需要扩容,若容量不足则通过创建一个新的长度为原长度1.5倍的数组,并将原数组拷贝至新数组并返回。 +(2)ArrayList随机访问时间复杂度为O(1) +(3)ArrayList添加元素到末尾的时间复杂度为O(1),添加元素到中间位置的时间复杂度为O(n) +(4)ArrayList删除末尾元素的时间复杂度为O(1),删除中间位置元素的时间复杂度为O(n) +(5)ArrayList支持求并集,调用addAll(Collection c) +(6)ArrayList支持求交集,调用retainAll(Collection c) +(7)ArrayList支持求单向差集,调用removeAll(Collection c) + +一些需要注意的点写在末尾: +### 1. 扩容导致的问题 +不难发现ArrayList如果一开始通过默认构造函数创建,那么过多的添加操作就会导致过多的扩容操作,并且每一次扩容操作都会进行一次arraycopy,这个操作是非常耗时的,因此为了尽可能的避免扩容带来的性能开销,可以在事先就指定好ArrayList的容量进行创建,这也是阿里巴巴开发手册中明确指出的,同理针对HashMap这类需要指定初始容量的集合都需要考虑扩容造成的性能影响。 + +### 2. 使用集合转数组方法必须使用集合的toArray(T[] array) +使用附带参数的toArray(T[] array)方法,其目的是为了确保了转换后的数组类型,直接使用toArray()的无参方法返回值只能是Object类型,且强制转换其他类型的数组会出现ClassCastException错误。下面是测试代码: + +```java + String[] str1 = new String[2]; + String[] str2 = new String[2]; + List temp = new ArrayList<>(); + temp.add("w"); + temp.add("h"); + temp.toArray(str1);//success + str2 = (String[])temp.toArray();//ClassCastException +``` diff --git a/week_01/25/HashMap_25.md b/week_01/25/HashMap_25.md new file mode 100644 index 0000000..0832b2c --- /dev/null +++ b/week_01/25/HashMap_25.md @@ -0,0 +1,505 @@ +# HashMap +HashMap采用key-value的存储结构,每个唯一key对应一个唯一的value,通常情况下HashMap的查询和修改时间复杂度为O(1),因为是散列存储,HashMap不能保证元素存储的顺序,且线程不安全。 + +```java +public class HashMap extends AbstractMap + implements Map, Cloneable, Serializable { + ``` + (1)继承了AbstractMap,实现了Map接口,具备Map的所有功能 + (2)实现了Cloneable,可以被克隆 + (3)实现了Serializable,可以被序列化 + +### 属性 +```java + /** + * 默认初始容量为16 + * 容量必须指定为2的n次方, 目的是为了使hash函数能够更加有效的获取散列值 + * index = hashCode & (capacity - 1) + */ + static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16 + + /** + * 最大容量 = 2^30 + */ + static final int MAXIMUM_CAPACITY = 1 << 30; + + /** + * 默认负载因子 + * 意味着当HashMap的容量被使用75%的时候会进行扩容 + */ + static final float DEFAULT_LOAD_FACTOR = 0.75f; + + /** + * 当一个桶中的链表长度大于等于8时转化为红黑树 + */ + static final int TREEIFY_THRESHOLD = 8; + + /** + * 当一个桶中的链表长度小于等于6时转化为链表 + */ + static final int UNTREEIFY_THRESHOLD = 6; + + /** + * 当桶的个数到达64个才能够进行树化 + */ + static final int MIN_TREEIFY_CAPACITY = 64; + + /* ---------------- Fields -------------- */ + + /** + * 位桶数组 + */ + transient Node[] table; + + /** + * 作为entrySet()的缓存 + */ + transient Set> entrySet; + + /** + * Map中的元素个数 + */ + transient int size; + + /** + * 修改次数 + */ + transient int modCount; + + /** + * 当位桶数组的数量到达多少时可以进行扩容 , shreshold = (capacity * load factor). + */ + int threshold; + + /** + * 负载因子 + */ + final float loadFactor; +``` + +### Node内部类 +```java + /** + * 典型的单链表节点 + */ + static class Node implements Map.Entry { + final int hash;//用于存储通过hash函数处理后的key.hashCode() + final K key; + V value; + Node next; + .... + } +``` + +### TreeNode内部类 +```java + static final class TreeNode extends LinkedHashMap.Entry { + TreeNode parent; // red-black tree links + TreeNode left; + TreeNode right; + TreeNode prev; // needed to unlink next upon deletion + boolean red; +``` + +### 构造方法 +```java + /** + * 1. 指定初始容量和负载因子的构造方法 + */ + public HashMap(int initialCapacity, float loadFactor) { + //判断初始Capacity是否合法 + 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); + } + + /** + * 将Capacity转换为往上取最近的2的n次方 + * 该算法的思想就是将Capacity的有效二进制位转换为全1, 然后加1取到二进制位 + * 例如(14)2 = 1100, 1100低位全部转换为1, 1100 -> 1111, 1111 + 1 = 100000 + * 如果日常需要类似的算法场景, 就可以直接从这里照搬了(●'◡'●) + */ + static final int tableSizeFor(int cap) { + int n = cap - 1;//-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; + } + + /** + * 2. 只指定初始容量的构造方法, 底层调用了第一个构造方法, 对其设置了默认负载因子 + */ + public HashMap(int initialCapacity) { + this(initialCapacity, DEFAULT_LOAD_FACTOR); + } + + /** + * 3. 空构造方法, 使用默认负载因子 + */ + public HashMap() { + this.loadFactor = DEFAULT_LOAD_FACTOR; // all other fields defaulted + } +``` + +### put(K key, V value) 添加键值对 +(1)计算节点key的hash值 +(2)如果是刚初始化的map,调用resize()初始化位桶数组 +(3)hash&(n - 1)计算出newNode存放的下标值 +(4)如果下标位置桶为空,那么直接放入newNode即可,跳转到步骤(7) +(5)如果下标位置桶不为空: +  (5.1)观察桶上第一个节点的key与newNode.key是否相同,若相同保存该节点,跳转到步骤(6) +  (5.2)若不相同,且第一个节点为TreeNode,则按照红黑树的方式进行添加 +  (5.3)若不相同,且第一个节点不为红黑树,则遍历链表寻找具有相同key的节点,若找到了保存该节点,跳转到步骤(6),若未找到则在末尾添加newNode +  (5.4)观察添加newNode后是否需要树化 +(6)将保存的具有相同key的节点value进行更新,并返回oldValue +(7)观察是否需要扩容,若需要调用resize() +(8)因为寻找到相同key的结果会在步骤(6)中return,这里只可能存在未找到相同key的情况,return null + +```java + public V put(K key, V value) { + //调用hash方法计算key的hash值, 然后进行putVal + //如果替换了相同key节点的value, 那么return oldValue, else return null + return putVal(hash(key), key, value, false, true); + } + + static final int hash(Object key) { + int h; + //if key = null, return 0, else return (key的32位hashCode异或key的高16位) + //目的时为了让hash更加分散 + return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16); + } + + final V putVal(int hash, K key, V value, boolean onlyIfAbsent, + boolean evict) { + Node[] tab; + Node p; + int n, i; + //如果桶数组为空, 则初始化位桶数组(Lazy-load) + if ((tab = table) == null || (n = tab.length) == 0) + //调用resize()初始化, 将初始化后的位桶数组长度赋给n + n = (tab = resize()).length; + //-------------------------important---------------------------// + //这里就是HashMap非常经典的计算下标算法了 + //(n - 1) & hash这个算法充分利用了位桶数组的长度n和hash值计算出更加散列的下标 + //观察该下标是否存在元素, 如果为空就直接把newNode放入 + if ((p = tab[i = (n - 1) & hash]) == null) + tab[i] = newNode(hash, key, value, null); + //如果该位置上已经存在了节点 + else { + Node e; + K k; + //如果桶中第一个节点的key与待插入节点的key相同, 保存该结点为e, 用于后续修改value + if (p.hash == hash && + ((k = p.key) == key || (key != null && key.equals(k)))) + e = p; + //如果桶中第一个节点的key与待插入节点的key不相同, 且第一个节点是树化的节点 + else if (p instanceof TreeNode) + //此时调用putTreeVal方法将node插入 + e = ((TreeNode)p).putTreeVal(this, tab, hash, key, value); + //如果桶中第一个节点的key与待插入节点的key不相同, 且第一个节点是链表节点 + else { + //那么就需要遍历这个链表, 寻找相同key的节点 + for (int binCount = 0; ; ++binCount) { + //如果链表遍历完了都没有找到相同key的节点, 则在末尾追加新节点 + 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; + } + //如果找到了相同的key的节点, 则退出循环 + if (e.hash == hash && + ((k = e.key) == key || (key != null && key.equals(k)))) + break; + p = e; + } + } + //e != null, 说明找到了相同key的节点, 那么需要进行value替换 + if (e != null) { // existing mapping for key + //记录旧值 + V oldValue = e.value; + //判断是否需要替换旧值 + if (!onlyIfAbsent || oldValue == null) + //替换旧值为新值 + e.value = value; + //在节点被访问后需要做点什么事, LinkedListHashMap中用到 + afterNodeAccess(e); + //返回旧值 + return oldValue; + } + } + //下面这些代码会处理没有寻找到相同key节点的情况 + ++modCount; + //观察放入Node之后的size是否需要扩容 + if (++size > threshold) + resize(); + //在节点被访问后做点什么事, 在LinkedHashMap中用到 + afterNodeInsertion(evict); + //未替换相同key节点的value, return null + return null; + } +``` + +### resize()方法 +(1)如果使用默认构造方法,则第一次插入元素时初始化容量为16,扩容门槛为12 +(2)如果使用非默认构造方法,则第一次插入元素时初始化容量等于扩容门槛(初始容量往上取2的n次方) +(3)如果旧容量大于0,设置新容量和新扩容门槛 +(4)创建新容量的桶 +(5)搬迁元素 +```java + /** + * 对位桶数组的扩容方法 + * 这里是创建了一个新的位桶数组, 并将老的位桶数组搬家到新数组中 + */ + final Node[] resize() { + Node[] oldTab = table; + int oldCap = (oldTab == null) ? 0 : oldTab.length; + int oldThr = threshold; + int newCap, newThr = 0; + //如果oldCapacity > 0 + if (oldCap > 0) { + //且oldCapacity已经到达最大容量, 那么不再进行扩容, 直接返回oldTable + if (oldCap >= MAXIMUM_CAPACITY) { + threshold = Integer.MAX_VALUE; + return oldTab; + } + //oldCapacity*2 < 最大容量, 并且oldCapacity >= 默认初始容量(16) + //那么新容量 = oldCapacity*2, 新扩容门槛 = oldThreshold*2 + else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY && + oldCap >= DEFAULT_INITIAL_CAPACITY) + newThr = oldThr << 1; // double threshold + } + //使用非默认构造方法指定initialCapacity创建的map, 第一次put时会进入这里, capa + //如果旧容量为0, 且旧扩容门槛大于0, 则把oldThreShold = 往上取2^n 赋值给新容量 + else if (oldThr > 0) + newCap = oldThr; + //调用默认构造方法创建的map, 第一次put会进入这里 + else { // zero initial threshold signifies using defaults + //newCapacity = 16 + newCap = DEFAULT_INITIAL_CAPACITY; + //newThreshold = 0.75 * 16 + newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY); + } + //如果新扩容门槛为0, 在保证不超过最大容量的情况下, 设置新扩容门槛为newCapacity*负载因子 + if (newThr == 0) { + float ft = (float)newCap * loadFactor; + newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ? + (int)ft : Integer.MAX_VALUE); + } + //将新扩容门槛赋值给HashMap.threshold + threshold = newThr; + //根据新容量新建一个位桶数组 + @SuppressWarnings({"rawtypes","unchecked"}) + Node[] newTab = (Node[])new Node[newCap]; + //将新数组赋值给HashMap.table + table = newTab; + //如果旧数组不为空, 那么需要把原来的元素搬到新的位桶数组中 + if (oldTab != null) { + //遍历旧数组 + for (int j = 0; j < oldCap; ++j) { + Node e; + //如果旧数组的当前位置不为空, + if ((e = oldTab[j]) != null) { + //清空旧数组便于GC回收 + oldTab[j] = null; + //若当前位置的桶只存在一个元素 + if (e.next == null) + //只需要计算该元素在新桶中的位置然后搬到新桶中即可 + newTab[e.hash & (newCap - 1)] = e; + //若当前位置的桶下, 第一个节点为TreeNode + else if (e instanceof TreeNode) + //那么把这棵树打散成两棵树搬到新桶中 + ((TreeNode)e).split(this, newTab, j, oldCap); + else { + //将链表分化为两个链表存放到新位桶数组中 + Node loHead = null, loTail = null; + Node hiHead = null, hiTail = null; + Node next; + do {//遍历链表所有节点 + next = e.next; + //当前节点的hash对oldCapacity取余 == 0, 将其归为lowLinkedList + if ((e.hash & oldCap) == 0) { + //若lowLinkedList为空, 头节点为该节点 + if (loTail == null) + loHead = e; + else + //否则尾部追加 + loTail.next = e; + loTail = e; + } + else {//若不满足取余==0的条件, 则以同样的操作将该节点赋给highLinkedList + if (hiTail == null) + hiHead = e; + else + hiTail.next = e; + hiTail = e; + } + } while ((e = next) != null); + //遍历完成后就将原链表分化为2个链表了 + //低位链表在新桶中的位置还是与旧桶一样 + if (loTail != null) { + loTail.next = null; + newTab[j] = loHead; + } + //高位链表在新桶中的位置刚好实在原位置之上加上旧容量 + if (hiTail != null) { + hiTail.next = null; + newTab[j + oldCap] = hiHead; + } + } + } + } + } + return newTab; + } +``` + + + +### get(Object key) 获取map中key对应的value +(1)计算key的hash值 +(2)通过计算找到key所在的桶数组下标 +(3)如果第一个节点就是要查找的key节点,return +(4)如果第一个节点不是,且第一个节点是TreeNode,那么通过红黑树的方式查找 +(5)如果第一个节点不是,且第一个节点是链表,那么遍历链表查找 +```java + public V get(Object key) { + Node e; + //根据传入的key计算其hash, 并下到位桶数组中寻找对应桶的位置 + return (e = getNode(hash(key), key)) == null ? null : e.value; + } + + final Node getNode(int hash, Object key) { + Node[] tab; + Node first, e; + int n; + K k; + //若位桶数组不为空且长度>0, 且根据hash值计算出的下标对应的桶下存在节点 + if ((tab = table) != null && (n = tab.length) > 0 && + (first = tab[(n - 1) & hash]) != null) { + //检查第一个节点是否是要查的元素, if true, return value + if (first.hash == hash && // always check first node + ((k = first.key) == key || (key != null && key.equals(k)))) + return first; + //若第一个节点不是, 且下一个节点不为空 + if ((e = first.next) != null) { + //下一个节点若是TreeNode, 则按红黑树的方式查找 + if (first instanceof TreeNode) + return ((TreeNode)first).getTreeNode(hash, key); + //否则遍历整个链表查找相同key的节点 + do { + if (e.hash == hash && + ((k = e.key) == key || (key != null && key.equals(k)))) + return e; + } while ((e = e.next) != null); + } + } + return null; + } +``` + +### remove(Object key) 根据传入key删除节点 +(1)计算key的hash值 +(2)计算下标,看下标对应的桶上第一个节点是否是我们要删除的 +(3)若是,保存该结点 +(4)若不是,且头结点是TreeNode,按照红黑树的方式遍历获取到该节点 +(5)若不是,且头结点是链表,则遍历链表获取到该结点 +(6)观察保存的结点是否是TreeNode,如果是则按照红黑树的方式删除 +(7)若不是TreeNode,则按照链表的方式删除 + +```java + public V remove(Object key) { + Node e; + //计算出key的hash值, 然后下到removeNode方法进行删除 + return (e = removeNode(hash(key), key, null, false, true)) == null ? + null : e.value; + } + + final Node removeNode(int hash, Object key, Object value, + boolean matchValue, boolean movable) { + Node[] tab; Node p; int n, index; + //如果桶数组不为空且长度>0, 并且计算出下标对应的桶上存在节点 + if ((tab = table) != null && (n = tab.length) > 0 && + (p = tab[index = (n - 1) & hash]) != null) { + Node node = null, e; K k; V v; + //如果桶上第一个节点恰好是要删除的, 赋值给node后续删除使用 + if (p.hash == hash && + ((k = p.key) == key || (key != null && key.equals(k)))) + node = p; + //如果桶上第一个节点不是我们寻找的, 且该节点的next节点不为空 + else if ((e = p.next) != null) { + //如果桶上第一个节点是TreeNode + if (p instanceof TreeNode) + //按照红黑树的方式遍历获取到该节点 + node = ((TreeNode)p).getTreeNode(hash, key); + //否则遍历链表查找要删除的节点 + else { + do { + if (e.hash == hash && + ((k = e.key) == key || + (key != null && key.equals(k)))) { + node = e; + break; + } + p = e; + } while ((e = e.next) != null); + } + } + //如果找到了要删除的节点, 则看参数是否需要匹配value值, 如果不需要匹配value值则直接删除, 否则判断value是否相同 + if (node != null && (!matchValue || (v = node.value) == value || + (value != null && value.equals(v)))) { + if (node instanceof TreeNode) + //如果该节点是TreeNode, 按照红黑树的方式删除 + ((TreeNode)node).removeTreeNode(this, tab, movable); + //如果删除的元素是第一个节点, 把next节点移动到头节点位置 + else if (node == p) + tab[index] = node.next; + else//否则删除node节点 + p.next = node.next; + ++modCount; + --size; + //删除节点的后续处理 + afterNodeRemoval(node); + //成功删除, 返回删除节点 + return node; + } + } + //若删除失败, 返回null + return null; + } +``` + +### 总结 +(1)HashMap是一种散列表,采用数组 + 链表 + 红黑树存储结构 +(2)若未预先指定,HashMap的初始容量是16,负载因子是0.75 +(3)若预先指定,HashMap的初始容量必须是2的n次方 +(4)HashMap除了通过默认构造创建时扩容门槛是16*0.75,其余情况下每次扩容容量为原来的两倍,扩容门槛也为原来的两倍 +(5)当桶数组的数量<64时不会进行树化,只会扩容 +(6)当桶数组的数量>64,且桶中元素个数大于8,进行树化 +(7)当桶中元素小于6,进行反树化 +(8)非线程安全 +(9)通常情况下,查找和添加元素的时间复杂度都是O(1) + +一些需要注意的点写在末尾: +### 1. 扩容导致的性能影响 +因为每一次调用resize()方法,都会创建一次新的位桶数组,并且将旧数组中的元素移动到新数组中,整个过程非常耗时,因此推荐使用HashMap(int initialCapacity)这个构造器,并在最初就尽量指定好容量大小。 + +### 2. 为什么HashMap要树化 +HashMap在大多数情况下,查询的时间复杂度为O(1),且HashMap的扰动函数和散列处理也足够高效了,可以理解为即便存在链表,这个链表也不会太长。那么为什么要大费周折添加一个红黑树的结构呢? +其本质是一个安全问题,在现实环境下,构建冲突的数据并不是非常复杂的事,恶意代码就可以利用这些数据大量与服务端进行交互,导致服务端CPU大量被占用,这就构成了hash碰撞拒接服务攻击。树化可以一定程度上减少碰撞攻击带来的性能损失。 + +### 3. 线程不安全 +HashMap是线程不安全的,并发场景下很可能出现两个线程同时对HashMap进行操作导致死锁问题,在并发场景下建议使用ConcurrentHashMap。 diff --git a/week_01/25/LinkedHashMap_25.md b/week_01/25/LinkedHashMap_25.md new file mode 100644 index 0000000..4ed0231 --- /dev/null +++ b/week_01/25/LinkedHashMap_25.md @@ -0,0 +1,323 @@ +# LinkedHashMap +LinkedHashMap结合了HashMap查询时间复杂度为O(1)和LinkedList增删时间复杂度为O(1)的特性,使其相比起LinkedList的随机访问更加高效,并且相比起HashMap拥有了有序的特性,但由于每一次对元素操作之后需要同时维护HashMap和LinkedList中的存储,性能上相较于HashMap稍慢。 + +LinkedHashMap也可以用来实现LRU缓存策略,且只需要将accessOrder设置为true即可,若需要设置缓存淘汰策略,重写removeEldestEntry()方法即可。 + +```java +public class LinkedHashMap extends HashMap implements Map + ``` +继承了HashMap,实现了Map接口,拥有HashMap的所有特性,并且额外增加了一定按顺序访问的特性 + +### 属性 +```java + /** + * 双向链表的头节点, 旧数据存在头节点 + */ + transient LinkedHashMap.Entry head; + + /** + * 双向链表的尾节点, 新数据存在尾结点 + */ + transient LinkedHashMap.Entry tail; + + /** + * 标识是否按访问顺序排序 + * true: 按照访问顺序存储元素 + * false: 按照插入顺序存储元素 + */ + final boolean accessOrder; +``` + +### 链表节点 +```java + /** + * 位于LinkedHashMap中的Node节点, 也就是LinkedList + HashMap中属于链表的节点 + */ + static class Entry extends HashMap.Node { + Entry before, after; + Entry(int hash, K key, V value, Node next) { + super(hash, key, value, next); + } + } +``` + +### 构造方法 +```java + /** + * 1. 指定初始容量与扩容因子的构造方法 + * 内部是通过HashMap.HashMap(int initialCapacity, float loadFactor)这个构造方法创建的map + * 并且默认按照元素插入顺序进行排序 + */ + public LinkedHashMap(int initialCapacity, float loadFactor) { + super(initialCapacity, loadFactor); + accessOrder = false; + } + + /** + * 2. 指定初始容量的构造方法 + * 内部是通过HashMap.HashMap(int initialCapacity)这个构造方法创建的默认扩容因子为0.75的map + * 并且默认按照元素插入顺序进行排序 + */ + public LinkedHashMap(int initialCapacity) { + super(initialCapacity); + accessOrder = false; + } + + /** + * 3. 默认构造方法 + * 内部是通过HashMap.HashMap()这个构造方法创建的默认初始容量为16且默认扩容因子为0.75的map + * 并且默认按照元素插入顺序进行排序 + */ + public LinkedHashMap() { + super(); + accessOrder = false; + } + + /** + * 4. 通过传入一个Map进行构建LinkedHashMap, 底层调用了HashMap(Map m) + * 并且默认按照元素插入顺序进行排序 + */ + public LinkedHashMap(Map m) { + super(); + accessOrder = false; + putMapEntries(m, false); + } + + /** + * 5. 通过指定初始容量, 扩容因子, 插入顺序进行构建LinkedHashMap + * 底层先通过HashMap(initialCapacity, loadFactor)这个构造方法创建map, 并指定排序顺序 + * 这个构造方法也是实现LRU缓存的关键 + */ + public LinkedHashMap(int initialCapacity, + float loadFactor, + boolean accessOrder) { + super(initialCapacity, loadFactor); + this.accessOrder = accessOrder; + } +``` + +### afterNodeInsertion(boolean evict) +指定LinkedHashMap在完成put操作之后还需做什么,这个方法在HashMap中putVal()方法被调用,但是实现为空 +```java + void afterNodeInsertion(boolean evict) { // possibly remove eldest + LinkedHashMap.Entry first; + //如果evict = true, 并且双向链表的头节点不为空, 且确定移除最老的元素 + if (evict && (first = head) != null && removeEldestEntry(first)) { + K key = first.key; + //调用removeNode方法移除头节点 + //在removeNode方法内部移除节点之后会调用afterNodeRemoval()方法用于修改双向链表 + removeNode(hash(key), key, null, false, true); + } + } + + //是否移除最老的元素, 默认为false + protected boolean removeEldestEntry(Map.Entry eldest) { + return false; + } +``` + +### afterNodeAcces(Node e) +指定LinkedHashMap在完成访问操作之后还需做什么,这个方法在HashMap中调用put()方法时,更新相同key节点的value时有调用,但实现也为空。在LinkedHashMap中调用put()、get()方法时会用到,若指定为true,则调用这个方法把最近访问过的节点移动到双端链表末尾。 + +(1)若指定accessOrder = true,且访问的节点不是末尾节点 +(2)双向链表中移除该结点并再次添加到链表末尾 +```java + void afterNodeAccess(Node e) { // move node to last + LinkedHashMap.Entry last; + //若指定accessOrder = true, 也就是需要按访问顺序进行排序, 且访问的不是末尾节点 + if (accessOrder && (last = tail) != e) { + LinkedHashMap.Entry p = + (LinkedHashMap.Entry)e, b = p.before, a = p.after; + p.after = null; + //将节点p从双端链表中删除 + if (b == null) + head = a; + else + b.after = a; + if (a != null) + a.before = b; + else + last = b; + //把节点p放在双端链表末尾 + if (last == null) + head = p; + else { + p.before = last; + last.after = p; + } + //尾结点等于p + tail = p; + ++modCount; + } + } +``` + +### afterNodeRemoval(Node e) +在HashMap中将该节点删除之后,在双端链表也将该节点进行删除 +```java + void afterNodeRemoval(Node e) { // unlink + LinkedHashMap.Entry p = + (LinkedHashMap.Entry)e, b = p.before, a = p.after; + p.before = p.after = null; + if (b == null) + head = a; + else + b.after = a; + if (a == null) + tail = b; + else + a.before = b; + } +``` + +### get(Object key) 通过传入key获取指定节点的value +(1)调用HashMap的getNode方法检索节点e +(2)若节点e不为空,且指定按访问顺序排序,更新该节点到链表末尾 +```java + public V get(Object key) { + Node e; + //若未查找到对应节点 return null + if ((e = getNode(hash(key), key)) == null) + return null; + //若找到了对应节点, 且accessOrder = true + if (accessOrder) + //更新该节点为最近访问节点, 移动到双端链表末尾 + afterNodeAccess(e); + return e.value; + } +``` + +### 总结: +(1)LinkedHashMap继承自HashMap,具有HashMap的所有特性 +(2)LinkedHashMap内部维护了一个双端链表存储所有的元素 +(3)若accessOrder = false,则按插入元素的顺序进行排序 +(4)若accessOrder = true,则按访问元素的顺序进行排序 +(5)默认的LinkedHashMap并不会移除旧元素,如果需要移除达到某个条件的最久未使用的旧元素,则需要重写removeEldestEntry()方法设置淘汰策略 + + +下面是LRU基于LinkedHashMap和手写HashMap+双端链表的两种实现: +### LRU基于LinkedHashMap的实现 +```java +public class LRUCache extends LinkedHashMap { + int capacity; + + public LRUCache(int capacity) { + super(capacity, 0.75f, true); + this.capacity = capacity; + } + + public int get(int key) { + return super.getOrDefault(key, -1); + } + + public void put(int key, int value) { + super.put(key, value); + } + + @Override + protected boolean removeEldestEntry(Map.Entry eldest) { + return size() > capacity; + } +} +``` + +### LRU基于HashMap + LinkedList的实现 +```java +class LRUCache { + HashMap map; + DoubleLinkedList list; + int capacity; + + public LRUCache(int capacity) { + this.map = new HashMap<>(); + this.list = new DoubleLinkedList(); + this.capacity = capacity; + } + + public int get(int key) { + //if exist, get and update + if (map.containsKey(key)) { + int v = map.get(key).value; + put(key, v); + return v; + } + return -1; + } + + public void put(int key, int value) { + DoubleLinkedList.ListNode x = new DoubleLinkedList.ListNode(key, value); + //if key is already exist in cache + if (map.containsKey(key)) { + //update cache + DoubleLinkedList.ListNode temp = map.get(key); + list.remove(temp); + list.addEnd(x); + map.put(key, x); + } else { + if (list.size >= capacity) { + //remove oldest, then add + DoubleLinkedList.ListNode rmv = list.removeFirst(); + map.remove(rmv.key); + } + list.addEnd(x); + map.put(key, x); + } + } +} + +class DoubleLinkedList { + private ListNode head; + private ListNode tail; + int size; + + public DoubleLinkedList() { + this.head = new ListNode(0, 0); + this.tail = new ListNode(0, 0); + this.head.next = tail; + this.tail.prev = head; + this.size = 0; + + } + + public void remove(ListNode node) { + if (node == head || node == tail) throw new RuntimeException("node is can't to be head or tail"); + ListNode prev = node.prev; + ListNode next = node.next; + prev.next = next; + next.prev = prev; + size--; + } + + + public ListNode removeFirst() { + if (head.next != null) { + ListNode deleteHead = head.next; + remove(deleteHead); + return deleteHead; + } + return null; + } + + public void addEnd(ListNode node) { + if (node == null) throw new RuntimeException("node is can't to be null"); + ListNode tailPrev = tail.prev; + tailPrev.next = node; + node.prev = tailPrev; + node.next = tail; + tail.prev = node; + size++; + } + + static class ListNode { + int key; + int value; + ListNode prev; + ListNode next; + + public ListNode(int key, int value) { + this.key = key; + this.value = value; + } + } +} +``` diff --git a/week_01/26/ArrayList-026.md b/week_01/26/ArrayList-026.md new file mode 100644 index 0000000..a70fb52 --- /dev/null +++ b/week_01/26/ArrayList-026.md @@ -0,0 +1,157 @@ +# ArrayList +## 简单说明 +#### ArrayList 是一个数组队列,相当于动态数组。容量能动态增,但是操作不是线程安全的。当多个线程并发访问同一个ArrayList时,会抛出ConcurrentModificationException,这就是fail-fast机制。 +#### 在ArrayList中有两个情况可以导致OutOfMemoryError:1、当minCapacity<0时,系统无法创建长度小于0 的数组;2、当数组容量超过VM中堆的剩余空间大小时,VM无法为其分配足够的内存。 +## 常用方法 +#### 1、get方法 +```Java +public E get(int index) { // 根据索引获取元素 + rangeCheck(index); // 校验索引是否越界 + + return elementData(index); // 直接根据index返回对应位置的元素(底层elementData是个数组) +} +``` +#### 2、set方法 +```Java +public E set(int index, E element) { // 用指定的元素(element)替换指定位置(index)的元素 + rangeCheck(index); // 校验索引是否越界 + + E oldValue = elementData(index); // 根据index获取指定位置的元素 + elementData[index] = element; // 用传入的element替换index位置的元素 + return oldValue; // 返回index位置原来的元素 +} +``` +#### 3、add方法 +```Java +public boolean add(E e) { // 增加一个元素 + ensureCapacityInternal(size + 1); // 将modCount+1,并校验添加元素后是否需要扩容 + elementData[size++] = e; // 在数组尾部添加元素,并将size+1 + return true; +} +``` +```Java +public void add(int index, E element) { // 将指定的元素(element)插入此列表中的指定位置(index)。将index位置及后面的所有元素(如果有的话)向右移动一个位置 + rangeCheckForAdd(index); // 校验索引是否越界 + + ensureCapacityInternal(size + 1); // 将modCount+1,并校验添加元素后是否需要扩容 + System.arraycopy(elementData, index, elementData, index + 1, // 将index位置及之后的所有元素向右移动一个位置(为要添加的元素腾出1个位置) + size - index); + elementData[index] = element; // index位置设置为element元素 + size++; // 元素数量+1 +} +``` +#### 4、remove方法 +```Java +public E remove(int index) { // 删除列表中index位置的元素,将index位置后面的所有元素向左移一个位置 + rangeCheck(index); // 校验索引是否越界 + + modCount++; // 修改次数+1 + E oldValue = elementData(index); // index位置的元素,也就是将要被移除的元素 + + int numMoved = size - index - 1; // 计算需要移动的元素个数,例如:size为10,index为9,此时numMoved为0,则无需移动元素,因为此时index为9的元素刚好是最后一个元素,直接执行下面的代码,将索引为9的元素赋值为空即可 + if (numMoved > 0) // 如果需要移动元素 + System.arraycopy(elementData, index+1, elementData, index, + numMoved); // 将index+1位置及之后的所有元素,向左移动一个位置 + elementData[--size] = null; // 将size-1,并将size-1位置的元素赋值为空(因为上面将元素左移了,所以size-1位置的元素为重复的,将其移除) + + return oldValue; // 返回index位置原来的元素 +} +``` +```Java +public boolean remove(Object o) { // 如果存在与入参相同的元素,则从该列表中删除指定元素的第一个匹配项。如果列表不包含元素,则不变 + if (o == null) { // 如果入参元素为空,则遍历数组查找是否存在元素为空,如果存在则调用fastRemove将该元素移除,并返回true表示移除成功 + for (int index = 0; index < size; index++) + if (elementData[index] == null) { + fastRemove(index); + return true; + } + } else { // 如果入参元素不为空,则遍历数组查找是否存在元素与入参元素使用equals比较返回true,如果存在则调用fastRemove将该元素移除,并返回true表示移除成功 + for (int index = 0; index < size; index++) + if (o.equals(elementData[index])) { + fastRemove(index); + return true; + } + } + return false; // 不存在目标元素,返回false +} +``` +```Java +private void fastRemove(int index) { // 私有方法,供上面的remove方法调用,直接删除掉index位置的元素 + modCount++; // 修改次数+1 + int numMoved = size - index - 1; // 计算需要移动的元素个数,例如:size为10,index为9,此时numMoved为0,则无需移动元素,因为此时index为9的元素刚好是最后一个元素,直接执行下面的代码,将索引为9的元素赋值为空即可 + if (numMoved > 0) + System.arraycopy(elementData, index+1, elementData, index, + numMoved); // 将index+1位置及之后的所有元素,向左移动一个位置 + elementData[--size] = null; // 将size-1,并将size-1位置的元素赋值为空(因为上面将元素左移了,所以size-1位置的元素为重复的,将其移除) +} +``` +#### 5、clear方法 +``` Java +public void clear() { // 删除此列表中的所有元素。 + modCount++; // 修改次数+1 + for (int i = 0; i < size; i++) // 遍历数组将所有元素清空 + elementData[i] = null; + + size = 0; // 元素数量赋0 +} +``` +## 扩容 +#### 当数组容量不够时,数组有一个扩容的过程,在扩容的过程中,会将原来数组的元素拷贝到新的数组中,这是一个很耗时的操作。动态数组(ArrayList)在使用方便的同时,也会承担降低性能的风险。 +```Java +// 初始容量10 +private static final int DEFAULT_CAPACITY = 10; + // 空实例数组 +private static final Object[] EMPTY_ELEMENTDATA = {}; +// 默认大小的空实例数组,在第一次调用ensureCapacityInternal时会初始化长度为10 +private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {}; +// 存放元素的数组 +transient Object[] elementData; + // 数组当前的元素数量 +private int size; +// 数组允许的最大长度 +private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8; +``` +```Java +private void ensureCapacityInternal(int minCapacity) { + // 校验当前数组是否为DEFAULTCAPACITY_EMPTY_ELEMENTDATA, + // 如果是则将minCapacity设为DEFAULT_CAPACITY, + // 主要是给DEFAULTCAPACITY_EMPTY_ELEMENTDATA设置初始容量 + if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) { + minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity); + } + + ensureExplicitCapacity(minCapacity); +} +``` +```Java +private void ensureExplicitCapacity(int minCapacity) { + modCount++; // 修改次数+1 + + // 如果添加该元素后的大小超过数组大小,则进行扩容 + if (minCapacity - elementData.length > 0) + grow(minCapacity); // 进行扩容 +} +``` +```Java +private void grow(int minCapacity) { // 数组扩容 + // overflow-conscious code + int oldCapacity = elementData.length; // 原来的容量 + int newCapacity = oldCapacity + (oldCapacity >> 1); // 新容量 = 老容量 + 老容量 / 2 + if (newCapacity - minCapacity < 0) // 如果新容量比minCapacity小, + newCapacity = minCapacity; // 则将新容量设为minCapacity,兼容初始化情况 + if (newCapacity - MAX_ARRAY_SIZE > 0) // 如果新容量比最大允许容量大, + newCapacity = hugeCapacity(minCapacity); // 则调用hugeCapacity方法设置一个合适的容量 + // 将原数组元素拷贝到一个容量为newCapacity的新数组(使用System.arraycopy), + // 并且将elementData设置为新数组 + elementData = Arrays.copyOf(elementData, newCapacity); +} +``` +```Java +private static int hugeCapacity(int minCapacity) { + if (minCapacity < 0) // overflow + throw new OutOfMemoryError(); // 越界 + // 如果minCapacity大于MAX_ARRAY_SIZE,则返回Integer.MAX_VALUE,否则返回MAX_ARRAY_SIZE + return (minCapacity > MAX_ARRAY_SIZE) ? + Integer.MAX_VALUE : + MAX_ARRAY_SIZE; +``` \ No newline at end of file diff --git a/week_01/26/HashMap-026.md b/week_01/26/HashMap-026.md new file mode 100644 index 0000000..8383f5e --- /dev/null +++ b/week_01/26/HashMap-026.md @@ -0,0 +1,722 @@ +# HashMap +## 简单说明 +#### 1、JDK1.8对HashMap进行了比较大的优化,底层实现由之前的“数组+链表”改为“数组+链表+红黑树”。当链表节点较少是仍然是以链表存在,当链表节点较多是(大于8)会转为红黑树 +#### 2、头节点指的是table表上索引位置的节点,也就是头节点。 +#### 3、根节点(root节点)是指红黑树最上面的哪个节点,也就是没有父节点的节点。 +#### 4、红黑树的根节点不一定是索引位置的头节点。 +#### 5、转为红黑树节点后,链表的结构还存在,通过next属性维持,红黑树节点在进行操作是都会维护链表的结构,并不是转为红黑树节点,链表结构就不存在了。 +#### 6、在红黑树上,叶子节点可能有next节点,因为红黑树的结构跟链表的结构是互不影响的,不会因为叶子节点就说该节点没有next节点了。 +#### 7、源码中的一些变量定义:如果定义了一个节点p,则pl为p的左节点,pr为p的右节点,ph为p的hash值,pk为p的key值,kc为key的类等,源码中很喜欢在if/for等语句中进行赋值并判断。 +#### 8、链表移除一个节点只需要将需移除节点的上一个节点的next节点设置为需移除节点的下一个节点 +#### 9、红黑树在维护链表结构时移除一个节点(红黑树中增加了一个prev属性),只需要将需移除节点的prev节点的next设置为需移除节点的next节点,将需移除节点的next节点的prev设置为需移除节点的pre节点(此处只是红黑树的维护链表结构的操作,红黑树还需要单独进行红黑树的移除或其他操作) +#### 10、 源码中进行红黑树的查找时,会反复用到一下两条规则,(1)如果目标节点的hash 值小于p节点的hah值,则向p节点的左边遍历,否则向p节点的右节点遍历;(2) 如果目标节点的key值小于p节点的key值,则向p节点的左边遍历,否则向p节点的右边遍历。这两条规则是利用了红黑树的特性(左节点<根节点<右节点)。 +#### 11、源码中进行红黑树查找时,会用dir(direction) 来表示向左还是向右查找,dir存在的值是目标节点的hash/key与p节点的hash/key的比较结果 +## 定位哈希桶数组的位置 +```Java +static final int hash(Object key) { // 计算key的hash值 + int h; + // 1.先拿到key的hashCode值; + // 2.将hashCode的高16位参与运算 + return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16); +} +int n = tab.length; +// 3.将(tab.length - 1) 与 hash值进行&运算 +int index = (n - 1) & hash; +``` +## 常用方法 +#### get 方法 +```Java +public V get(Object key) { + Node e; + return (e = getNode(hash(key), key)) == null ? null : e.value; +} +``` +```Java +final Node getNode(int hash, Object key) { + Node[] tab; Node first, e; int n; K k; + // table不为空 && table长度大于0 + // 使用table.length - 1 和 hash 值进行与运算,得出在table上的索引位置,将该索引位置的节点赋值给first 节点,校验该索引位置是否为空 + 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; // 检查first节点的hash值和key是否和入参的一样,如果一样则first即为目标节点,直接返回first节点 + if ((e = first.next) != null) { // 如果first的next节点不为空则继续遍历 + if (first instanceof TreeNode) // 判断是否为TreeNode + // 如果是红黑树节点,则调用红黑树的查找目标节点方法getTreeNode + return ((TreeNode)first).getTreeNode(hash, key); + // 走到这代表节点为链表节点 + do { // 向下遍历链表, 直至找到节点的key和传入的key相等时,返回该节点 + if (e.hash == hash && + ((k = e.key) == key || (key != null && key.equals(k)))) + return e; + } while ((e = e.next) != null); + } + } + return null; // 找不到符合的返回空 +} +``` +```Java +final TreeNode getTreeNode(int h, Object k) { + // 使用根结点调用find方法 + return ((parent != null) ? root() : this).find(h, k, null); +} +``` +```Java +/** + * 从调用此方法的结点开始查找, 通过hash值和key找到对应的节点 + * 此处是红黑树的遍历, 红黑树是特殊的自平衡二叉查找树 + * 平衡二叉查找树的特点:左节点<根节点<右节点 + */ +final TreeNode find(int h, Object k, Class kc) { + TreeNode p = this; / /将p节点赋值为调用此方法的节点 + do { + int ph, dir; K pk; + TreeNode pl = p.left, pr = p.right, q; + if ((ph = p.hash) > h) // 传入的hash值小于p节点的hash值, 则往p节点的左边遍历 + p = pl; // p赋值为p节点的左节点 + else if (ph < h) // 传入的hash值大于p节点的hash值, 则往p节点的右边遍历 + p = pr; // p赋值为p节点的右节点 + // 传入的hash值和key值等于p节点的hash值和key值,则p节点为目标节点,返回p节点 + else if ((pk = p.key) == k || (k != null && k.equals(pk))) + return p; + else if (pl == null) // p节点的左节点为空则将向右遍历 + p = pr; + else if (pr == null) // p节点的右节点为空则向左遍历 + p = pl; + else if ((kc != null || + // 如果传入的key(k)所属的类实现了Comparable接口,则将传入的key跟p节点的key比较 + (kc = comparableClassFor(k)) != null) && // 此行不为空代表k实现了Comparable + (dir = compareComparables(kc, k, pk)) != 0)//kpk则dir>0 + p = (dir < 0) ? pl : pr; // k < pk则向左遍历(p赋值为p的左节点), 否则向右遍历 + // 代码走到此处, 代表key所属类没有实现Comparable, 直接指定向p的右边遍历 + else if ((q = pr.find(h, k, kc)) != null) + return q; + else// 代码走到此处代表上一个向右遍历(pr.find(h, k, kc))为空, 因此直接向左遍历 + p = pl; + } while (p != null); + return null; // 以上都找不到目标节点则返回空 +} +``` +```Java +//如果x实现了Comparable接口,则返回 x的Class +static Class comparableClassFor(Object x) { + if (x instanceof Comparable) { + Class c; Type[] ts, as; Type t; ParameterizedType p; + if ((c = x.getClass()) == String.class) + return c; + if ((ts = c.getGenericInterfaces()) != null) { + for (int i = 0; i < ts.length; ++i) { + if (((t = ts[i]) instanceof ParameterizedType) && + ((p = (ParameterizedType)t).getRawType() == + Comparable.class) && + (as = p.getActualTypeArguments()) != null && + as.length == 1 && as[0] == c) + return c; + } + } + } + return null; +} +``` +#### put方法 +```Java +public V put(K key, V value) { + return putVal(hash(key), key, value, false, true); +} + ``` +```Java +final V putVal(int hash, K key, V value, boolean onlyIfAbsent, + boolean evict) { + Node[] tab; Node p; int n, i; + // table是否为空或者length等于0, 如果是则调用resize方法进行初始化 + if ((tab = table) == null || (n = tab.length) == 0) + n = (tab = resize()).length; + // 通过hash值计算索引位置, 如果table表该索引位置节点为空则新增一个 + if ((p = tab[i = (n - 1) & hash]) == null)// 将索引位置的头节点赋值给p + tab[i] = newNode(hash, key, value, null); + else { // table表该索引位置不为空 + Node e; K k; + if (p.hash == hash && // 判断p节点的hash值和key值是否跟传入的hash值和key值相等 + ((k = p.key) == key || (key != null && key.equals(k)))) + e = p; // 如果相等, 则p节点即为要查找的目标节点,赋值给e + // 判断p节点是否为TreeNode, 如果是则调用红黑树的putTreeVal方法查找目标节点 + else if (p instanceof TreeNode) + e = ((TreeNode)p).putTreeVal(this, tab, hash, key, value); + else { // 走到这代表p节点为普通链表节点 + for (int binCount = 0; ; ++binCount) { // 遍历此链表, binCount用于统计节点数 + if ((e = p.next) == null) { // p.next为空代表不存在目标节点则新增一个节点插入链表尾部 + p.next = newNode(hash, key, value, null); + // 计算节点是否超过8个, 减一是因为循环是从p节点的下一个节点开始的 + if (binCount >= TREEIFY_THRESHOLD - 1) + treeifyBin(tab, hash);// 如果超过8个,调用treeifyBin方法将该链表转换为红黑树 + break; + } + if (e.hash == hash && // e节点的hash值和key值都与传入的相等, 则e即为目标节点,跳出循环 + ((k = e.key) == key || (key != null && key.equals(k)))) + break; + p = e; // 将p指向下一个节点 + } + } + // e不为空则代表根据传入的hash值和key值查找到了节点,将该节点的value覆盖,返回oldValue + if (e != null) { + V oldValue = e.value; + if (!onlyIfAbsent || oldValue == null) + e.value = value; + afterNodeAccess(e); // 用于LinkedHashMap + return oldValue; + } + } + ++modCount; + if (++size > threshold) // 插入节点后超过阈值则进行扩容 + resize(); + afterNodeInsertion(evict); // 用于LinkedHashMap + return null; +} +``` +```Java +/** + * 红黑树插入会同时维护原来的链表属性, 即原来的next属性 + */ +final TreeNode putTreeVal(HashMap map, Node[] tab, + int h, K k, V v) { + Class kc = null; + boolean searched = false; + // 查找根节点, 索引位置的头节点并不一定为红黑树的根结点 + TreeNode root = (parent != null) ? root() : this; + for (TreeNode p = root;;) { // 将根节点赋值给p, 开始遍历 + int dir, ph; K pk; + if ((ph = p.hash) > h) // 如果传入的hash值小于p节点的hash值 + dir = -1; // 则将dir赋值为-1, 代表向p的左边查找树 + else if (ph < h) // 如果传入的hash值大于p节点的hash值, + dir = 1; // 则将dir赋值为1, 代表向p的右边查找树 + // 如果传入的hash值和key值等于p节点的hash值和key值, 则p节点即为目标节点, 返回p节点 + else if ((pk = p.key) == k || (k != null && k.equals(pk))) + return p; + // 如果k所属的类没有实现Comparable接口 或者 k和p节点的key相等 + else if ((kc == null && + (kc = comparableClassFor(k)) == null) || + (dir = compareComparables(kc, k, pk)) == 0) { + if (!searched) { // 第一次符合条件, 该方法只有第一次才执行 + TreeNode q, ch; + searched = true; + // 从p节点的左节点和右节点分别调用find方法进行查找, 如果查找到目标节点则返回 + if (((ch = p.left) != null && + (q = ch.find(h, k, kc)) != null) || + ((ch = p.right) != null && + (q = ch.find(h, k, kc)) != null)) + return q; + } + // 否则使用定义的一套规则来比较k和p节点的key的大小, 用来决定向左还是向右查找 + dir = tieBreakOrder(k, pk); // dir<0则代表k xp = p; // xp赋值为x的父节点,中间变量,用于下面给x的父节点赋值 + // dir<=0则向p左边查找,否则向p右边查找,如果为null,则代表该位置即为x的目标位置 + if ((p = (dir <= 0) ? p.left : p.right) == null) { + // 走进来代表已经找到x的位置,只需将x放到该位置即可 + Node xpn = xp.next; // xp的next节点 + // 创建新的节点, 其中x的next节点为xpn, 即将x节点插入xp与xpn之间 + TreeNode x = map.newTreeNode(h, k, v, xpn); + if (dir <= 0) // 如果时dir <= 0, 则代表x节点为xp的左节点 + xp.left = x; + else // 如果时dir> 0, 则代表x节点为xp的右节点 + xp.right = x; + xp.next = x; // 将xp的next节点设置为x + x.parent = x.prev = xp; // 将x的parent和prev节点设置为xp + // 如果xpn不为空,则将xpn的prev节点设置为x节点,与上文的x节点的next节点对应 + if (xpn != null) + ((TreeNode)xpn).prev = x; + moveRootToFront(tab, balanceInsertion(root, x)); // 进行红黑树的插入平衡调整 + return null; + } + } +} +``` +```Java +// 用于不可比较或者hashCode相同时进行比较的方法, 只是一个一致的插入规则,用来维护重定位的等价性。 +static int tieBreakOrder(Object a, Object b) { + int d; + if (a == null || b == null || + (d = a.getClass().getName(). + compareTo(b.getClass().getName())) == 0) + d = (System.identityHashCode(a) <= System.identityHashCode(b) ? + -1 : 1); + return d; +} +``` +```Java +final void treeifyBin(Node[] tab, int hash) { + int n, index; Node e; + // table为空或者table的长度小于64, 进行扩容 + if (tab == null || (n = tab.length) < MIN_TREEIFY_CAPACITY) + resize(); + // 根据hash值计算索引值, 遍历该索引位置的链表 + else if ((e = tab[index = (n - 1) & hash]) != null) { + TreeNode hd = null, tl = null; + do { + TreeNode p = replacementTreeNode(e, null); // 链表节点转红黑树节点 + if (tl == null) // tl为空代表为第一次循环 + hd = p; // 头结点 + else { + p.prev = tl; // 当前节点的prev属性设为上一个节点 + tl.next = p; // 上一个节点的next属性设置为当前节点 + } + tl = p; // tl赋值为p, 在下一次循环中作为上一个节点 + } while ((e = e.next) != null); // e指向下一个节点 + // 将table该索引位置赋值为新转的TreeNode的头节点 + if ((tab[index] = hd) != null) + hd.treeify(tab); // 以头结点为根结点, 构建红黑树 + } +} +``` +```Java +final void treeify(Node[] tab) { // 构建红黑树 + TreeNode root = null; + for (TreeNode x = this, next; x != null; x = next) {// this即为调用此方法的TreeNode + next = (TreeNode)x.next; // next赋值为x的下个节点 + x.left = x.right = null; // 将x的左右节点设置为空 + if (root == null) { // 如果还没有根结点, 则将x设置为根结点 + x.parent = null; // 根结点没有父节点 + x.red = false; // 根结点必须为黑色 + root = x; // 将x设置为根结点 + } + else { + K k = x.key; // k赋值为x的key + int h = x.hash; // h赋值为x的hash值 + Class kc = null; + // 如果当前节点x不是根结点, 则从根节点开始查找属于该节点的位置 + for (TreeNode p = root;;) { + int dir, ph; + K pk = p.key; + if ((ph = p.hash) > h) // 如果x节点的hash值小于p节点的hash值 + dir = -1; // 则将dir赋值为-1, 代表向p的左边查找 + else if (ph < h) // 与上面相反, 如果x节点的hash值大于p节点的hash值 + dir = 1; // 则将dir赋值为1, 代表向p的右边查找 + // 走到这代表x的hash值和p的hash值相等,则比较key值 + else if ((kc == null && // 如果k没有实现Comparable接口 或者 x节点的key和p节点的key相等 + (kc = comparableClassFor(k)) == null) || + (dir = compareComparables(kc, k, pk)) == 0) + // 使用定义的一套规则来比较x节点和p节点的大小,用来决定向左还是向右查找 + dir = tieBreakOrder(k, pk); + + TreeNode xp = p; // xp赋值为x的父节点,中间变量用于下面给x的父节点赋值 + // dir<=0则向p左边查找,否则向p右边查找,如果为null,则代表该位置即为x的目标位置 + if ((p = (dir <= 0) ? p.left : p.right) == null) { + x.parent = xp; // x的父节点即为最后一次遍历的p节点 + if (dir <= 0) // 如果时dir <= 0, 则代表x节点为父节点的左节点 + xp.left = x; + else // 如果时dir > 0, 则代表x节点为父节点的右节点 + xp.right = x; + // 进行红黑树的插入平衡(通过左旋、右旋和改变节点颜色来保证当前树符合红黑树的要求) + root = balanceInsertion(root, x); + break; + } + } + } + } + moveRootToFront(tab, root); // 如果root节点不在table索引位置的头结点, 则将其调整为头结点 +} +``` +```Java +/** + * 如果当前索引位置的头节点不是root节点, 则将root的上一个节点和下一个节点进行关联, + * 将root放到头节点的位置, 原头节点放在root的next节点上 + */ +static void moveRootToFront(Node[] tab, TreeNode root) { + int n; + if (root != null && tab != null && (n = tab.length) > 0) { + int index = (n - 1) & root.hash; + TreeNode first = (TreeNode)tab[index]; + if (root != first) { // 如果root节点不是该索引位置的头节点 + Node rn; + tab[index] = root; // 将该索引位置的头节点赋值为root节点 + TreeNode rp = root.prev; // root节点的上一个节点 + // 如果root节点的下一个节点不为空, + // 则将root节点的下一个节点的prev属性设置为root节点的上一个节点 + if ((rn = root.next) != null) + ((TreeNode)rn).prev = rp; + // 如果root节点的上一个节点不为空, + // 则将root节点的上一个节点的next属性设置为root节点的下一个节点 + if (rp != null) + rp.next = rn; + if (first != null) // 如果原头节点不为空, 则将原头节点的prev属性设置为root节点 + first.prev = root; + root.next = first; // 将root节点的next属性设置为原头节点 + root.prev = null; + } + assert checkInvariants(root); // 检查树是否正常 + } +} +``` +```Java +//将传入的节点作为根结点,遍历所有节点,校验节点的合法性,主要是保证该树符合红黑树的规则 +static boolean checkInvariants(TreeNode t) { // 一些基本的校验 + TreeNode tp = t.parent, tl = t.left, tr = t.right, + tb = t.prev, tn = (TreeNode)t.next; + if (tb != null && tb.next != t) + return false; + if (tn != null && tn.prev != t) + return false; + if (tp != null && t != tp.left && t != tp.right) + return false; + if (tl != null && (tl.parent != t || tl.hash > t.hash)) + return false; + if (tr != null && (tr.parent != t || tr.hash < t.hash)) + return false; + if (t.red && tl != null && tl.red && tr != null && tr.red) // 如果当前节点为红色, 则该节点的左右节点都不能为红色 + return false; + if (tl != null && !checkInvariants(tl)) + return false; + if (tr != null && !checkInvariants(tr)) + return false; + return true; +} +``` +#### resize方法 +```Java +final Node[] resize() { + Node[] oldTab = table; + int oldCap = (oldTab == null) ? 0 : oldTab.length; + int oldThr = threshold; + int newCap, newThr = 0; + if (oldCap > 0) { // 老table不为空 + if (oldCap >= MAXIMUM_CAPACITY) { // 老table的容量超过最大容量值 + threshold = Integer.MAX_VALUE; // 设置阈值为Integer.MAX_VALUE + return oldTab; + } + // 如果容量*2<最大容量并且>=16, 则将阈值设置为原来的两倍 + else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY && + oldCap >= DEFAULT_INITIAL_CAPACITY) + newThr = oldThr << 1; // double threshold + } + else if (oldThr > 0) // 老表的容量为0, 老表的阈值大于0, 是因为初始容量被放入阈值 + newCap = oldThr; // 则将新表的容量设置为老表的阈值 + else { // 老表的容量为0, 老表的阈值为0, 则为空表,设置默认容量和阈值 + newCap = DEFAULT_INITIAL_CAPACITY; + newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY); + } + if (newThr == 0) { // 如果新表的阈值为空, 则通过新的容量*负载因子获得阈值 + float ft = (float)newCap * loadFactor; + newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ? + (int)ft : Integer.MAX_VALUE); + } + threshold = newThr; // 将当前阈值赋值为刚计算出来的新的阈值 + @SuppressWarnings({"rawtypes","unchecked"}) + // 定义新表,容量为刚计算出来的新容量 + Node[] newTab = (Node[])new Node[newCap]; + table = newTab; // 将当前的表赋值为新定义的表 + if (oldTab != null) { // 如果老表不为空, 则需遍历将节点赋值给新表 + for (int j = 0; j < oldCap; ++j) { + Node e; + if ((e = oldTab[j]) != null) { // 将索引值为j的老表头节点赋值给e + oldTab[j] = null; // 将老表的节点设置为空, 以便垃圾收集器回收空间 + // 如果e.next为空, 则代表老表的该位置只有1个节点, + // 通过hash值计算新表的索引位置, 直接将该节点放在该位置 + if (e.next == null) + newTab[e.hash & (newCap - 1)] = e; + else if (e instanceof TreeNode) + // 调用treeNode的hash分布(跟下面最后一个else的内容几乎相同) + ((TreeNode)e).split(this, newTab, j, oldCap); + else { // preserve order + Node loHead = null, loTail = null; // 存储跟原索引位置相同的节点 + Node hiHead = null, hiTail = null; // 存储索引位置为:原索引+oldCap的节点 + Node next; + do { + next = e.next; + //如果e的hash值与老表的容量进行与运算为0,则扩容后的索引位置跟老表的索引位置一样 + if ((e.hash & oldCap) == 0) { + if (loTail == null) // 如果loTail为空, 代表该节点为第一个节点 + loHead = e; // 则将loHead赋值为第一个节点 + else + loTail.next = e; // 否则将节点添加在loTail后面 + loTail = e; // 并将loTail赋值为新增的节点 + } + //如果e的hash值与老表的容量进行与运算为1,则扩容后的索引位置为:老表的索引位置+oldCap + else { + if (hiTail == null) // 如果hiTail为空, 代表该节点为第一个节点 + hiHead = e; // 则将hiHead赋值为第一个节点 + else + hiTail.next = e; // 否则将节点添加在hiTail后面 + hiTail = e; // 并将hiTail赋值为新增的节点 + } + } while ((e = next) != null); + if (loTail != null) { + loTail.next = null; // 最后一个节点的next设为空 + newTab[j] = loHead; // 将原索引位置的节点设置为对应的头结点 + } + if (hiTail != null) { + hiTail.next = null; // 最后一个节点的next设为空 + newTab[j + oldCap] = hiHead; // 将索引位置为原索引+oldCap的节点设置为对应的头结点 + } + } + } + } + } + return newTab; +} +``` +```Java +final void split(HashMap map, Node[] tab, int index, int bit) { + TreeNode b = this; // 拿到调用此方法的节点 + TreeNode loHead = null, loTail = null; // 存储跟原索引位置相同的节点 + TreeNode hiHead = null, hiTail = null; // 存储索引位置为:原索引+oldCap的节点 + int lc = 0, hc = 0; + for (TreeNode e = b, next; e != null; e = next) { // 从b节点开始遍历 + next = (TreeNode)e.next; // next赋值为e的下个节点 + e.next = null; // 同时将老表的节点设置为空,以便垃圾收集器回收 + //如果e的hash值与老表的容量进行与运算为0,则扩容后的索引位置跟老表的索引位置一样 + if ((e.hash & bit) == 0) { + if ((e.prev = loTail) == null) // 如果loTail为空, 代表该节点为第一个节点 + loHead = e; // 则将loHead赋值为第一个节点 + else + loTail.next = e; // 否则将节点添加在loTail后面 + loTail = e; // 并将loTail赋值为新增的节点 + ++lc; // 统计原索引位置的节点个数 + } + //如果e的hash值与老表的容量进行与运算为1,则扩容后的索引位置为:老表的索引位置+oldCap + else { + if ((e.prev = hiTail) == null) // 如果hiHead为空, 代表该节点为第一个节点 + hiHead = e; // 则将hiHead赋值为第一个节点 + else + hiTail.next = e; // 否则将节点添加在hiTail后面 + hiTail = e; // 并将hiTail赋值为新增的节点 + ++hc; // 统计索引位置为原索引+oldCap的节点个数 + } + } + + if (loHead != null) { // 原索引位置的节点不为空 + if (lc <= UNTREEIFY_THRESHOLD) // 节点个数少于6个则将红黑树转为链表结构 + tab[index] = loHead.untreeify(map); + else { + tab[index] = loHead; // 将原索引位置的节点设置为对应的头结点 + // hiHead不为空则代表原来的红黑树(老表的红黑树由于节点被分到两个位置) + // 已经被改变, 需要重新构建新的红黑树 + if (hiHead != null) + loHead.treeify(tab); // 以loHead为根结点, 构建新的红黑树 + } + } + if (hiHead != null) { // 索引位置为原索引+oldCap的节点不为空 + if (hc <= UNTREEIFY_THRESHOLD) // 节点个数少于6个则将红黑树转为链表结构 + tab[index + bit] = hiHead.untreeify(map); + else { + tab[index + bit] = hiHead; // 将索引位置为原索引+oldCap的节点设置为对应的头结点 + // loHead不为空则代表原来的红黑树(老表的红黑树由于节点被分到两个位置) + // 已经被改变, 需要重新构建新的红黑树 + if (loHead != null) + hiHead.treeify(tab); // 以hiHead为根结点, 构建新的红黑树 + } + } +} +``` +```Java + // 将红黑树节点转为链表节点, 当节点<=6个时会被触发 +final Node untreeify(HashMap map) { + Node hd = null, tl = null; // hd指向头结点, tl指向尾节点 + // 从调用该方法的节点, 即链表的头结点开始遍历, 将所有节点全转为链表节点 + for (Node q = this; q != null; q = q.next) { + // 调用replacementNode方法构建链表节点 + Node p = map.replacementNode(q, null); + // 如果tl为null, 则代表当前节点为第一个节点, 将hd赋值为该节点 + if (tl == null) + hd = p; + else // 否则, 将尾节点的next属性设置为当前节点p + tl.next = p; + tl = p; // 每次都将tl节点指向当前节点, 即尾节点 + } + return hd; // 返回转换后的链表的头结点 +} +``` +#### remove方法 +```Java +public V remove(Object key) { + Node e; + return (e = removeNode(hash(key), key, null, false, true)) == null ? + null : e.value; +} +``` +```Java +final Node removeNode(int hash, Object key, Object value, + boolean matchValue, boolean movable) { + Node[] tab; Node p; int n, index; + // 如果table不为空并且根据hash值计算出来的索引位置不为空, 将该位置的节点赋值给p + if ((tab = table) != null && (n = tab.length) > 0 && + (p = tab[index = (n - 1) & hash]) != null) { + Node node = null, e; K k; V v; + // 如果p的hash值和key都与入参的相同, 则p即为目标节点, 赋值给node + if (p.hash == hash && + ((k = p.key) == key || (key != null && key.equals(k)))) + node = p; + else if ((e = p.next) != null) { // 否则向下遍历节点 + if (p instanceof TreeNode) // 如果p是TreeNode则调用红黑树的方法查找节点 + node = ((TreeNode)p).getTreeNode(hash, key); + else { + do { // 遍历链表查找符合条件的节点 + // 当节点的hash值和key与传入的相同,则该节点即为目标节点 + if (e.hash == hash && + ((k = e.key) == key || + (key != null && key.equals(k)))) { + node = e; // 赋值给node, 并跳出循环 + break; + } + p = e; // p节点赋值为本次结束的e + } while ((e = e.next) != null); // 指向像一个节点 + } + } + // 如果node不为空(即根据传入key和hash值查找到目标节点),则进行移除操作 + if (node != null && (!matchValue || (v = node.value) == value || + (value != null && value.equals(v)))) { + if (node instanceof TreeNode) // 如果是TreeNode则调用红黑树的移除方法 + ((TreeNode)node).removeTreeNode(this, tab, movable); + // 走到这代表节点是普通链表节点 + // 如果node是该索引位置的头结点则直接将该索引位置的值赋值为node的next节点 + else if (node == p) + tab[index] = node.next; + // 否则将node的上一个节点的next属性设置为node的next节点, + // 即将node节点移除, 将node的上下节点进行关联(链表的移除) + else + p.next = node.next; + ++modCount; // 修改次数+1 + --size; // table的总节点数-1 + afterNodeRemoval(node); // 供LinkedHashMap使用 + return node; // 返回被移除的节点 + } + } + return null; +} +``` +```Java +final void removeTreeNode(HashMap map, Node[] tab, + boolean movable) { + // 链表的处理start + int n; + if (tab == null || (n = tab.length) == 0) // table为空或者length为0直接返回 + return; + int index = (n - 1) & hash; // 根据hash计算出索引的位置 + // 索引位置的头结点赋值给first和root + TreeNode first = (TreeNode)tab[index], root = first, rl; + // 该方法被将要被移除的node(TreeNode)调用, 因此此方法的this为要被移除node节点, + // 则此处next即为node的next节点, prev即为node的prev节点 + TreeNode succ = (TreeNode)next, pred = prev; + if (pred == null) // 如果node节点的prev节点为空 + // 则将table索引位置的值和first节点的值赋值为succ节点(node的next节点)即可 + tab[index] = first = succ; + else + // 否则将node的prev节点的next属性设置为succ节点(node的next节点)(链表的移除) + pred.next = succ; + if (succ != null) // 如果succ节点不为空 + succ.prev = pred; // 则将succ的prev节点设置为pred, 与上面对应 + if (first == null) // 如果此处first为空, 则代表该索引位置已经没有节点则直接返回 + return; + // 如果root的父节点不为空, 则将root赋值为根结点 + // (root在上面被赋值为索引位置的头结点, 索引位置的头节点并不一定为红黑树的根结点) + if (root.parent != null) + root = root.root(); + // 通过root节点来判断此红黑树是否太小, 如果是则调用untreeify方法转为链表节点并返回 + // (转链表后就无需再进行下面的红黑树处理) + if (root == null || root.right == null || + (rl = root.left) == null || rl.left == null) { + tab[index] = first.untreeify(map); // too small + return; + } + // 链表的处理end + // 以下代码为红黑树的处理, 上面的代码已经将链表的部分处理完成 + // 上面已经说了this为要被移除的node节点, + // 将p赋值为node节点,pl赋值为node的左节点,pr赋值为node的右节点 + TreeNode p = this, pl = left, pr = right, replacement; + if (pl != null && pr != null) { // node的左节点和右节点都不为空时 + TreeNode s = pr, sl; // s节点赋值为node的右节点 + while ((sl = s.left) != null)//向左一直查找,直到叶子节点,跳出循环时,s为叶子节点 + s = sl; + boolean c = s.red; s.red = p.red; p.red = c; //交换p节点和s节点(叶子节点)的颜色 + TreeNode sr = s.right; // s的右节点 + TreeNode pp = p.parent; // p的父节点 + // 第一次调整start + if (s == pr) { // 如果p节点的右节点即为叶子节点 + p.parent = s; // 将p的父节点赋值为s + s.right = p; // 将s的右节点赋值为p + } + else { + TreeNode sp = s.parent; + if ((p.parent = sp) != null) { // 将p的父节点赋值为s的父节点, 如果sp不为空 + if (s == sp.left) // 如果s节点为左节点 + sp.left = p; // 则将s的父节点的左节点赋值为p节点 + else // 如果s节点为右节点 + sp.right = p; // 则将s的父节点的右节点赋值为p节点 + } + if ((s.right = pr) != null) // s的右节点赋值为p节点的右节点 + pr.parent = s; // p节点的右节点的父节点赋值为s + } + // 第二次调整start + p.left = null; + if ((p.right = sr) != null) // 将p节点的右节点赋值为s的右节点, 如果sr不为空 + sr.parent = p; // 则将s右节点的父节点赋值为p节点 + if ((s.left = pl) != null) // 将s节点的左节点赋值为p的左节点, 如果pl不为空 + pl.parent = s; // 则将p左节点的父节点赋值为s节点 + if ((s.parent = pp) == null) // 将s的父节点赋值为p的父节点pp, 如果pp为空 + root = s; // 则p节点为root节点, 此时交换后s成为新的root节点 + else if (p == pp.left) // 如果p不为root节点, 并且p是父节点的左节点 + pp.left = s; // 将p父节点的左节点赋值为s节点 + else // 如果p不为root节点, 并且p是父节点的右节点 + pp.right = s; // 将p父节点的右节点赋值为s节点 + if (sr != null) + replacement = sr; // 寻找replacement节点(用来替换掉p节点) + else + replacement = p; // 寻找replacement节点 + } + else if (pl != null) // 如果p的左节点不为空,右节点为空,replacement节点为p的左节点 + replacement = pl; + else if (pr != null) // 如果p的右节点不为空,左节点为空,replacement节点为p的右节点 + replacement = pr; + else // 如果p的左右节点都为空, 即p为叶子节点, 替换节点为p节点本身 + replacement = p; + // 第三次调整start + if (replacement != p) { // 如果p节点不是叶子节点 + //将replacement节点的父节点赋值为p节点的父节点, 同时赋值给pp节点 + TreeNode pp = replacement.parent = p.parent; + if (pp == null) // 如果p节点没有父节点, 即p为root节点 + root = replacement; // 则将root节点赋值为replacement节点即可 + else if (p == pp.left) // 如果p节点不是root节点, 并且p节点为父节点的左节点 + pp.left = replacement; // 则将p父节点的左节点赋值为替换节点 + else // 如果p节点不是root节点, 并且p节点为父节点的右节点 + pp.right = replacement; // 则将p父节点的右节点赋值为替换节点 + // p节点的位置已经被完整的替换为替换节点, 将p节点清空, 以便垃圾收集器回收 + p.left = p.right = p.parent = null; + } + // 如果p节点不为红色则进行红黑树删除平衡调整 + // (如果删除的节点是红色则不会破坏红黑树的平衡无需调整) + TreeNode r = p.red ? root : balanceDeletion(root, replacement); + + if (replacement == p) { // 如果p节点为叶子节点, 则简单的将p节点去除即可 + TreeNode pp = p.parent; // pp赋值为p节点的父节点 + p.parent = null; // 将p的parent节点设置为空 + if (pp != null) { // 如果p的父节点存在 + if (p == pp.left) // 如果p节点为父节点的左节点 + pp.left = null; // 则将父节点的左节点赋值为空 + else if (p == pp.right) // 如果p节点为父节点的右节点 + pp.right = null; // 则将父节点的右节点赋值为空 + } + } + if (movable) + moveRootToFront(tab, r); // 将root节点移到索引位置的头结点 +} +``` +## HashMap和Hashtable的区别: +#### 1、HashMap允许key和value为null,Hashtable不允许。 +#### 2、HashMap的默认初始容量为16,Hashtable为11。 +#### 3、HashMap的扩容为原来的2倍,Hashtable的扩容为原来的2倍加1。 +#### 4、HashMap是非线程安全的,Hashtable是线程安全的。 +#### 5、HashMap的hash值重新计算过,Hashtable直接使用hashCode。 +#### 6、HashMap去掉了Hashtable中的contains方法。 +#### 7、HashMap继承自AbstractMap类,Hashtable继承自Dictionary类。 +## 小结: +#### 1、HashMap的底层是个Node数组(Node[] table),在数组的具体索引位置,如果存在多个节点,则可能是以链表或红黑树的形式存在。 +#### 2、增加、删除、查找键值对时,定位到哈希桶数组的位置是很关键的一步,源码中是通过下面3个操作来完成这一步:1)拿到key的hashCode值;2)将hashCode的高位参与运算,重新计算hash值;3)将计算出来的hash值与(table.length - 1)进行&运算。 +#### 3、HashMap的默认初始容量(capacity)是16,capacity必须为2的幂次方;默认负载因子(load factor)是0.75;实际能存放的节点个数(threshold,即触发扩容的阈值)= capacity * load factor。 +#### 4、HashMap在触发扩容后,阈值会变为原来的2倍,并且会进行重hash,重hash后索引位置index的节点的新分布位置最多只有两个:原索引位置或原索引+oldCap位置。例如capacity为16,索引位置5的节点扩容后,只可能分布在新报索引位置5和索引位置21(5+16)。 +#### 5、导致HashMap扩容后,同一个索引位置的节点重hash最多分布在两个位置的根本原因是:1)table的长度始终为2的n次方;2)索引位置的计算方法为“(table.length - 1) & hash”。HashMap扩容是一个比较耗时的操作,定义HashMap时尽量给个接近的初始容量值。 +#### 6、HashMap有threshold属性和loadFactor属性,但是没有capacity属性。初始化时,如果传了初始化容量值,该值是存在threshold变量,并且Node数组是在第一次put时才会进行初始化,初始化时会将此时的threshold值作为新表的capacity值,然后用capacity和loadFactor计算新表的真正threshold值。 +#### 7、当同一个索引位置的节点在增加后达到9个时,会触发链表节点(Node)转红黑树节点(TreeNode,间接继承Node),转成红黑树节点后,其实链表的结构还存在,通过next属性维持。链表节点转红黑树节点的具体方法为源码中的treeifyBin(Node[] tab, int hash)方法。 +#### 8、当同一个索引位置的节点在移除后达到6个时,并且该索引位置的节点为红黑树节点,会触发红黑树节点转链表节点。红黑树节点转链表节点的具体方法为源码中的untreeify(HashMap map)方法。 +#### 9、HashMap在JDK1.8之后不再有死循环的问题,JDK1.8之前存在死循环的根本原因是在扩容后同一索引位置的节点顺序会反掉。 +#### 10、HashMap是非线程安全的,在并发场景下使用ConcurrentHashMap来代替。 \ No newline at end of file diff --git a/week_01/26/LinkedList-026.md b/week_01/26/LinkedList-026.md new file mode 100644 index 0000000..9962086 --- /dev/null +++ b/week_01/26/LinkedList-026.md @@ -0,0 +1,157 @@ +# LindedList +## 简单说明 +#### LinkedList底层的数据结构是基于双向循环链表的,存在一种数据结构——链表节点。每个节点所对应的类是Entry的实例。Entry中包含成员变量: previous、next、element。previous是该节点的上一个节点,next是该节点的下一个节点,element是该节点所包含的值 +#### LinkedList集合和ArrayList集合一样,也不是线程安全的,在多线程开发时也要额外添加同步代码,保证集合的线程安全。 + +## 基础属性 +```Java +transient int size = 0; // 节点数量 +transient Node first; // 第一个节点(头结点) +transient Node last; // 最后一个节点(尾节点) +private static class Node { // Node的数据结构 + E item; // 存放的对象 + Node next; // 下一个节点 + Node prev; // 上一个节点 + + Node(Node prev, E element, Node next) { + this.item = element; + this.next = next; + this.prev = prev; + } +} +``` +## 常用方法 +#### add方法 +```Java +public boolean add(E e) { + linkLast(e); // 调用linkLast方法, 将节点添加到尾部 + return true; +} +``` +```Java +public void add(int index, E element) { // 在index位置插入节点,节点值为element + checkPositionIndex(index); + + if (index == size) // 如果索引为size,即将element插入链表尾部 + linkLast(element); + else // 否则,将element插入原index位置节点的前面,即:将element插入index位置,将原index位置节点移到index+1的位置 + linkBefore(element, node(index)); // 将element插入index位置 +} +``` +```Java +void linkLast(E e) { // 将e放到链表的最后一个节点 + final Node l = last; // 拿到当前的尾节点l节点 + final Node newNode = new Node<>(l, e, null); // 使用e创建一个新的节点newNode, prev属性为l节点, next属性为null + last = newNode; // 将当前尾节点设置为上面新创建的节点newNode + if (l == null) // 如果l节点为空则代表当前链表为空, 将newNode设置为头结点 + first = newNode; + else // 否则将l节点的next属性设置为newNode + l.next = newNode; + size++; + modCount++; +} +``` +```Java +void linkBefore(E e, Node succ) { // 将e插入succ节点前面 + // assert succ != null; + final Node pred = succ.prev; // 拿到succ节点的prev节点 + final Node newNode = new Node<>(pred, e, succ); // 使用e创建一个新的节点newNode,其中prev属性为pred节点,next属性为succ节点 + succ.prev = newNode; // 将succ节点的prev属性设置为newNode + if (pred == null) // 如果pred节点为null,则代表succ节点为头结点,要把e插入succ前面,因此将first设置为newNode + first = newNode; + else // 否则将pred节点的next属性设为newNode + pred.next = newNode; + size++; + modCount++; +} +``` +#### get方法 +```Java +public E get(int index) { + checkElementIndex(index); // 校验index是否越界 + return node(index).item; // 根据index, 调用node方法寻找目标节点,返回目标节点的item +} +``` +#### set方法 +```Java +public E set(int index, E element) { // 替换index位置节点的值为element + checkElementIndex(index); // 检查index是否越界 + Node x = node(index); // 根据index, 调用node方法寻找到目标节点 + E oldVal = x.item; // 节点的原值 + x.item = element; // 将节点的item属性设为element + return oldVal; //返回节点原值 +} +``` +#### remove方法 +```Java +public boolean remove(Object o) { + if (o == null) { // 如果o为空, 则遍历链表寻找item属性为空的节点, 并调用unlink方法将该节点移除 + for (Node x = first; x != null; x = x.next) { + if (x.item == null) { + unlink(x); + return true; + } + } + } else { // 如果o不为空, 则遍历链表寻找item属性跟o相同的节点, 并调用unlink方法将该节点移除 + for (Node x = first; x != null; x = x.next) { + if (o.equals(x.item)) { + unlink(x); + return true; + } + } + } + return false; +} +``` +```Java +public E remove(int index) { // 移除index位置的节点 + checkElementIndex(index); // 检查index是否越界 + return unlink(node(index)); // 移除index位置的节点 +} +``` +```Java +E unlink(Node x) { // 移除链表上的x节点 + // assert x != null; + final E element = x.item; // x节点的值 + final Node next = x.next; // x节点的下一个节点 + final Node prev = x.prev; // x节点的上一个节点 + + if (prev == null) { // 如果prev为空,则代表x节点为头结点,则将first指向next即可 + first = next; + } else { // 否则,x节点不为头结点, + prev.next = next; // 将prev节点的next属性指向x节点的next属性 + x.prev = null; // 将x的prev属性清空 + } + + if (next == null) { // 如果next为空,则代表x节点为尾节点,则将last指向prev即可 + last = prev; + } else { // 否则,x节点不为尾节点 + next.prev = prev; // 将next节点的prev属性指向x节点的prev属性 + x.next = null; // 将x的next属性清空 + } + + x.item = null; // 将x的值清空,以便垃圾收集器回收x对象 + size--; + modCount++; + return element; +} +``` +#### clear方法 +```Java +public void clear() { // 清除链表的所有节点 + for (Node x = first; x != null; ) { // 从头结点开始遍历将所有节点的属性清空 + Node next = x.next; + x.item = null; + x.next = null; + x.prev = null; + x = next; + } + first = last = null; // 将头结点和尾节点设为null + size = 0; + modCount++; +} +``` +## ArrayList和LinkedList比较 +#### 1、ArrayList底层基于动态数组实现,LinkedList底层基于链表实现 +#### 2、对于随机访问(get/set方法),ArrayList通过index直接定位到数组对应位置的节点,而LinkedList需要从头结点或尾节点开始遍历,直到寻找到目标节点,因此在效率上ArrayList优于LinkedList +#### 3、对于插入和删除(add/remove方法),ArrayList需要移动目标节点后面的节点(使用System.arraycopy方法移动节点),而LinkedList只需修改目标节点前后节点的next或prev属性即可,因此在效率上LinkedList优于ArrayList。 diff --git a/week_01/28/ArrayList.md b/week_01/28/ArrayList.md new file mode 100644 index 0000000..2d93c0c --- /dev/null +++ b/week_01/28/ArrayList.md @@ -0,0 +1,177 @@ +# ArrayList源码分析 + +## ArrayList实际上是一个动态数组,容量可以动态的增长,其继承了AbstractList,实现了List, RandomAccess, Cloneable, java.io.Serializable这些接口。 +## RandomAccess接口,被List实现之后,为List提供了随机访问功能,也就是通过下标获取元素对象的功能。实现了Cloneable, + +## 添加方法 add(E e) + +```java +//增加元素到集合的最后 +public boolean add(E e) { + ensureCapacityInternal(size + 1); // Increments modCount!! + //因为++运算符的特点 先使用后运算 这里实际上是 + //elementData[size] = e + //size+1 + elementData[size++] = e; + return true; +} +``` + +将一个元素增加到数组的size++位置上。 +ensureCapacityInternal用来进行扩容检查 + +```java +//初始长度是10,size默认值0,假定添加的是第一个元素,那么minCapacity=1 这是最小容量 如果小于这个容量就会报错 +//如果底层数组就是默认数组,那么选一个大的值,就是10 +private void ensureCapacityInternal(int minCapacity) { + if (elementData == EMPTY_ELEMENTDATA) { + minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity); + } + //调用另一个方法ensureExplicitCapacity + ensureExplicitCapacity(minCapacity); +} + ​ +private void ensureExplicitCapacity(int minCapacity) { + //记录修改的次数 + modCount++; + ​ + // overflow-conscious code + //如果传过来的值大于初始长度 执行grow方法(参数为传过来的值)扩容 + if (minCapacity - elementData.length > 0) + grow(minCapacity); + } + //真正的扩容 + private void grow(int minCapacity) { + // overflow-conscious code + int oldCapacity = elementData.length; + //新的容量是在原有的容量基础上+50% 右移一位就是二分之一 + int newCapacity = oldCapacity + (oldCapacity >> 1); + //如果新容量小于最小容量,按照最小容量进行扩容 + if (newCapacity - minCapacity < 0) + newCapacity = minCapacity; + if (newCapacity - MAX_ARRAY_SIZE > 0) + newCapacity = hugeCapacity(minCapacity); + // minCapacity is usually close to size, so this is a win: + //这里是重点 调用工具类Arrays的copyOf扩容 + elementData = Arrays.copyOf(elementData, newCapacity); + } + ​ + //Arrays + public static T[] copyOf(U[] original, int newLength, Class newType) { + T[] copy = ((Object)newType == (Object)Object[].class) + ? (T[]) new Object[newLength] + : (T[]) Array.newInstance(newType.getComponentType(), newLength); + System.arraycopy(original, 0, copy, 0, + Math.min(original.length, newLength)); + return copy; + } +``` + +```java ​ + add(int index, E element) + +//添加到指定的位置 +public void add(int index, E element) { + //判断索引是否越界,如果越界就会抛出下标越界异常 + rangeCheckForAdd(index); + //扩容检查 + ensureCapacityInternal(size + 1); // Increments modCount!! + //将指定下标空出 具体作法就是index及其后的所有元素后移一位 + System.arraycopy(elementData, index, elementData, index + 1,size - index); + //将要添加元素赋值到空出来的指定下标处 + elementData[index] = element; + //长度加1 + size++; +} +//判断是否出现下标是否越界 +private void rangeCheckForAdd(int index) { + //如果下标超过了集合的尺寸 或者 小于0就是越界 + if (index > size || index < 0) + throw new IndexOutOfBoundsException(outOfBoundsMsg(index)); + } +``` + +remove(int index) +ArrayList支持两种删除元素的方式 +remove(int index) 按照下标删除 常用 +remove(Object o) 按照元素删除 会删除和参数匹配的第一个元素 + +下面我们看一下ArrayList的实现 + +```java +/** + 移除list中指定位置的元素 + * Removes the element at the specified position in this list. + 所有后续元素左移 下标减1 + * Shifts any subsequent elements to the left (subtracts one from their + * indices). + *参数是要移除元素的下标 + * @param index the index of the element to be removed +返回值是被移除的元素 + * @return the element that was removed from the list + * @throws IndexOutOfBoundsException {@inheritDoc} + */ +public E remove(int index) { + //下标越界检查 + rangeCheck(index); + //修改次数统计 + modCount++; + //获取这个下标上的值 + E oldValue = elementData(index); + //计算出需要移动的元素个数 (因为需要将后续元素左移一位 此处计算出来的是后续元素的位数) + int numMoved = size - index - 1; + //如果这个值大于0 说明后续有元素需要左移 是0说明被移除的对象就是最后一位元素 + if (numMoved > 0) + //索引index只有的所有元素左移一位 覆盖掉index位置上的元素 + System.arraycopy(elementData, index+1, elementData, index,numMoved); + // 将最后一个元素赋值为null 这样就可以被gc回收了 + elementData[--size] = null; // clear to let GC do its work + //返回index位置上的元素 + return oldValue; +} + ​ +//移除特定元素 +public boolean remove(Object o) { + //如果元素是null 遍历数组移除第一个null + if (o == null) { + for (int index = 0; index < size; index++) + if (elementData[index] == null) { + //遍历找到第一个null元素的下标 调用下标移除元素的方法 + fastRemove(index); + return true; + } + } else { + //找到元素对应的下标 调用下标移除元素的方法 + for (int index = 0; index < size; index++) + if (o.equals(elementData[index])) { + fastRemove(index); + return true; + } + } + return false; +} + ​ +//按照下标移除元素 +private void fastRemove(int index) { + modCount++; + int numMoved = size - index - 1; + if (numMoved > 0) + System.arraycopy(elementData, index+1, elementData, index, + numMoved); + elementData[--size] = null; // clear to let GC do its work +} +``` + +### ArrayList总结 + 底层数组实现,使用默认构造方法初始化出来的容量是10 + 扩容的长度是在原长度基础上加二分之一 + 实现了RandomAccess接口,底层又是数组,get读取元素性能很好 + 线程不安全,所有的方法均不是同步方法也没有加锁,因此多线程下慎用 + 顺序添加很方便 + 删除和插入需要复制数组 性能很差(可以使用LinkindList) + 为什么ArrayList的elementData是用transient修饰的? + transient修饰的属性意味着不会被序列化,也就是说在序列化ArrayList的时候,不序列化elementData。 + 为什么要这么做呢? + elementData不总是满的,每次都序列化,会浪费时间和空间 + 重写了writeObject 保证序列化的时候虽然不序列化全部 但是有的元素都序列化 + 所以说不是不序列化 而是不全部序列化 diff --git a/week_01/28/HashMap.md b/week_01/28/HashMap.md new file mode 100644 index 0000000..c28ab3d --- /dev/null +++ b/week_01/28/HashMap.md @@ -0,0 +1,220 @@ +# HashMap 源码分析 + +## init +```java +// initialCapacity 初始化容量大小 +// loadFactor 负载因子 +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; + // threshold 是HashMap是否要进行扩容的标记量 + this.threshold = tableSizeFor(initialCapacity); + } +``` + +## putVal +```java +final V putVal(int hash, K key, V value, boolean onlyIfAbsent, + boolean evict) { + Node[] tab; Node p; int n, i; + + // table 为 null 说明是首次调用 put 方法 , 进行 resize 操作真正为 table 分配存储空间 + + if ((tab = table) == null || (n = tab.length) == 0) + n = (tab = resize()).length; + + // i = (n - 1) & hash 计算出的值判断 table[i] 是否为 null , + // 如果为 null 就为 key , value 创建一个新的 Node 节点 , + // 不需要进行碰撞检测直接存储在 table 中 i 的位置上 + + if ((p = tab[i = (n - 1) & hash]) == null) + tab[i] = newNode(hash, key, value, null); + else { + Node e; K k; + + // 检测要存储的 key 是否和 bucket 中存储的头节点相同 + if (p.hash == hash && + ((k = p.key) == key || (key != null && key.equals(k)))) + e = p; + + // 检测 bucket 中当前存放的节点类型是不是红黑树结构 , + // 是红黑树结构 , 存储为一个红黑树节点 + + else if (p instanceof TreeNode) + e = ((TreeNode)p).putTreeVal(this, tab, hash, key, value); + else { + + // 这个 bucket 中存放的节点是链表结构 , + // 循环直到链表的末尾或者是找到相同的 key + + for (int binCount = 0; ; ++binCount) { + if ((e = p.next) == null) { + p.next = newNode(hash, key, value, null); + + // 存储新节点的时候 , 检测链表长度是否超过 TREEIFY_THRESHOLD - 1 , + // 超过的话将链表转换为红黑树结构 ,提高性能 + + 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; + } + } + + // 并发修改计数器 ,有并发修改就抛异常 ConcurrentModificationException + ++modCount; + + // 存储了一个新节点 , 检测 size 是否超过 threshold + // 如果超过了要进行 resize 操作 + if (++size > threshold) + resize(); + afterNodeInsertion(evict); + return null; + } +``` + +## getNode +```java +final Node getNode(int hash, Object key) { + Node[] tab; Node first, e; int n; K k; + + // 检测 table 中是否已经存储了节点 , + // 检测key所在的 bucket 是否存储了节点 , + // 以上两点都不满足说明 key 不存在 + + if ((tab = table) != null && (n = tab.length) > 0 && + (first = tab[(n - 1) & hash]) != null) { + + // 对 bucket 中存储节点的头结点进行碰撞检测 , + // 如果运气好的话只需要进行这一次碰撞检测 + + if (first.hash == hash && // always check first node + ((k = first.key) == key || (key != null && key.equals(k)))) + return first; + + // 检测 bucket 存储的节点是否是单个节点 + + if ((e = first.next) != null) { + + // 检测节点数据结构是否是红黑树 + + if (first instanceof TreeNode) + return ((TreeNode)first).getTreeNode(hash, key); + do { + + // 节点是链表数据结构,循环直到链表末尾或者是发现 key 一致的节点 + if (e.hash == hash && + ((k = e.key) == key || (key != null && key.equals(k)))) + return e; + } while ((e = e.next) != null); + } + } + return null; + } +``` +## removeNode +```java +final Node removeNode(int hash, Object key, Object value, + boolean matchValue, boolean movable) { + Node[] tab; Node p; int n, index; + if ((tab = table) != null && (n = tab.length) > 0 && + (p = tab[index = (n - 1) & hash]) != null) { + Node node = null, e; K k; V v; + if (p.hash == hash && + ((k = p.key) == key || (key != null && key.equals(k)))) + node = p; + else if ((e = p.next) != null) { + if (p instanceof TreeNode) + node = ((TreeNode)p).getTreeNode(hash, key); + else { + do { + if (e.hash == hash && + ((k = e.key) == key || + (key != null && key.equals(k)))) { + node = e; + break; + } + + // p 是 node 的前一个节点 + p = e; + } while ((e = e.next) != null); + } + } + + // 以上代码是 getNode(hash , key) , 个人觉得这个函数中的代码冗余了 + + // 获取到 key 对应的节点 , 判断是否要进行值匹配 + + if (node != null && (!matchValue || (v = node.value) == value || + (value != null && value.equals(v)))) { + + // 进行删除操作 , 红黑树的删除是比较复杂的 , 链表的删除十分简单 + + if (node instanceof TreeNode) + ((TreeNode)node).removeTreeNode(this, tab, movable); + else if (node == p) + tab[index] = node.next; + else + p.next = node.next; + ++modCount; + --size; + afterNodeRemoval(node); + return node; + } + } + return null; + } + +``` + +## clear +```java + public void clear() { + Node[] tab; + modCount++; + if ((tab = table) != null && size > 0) { + size = 0; + + // 很简单把数组中每个位置设置为 null + for (int i = 0; i < tab.length; ++i) + tab[i] = null; + } + } +``` +## HashMap 总结 + +initialCapacity , loadFactor 这两个参数都影响着HashMap的性能。initialCapacity 决定了下一次 resize 后的容量(capacity << 1) , loadFactor 决定了 resize 发生的条件 (size > (capacity * loadFactor )) (一般情况下 , 极端情况下是 size > Integer.MAX_VALUE) 。如果初始化时不指定这两个参数,会使用默认值 , capacity = 16 , loadFactor = 0.75 。对于 16 的容量空间,如果不能充分利用的话会造成空间资源的浪费。 + +散列的过程就是将存入的元素均匀的分布到HashMap内部Node数据的过程。均匀分布指的是 , 数组中的每个位置尽量都存储了一个Node节点,并且该位置上的链表只有一个元素。散列分布的越均匀进行碰撞检测的次数就越少,查询性能就越高。 + +散列较为均匀的,查询时最好情况下可以直接定位,最坏情况下需要进行一次碰撞检测。 + +散列的不均匀的,查询时会进行多次碰撞检测造成效率较低。 + + ((capacity - 1) & hash) 会计算出 key 存储在 Node 数组中的那个位置上 (得到的值始终会落在 Node数组的长度范围内 , 等同于 hash % capacity , 不过位运算的效率更高), 如果发现该位置上已经存在Node 了,会将新存入的数据作为链表的尾节点。这样存储和查询时都会进行碰撞检测。碰撞检测其实就是比较传入的 key 的 hash 与同一 bucket 上所有的 key 的 hash 是否一致的过程。 jdk8 在这方面做了优化,加入了树型结构来弥补链表线性结构性能较低的不足。 + +提高碰撞检测的性能,从代码中也能看出来, 该运算的最理想情况(hash 相等情况下)是执行两步 , 比较 hash , 比较 key 。 最坏情况是执行 4 步 , 只要取最好情况就达到了提高性能的目的 。以此类推 key 就可以用一些 String , Enum 这之类的数据类型。 + +rezise 是一个较为消耗性能的过程 , 在首次向HashMap中存入元素的时候会进行首次resize , 在之后每当产生新节点(这里的节点指的是Node) , 同时 size > threshold 的时候会进行 resize ,resize 的过程也是 rehash 的过程。 + +HashMap 是不支持并发的 , 在并发修改时它采用 fail-fast 的策略 , 抛出 ConcurrentModificationException 。 多线程环境下操作HashMap有可能会造成死循环 , 在 resize 的过程当中。不要在多线程场景下使用HashMap 。 \ No newline at end of file diff --git a/week_01/28/LinkedList.md b/week_01/28/LinkedList.md new file mode 100644 index 0000000..ae48e7a --- /dev/null +++ b/week_01/28/LinkedList.md @@ -0,0 +1,84 @@ +# LinkedList 源码分析 + +## 在LinkedList中,每一个元素都是Node存储,Node拥有一个存储值的item与一个前驱prev和一个后继next + +```java +// 数据结构 +private static class Node { + E item;// 存储元素 + Node next;// 指向上一个元素 + Node prev;// 指向下一个元素 + Node(Node prev, E element, Node next) { + this.item = element; + this.next = next; + this.prev = prev; + } +} +``` + +## 添加数据方法 + +```java +public boolean addAll(int index, Collection c) { + checkPositionIndex(index); //校验index是否下标越界。 + + Object[] a = c.toArray(); + int numNew = a.length; + if (numNew == 0) //没有添加新的元素,返回false + return false; + + Node pred, succ; //建立前节点,后节点 + if (index == size) { //index==size,说明是在当前集合的末尾插入新数据,因此没有后节点,succ=null,前节点为当前集合的最后一个节点pred=last + succ = null; + pred = last; + } else { + succ = node(index); //找到当前下标指代的节点,要在该节点前插入数据,因此令succ等于该节点。 + pred = succ.prev; //pred则等于succ前面一位的节点。需要注意当index=0时,该点可以为null。 + } + + for (Object o : a) { + @SuppressWarnings("unchecked") E e = (E) o; //强制类型转换Object转为E + Node newNode = new Node<>(pred, e, null); //创建一个新的节点,节点元素为e,前节点为pred,后节点为null。 + if (pred == null) //说明index=0,因此新插入的集合的第一个元素,作为first + first = newNode; + else + pred.next = newNode; //说明index<>0,因此新的节点,作为前节点的后节点(pred.next) + pred = newNode; //令当前节点作为前节点,获取下一个元素,循环。 + } + + if (succ == null) { //说明是从当前集合的末尾开始插入数据,因此数据的最后一个元素,作为当前集合的last + last = pred; + } else { //pred为新插入的最后一个数据,令其的后节点等于之前拆开位置的后节点,succ为之前拆开位置的前节点,令其前节点prev等于新插入的元素的最后一个数据。 + pred.next = succ; + succ.prev = pred; + } + + size += numNew; //新插入numNew条数据,size增加相应的大小。 + modCount++; + return true; + } +``` + +## 在addAll(int,Collection)方法中,首先执行checkPositionIndex(int)检验index的合法性(是否在当前对象的size范围内) + +```java +private void checkPositionIndex(int index) { + if (!isPositionIndex(index)) + throw new IndexOutOfBoundsException(outOfBoundsMsg(index)); + } + + private boolean isPositionIndex(int index) { + return index >= 0 && index <= size; + } +``` +### LinkedList 总结 + +LinkedList重点在于对内部类Node的理解。 +每一个元素在LinkedList中都是在Node中存储,每个Node中同时还存储了当前元素的前节点和后节点。 +新增元素或集合,只要找到添加的位置,获取该位置的前节点pred,和后节点succ,令pred.next=新插入集合的第一个元素节点,令succ.pred=新插入的集合的最后一个元素节点即可。 +删除元素或集合,找到删除的位置,获取该位置的前节点pred,和后节点succ,令pred.next=succ.pred即可。 +注意不论是新增还是删除,均要考虑到起始节点没有pred和结束节点没有next的问题。 +每一个LinkedList对象中均只存了first和last两个节点,因此当根据索引寻找元素时,代码会判断索引是在前半部分还是后半部分,从而决定从first出发,还是从last出发。 + + + diff --git a/week_01/32/ArrayList.emmx b/week_01/32/ArrayList.emmx new file mode 100644 index 0000000..63d9ef6 Binary files /dev/null and b/week_01/32/ArrayList.emmx differ diff --git a/week_01/35/ArrayList-035.md b/week_01/35/ArrayList-035.md new file mode 100644 index 0000000..9c0ea9b --- /dev/null +++ b/week_01/35/ArrayList-035.md @@ -0,0 +1,45 @@ + ArrayList + * 基于数组实现 + * 继承抽象类AbstractList + * -->List(接口->Collection、Iterable) + * -->RandomAccess(随机访问) + * -->Cloneable(可克隆) + * -->Serializable(可序列化) + + 主要参数: + * DEFAULT_CAPACITY:默认初始化大小 + * EMPTY_ELEMENTDATA:指定list为空时使用的数组 + * DEFAULTCAPACITY_EMPTY_ELEMENTDATA:当使用默认无参构造器创建的空list数组,在扩容时会考虑使用默认的扩容方案DEFAULT_CAPACITY + * elementData:存放元素的数组 + * size:数组大小 + + madCount(全局变量): + * madCount交由迭代器(Iterator)和列表迭代器(ListIterator)使用, + * 当进行next()、remove()、previous()、set()、add()等操作时, + * 如果madCount的值意外改变,那么迭代器或者列表迭代器就会抛出ConcurrentModificationException异常。 + + ArrayList中就继承了AbstractList并在每个结构性改变的方法中让madCount变量自增1,并且实现了自己的迭代器; + 在next()方法中,判断了当前的modCount跟初始化Itr时的expectedModCount是否相同, + 不同则说明列表数据进行了结构性改变,此时就会抛出ConcurrentModificationException。 + + 1、三个构造方法 + * 无参 + 初始大小10 + * int参数 + 大于0初始化传入值,新建数组对象;等于0等于空实例数组;否则抛出IllegalArgumentException + * Collection参数 + 定义为空集合时,初始化大小10;否则获取toArry的length进行初始化 + if (elementData.getClass() != Object[].class)->调用toArray方法返回的不一定是Object[]类型 + 不一致时,调用Arrays.copyof()定义了一个新的数组,将原数组的数据拷贝到了新的数组中去. + 源码: + public static T[] copyOf(U[] original, int newLength, Class newType) { + @SuppressWarnings("unchecked") + T[] copy = ((Object)newType == (Object)Object[].class) + ? (T[]) new Object[newLength] + : (T[]) Array.newInstance(newType.getComponentType(), newLength); + System.arraycopy(original, 0, copy, 0, + Math.min(original.length, newLength)); + return copy; + } + + \ No newline at end of file diff --git a/week_01/35/HashMap-035.md b/week_01/35/HashMap-035.md new file mode 100644 index 0000000..fcc7da4 --- /dev/null +++ b/week_01/35/HashMap-035.md @@ -0,0 +1,120 @@ + ~~HashMap~~ + 继承AbstractMap + >实现Map, Cloneable, Serializable + >JDK版本 实现方式 节点数>=8 节点数<=6 + >1.8以前 数组+单向链表 数组+单向链表 数组+单向链表 + >1.8以后 数组+单向链表+红黑树(提高查找效率) 数组+红黑树 数组+单向链表 + + 主要参数: + * DEFAULT_INITIAL_CAPACITY:1 << 4 -> 初始容量大小 + * MAXIMUM_CAPACITY:1 << 30 -> 容量极限大小 + * DEFAULT_LOAD_FACTOR:负载因子默认大小 + * TREEIFY_THRESHOLD:节点数大于8时会转为红黑树存储 + * UNTREEIFY_THRESHOLD:节点数小于6时会转为单向链表存储 + * MIN_TREEIFY_CAPACITY:红黑树最小长度为64 + * Node:实现Map.Entry , 单向链表 + Node是Map.Entry接口的实现类,存储数据的Node数组容量是2次幂,每个Node本质都是一个单向链表 + * TreeNode:extends LinkedHashMap.Entry , 红黑树) + * size:HashMap大小,代表HashMap保存的键值对的大小 + * modCount:被改变的次数 + * threshold:下一次HashMap扩容的大小->put时根据oldCap和newCap比较确定下一次扩容大小 + * loadFactor:存储负载因子的常量 + + 1、hash的计算: + //将传入的参数key本身的hashCode与h无符号右移16位进行二进制异或运算得出一个新的hash值 + static final int hash(Object key) { + int h; + return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16); + } + 那为什么要这么做呢?直接通过key.hashCode()获取hash不得了吗? 为什么在右移16位后进行异或运算? + 与HashMap的table数组下计算标有关系 + //put函数代码块中 + tab[i = (n - 1) & hash]) + //get函数代码块中 + tab[(n - 1) & hash]) + 根据索引得到tab中节点数据,key.hashCode()>>>16经过^异或运算和经过&与运算后得到的二进制是一致的 + 当发生较大碰撞时也用树形存储降低了冲突。减少了系统的开销 + 1.1、put方法 + 1、首先获取Node数组table对象和长度,若table为null或长度为0,则调用resize()扩容方法获取table最新对象,并通过此对象获取长度大小 + if ((tab = table) == null || (n = tab.length) == 0) + n = (tab = resize()).length; + 2、判定数组中指定索引下的节点是否为Null,若为Null则new出一个单向链表赋给table中索引下的这个节点 + if ((p = tab[i = (n - 1) & hash]) == null) + tab[i] = newNode(hash, key, value, null) + 3、若判定不为Null,我们的判断再做分支 + 3.1、首先对hash和key进行匹配,若判定成功直接赋予e + if (p.hash == hash && ((k = p.key) == key || (key != null && key.equals(k)))) + e = p; + 3.2、若匹配判定失败,则进行类型匹配是否为TreeNode 若判定成功则在红黑树中查找符合条件的节点并将其回传赋给e + else if (p instanceof TreeNode) + e = ((TreeNode)p).putTreeVal(this, tab, hash, key, value); + 3.3、若以上判定全部失败则进行最后操作,向单向链表中添加数据若单向链表的长度大于等于8, + 则将其转为红黑树保存,记录下一个节点,对e进行判定若成功则返回旧值 + for (int binCount = 0; ; ++binCount) { + if ((e = p.next) == null) { + p.next = newNode(hash, key, value, null); + 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; + } + 4、最后判定数组大小需不需要扩容 + if (++size > threshold) + resize() + 1.2、get方法 + 1、判定三个条件 table不为Null & table的长度大于0 & table指定的索引值不为Null + if ((tab = table) != null && (n = tab.length) > 0 &&(first = tab[(n - 1) & hash]) != null) + 2、判定 匹配hash值 & 匹配key值 成功则返回该值 + if (first.hash == hash && ((k = first.key) == key || (key != null && key.equals(k)))) + return first + 3、若first节点的下一个节点不为Null + if ((e = first.next) != null) + 3.1、若first的类型为TreeNode红黑树,通过红黑树查找匹配值并返回查询值 + if (first instanceof TreeNode) + return ((TreeNode)first).getTreeNode(hash, key) + 3.2、若上面判定不成功则认为下一个节点为单向链表,通过循环匹配值 + do { + if (e.hash == hash && + ((k = e.key) == key || (key != null && key.equals(k)))) + return e; + } while ((e = e.next) != null); + + + 2、三个构造方法 + //指定容量大小 + public HashMap(int initialCapacity) { + this(initialCapacity, DEFAULT_LOAD_FACTOR); + } + //指定容量大小和负载因子大小 + public HashMap(int initialCapacity, float loadFactor) { + //指定的容量大小不可以小于0,否则将抛出IllegalArgumentException异常 + if (initialCapacity < 0) + throw new IllegalArgumentException("Illegal initial capacity: " + initialCapacity); + //判定指定的容量大小是否大于HashMap的容量极限 + if (initialCapacity > MAXIMUM_CAPACITY) + initialCapacity = MAXIMUM_CAPACITY; + //指定的负载因子不可以小于0或为Null,若判定成立则抛出IllegalArgumentException异常 + if (loadFactor <= 0 || Float.isNaN(loadFactor)) + throw new IllegalArgumentException("Illegal load factor: " + loadFactor); + this.loadFactor = loadFactor; + //设置HashMap阈值,当HashMap中存储数据的数量达到threshold时,就需要将HashMap的容量加倍。 + this.threshold = tableSizeFor(initialCapacity); + } + //传入Map集合,将Map集合中元素Map.Entry全部添加进HashMap实例中 + public HashMap(Map m) { + this.loadFactor = DEFAULT_LOAD_FACTOR; + //此构造方法主要实现了Map.putAll() + putMapEntries(m, false); + } \ No newline at end of file diff --git a/week_01/35/LinkedList-035.md b/week_01/35/LinkedList-035.md new file mode 100644 index 0000000..6a1c1f6 --- /dev/null +++ b/week_01/35/LinkedList-035.md @@ -0,0 +1,57 @@ + LinkedList继承AbstractSequentialList的双向循环链表。它也可以被当作堆栈、队列或双端队列进行操作 + 实现List, Deque(双端队列), Cloneable, Serializable接口,非同步的 + + 主要参数: + size:集合的长度 + first:双向链表头部节点 + last:双向链表尾部节点 + + 与ArrayList比较: + * 内部使用链表实现,相比于ArrayList更加耗费空间。 + * 插入,删除节点不用大量copy原来元素,效率更高。 + * 查找元素使用遍历,效率一般。 + * 双向队列的实现 + + + 构造函数: + * 构造一个空的LinkedList + public LinkedList() { + //将header节点的前一节点和后一节点都设置为自身 + header.next = header. previous = header ; + } + + * 构造一个包含指定 collection 中的元素的列表,这些元素按其 collection 的迭代器返回的顺序排列 + public LinkedList(Collection c) { + this(); + addAll(c); + } + + 新增元素 + public boolean add(E e) { + linkLast(e); + return true; + } + + 在指定位置插入新元素 + public void add(int index, E element) { + checkPositionIndex(index);//index位置检查(不能小于0,大于size)-->index >= 0 && index <= size,否则抛出异常IndexOutOfBoundsException + if (index == size) //如果index==size,直接在链表最后插入,相当于调用add(E e)方法 + linkLast(element); //size++,没有扩容机制 + else + linkBefore(element, node(index)); + } + + //判断index在链表的哪边。遍历查找index或者size,找出对应节点 + //相比于数组的直接索引获取,遍历获取节点效率不高 + public E get(int index) { + checkElementIndex(index); + return node(index).item; + } + + //检查index位置 + // 调用node方法获取节点,接着调用unlink(E e) + //相比于ArrayList的copy数组覆盖原来节点,效率同样更高 + public E remove(int index) { + checkElementIndex(index); + return unlink(node(index)); + } \ No newline at end of file diff --git a/week_01/35/com.dans.demo/ArrayListDemo.java b/week_01/35/com.dans.demo/ArrayListDemo.java new file mode 100644 index 0000000..9a6b39b --- /dev/null +++ b/week_01/35/com.dans.demo/ArrayListDemo.java @@ -0,0 +1,35 @@ +package com.dans.demo; + +import java.util.ArrayList; +import java.util.List; + +/** + * @ProjectName: JavaStudy + * @Package: com.dans.demo + * @ClassName: ArrayListDemo + * @Author: dans + * @Description: demo + * @Date: 2019/12/12 21:53 + */ +public class ArrayListDemo { + + public static void main(String[] arg0) { + List demo = new ArrayList<>(); + demo.add("0"); + demo.add("1"); + + //指定位置插入数据(返回该下标的老数据) + String oldVal = demo.set(0, "2"); + System.out.println("oldVal: " + oldVal); + //根据内容移出(返回boolean类型; + // 若传入为null,则循环找到空值,执行fastRemove方法,根据下标移出,内部调用了System.arraycopy(); + // 不为空,则循环用equals做比较,再执行fastRemove方法,根据下标移出,内部调用System.arraycopy()) +// demo.remove("2"); +// 指定下标移出(返回移出数据) + demo.remove(0); + + demo.stream().forEach(str -> { + System.out.println(str); + }); + } +} diff --git a/week_01/35/com.dans.demo/HashMapDemo.java b/week_01/35/com.dans.demo/HashMapDemo.java new file mode 100644 index 0000000..c517d7a --- /dev/null +++ b/week_01/35/com.dans.demo/HashMapDemo.java @@ -0,0 +1,56 @@ +package com.dans.demo; + +import java.util.HashMap; +import java.util.Iterator; +import java.util.Map; +import java.util.Map.Entry; +import java.util.function.BiConsumer; + +/** + * @ProjectName: JavaStudy + * @Package: com.dans.demo + * @ClassName: HashMapDemo + * @Author: dans + * @Description: demo + * @Date: 2019/12/13 21:30 + */ +public class HashMapDemo { + + public static void main(String[] arg0) { + Map map = new HashMap<>(); + map.put(0, 1); + map.put(1, 2); + map.put(2, 3); + map.put(3, 4); + + //HashMap中modCount和expectedModCount不相等,则会抛出异常 + //遍历时禁止执行有modCount++相关的操作 + + //1.8之后新方法 + map.forEach(new BiConsumer() { + @Override + public void accept(Integer key, Integer val) { + System.out.println("foreach--------->key:" + key + "--------val:" + val); + //执行remove,抛出错误(ConcurrentModificationException) + /*if (key == 1) { + map.remove(key); + }*/ + } + }); + + Iterator iterator = map.entrySet().iterator(); + Entry entry = null; + while (iterator.hasNext()) { + entry = (Entry) iterator.next(); + if (entry.getKey() == 1) + iterator.remove(); + System.out.println("iterator------>key:" + entry.getKey() + "--------val:" + entry.getValue()); + } + + for (Entry entry1 : map.entrySet()) { + //执行remove()抛错(ConcurrentModificationException) +// map.remove(entry.getKey()); + System.out.println("entry1------->key:" + entry1.getKey() + "--------val:" + entry1.getValue()); + } + } +} diff --git a/week_01/35/com.dans.demo/LinkedListDemo.java b/week_01/35/com.dans.demo/LinkedListDemo.java new file mode 100644 index 0000000..463dfd8 --- /dev/null +++ b/week_01/35/com.dans.demo/LinkedListDemo.java @@ -0,0 +1,27 @@ +package com.dans.demo; +import java.util.LinkedList; +import java.util.List; + +/** + * @ProjectName: JavaStudy + * @Package: com.dans.demo + * @ClassName: LinkedListemo + * @Author: dans + * @Description: demo + * @Date: 2019/12/13 23:30 + */ +public class LinkedListDemo { + + public static void main(String[] arg0) { + List list = new LinkedList<>(); + list.add(1); + list.add(2); + list.add(3); + + list.remove(1); + + list.stream().forEach(i -> { + System.out.println(i); + }); + } +} diff --git "a/week_01/39/ArrayList\346\272\220\347\240\201\351\230\205\350\257\273\347\254\224\350\256\260.md" "b/week_01/39/ArrayList\346\272\220\347\240\201\351\230\205\350\257\273\347\254\224\350\256\260.md" new file mode 100644 index 0000000..79c7951 --- /dev/null +++ "b/week_01/39/ArrayList\346\272\220\347\240\201\351\230\205\350\257\273\347\254\224\350\256\260.md" @@ -0,0 +1,147 @@ +## ArrayList简介 + +ArrayList是基于数组实现的,是一个动态数组,其容量能自动增长,类似于C语言中的动态申请内存,动态增长内存。 + +ArrayList不是线程安全的,只能用在单线程环境下,多线程环境下可以考虑用Collections.synchronizedList(List l)函数返回一个线程安全的ArrayList类,也可以使用concurrent并发包下的CopyOnWriteArrayList类。 + +ArrayList实现了Serializable接口,因此它支持序列化,能够通过序列化传输,实现了RandomAccess接口,支持快速随机访问,实际上就是通过下标序号进行快速访问,实现了Cloneable接口,能被克隆。 + +以下为ArrayList源码的几个重要定义: + + + // 序列版本号 + private static final long serialVersionUID = 8683452581122892189L; + + // ArrayList基于该数组实现,用该数组保存数据 + private transient Object[] elementData; + + // ArrayList中实际数据的数量 + private int size; + + + +## 关于ArrayList的源码,几点比较重要的总结: + +1、注意其三个不同的构造方法。无参构造方法构造的ArrayList的容量默认为10,带有Collection参数的构造方法,将Collection转化为数组赋给ArrayList的实现数组elementData。 + + public ArrayList(int initialCapacity) { + super(); + if (initialCapacity < 0) + throw new IllegalArgumentException("Illegal Capacity: "+ + initialCapacity); + // 新建一个数组 + this.elementData = new Object[initialCapacity]; + } + + // ArrayList无参构造函数。默认容量是10。 + public ArrayList() { + this(10); + } + + // 创建一个包含collection的ArrayList + public ArrayList(Collection c) { + elementData = c.toArray(); + size = elementData.length; + if (elementData.getClass() != Object[].class) + elementData = Arrays.copyOf(elementData, size, Object[].class); + } + + + + +2、注意扩充容量的方法ensureCapacity。ArrayList在每次增加元素(可能是1个,也可能是一组)时,都要调用该方法来确保足够的容量。当容量不足以容纳当前的元素个数时,就设置新的容量为旧的容量的1.5倍加1,如果设置后的新容量还不够,则直接新容量设置为传入的参数(也就是所需的容量),而后用Arrays.copyof()方法将元素拷贝到新的数组(详见下面的第3点)。从中可以看出,当容量不够时,每次增加元素,都要将原来的元素拷贝到一个新的数组中,非常之耗时,也因此建议在事先能确定元素数量的情况下,才使用ArrayList,否则建议使用LinkedList。 + + + public void ensureCapacity(int minCapacity) { + // 将“修改统计数”+1,该变量主要是用来实现fail-fast机制的 + modCount++; + int oldCapacity = elementData.length; + // 若当前容量不足以容纳当前的元素个数,设置 新的容量=“(原始容量x3)/2 + 1” + if (minCapacity > oldCapacity) { + Object oldData[] = elementData; + int newCapacity = (oldCapacity * 3)/2 + 1; + //如果还不够,则直接将minCapacity设置为当前容量 + if (newCapacity < minCapacity) + newCapacity = minCapacity; + elementData = Arrays.copyOf(elementData, newCapacity); + } + } + + +3、ArrayList的实现中大量地调用了Arrays.copyof()和System.arraycopy()方法。我们有必要对这两个方法的实现做下深入的了解。 + +首先来看Arrays.copyof()方法。它有很多个重载的方法,但实现思路都是一样的,我们来看泛型版本的源码: + + public static T[] copyOf(T[] original, int newLength) { + return (T[]) copyOf(original, newLength, original.getClass()); + } + + + 很明显调用了另一个copyof方法,该方法有三个参数,最后一个参数指明要转换的数据的类型,其源码如下: + + + + public static T[] copyOf(U[] original, int newLength, Class newType) { + T[] copy = ((Object)newType == (Object)Object[].class) + ? (T[]) new Object[newLength] + : (T[]) Array.newInstance(newType.getComponentType(), newLength); + System.arraycopy(original, 0, copy, 0, + Math.min(original.length, newLength)); + return copy; + } + + + 这里可以很明显地看出,该方法实际上是在其内部又创建了一个长度为newlength的数组,调用System.arraycopy()方法,将原来数组中的元素复制到了新的数组中。 + +下面来看System.arraycopy()方法。该方法被标记了native,调用了系统的C/C++代码,在JDK中是看不到的,但在openJDK中可以看到其源码。该函数实际上最终调用了C语言的memmove()函数,因此它可以保证同一个数组内元素的正确复制和移动,比一般的复制方法的实现效率要高很多,很适合用来批量处理数组。Java强烈推荐在复制大量数组元素时用该方法,以取得更高的效率。 + +4、注意ArrayList的两个转化为静态数组的toArray方法。 + +第一个,Object[] toArray()方法。该方法有可能会抛出java.lang.ClassCastException异常,如果直接用向下转型的方法,将整个ArrayList集合转变为指定类型的Array数组,便会抛出该异常,而如果转化为Array数组时不向下转型,而是将每个元素向下转型,则不会抛出该异常,显然对数组中的元素一个个进行向下转型,效率不高,且不太方便。 + +第二个, T[] toArray(T[] a)方法。该方法可以直接将ArrayList转换得到的Array进行整体向下转型(转型其实是在该方法的源码中实现的),且从该方法的源码中可以看出,参数a的大小不足时,内部会调用Arrays.copyOf方法,该方法内部创建一个新的数组返回,因此对该方法的常用形式如下: + + public static Integer[] vectorToArray2(ArrayList v) { + Integer[] newText = (Integer[])v.toArray(new Integer[0]); + return newText; +} + + + 5、ArrayList基于数组实现,可以通过下标索引直接查找到指定位置的元素,因此查找效率高,但每次插入或删除元素,就要大量地移动元素,插入删除元素的效率低(插入元素时效率略高于LinkedList,但删除LinkedList的效率远高于ArrayList)。 + +6、在查找给定元素索引值等的方法中,源码都将该元素的值分为null和不为null两种情况处理,ArrayList中允许元素为null。如下面几个方法: + + public int indexOf(Object o) { + if (o == null) { + for (int i = 0; i < size; i++) + if (elementData[i]==null) + return i; + } else { + for (int i = 0; i < size; i++) + if (o.equals(elementData[i])) + return i; + } + return -1; + } + + /** + * Returns the index of the last occurrence of the specified element + * in this list, or -1 if this list does not contain the element. + * More formally, returns the highest index i such that + * (o==null ? get(i)==null : o.equals(get(i))), + * or -1 if there is no such index. + */ + public int lastIndexOf(Object o) { + if (o == null) { + for (int i = size-1; i >= 0; i--) + if (elementData[i]==null) + return i; + } else { + for (int i = size-1; i >= 0; i--) + if (o.equals(elementData[i])) + return i; + } + return -1; + } + + diff --git "a/week_01/39/HashMap\346\272\220\347\240\201\351\230\205\350\257\273\347\254\224\350\256\260.md" "b/week_01/39/HashMap\346\272\220\347\240\201\351\230\205\350\257\273\347\254\224\350\256\260.md" new file mode 100644 index 0000000..f23b5a1 --- /dev/null +++ "b/week_01/39/HashMap\346\272\220\347\240\201\351\230\205\350\257\273\347\254\224\350\256\260.md" @@ -0,0 +1,389 @@ +## HashMap简介 + +HashMap是基于哈希表实现的,每一个元素是一个key-value对,其内部通过单链表解决冲突问题,容量不足(超过了阀值)时,同样会自动增长。 + +HashMap是非线程安全的,只是用于单线程环境下,多线程环境下可以采用concurrent并发包下的concurrentHashMap。 + +HashMap 实现了Serializable接口,因此它支持序列化,实现了Cloneable接口,能被克隆。 + + 1、首先要清楚HashMap的存储结构。 + +![在这里插入图片描述](https://img-blog.csdnimg.cn/20190822185752822.jpg?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L01pYW9zaHVvd2Vu,size_16,color_FFFFFF,t_70) + +图中,紫色部分即代表哈希表,也称为哈希数组,数组的每个元素都是一个单链表的头节点,链表是用来解决冲突的,如果不同的key映射到了数组的同一位置处,就将其放入单链表中。 + +2.链表节点的数据结构 + + // Entry是单向链表。 + // 它是 “HashMap链式存储法”对应的链表。 + // 它实现了Map.Entry 接口,即实现getKey(), getValue(), setValue(V value), equals(Object o), hashCode()这些函数 + static class Entry implements Map.Entry { + final K key; + V value; + // 指向下一个节点 + Entry next; + final int hash; + + // 构造函数。 + // 输入参数包括"哈希值(h)", "键(k)", "值(v)", "下一节点(n)" + Entry(int h, K k, V v, Entry n) { + value = v; + next = n; + key = k; + hash = h; + } + + public final K getKey() { + return key; + } + + public final V getValue() { + return value; + } + + public final V setValue(V newValue) { + V oldValue = value; + value = newValue; + return oldValue; + } + + // 判断两个Entry是否相等 + // 若两个Entry的“key”和“value”都相等,则返回true。 + // 否则,返回false + public final boolean equals(Object o) { + if (!(o instanceof Map.Entry)) + return false; + Map.Entry e = (Map.Entry)o; + Object k1 = getKey(); + Object k2 = e.getKey(); + if (k1 == k2 || (k1 != null && k1.equals(k2))) { + Object v1 = getValue(); + Object v2 = e.getValue(); + if (v1 == v2 || (v1 != null && v1.equals(v2))) + return true; + } + return false; + } + + // 实现hashCode() + public final int hashCode() { + return (key==null ? 0 : key.hashCode()) ^ + (value==null ? 0 : value.hashCode()); + } + + public final String toString() { + return getKey() + "=" + getValue(); + } + + // 当向HashMap中添加元素时,绘调用recordAccess()。 + // 这里不做任何处理 + void recordAccess(HashMap m) { + } + + // 当从HashMap中删除元素时,绘调用recordRemoval()。 + // 这里不做任何处理 + void recordRemoval(HashMap m) { + } + } +它的结构元素除了key、value、hash外,还有next,next指向下一个节点。另外,这里覆写了equals和hashCode方法来保证键值对的独一无二。 + +3、HashMap共有四个构造方法。构造方法中提到了两个很重要的参数:初始容量和加载因子。这两个参数是影响HashMap性能的重要参数,其中容量表示哈希表中槽的数量(即哈希数组的长度),初始容量是创建哈希表时的容量(从构造函数中可以看出,如果不指明,则默认为16),加载因子是哈希表在其容量自动增加之前可以达到多满的一种尺度,当哈希表中的条目数超出了加载因子与当前容量的乘积时,则要对该哈希表进行 resize 操作(即扩容)。 + +下面说下加载因子,如果加载因子越大,对空间的利用更充分,但是查找效率会降低(链表长度会越来越长);如果加载因子太小,那么表中的数据将过于稀疏(很多空间还没用,就开始扩容了),对空间造成严重浪费。如果我们在构造方法中不指定,则系统默认加载因子为0.75,这是一个比较理想的值,一般情况下我们是无需修改的。 + +另外,无论我们指定的容量为多少,构造方法都会将实际容量设为不小于指定容量的2的次方的一个数,且最大值不能超过2的30次方。 + + + // 默认的初始容量(容量为HashMap中槽的数目)是16,且实际容量必须是2的整数次幂。 + static final int DEFAULT_INITIAL_CAPACITY = 16; + + // 最大容量(必须是2的幂且小于2的30次方,传入容量过大将被这个值替换) + static final int MAXIMUM_CAPACITY = 1 << 30; + + // 默认加载因子为0.75 + static final float DEFAULT_LOAD_FACTOR = 0.75f; + + // 存储数据的Entry数组,长度是2的幂。 + // HashMap采用链表法解决冲突,每一个Entry本质上是一个单向链表 + transient Entry[] table; + + // HashMap的底层数组中已用槽的数量 + transient int size; + + // HashMap的阈值,用于判断是否需要调整HashMap的容量(threshold = 容量*加载因子) + int threshold; + + // 加载因子实际大小 + final float loadFactor; + + // HashMap被改变的次数 + transient volatile int modCount; + + // 指定“容量大小”和“加载因子”的构造函数 + public HashMap(int initialCapacity, float loadFactor) { + if (initialCapacity < 0) + throw new IllegalArgumentException("Illegal initial capacity: " + + initialCapacity); + // HashMap的最大容量只能是MAXIMUM_CAPACITY + if (initialCapacity > MAXIMUM_CAPACITY) + initialCapacity = MAXIMUM_CAPACITY; + //加载因子不能小于0 + if (loadFactor <= 0 || Float.isNaN(loadFactor)) + throw new IllegalArgumentException("Illegal load factor: " + + loadFactor); + + // 找出“大于initialCapacity”的最小的2的幂 + int capacity = 1; + while (capacity < initialCapacity) + capacity <<= 1; + + // 设置“加载因子” + this.loadFactor = loadFactor; + // 设置“HashMap阈值”,当HashMap中存储数据的数量达到threshold时,就需要将HashMap的容量加倍。 + threshold = (int)(capacity * loadFactor); + // 创建Entry数组,用来保存数据 + table = new Entry[capacity]; + init(); + } + + + // 指定“容量大小”的构造函数 + public HashMap(int initialCapacity) { + this(initialCapacity, DEFAULT_LOAD_FACTOR); + } + + // 默认构造函数。 + public HashMap() { + // 设置“加载因子”为默认加载因子0.75 + this.loadFactor = DEFAULT_LOAD_FACTOR; + // 设置“HashMap阈值”,当HashMap中存储数据的数量达到threshold时,就需要将HashMap的容量加倍。 + threshold = (int)(DEFAULT_INITIAL_CAPACITY * DEFAULT_LOAD_FACTOR); + // 创建Entry数组,用来保存数据 + table = new Entry[DEFAULT_INITIAL_CAPACITY]; + init(); + } + + // 包含“子Map”的构造函数 + public HashMap(Map m) { + this(Math.max((int) (m.size() / DEFAULT_LOAD_FACTOR) + 1, + DEFAULT_INITIAL_CAPACITY), DEFAULT_LOAD_FACTOR); + // 将m中的全部元素逐个添加到HashMap中 + putAllForCreate(m); + } + + + + +4、HashMap中key和value都允许为null。 + +5、要重点分析下HashMap中用的最多的两个方法put和get。先从比较简单的get方法着手,源码如下: + + + + // 获取key对应的value + public V get(Object key) { + if (key == null) + return getForNullKey(); + // 获取key的hash值 + int hash = hash(key.hashCode()); + // 在“该hash值对应的链表”上查找“键值等于key”的元素 + for (Entry e = table[indexFor(hash, table.length)]; + e != null; + e = e.next) { + Object k; + //判断key是否相同 + if (e.hash == hash && ((k = e.key) == key || key.equals(k))) + return e.value; + } + //没找到则返回null + return null; + } + + // 获取“key为null”的元素的值 + // HashMap将“key为null”的元素存储在table[0]位置,但不一定是该链表的第一个位置! + private V getForNullKey() { + for (Entry e = table[0]; e != null; e = e.next) { + if (e.key == null) + return e.value; + } + return null; + } + +首先,如果key为null,则直接从哈希表的第一个位置table[0]对应的链表上查找。记住,key为null的键值对永远都放在以table[0]为头结点的链表中,当然不一定是存放在头结点table[0]中。 + 如果key不为null,则先求的key的hash值,根据hash值找到在table中的索引,在该索引对应的单链表中查找是否有键值对的key与目标key相等,有就返回对应的value,没有则返回null。 + +put方法稍微复杂些,代码如下: + + // 将“key-value”添加到HashMap中 + + public V put(K key, V value) { + // 若“key为null”,则将该键值对添加到table[0]中。 + if (key == null) + return putForNullKey(value); + // 若“key不为null”,则计算该key的哈希值,然后将其添加到该哈希值对应的链表中。 + int hash = hash(key.hashCode()); + int i = indexFor(hash, table.length); + for (Entry e = table[i]; e != null; e = e.next) { + Object k; + // 若“该key”对应的键值对已经存在,则用新的value取代旧的value。然后退出! + if (e.hash == hash && ((k = e.key) == key || key.equals(k))) { + V oldValue = e.value; + e.value = value; + e.recordAccess(this); + return oldValue; + } + } + + // 若“该key”对应的键值对不存在,则将“key-value”添加到table中 + modCount++; + //将key-value添加到table[i]处 + addEntry(hash, key, value, i); + return null; + } + + +如果key为null,则将其添加到table[0]对应的链表中,putForNullKey的源码如下: + + + // putForNullKey()的作用是将“key为null”键值对添加到table[0]位置 + private V putForNullKey(V value) { + for (Entry e = table[0]; e != null; e = e.next) { + if (e.key == null) { + V oldValue = e.value; + e.value = value; + e.recordAccess(this); + return oldValue; + } + } + // 如果没有存在key为null的键值对,则直接题阿见到table[0]处! + modCount++; + addEntry(0, null, value, 0); + return null; + } + + +如果key不为null,则同样先求出key的hash值,根据hash值得出在table中的索引,而后遍历对应的单链表,如果单链表中存在与目标key相等的键值对,则将新的value覆盖旧的value,比将旧的value返回,如果找不到与目标key相等的键值对,或者该单链表为空,则将该键值对插入到改单链表的头结点位置(每次新插入的节点都是放在头结点的位置),该操作是有addEntry方法实现的,它的源码如下: + + + // 新增Entry。将“key-value”插入指定位置,bucketIndex是位置索引。 + void addEntry(int hash, K key, V value, int bucketIndex) { + // 保存“bucketIndex”位置的值到“e”中 + Entry e = table[bucketIndex]; + // 设置“bucketIndex”位置的元素为“新Entry”, + // 设置“e”为“新Entry的下一个节点” + table[bucketIndex] = new Entry(hash, key, value, e); + // 若HashMap的实际大小 不小于 “阈值”,则调整HashMap的大小 + if (size++ >= threshold) + resize(2 * table.length); + } + + +注意这里倒数第三行的构造方法,将key-value键值对赋给table[bucketIndex],并将其next指向元素e,这便将key-value放到了头结点中,并将之前的头结点接在了它的后面。该方法也说明,每次put键值对的时候,总是将新的该键值对放在table[bucketIndex]处(即头结点处)。 + 两外注意最后两行代码,每次加入键值对时,都要判断当前已用的槽的数目是否大于等于阀值(容量*加载因子),如果大于等于,则进行扩容,将容量扩为原来容量的2倍。 + + 6、关于扩容。上面我们看到了扩容的方法,resize方法,它的源码如下: + + + + // 重新调整HashMap的大小,newCapacity是调整后的单位 + void resize(int newCapacity) { + Entry[] oldTable = table; + int oldCapacity = oldTable.length; + if (oldCapacity == MAXIMUM_CAPACITY) { + threshold = Integer.MAX_VALUE; + return; + } + + // 新建一个HashMap,将“旧HashMap”的全部元素添加到“新HashMap”中, + // 然后,将“新HashMap”赋值给“旧HashMap”。 + Entry[] newTable = new Entry[newCapacity]; + transfer(newTable); + table = newTable; + threshold = (int)(newCapacity * loadFactor); + } + +很明显,是新建了一个HashMap的底层数组,而后调用transfer方法,将就HashMap的全部元素添加到新的HashMap中(要重新计算元素在新的数组中的索引位置)。transfer方法的源码如下: + + + // 将HashMap中的全部元素都添加到newTable中 + void transfer(Entry[] newTable) { + Entry[] src = table; + int newCapacity = newTable.length; + for (int j = 0; j < src.length; j++) { + Entry e = src[j]; + if (e != null) { + src[j] = null; + do { + Entry next = e.next; + int i = indexFor(e.hash, newCapacity); + e.next = newTable[i]; + newTable[i] = e; + e = next; + } while (e != null); + } + } + } + + +很明显,扩容是一个相当耗时的操作,因为它需要重新计算这些元素在新的数组中的位置并进行复制处理。因此,我们在用HashMap的时,最好能提前预估下HashMap中元素的个数,这样有助于提高HashMap的性能。 + + 7、注意containsKey方法和containsValue方法。前者直接可以通过key的哈希值将搜索范围定位到指定索引对应的链表,而后者要对哈希数组的每个链表进行搜索。 + + + // HashMap是否包含key + public boolean containsKey(Object key) { + return getEntry(key) != null; + } + + // 返回“键为key”的键值对 + final Entry getEntry(Object key) { + // 获取哈希值 + // HashMap将“key为null”的元素存储在table[0]位置,“key不为null”的则调用hash()计算哈希值 + int hash = (key == null) ? 0 : hash(key.hashCode()); + // 在“该hash值对应的链表”上查找“键值等于key”的元素 + for (Entry e = table[indexFor(hash, table.length)]; + e != null; + e = e.next) { + Object k; + if (e.hash == hash && + ((k = e.key) == key || (key != null && key.equals(k)))) + return e; + } + return null; + } + + + + // 是否包含“值为value”的元素 + public boolean containsValue(Object value) { + // 若“value为null”,则调用containsNullValue()查找 + if (value == null) + return containsNullValue(); + + // 若“value不为null”,则查找HashMap中是否有值为value的节点。 + Entry[] tab = table; + for (int i = 0; i < tab.length ; i++) + for (Entry e = tab[i] ; e != null ; e = e.next) + if (value.equals(e.value)) + return true; + return false; + } + +8、我们重点来分析下求hash值和索引值的方法,这两个方法便是HashMap设计的最为核心的部分,二者结合能保证哈希表中的元素尽可能均匀地散列。 +计算哈希值的方法如下: + + static int hash(int h) { + h ^= (h >>> 20) ^ (h >>> 12); + return h ^ (h >>> 7) ^ (h >>> 4); + } + + +由hash值找到对应索引的方法如下: + + static int indexFor(int h, int length) { + return h & (length-1); + } + +这个我们要重点说下,我们一般对哈希表的散列很自然地会想到用hash值对length取模(即除法散列法),Hashtable中也是这样实现的,这种方法基本能保证元素在哈希表中散列的比较均匀,但取模会用到除法运算,效率很低,HashMap中则通过h&(length-1)的方法来代替取模,同样实现了均匀的散列,但效率要高很多,这也是HashMap对Hashtable的一个改进。 + +接下来,我们分析下为什么哈希表的容量一定要是2的整数次幂。首先,length为2的整数次幂的话,h&(length-1)就相当于对length取模,这样便保证了散列的均匀,同时也提升了效率;其次,length为2的整数次幂的话,为偶数,这样length-1为奇数,奇数的最后一位是1,这样便保证了h&(length-1)的最后一位可能为0,也可能为1(这取决于h的值),即与后的结果可能为偶数,也可能为奇数,这样便可以保证散列的均匀性,而如果length为奇数的话,很明显length-1为偶数,它的最后一位是0,这样h&(length-1)的最后一位肯定为0,即只能为偶数,这样任何hash值都只会被散列到数组的偶数下标位置上,这便浪费了近一半的空间,因此,length取2的整数次幂,是为了使不同hash值发生碰撞的概率较小,这样就能使元素在哈希表中均匀地散列。 diff --git a/week_01/42/ArrayList.md b/week_01/42/ArrayList.md new file mode 100644 index 0000000..ccaba50 --- /dev/null +++ b/week_01/42/ArrayList.md @@ -0,0 +1,317 @@ +/** +* +* 实现了 RandomAccess 可以随机访问;Cloneable 可以进行clone ;实现了序列号接口Serializable +* 遗漏问题:modCount 作用是什么?????? +*/ +public class ArrayList extends AbstractList + implements List, RandomAccess, Cloneable, java.io.Serializable +{ + private static final long serialVersionUID = 8683452581122892189L; + + /** + * 默认大小(实际上如果用无参构造方法创建一个集合的时候初始时候大小是0,当第一次调用add方法的时候会直接设置大小为10) + */ + private static final int DEFAULT_CAPACITY = 10; + + /** + * 两个不同的属性,不同的方法初始化集合的时候会用到, + */ + private static final Object[] EMPTY_ELEMENTDATA = {}; + private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {}; + + /** + * The array buffer into which the elements of the ArrayList are stored. + * The capacity of the ArrayList is the length of this array buffer. Any + * empty ArrayList with elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA + * will be expanded to DEFAULT_CAPACITY when the first element is added. + * 集合底层就是一个数组的实现,通过数组来进行数据的存储。这里英文说得非常的明确, + * 初始化为空的集合的时候该属性指向DEFAULTCAPACITY_EMPTY_ELEMENTDATA + * 当添加第一个元素的时候,扩展长度为DEFAULT_CAPACITY(10) + */ + transient Object[] elementData; + + + private int size; + + /** + * 指定大小创建集合对象 + */ + public ArrayList(int initialCapacity) { + if (initialCapacity > 0) {// 长度大于0则初始化的时候集合大小为指定大小 + this.elementData = new Object[initialCapacity]; + } else if (initialCapacity == 0) { // 指定大小= 0则,默认EMPTY_ELEMENTDATA + this.elementData = EMPTY_ELEMENTDATA; + } else { // 小于0,会报该异常(属于RuntimeException) + throw new IllegalArgumentException("Illegal Capacity: "+ + initialCapacity); + } + } + + /** + * Constructs an empty list with an initial capacity of ten. + * 把原英文注解留在这里主要是说明并不是初始化的时候长度就是10,而是add第一个元素的时候,会直接设置长度为10 + */ + public ArrayList() { + this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA; + } + + /** + * Constructs a list containing the elements of the specified + * collection, in the order they are returned by the collection's + * iterator. + * + * @param c the collection whose elements are to be placed into this list + * @throws NullPointerException if the specified collection is null + */ + public ArrayList(Collection c) { + // 通过拷贝的方式,把入参的数据拷贝到该集合对象的数组下面 + elementData = c.toArray(); + if ((size = elementData.length) != 0) { + // 当前集合A如果是ArrayList 直接添加Integer元素是不可行的,但是如果添加一个ArrayList 集合B则可以 + // 如果代码就是做了一个容错处理 + // c.toArray might (incorrectly) not return Object[] (see 6260652) + if (elementData.getClass() != Object[].class) + elementData = Arrays.copyOf(elementData, size, Object[].class); + } else { + // 如果入参集合为空,则当前集合指向 EMPTY_ELEMENTDATA (保证空集合都指向 EMPTY_ELEMENTDATA 减少内存开销) + this.elementData = EMPTY_ELEMENTDATA; + } + } + + + // 实际应用场景不太知道有何作用 + public void trimToSize() { + modCount++;(问题:不知道有何作用) + if (size < elementData.length) { + elementData = (size == 0) + ? EMPTY_ELEMENTDATA + : Arrays.copyOf(elementData, size); + } + } + + + // 保证指定大小长度,如果长度不够则扩容,如果指定长度小于集合长度,不会处理。 + public void ensureCapacity(int minCapacity) { + int minExpand = (elementData != DEFAULTCAPACITY_EMPTY_ELEMENTDATA) + // any size if not default element table + ? 0 + // larger than default for default empty table. It's already + // supposed to be at default size. + : DEFAULT_CAPACITY; + + if (minCapacity > minExpand) { + ensureExplicitCapacity(minCapacity); + } + } + + // 添加元素的时候,保证集合长度 + private void ensureCapacityInternal(int minCapacity) { + if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) { + minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity); + } + + ensureExplicitCapacity(minCapacity); + } + + private void ensureExplicitCapacity(int minCapacity) { + modCount++; + + // overflow-conscious code + if (minCapacity - elementData.length > 0) + grow(minCapacity); + } + + /** + * The maximum size of array to allocate. + * Some VMs reserve some header words in an array. + * Attempts to allocate larger arrays may result in + * OutOfMemoryError: Requested array size exceeds VM limit + * 也就是说集合最大长度只能是Integer.MAX_VALUE - 8,有的JVM需要额外的长度来存储一些关键字 + */ + private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8; + + /** + * Increases the capacity to ensure that it can hold at least the + * number of elements specified by the minimum capacity argument. + * + * @param minCapacity the desired minimum capacity + */ + private void grow(int minCapacity) { + // overflow-conscious code + int oldCapacity = elementData.length; + int newCapacity = oldCapacity + (oldCapacity >> 1);// 右移运算,右移一位,相当于除以2 + if (newCapacity - minCapacity < 0) + newCapacity = minCapacity; + 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(); + return (minCapacity > MAX_ARRAY_SIZE) ? + Integer.MAX_VALUE : + MAX_ARRAY_SIZE; + } + + + public int size() { + return size; + } + + + public boolean isEmpty() { + return size == 0; + } + + + // 通过遍历的方式,查找对应的元素 + public boolean contains(Object o) { + return indexOf(o) >= 0; + } + + // 这里很明显可以看出来,ArrayList是可以存储null的 + public int indexOf(Object o) { + if (o == null) { + for (int i = 0; i < size; i++) + if (elementData[i]==null) + return i; + } else { + for (int i = 0; i < size; i++) + if (o.equals(elementData[i])) + return i; + } + return -1; + } + + + public int lastIndexOf(Object o) { + if (o == null) { + for (int i = size-1; i >= 0; i--) + if (elementData[i]==null) + return i; + } else { + for (int i = size-1; i >= 0; i--) + if (o.equals(elementData[i])) + return i; + } + return -1; + } + + + public Object clone() { + try { + ArrayList v = (ArrayList) super.clone(); + v.elementData = Arrays.copyOf(elementData, size); + v.modCount = 0; + return v; + } catch (CloneNotSupportedException e) { + // this shouldn't happen, since we are Cloneable + throw new InternalError(e); + } + } + + + // 设置指定位置元素,返回老元素 + public E set(int index, E element) { + rangeCheck(index); + + E oldValue = elementData(index); + elementData[index] = element; + return oldValue; + } + + //添加元素 + public boolean add(E e) { + ensureCapacityInternal(size + 1); // Increments modCount!! + elementData[size++] = e; + return true; + } + + // 在指定位置添加元素,指定位置元素,全部向后移 + public void add(int index, E element) { + rangeCheckForAdd(index); + + ensureCapacityInternal(size + 1); // Increments modCount!! + System.arraycopy(elementData, index, elementData, index + 1, + size - index); + elementData[index] = element; + size++; + } + + // 删除元素的时候相当于指定元素之后的全部元素向前移动一位,然后最后一位元素设置为null + public E remove(int index) { + rangeCheck(index); + + modCount++; + E oldValue = elementData(index); + + int numMoved = size - index - 1; + if (numMoved > 0) + System.arraycopy(elementData, index+1, elementData, index, + numMoved); + elementData[--size] = null; // clear to let GC do its work + + return oldValue; + } + + // 删除指定元素,集合可以有重复元素,有该元素则都删除。 + // 遍历集合,找到该元素的位置,然后通过拷贝后面的元素,再把集合最后一位元素设置为null + public boolean remove(Object o) { + if (o == null) { + for (int index = 0; index < size; index++) + if (elementData[index] == null) { + fastRemove(index); + return true; + } + } else { + for (int index = 0; index < size; index++) + if (o.equals(elementData[index])) { + fastRemove(index); + return true; + } + } + return false; + } + + // 拷贝 + private void fastRemove(int index) { + modCount++; + int numMoved = size - index - 1; + if (numMoved > 0) + System.arraycopy(elementData, index+1, elementData, index, + numMoved); + elementData[--size] = null; // clear to let GC do its work + } + + // 清除集合,设置所有元素为null + public void clear() { + modCount++; + + // clear to let GC do its work + for (int i = 0; i < size; i++) + elementData[i] = null; + + size = 0; + } + + + + // 删除指定范围内元素, + protected void removeRange(int fromIndex, int toIndex) { + modCount++; + int numMoved = size - toIndex; + System.arraycopy(elementData, toIndex, elementData, fromIndex, + numMoved); + + // clear to let GC do its work + int newSize = size - (toIndex-fromIndex); + for (int i = newSize; i < size; i++) { + elementData[i] = null; + } + size = newSize; + } + +} diff --git a/week_01/44/ArrayList.md b/week_01/44/ArrayList.md new file mode 100644 index 0000000..fcbe1db --- /dev/null +++ b/week_01/44/ArrayList.md @@ -0,0 +1,513 @@ +# 一:简介 # +ArrayList是一种以数组实现的List。 +![](C:\Users\asiaw\Desktop\考研报名\ss\1.png) + +- 继承了AbstractList类 +- 实现了List, RandomAccess, Cloneable, java.io.Serializable接口 +- ArrayList实现了List,提供了基础的添加、删除、遍历等操作。 +- ArrayList实现了RandomAccess,提供了随机访问的能力。 +- ArrayList实现了Cloneable,可以被克隆。 +- ArrayList实现了Serializable,可以被序列化。 + +# 二:源码分析 # +## 1. 字段 ## + +```java +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 + +private int size; +``` +(1)DEFAULT_CAPACITY = 10 + +默认容量为10,初始化时没有参数传递进来时的默认初始容量 + +(2)EMPTY_ELEMENTDATA = {} + +空数组。通过 new ArrayList(0)方法创建ArrayList时使用的空数组 + +(3)DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {} + +空数组。通过 new ArrayList()方法创建ArrayList时使用的空数组。与EMPTY_ELEMENTDATA不同的地方是在添加第一个元素时使用这个空数组的会初始化为DEFAULT_CAPACITY(10)个元素 + +(4)elementData + +真正存放数据的地方,使用 transient 关键字是为了不序列化这个字段。没有使用private修饰备注解释为简化nested class access + +(5)size + +当前元素的个数 + +## 2. 构造方法 + +```java + public ArrayList() { + this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA; + } +``` + +ArrayList() + +没有参数,elementData初始化为DEFAULTCAPACITY_EMPTY_ELEMENTDATA空数组,在第一次添加元素时会扩容成默认的容量大小10。 + +```java + public ArrayList(int initialCapacity) { + if (initialCapacity > 0) { + this.elementData = new Object[initialCapacity]; + } else if (initialCapacity == 0) { + this.elementData = EMPTY_ELEMENTDATA; + } else { + throw new IllegalArgumentException("Illegal Capacity: "+ + initialCapacity); + } + } +``` + +public ArrayList(int initialCapacity) + +参数为初始容量。如果该数 > 0 即将elementData初始化为参数大小。若 = 0,elementData初始化为EMPTY_ELEMENTDATA空数组。若 < 0,抛出异常,显示“Illegal Capacity”。 + +```Java +public ArrayList(Collection c) { + elementData = c.toArray(); + if ((size = elementData.length) != 0) { + if (elementData.getClass() != Object[].class) + elementData = Arrays.copyOf(elementData, size, Object[].class); + } else { + this.elementData = EMPTY_ELEMENTDATA; + } + } +``` + +ArrayList(Collection c) + +参数为集合。用这个集合的 toArray() 方法初始化elementData。如果元素个数为0,elementData初始化为EMPTY_ELEMENTDATA;不为0,并且elementData的类型与Object的类型不相同时,利用Arrays的copyof方法把类型转换为Object[].class类型。 + +## 3. add方法 + +```java + public boolean add(E e) { + // 检查是否需要扩容 + ensureCapacityInternal(size + 1); // Increments modCount!! + // 添加元素到最后一位。size++。返回true + elementData[size++] = e; + return true; + } + + private void ensureCapacityInternal(int minCapacity) { + ensureExplicitCapacity(calculateCapacity(elementData, minCapacity)); + } + + // 计算最小容量 + private static int calculateCapacity(Object[] elementData, int minCapacity) { + // 如果是DEFAULTCAPACITY_EMPTY_ELEMENTDATA这个空数组,返回默认大小10 + if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) { + return Math.max(DEFAULT_CAPACITY, minCapacity); + } + return minCapacity; + } + + protected transient int modCount = 0; + + private void ensureExplicitCapacity(int minCapacity) { + modCount++; + + // 溢出则扩容 + if (minCapacity - elementData.length > 0) + grow(minCapacity); + } + + // 定义的最大容量 + private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8; + + private void grow(int minCapacity) { + // overflow-conscious code + int oldCapacity = elementData.length; + // 新容量为旧容量的1.5倍 + int newCapacity = oldCapacity + (oldCapacity >> 1); + // 如果新容量仍小于最小容量,改为所需的最小容量 + if (newCapacity - minCapacity < 0) + newCapacity = minCapacity; + // 若新容量大于定义最大容量,则若所需最小容量小于定义最大容量,使用定义最大容量,否则使用Integer.MAX_VALUE + 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(); + return (minCapacity > MAX_ARRAY_SIZE) ? + Integer.MAX_VALUE : + MAX_ARRAY_SIZE; + } +``` + +add(E e) + +一个参数,要加入的元素。 + +(1)先检查是否需要扩容。 + +(2)如果elementData是用 new ArrayList()初始化的,将容量初始化为默认大小10。 + +(3)扩容时,新容量为旧容量的1.5倍。如果还不够,则扩容为所需大小。如果超出定义最大容量,则看情况改为定义最大容量或Integer的最大数。 + +(4)创建新数组拷贝数据 + +```java + public void add(int index, E element) { + // 检查是否越界 + rangeCheckForAdd(index); + // 检查是否需要扩容 + ensureCapacityInternal(size + 1); // Increments modCount!! + // 将index及以后的数据都往后移一位,将index处空出来 + System.arraycopy(elementData, index, elementData, index + 1, + size - index); + // 添加元素到指定位置 + elementData[index] = element; + // 元素个数加 1 + size++; + } + + private void rangeCheckForAdd(int index) { + if (index > size || index < 0) + throw new IndexOutOfBoundsException(outOfBoundsMsg(index)); + } +``` + +add(int index, E element) + +参数指定位置和元素。将元素添加到指定位置,平均时间复杂度为O(n)。 + +(1)检查是否越界 + +(2)检查是否需要扩容 + +(3)把指定位置往后的元素都向后移动一位 + +(4)添加元素到指定位置 + +(5)元素个数加1 + +```java + public boolean addAll(Collection c) { + // 转化为数组 + Object[] a = c.toArray(); + int numNew = a.length; + // 检查是否需要扩容 + ensureCapacityInternal(size + numNew); // Increments modCount + // 将C中的所有元素拷贝到elementData的最后 + System.arraycopy(a, 0, elementData, size, numNew); + // 元素个数增加C的元素个数 + size += numNew; + // c 不为空返回true,为空返回false + return numNew != 0; + } +``` + +addAll(Collection c) + +将集合C中的所有元素添加到当前的ArrayList中 + +(1)将集合C转化为数组 + +(2)检查是否需要扩容 + +(3)将C中元素拷贝到elementData的末尾,size添加C的元素个数 + +```java + public boolean addAll(int index, Collection c) { + rangeCheckForAdd(index); + + Object[] a = c.toArray(); + int numNew = a.length; + ensureCapacityInternal(size + numNew); // Increments modCount + + int numMoved = size - index; + if (numMoved > 0) + System.arraycopy(elementData, index, elementData, index + numNew, + numMoved); + + System.arraycopy(a, 0, elementData, index, numNew); + size += numNew; + return numNew != 0; + } +``` + +addAll(int index, Collection c) + +将集合C拷贝到当前list的指定位置 + +## 4. get方法 + +```java + public E get(int index) { + // 检查是否越界 + rangeCheck(index); + // 返回指定位置元素 + return elementData(index); + } + + private void rangeCheck(int index) { + if (index >= size) + throw new IndexOutOfBoundsException(outOfBoundsMsg(index)); + } + + E elementData(int index) { + return (E) elementData[index]; + } +``` + +get(int index) + +返回指定位置的元素。时间复杂度O(1) + +## 5. remove方法 + +```Java + public E remove(int index) { + // 检查是否越界 + rangeCheck(index); + + modCount++; + // 获取index处元素 + E oldValue = elementData(index); + // 计算需要移动的元素 + int numMoved = size - index - 1; + // 将index后面的元素向前移动一位 + if (numMoved > 0) + System.arraycopy(elementData, index+1, elementData, index, + numMoved); + // 将最后一个元素删除,帮助GC + elementData[--size] = null; // clear to let GC do its work + // 返回删除元素 + return oldValue; + } +``` + +remove(int index):删除指定位置的元素,时间复杂度O(n) + +(1)检查是否越界 + +(2)获取index处位置 + +(3)将index后的元素向前移动一位 + +(4)删除最后一个元素并返回删除的index处的元素 + +```java + public boolean remove(Object o) { + if (o == null) { + // 遍历数组,找到第一个为空的位置,删除并返回true + for (int index = 0; index < size; index++) + if (elementData[index] == null) { + fastRemove(index); + return true; + } + } else { + // 遍历数组,找到元素第一次出现的位置,删除并返回true + for (int index = 0; index < size; index++) + // 元素不为空,使用 equals() 方法进行比较是否相等 + if (o.equals(elementData[index])) { + fastRemove(index); + return true; + } + } + // 没找到返回false + return false; + } + + // 没有判断是否越界,因为不是指定位置。 + private void fastRemove(int index) { + modCount++; + int numMoved = size - index - 1; + if (numMoved > 0) + System.arraycopy(elementData, index+1, elementData, index, + numMoved); + elementData[--size] = null; // clear to let GC do its work + } +``` + +(1)找到第一个等于指定元素值的元素索引 + +(2)快速删除,不适用remove(index)方法是因为可以省略判断越界方法 + +```Java + public boolean retainAll(Collection c) { + // 集合C不能为空 + Objects.requireNonNull(c); + // 调用batchremove方法,传入true,表示删除不在C中的元素 + return batchRemove(c, true); + } + /** + * 批量删除元素 + * complement为true表示删除C中不包含的元素 + * complement为false表示删除C中包含的元素 + */ + private boolean batchRemove(Collection c, boolean complement) { + final Object[] elementData = this.elementData; + // 使用读写两个指针同时遍历数组 + // 读指针每次加1,写指针写入元素时才加1 + // 这样不需要额外空间,在原数组上操作 + int r = 0, w = 0; + boolean modified = false; + try { + // 遍历整个数组,如果C中包含该元素,则把该元素写入数组 + for (; r < size; r++) + if (c.contains(elementData[r]) == complement) + elementData[w++] = elementData[r]; + } finally { + // Preserve behavioral compatibility with AbstractCollection, + // even if c.contains() throws. + if (r != size) { + System.arraycopy(elementData, r, + elementData, w, + size - r); + w += size - r; + } + if (w != size) { + // clear to let GC do its work + // 将写指针之后的元素置空,帮助GC + for (int i = w; i < size; i++) + elementData[i] = null; + modCount += size - w; + // 更新元素个数 + size = w; + modified = true; + } + } + // 有修改返回true,无返回false + return modified; + } +``` + +retainAll(Collection c):返回两个集合的交集 + +(1)遍历elementData数组; + +(2)如果元素在c中,则把这个元素添加到elementData数组的w位置并将w位置往后移一位; + +(3)遍历完之后,w之前的元素都是两者共有的,w之后(包含)的元素不是两者共有的; + +(4)将w之后(包含)的元素置为null,方便GC回收; + +```Java + public boolean removeAll(Collection c) { + Objects.requireNonNull(c); + return batchRemove(c, false); + } +``` + +removeAll(Collection c):保留不在集合C中的位置 + +```java + public void clear() { + modCount++; + + // clear to let GC do its work + for (int i = 0; i < size; i++) + elementData[i] = null; + + size = 0; + } +``` + +clear():清空当前ArrayList + +## 6. size,set,contains,indexof,lastindexof,isempty,trimToSize方法 + +```java +public int size() { return size; } + +public E set(int index, E element) { + // 检查是否越界 + rangeCheck(index); + + E oldValue = elementData(index); + // 更新元素 + elementData[index] = element; + // 返回旧值 + return oldValue; +} + +public boolean isEmpty() { return size == 0; } + +public int indexOf(Object o) { + // 遍历查找是否存在元素,为空使用 == 方法,不为空使用 equals() 方法 + if (o == null) { + for (int i = 0; i < size; i++) + if (elementData[i]==null) + return i; + } else { + for (int i = 0; i < size; i++) + if (o.equals(elementData[i])) + return i; + } + // 没有找到返回 -1 + return -1; +} + +// 返回最后出现指定元素的位置。。从数组末尾开始遍历即可 +public int lastIndexOf(Object o) { + if (o == null) { + for (int i = size-1; i >= 0; i--) + if (elementData[i]==null) + return i; + } else { + for (int i = size-1; i >= 0; i--) + if (o.equals(elementData[i])) + return i; + } + return -1; +} + +// 调用indexof方法,不返回-1则表示存在此元素 +public boolean contains(Object o) { return indexOf(o) >= 0; } + +// 将数组大小调整为size +public void trimToSize() { + modCount++; + if (size < elementData.length) { + elementData = (size == 0) + ? EMPTY_ELEMENTDATA + : Arrays.copyOf(elementData, size); + } +} +``` + +# 三:总结 + +1. 序列化和反序列化没仔细看。接口实现部分也没有很仔细看 + +2. 一些ArrayList常见的方法都进行了分析。 + + + + + + + + + + + + + + + + + + + + + + + diff --git a/week_01/44/LinkedList.md b/week_01/44/LinkedList.md new file mode 100644 index 0000000..6edaa28 --- /dev/null +++ b/week_01/44/LinkedList.md @@ -0,0 +1,209 @@ +# 一:简介 + +概括的说,LinkedList 是线程不安全的,允许元素为null的双向链表。 +其底层数据结构是链表,它实现List, Deque, Cloneable, java.io.Serializable接口,它实现了Deque,所以它也可以作为一个双端队列。和ArrayList比,没有实现RandomAccess所以其以下标,随机访问元素速度较慢。 + +因其底层数据结构是链表,所以可想而知,它的增删只需要移动指针即可,故时间效率较高。不需要批量扩容,也不需要预留空间,所以空间效率比ArrayList高。 + +缺点就是需要随机访问元素时,时间效率很低,虽然底层在根据下标查询Node的时候,会根据index判断目标Node在前半段还是后半段,然后决定是顺序还是逆序查询,以提升时间效率。不过随着n的增大,总体时间效率依然很低。 + +当每次增、删时,都会修改modCount。 +———————————————— +参考:https://blog.csdn.net/zxt0601/article/details/77341098 + +​ 彤哥读源码 + +# 二:源码分析 + +## 1. 主要属性和node节点 + +```Java +// 元素个数 +transient int size = 0; +// 链表首节点 +transient Node first; +// 链表尾节点 +transient Node last; +``` + +```Java +// 从Node定义可以看出是个双向链表 +private static class Node { + E item; + Node next; + Node prev; + + Node(Node prev, E element, Node next) { + this.item = element; + this.next = next; + this.prev = prev; + } +} +``` + +## 2. 构造方法 + +```Java + public LinkedList() { + } + + public LinkedList(Collection c) { + this(); + addAll(c); + } +``` + +简单的构造方法,一个空参,一个调用addAll()函数添加集合C的元素 + +## 3. 添加元素 + +因为继承了Deque,是个双端队列,所以添加元素主要有两种方式,一种是在队列尾部添加元素,一种是在队列首部添加元素。而作为List,也能去中间插入数据。 + +```Java +// 从队首添加元素 +private void linkFirst(E e) { + // 存储首节点 + final Node f = first; + // 创建一个新节点,它的next是首节点 + final Node newNode = new Node<>(null, e, f); + // 首节点更新为新节点 + first = newNode; + // 判断是否是第一个元素 + // 是把last也置为新节点 + // 否则把原首节点的prev指针更新为新节点 + if (f == null) + last = newNode; + else + f.prev = newNode; + // 元素个数加1 + size++; + modCount++; +} + +// 从队尾添加元素 +void linkLast(E e) { + // 尾节点 + final Node l = last; + // 创建新节点,其prev节点是尾节点 + final Node newNode = new Node<>(l, e, null); + // 判断是不是第一个添加的元素 + // 如果是就把first也置为新节点 + // 否则把原尾节点的next指针置为新节点 + last = newNode; + if (l == null) + first = newNode; + else + l.next = newNode; + // 元素个数加1 + size++; + modCount++; +} + +public void addFirst(E e) { + linkFirst(e); +} + +public void addLast(E e) { + linkLast(e); +} + +public boolean offerFirst(E e) { + addFirst(e); + return true; +} + +public boolean offerLast(E e) { + addLast(e); + return true; +} + +// 在节点succ之前添加元素 +void linkBefore(E e, Node succ) { + // assert succ != null; + final Node pred = succ.prev; + final Node newNode = new Node<>(pred, e, succ); + succ.prev = newNode; + if (pred == null) + first = newNode; + else + pred.next = newNode; + size++; + modCount++; +} + + +``` + +## 4. 删除元素 + +```Java + private E unlinkLast(Node l) { + // assert l == last && l != null; + final E element = l.item; + final Node prev = l.prev; + l.item = null; + l.prev = null; // help GC + last = prev; + if (prev == null) + first = null; + else + prev.next = null; + size--; + modCount++; + return element; + } + + E unlink(Node x) { + // assert x != null; + final E element = x.item; + final Node next = x.next; + final Node prev = x.prev; + + if (prev == null) { + first = next; + } else { + prev.next = next; + x.prev = null; + } + + if (next == null) { + last = prev; + } else { + next.prev = prev; + x.next = null; + } + + x.item = null; + size--; + modCount++; + return element; + } +``` + +## 5. 栈 + +```Java +public void push(E e) { + addFirst(e); +} + +public void push(E e) { + addFirst(e); +} +``` + +# 三:总结 + +(1)LinkedList是一个以双链表实现的List; + +(2)LinkedList还是一个双端队列,具有队列、双端队列、栈的特性; + +(3)LinkedList在队列首尾添加、删除元素非常高效,时间复杂度为O(1); + +(4)LinkedList在中间添加、删除元素比较低效,时间复杂度为O(n); + +(5)LinkedList不支持随机访问,所以访问非队列首尾的元素比较低效; + +(6)LinkedList在功能上等于ArrayList + ArrayDeque。 + +参考:彤哥读源码 \ No newline at end of file diff --git a/week_01/46/test.txt b/week_01/46/test.txt deleted file mode 100644 index 30d74d2..0000000 --- a/week_01/46/test.txt +++ /dev/null @@ -1 +0,0 @@ -test \ No newline at end of file diff --git a/week_01/51/ArrayList-51.md b/week_01/51/ArrayList-51.md new file mode 100644 index 0000000..8d10fb3 --- /dev/null +++ b/week_01/51/ArrayList-51.md @@ -0,0 +1,256 @@ +# ArrayList + +ArrayList是一种以数组实现的List,与数组相比,它具有动态扩展的能力,因此也可称之为动态数组。 + +## 继承体系 + +![img](https://mmbiz.qpic.cn/mmbiz_png/C91PV9BDK3yUT9hffoWkuTicrshhl1d9JkxvvXoUmvsSzErEeTCcxTnYkYAgWexziaCbpcclHf0ocGhxBJdxlXqA/640?wx_fmt=png&tp=webp&wxfrom=5&wx_lazy=1&wx_co=1) + +ArrayList实现了List, RandomAccess, Cloneable, java.io.Serializable等接口。 + +ArrayList实现了List,提供了基础的添加、删除、遍历等操作。 + +ArrayList实现了RandomAccess,提供了随机访问的能力。 + +ArrayList实现了Cloneable,可以被克隆。 + +ArrayList实现了Serializable,可以被序列化。 + +## 源码解析 + +### 属性 + +``` +/** * 默认容量 */ +private static final int DEFAULT_CAPACITY = 10; +/** * 空数组,如果传入的容量为0时使用 */ +private static final Object[] EMPTY_ELEMENTDATA = {}; +/** * 空数组,传传入容量时使用,添加第一个元素的时候会重新初始为默认容量大小 */ +private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {}; +/** * 存储元素的数组 */ +transient Object[] elementData; // non-private to simplify nested class access +/** * 集合中元素的个数 */ +private int size; +``` + +(1)DEFAULT_CAPACITY + +默认容量为10,也就是通过new ArrayList()创建时的默认容量。 + +(2)EMPTY_ELEMENTDATA + +空的数组,这种是通过new ArrayList(0)创建时用的是这个空数组。 + +(3)DEFAULTCAPACITY_EMPTY_ELEMENTDATA + +也是空数组,这种是通过new ArrayList()创建时用的是这个空数组,与EMPTY_ELEMENTDATA的区别是在添加第一个元素时使用这个空数组的会初始化为DEFAULT_CAPACITY(10)个元素。 + +(4)elementData + +真正存放元素的地方,使用transient是为了不序列化这个字段。 + +至于没有使用private修饰,后面注释是写的“为了简化嵌套类的访问”,但是楼主实测加了private嵌套类一样可以访问。 + +private表示是类私有的属性,只要是在这个类内部都可以访问,嵌套类或者内部类也是在类的内部,所以也可以访问类的私有成员。 + +(5)size + +真正存储元素的个数,而不是elementData数组的长度。 + +### ArrayList(int initialCapacity)构造方法 + +传入初始容量,如果大于0就初始化elementData为对应大小,如果等于0就使用EMPTY_ELEMENTDATA空数组,如果小于0抛出异常。 + +``` +public ArrayList(int initialCapacity) { if (initialCapacity > 0) { // 如果传入的初始容量大于0,就新建一个数组存储元素 this.elementData = new Object[initialCapacity]; } else if (initialCapacity == 0) { // 如果传入的初始容量等于0,使用空数组EMPTY_ELEMENTDATA this.elementData = EMPTY_ELEMENTDATA; } else { // 如果传入的初始容量小于0,抛出异常 throw new IllegalArgumentException("Illegal Capacity: " + initialCapacity); }} +``` + +### ArrayList()构造方法 + +不传初始容量,初始化为DEFAULTCAPACITY_EMPTY_ELEMENTDATA空数组,会在添加第一个元素的时候扩容为默认的大小,即10。 + +``` +public ArrayList() { +// 如果没有传入初始容量,则使用空数组DEFAULTCAPACITY_EMPTY_ELEMENTDATA +// 使用这个数组是在添加第一个元素的时候会扩容到默认大小10 +this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;} +``` + +### ArrayList(Collection c)构造方法 + +传入集合并初始化elementData,这里会使用拷贝把传入集合的元素拷贝到elementData数组中,如果元素个数为0,则初始化为EMPTY_ELEMENTDATA空数组。 + +``` +/*** 把传入集合的元素初始化到ArrayList中*/ +public ArrayList(Collection c) { +// 集合转数组 +elementData = c.toArray(); +if ((size = elementData.length) != 0) { +// 检查c.toArray()返回的是不是Object[]类型,如果不是,重新拷贝成Object[].class类型 +if (elementData.getClass() != Object[].class) +elementData = Arrays.copyOf(elementData, size, Object[].class); } +else { // 如果c的空集合,则初始化为空数组EMPTY_ELEMENTDATA +this.elementData = EMPTY_ELEMENTDATA; }} +``` + +为什么 `c.toArray();`返回的有可能不是Object[]类型呢?请看下面的代码: + +``` +public class ArrayTest { public static void main(String[] args) { Father[] fathers = new Son[]{}; // 打印结果为class [Lcom.coolcoding.code.Son; System.out.println(fathers.getClass()); List strList = new MyList(); // 打印结果为class [Ljava.lang.String; System.out.println(strList.toArray().getClass()); }}class Father {}class Son extends Father {}class MyList extends ArrayList { /** * 子类重写父类的方法,返回值可以不一样 * 但这里只能用数组类型,换成Object就不行 * 应该算是java本身的bug */ @Override public String[] toArray() { // 为了方便举例直接写死 return new String[]{"1", "2", "3"}; }} +``` + +### add(E e)方法 + +添加元素到末尾,平均时间复杂度为O(1)。 + +``` +public boolean add(E e) { // 检查是否需要扩容 ensureCapacityInternal(size + 1); // 把元素插入到最后一位 elementData[size++] = e; return true;}private void ensureCapacityInternal(int minCapacity) { ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));}private static int calculateCapacity(Object[] elementData, int minCapacity) { // 如果是空数组DEFAULTCAPACITY_EMPTY_ELEMENTDATA,就初始化为默认大小10 if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) { return Math.max(DEFAULT_CAPACITY, minCapacity); } return minCapacity;}private void ensureExplicitCapacity(int minCapacity) { modCount++; if (minCapacity - elementData.length > 0) // 扩容 grow(minCapacity);}private void grow(int minCapacity) { int oldCapacity = elementData.length; // 新容量为旧容量的1.5倍 int newCapacity = oldCapacity + (oldCapacity >> 1); // 如果新容量发现比需要的容量还小,则以需要的容量为准 if (newCapacity - minCapacity < 0) newCapacity = minCapacity; // 如果新容量已经超过最大容量了,则使用最大容量 if (newCapacity - MAX_ARRAY_SIZE > 0) newCapacity = hugeCapacity(minCapacity); // 以新容量拷贝出来一个新数组 elementData = Arrays.copyOf(elementData, newCapacity);} +``` + +(1)检查是否需要扩容; + +(2)如果elementData等于DEFAULTCAPACITY_EMPTY_ELEMENTDATA则初始化容量大小为DEFAULT_CAPACITY; + +(3)新容量是老容量的1.5倍(oldCapacity + (oldCapacity >> 1)),如果加了这么多容量发现比需要的容量还小,则以需要的容量为准; + +(4)创建新容量的数组并把老数组拷贝到新数组; + +### add(int index, E element)方法 + +添加元素到指定位置,平均时间复杂度为O(n)。 + +``` +public void add(int index, E element) { // 检查是否越界 rangeCheckForAdd(index); // 检查是否需要扩容 ensureCapacityInternal(size + 1); // 将inex及其之后的元素往后挪一位,则index位置处就空出来了 System.arraycopy(elementData, index, elementData, index + 1, size - index); // 将元素插入到index的位置 elementData[index] = element; // 大小增1 size++;}private void rangeCheckForAdd(int index) { if (index > size || index < 0) throw new IndexOutOfBoundsException(outOfBoundsMsg(index));} +``` + +(1)检查索引是否越界; + +(2)检查是否需要扩容; + +(3)把插入索引位置后的元素都往后挪一位; + +(4)在插入索引位置放置插入的元素; + +(5)大小加1; + +### addAll(Collection c)方法 + +求两个集合的并集。 + +``` +/*** 将集合c中所有元素添加到当前ArrayList中*/public boolean addAll(Collection c) { // 将集合c转为数组 Object[] a = c.toArray(); int numNew = a.length; // 检查是否需要扩容 ensureCapacityInternal(size + numNew); // 将c中元素全部拷贝到数组的最后 System.arraycopy(a, 0, elementData, size, numNew); // 大小增加c的大小 size += numNew; // 如果c不为空就返回true,否则返回false return numNew != 0;} +``` + +(1)拷贝c中的元素到数组a中; + +(2)检查是否需要扩容; + +(3)把数组a中的元素拷贝到elementData的尾部; + +### get(int index)方法 + +获取指定索引位置的元素,时间复杂度为O(1)。 + +``` +public E get(int index) { // 检查是否越界 rangeCheck(index); // 返回数组index位置的元素 return elementData(index);}private void rangeCheck(int index) { if (index >= size) throw new IndexOutOfBoundsException(outOfBoundsMsg(index));}E elementData(int index) { return (E) elementData[index];} +``` + +(1)检查索引是否越界,这里只检查是否越上界,如果越上界抛出IndexOutOfBoundsException异常,如果越下界抛出的是ArrayIndexOutOfBoundsException异常。 + +(2)返回索引位置处的元素; + +### remove(int index)方法 + +删除指定索引位置的元素,时间复杂度为O(n)。 + +``` +public E remove(int index) { // 检查是否越界 rangeCheck(index); modCount++; // 获取index位置的元素 E oldValue = elementData(index); // 如果index不是最后一位,则将index之后的元素往前挪一位 int numMoved = size - index - 1; if (numMoved > 0) System.arraycopy(elementData, index+1, elementData, index, numMoved); // 将最后一个元素删除,帮助GC elementData[--size] = null; // clear to let GC do its work // 返回旧值 return oldValue;} +``` + +(1)检查索引是否越界; + +(2)获取指定索引位置的元素; + +(3)如果删除的不是最后一位,则其它元素往前移一位; + +(4)将最后一位置为null,方便GC回收; + +(5)返回删除的元素。 + +可以看到,ArrayList删除元素的时候并没有缩容。 + +### remove(Object o)方法 + +删除指定元素值的元素,时间复杂度为O(n)。 + +``` +public boolean remove(Object o) { if (o == null) { // 遍历整个数组,找到元素第一次出现的位置,并将其快速删除 for (int index = 0; index < size; index++) // 如果要删除的元素为null,则以null进行比较,使用== if (elementData[index] == null) { fastRemove(index); return true; } } else { // 遍历整个数组,找到元素第一次出现的位置,并将其快速删除 for (int index = 0; index < size; index++) // 如果要删除的元素不为null,则进行比较,使用equals()方法 if (o.equals(elementData[index])) { fastRemove(index); return true; } } return false;}private void fastRemove(int index) { // 少了一个越界的检查 modCount++; // 如果index不是最后一位,则将index之后的元素往前挪一位 int numMoved = size - index - 1; if (numMoved > 0) System.arraycopy(elementData, index+1, elementData, index, numMoved); // 将最后一个元素删除,帮助GC elementData[--size] = null; // clear to let GC do its work} +``` + +(1)找到第一个等于指定元素值的元素; + +(2)快速删除; + +fastRemove(int index)相对于remove(int index)少了检查索引越界的操作,可见jdk将性能优化到极致。 + +### retainAll(Collection c)方法 + +求两个集合的交集。 + +``` +public boolean retainAll(Collection c) { // 集合c不能为null Objects.requireNonNull(c); // 调用批量删除方法,这时complement传入true,表示删除不包含在c中的元素 return batchRemove(c, true);}/*** 批量删除元素* complement为true表示删除c中不包含的元素* complement为false表示删除c中包含的元素*/private boolean batchRemove(Collection c, boolean complement) { final Object[] elementData = this.elementData; // 使用读写两个指针同时遍历数组 // 读指针每次自增1,写指针放入元素的时候才加1 // 这样不需要额外的空间,只需要在原有的数组上操作就可以了 int r = 0, w = 0; boolean modified = false; try { // 遍历整个数组,如果c中包含该元素,则把该元素放到写指针的位置(以complement为准) for (; r < size; r++) if (c.contains(elementData[r]) == complement) elementData[w++] = elementData[r]; } finally { // 正常来说r最后是等于size的,除非c.contains()抛出了异常 if (r != size) { // 如果c.contains()抛出了异常,则把未读的元素都拷贝到写指针之后 System.arraycopy(elementData, r, elementData, w, size - r); w += size - r; } if (w != size) { // 将写指针之后的元素置为空,帮助GC for (int i = w; i < size; i++) elementData[i] = null; modCount += size - w; // 新大小等于写指针的位置(因为每写一次写指针就加1,所以新大小正好等于写指针的位置) size = w; modified = true; } } // 有修改返回true return modified;} +``` + +(1)遍历elementData数组; + +(2)如果元素在c中,则把这个元素添加到elementData数组的w位置并将w位置往后移一位; + +(3)遍历完之后,w之前的元素都是两者共有的,w之后(包含)的元素不是两者共有的; + +(4)将w之后(包含)的元素置为null,方便GC回收; + +### removeAll(Collection c) + +求两个集合的单方向差集,只保留当前集合中不在c中的元素,不保留在c中不在当前集体中的元素。 + +``` +public boolean removeAll(Collection c) { // 集合c不能为空 Objects.requireNonNull(c); // 同样调用批量删除方法,这时complement传入false,表示删除包含在c中的元素 return batchRemove(c, false);} +``` + +与retainAll(Collection c)方法类似,只是这里保留的是不在c中的元素。 + +## 总结 + +(1)ArrayList内部使用数组存储元素,当数组长度不够时进行扩容,每次加一半的空间,ArrayList不会进行缩容; + +(2)ArrayList支持随机访问,通过索引访问元素极快,时间复杂度为O(1); + +(3)ArrayList添加元素到尾部极快,平均时间复杂度为O(1); + +(4)ArrayList添加元素到中间比较慢,因为要搬移元素,平均时间复杂度为O(n); + +(5)ArrayList从尾部删除元素极快,时间复杂度为O(1); + +(6)ArrayList从中间删除元素比较慢,因为要搬移元素,平均时间复杂度为O(n); + +(7)ArrayList支持求并集,调用addAll(Collection c)方法即可; + +(8)ArrayList支持求交集,调用retainAll(Collection c)方法即可; + +(7)ArrayList支持求单向差集,调用removeAll(Collection c)方法即可; + +## 彩蛋 + +elementData设置成了transient,那ArrayList是怎么把元素序列化的呢? + +``` +private void writeObject(java.io.ObjectOutputStream s) throws java.io.IOException{ // 防止序列化期间有修改 int expectedModCount = modCount; // 写出非transient非static属性(会写出size属性) s.defaultWriteObject(); // 写出元素个数 s.writeInt(size); // 依次写出元素 for (int i=0; i 0) { // 计算容量 int capacity = calculateCapacity(elementData, size); SharedSecrets.getJavaOISAccess().checkArray(s, Object[].class, capacity); // 检查是否需要扩容 ensureCapacityInternal(size); Object[] a = elementData; // 依次读取元素到数组中 for (int i=0; i[] table; +//作为entrySet()的缓存 + transient Set> entrySet; +//元素的数量 + transient int size; +//修改次数,用于在迭代的时候执行快速失败策略 + transient int modCount; +//当桶的使用数量达到多少时进行扩容,threshold = capacity * loadFactor + int threshold; +//装载因子 +//装载因子是表示Hsah表中元素的填满的程度.若:加载因子越大,填满的元素越多,好处是,空间利用率高了,但:冲突的机会加大了.反之,加载因子越小,填满的元素越少,好处是:冲突的机会减小了,但:空间浪费多了 + final float loadFactor; +``` + +(1)容量 + +容量为数组的长度,亦即桶的个数,默认为16,最大为2的30次方,当容量达到64时才可以树化。 + +(2)装载因子 + +装载因子用来计算容量达到多少时才进行扩容,默认装载因子为0.75。 + +(3)树化 + +树化,当容量达到64且链表的长度达到8时进行树化,当链表的长度小于6时反树化。 + +### Node内部类 + +Node是一个典型的单链表节点,其中,hash用来存储key计算得来的hash值。 + +``` +static class Node implements Map.Entry { + final int hash; + final K key; + V value; + Node next; + + Node(int hash, K key, V value, Node 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; + } + } +``` + +### TreeNode内部类 + +这是一个神奇的类,它继承自LinkedHashMap中的Entry类,关于LInkedHashMap.Entry这个类我们后面再讲。 + +TreeNode是一个典型的树型节点,其中,prev是链表中的节点,用于在删除元素的时候可以快速找到它的前置节点。 + +``` +// 位于HashMap中 +static final class TreeNode extends LinkedHashMap.Entry { + TreeNode parent; // red-black tree links + TreeNode left; + TreeNode right; + TreeNode prev; // needed to unlink next upon deletion + boolean red; + TreeNode(int hash, K key, V val, Node next) { + super(hash, key, val, next); + } + + /** + * Returns root of tree containing this node. + */ + final TreeNode root() { + for (TreeNode r = this, p;;) { + if ((p = r.parent) == null) + return r; + r = p; + } + } + + /** + * Ensures that the given root is the first node of its bin. + */ + static void moveRootToFront(Node[] tab, TreeNode root) { + int n; + if (root != null && tab != null && (n = tab.length) > 0) { + int index = (n - 1) & root.hash; + TreeNode first = (TreeNode)tab[index]; + if (root != first) { + Node rn; + tab[index] = root; + TreeNode rp = root.prev; + if ((rn = root.next) != null) + ((TreeNode)rn).prev = rp; + if (rp != null) + rp.next = rn; + if (first != null) + first.prev = root; + root.next = first; + root.prev = null; + } + assert checkInvariants(root); + } + } + + /** + * Finds the node starting at root p with the given hash and key. + * The kc argument caches comparableClassFor(key) upon first use + * comparing keys. + */ + final TreeNode find(int h, Object k, Class kc) { + TreeNode p = this; + do { + int ph, dir; K pk; + TreeNode 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; + } + + /** + * Calls find for root node. + */ + final TreeNode getTreeNode(int h, Object k) { + return ((parent != null) ? root() : this).find(h, k, null); + } + + /** + * Tie-breaking utility for ordering insertions when equal + * hashCodes and non-comparable. We don't require a total + * order, just a consistent insertion rule to maintain + * equivalence across rebalancings. Tie-breaking further than + * necessary simplifies testing a bit. + */ + static int tieBreakOrder(Object a, Object b) { + int d; + if (a == null || b == null || + (d = a.getClass().getName(). + compareTo(b.getClass().getName())) == 0) + d = (System.identityHashCode(a) <= System.identityHashCode(b) ? + -1 : 1); + return d; + } + + /** + * Forms tree of the nodes linked from this node. + * @return root of tree + */ + final void treeify(Node[] tab) { + TreeNode root = null; + for (TreeNode x = this, next; x != null; x = next) { + next = (TreeNode)x.next; + x.left = x.right = null; + if (root == null) { + x.parent = null; + x.red = false; + root = x; + } + else { + K k = x.key; + int h = x.hash; + Class kc = null; + for (TreeNode 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 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); + } + + /** + * Returns a list of non-TreeNodes replacing those linked from + * this node. + */ + final Node untreeify(HashMap map) { + Node hd = null, tl = null; + for (Node q = this; q != null; q = q.next) { + Node p = map.replacementNode(q, null); + if (tl == null) + hd = p; + else + tl.next = p; + tl = p; + } + return hd; + } + + /** + * Tree version of putVal. + */ + final TreeNode putTreeVal(HashMap map, Node[] tab, + int h, K k, V v) { + Class kc = null; + boolean searched = false; + TreeNode root = (parent != null) ? root() : this; + for (TreeNode p = root;;) { + int dir, ph; K pk; + if ((ph = p.hash) > h) + dir = -1; + else if (ph < h) + dir = 1; + else if ((pk = p.key) == k || (k != null && k.equals(pk))) + return p; + else if ((kc == null && + (kc = comparableClassFor(k)) == null) || + (dir = compareComparables(kc, k, pk)) == 0) { + if (!searched) { + TreeNode q, ch; + searched = true; + if (((ch = p.left) != null && + (q = ch.find(h, k, kc)) != null) || + ((ch = p.right) != null && + (q = ch.find(h, k, kc)) != null)) + return q; + } + dir = tieBreakOrder(k, pk); + } + + TreeNode xp = p; + if ((p = (dir <= 0) ? p.left : p.right) == null) { + Node xpn = xp.next; + TreeNode x = map.newTreeNode(h, k, v, xpn); + if (dir <= 0) + xp.left = x; + else + xp.right = x; + xp.next = x; + x.parent = x.prev = xp; + if (xpn != null) + ((TreeNode)xpn).prev = x; + moveRootToFront(tab, balanceInsertion(root, x)); + return null; + } + } + } + + /** + * Removes the given node, that must be present before this call. + * This is messier than typical red-black deletion code because we + * cannot swap the contents of an interior node with a leaf + * successor that is pinned by "next" pointers that are accessible + * independently during traversal. So instead we swap the tree + * linkages. If the current tree appears to have too few nodes, + * the bin is converted back to a plain bin. (The test triggers + * somewhere between 2 and 6 nodes, depending on tree structure). + */ + final void removeTreeNode(HashMap map, Node[] tab, + boolean movable) { + int n; + if (tab == null || (n = tab.length) == 0) + return; + int index = (n - 1) & hash; + TreeNode first = (TreeNode)tab[index], root = first, rl; + TreeNode succ = (TreeNode)next, pred = prev; + if (pred == null) + tab[index] = first = succ; + else + pred.next = succ; + if (succ != null) + succ.prev = pred; + if (first == null) + return; + if (root.parent != null) + root = root.root(); + if (root == null || root.right == null || + (rl = root.left) == null || rl.left == null) { + tab[index] = first.untreeify(map); // too small + return; + } + TreeNode p = this, pl = left, pr = right, replacement; + if (pl != null && pr != null) { + TreeNode s = pr, sl; + while ((sl = s.left) != null) // find successor + s = sl; + boolean c = s.red; s.red = p.red; p.red = c; // swap colors + TreeNode sr = s.right; + TreeNode pp = p.parent; + if (s == pr) { // p was s's direct parent + p.parent = s; + s.right = p; + } + else { + TreeNode sp = s.parent; + if ((p.parent = sp) != null) { + if (s == sp.left) + sp.left = p; + else + sp.right = p; + } + if ((s.right = pr) != null) + pr.parent = s; + } + p.left = null; + if ((p.right = sr) != null) + sr.parent = p; + if ((s.left = pl) != null) + pl.parent = s; + if ((s.parent = pp) == null) + root = s; + else if (p == pp.left) + pp.left = s; + else + pp.right = s; + if (sr != null) + replacement = sr; + else + replacement = p; + } + else if (pl != null) + replacement = pl; + else if (pr != null) + replacement = pr; + else + replacement = p; + if (replacement != p) { + TreeNode pp = replacement.parent = p.parent; + if (pp == null) + root = replacement; + else if (p == pp.left) + pp.left = replacement; + else + pp.right = replacement; + p.left = p.right = p.parent = null; + } + + TreeNode r = p.red ? root : balanceDeletion(root, replacement); + + if (replacement == p) { // detach + TreeNode pp = p.parent; + p.parent = null; + if (pp != null) { + if (p == pp.left) + pp.left = null; + else if (p == pp.right) + pp.right = null; + } + } + if (movable) + moveRootToFront(tab, r); + } + + /** + * Splits nodes in a tree bin into lower and upper tree bins, + * or untreeifies if now too small. Called only from resize; + * see above discussion about split bits and indices. + * + * @param map the map + * @param tab the table for recording bin heads + * @param index the index of the table being split + * @param bit the bit of hash to split on + */ + final void split(HashMap map, Node[] tab, int index, int bit) { + TreeNode b = this; + // Relink into lo and hi lists, preserving order + TreeNode loHead = null, loTail = null; + TreeNode hiHead = null, hiTail = null; + int lc = 0, hc = 0; + for (TreeNode e = b, next; e != null; e = next) { + next = (TreeNode)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); + } + } + } + + /* ------------------------------------------------------------ */ + // Red-black tree methods, all adapted from CLR + + static TreeNode rotateLeft(TreeNode root, + TreeNode p) { + TreeNode r, pp, rl; + if (p != null && (r = p.right) != null) { + if ((rl = p.right = r.left) != null) + rl.parent = p; + if ((pp = r.parent = p.parent) == null) + (root = r).red = false; + else if (pp.left == p) + pp.left = r; + else + pp.right = r; + r.left = p; + p.parent = r; + } + return root; + } + + static TreeNode rotateRight(TreeNode root, + TreeNode p) { + TreeNode l, pp, lr; + if (p != null && (l = p.left) != null) { + if ((lr = p.left = l.right) != null) + lr.parent = p; + if ((pp = l.parent = p.parent) == null) + (root = l).red = false; + else if (pp.right == p) + pp.right = l; + else + pp.left = l; + l.right = p; + p.parent = l; + } + return root; + } + + static TreeNode balanceInsertion(TreeNode root, + TreeNode x) { + x.red = true; + for (TreeNode xp, xpp, xppl, xppr;;) { + if ((xp = x.parent) == null) { + x.red = false; + return x; + } + else if (!xp.red || (xpp = xp.parent) == null) + return root; + if (xp == (xppl = xpp.left)) { + if ((xppr = xpp.right) != null && xppr.red) { + xppr.red = false; + xp.red = false; + xpp.red = true; + x = xpp; + } + else { + if (x == xp.right) { + root = rotateLeft(root, x = xp); + xpp = (xp = x.parent) == null ? null : xp.parent; + } + if (xp != null) { + xp.red = false; + if (xpp != null) { + xpp.red = true; + root = rotateRight(root, xpp); + } + } + } + } + else { + if (xppl != null && xppl.red) { + xppl.red = false; + xp.red = false; + xpp.red = true; + x = xpp; + } + else { + if (x == xp.left) { + root = rotateRight(root, x = xp); + xpp = (xp = x.parent) == null ? null : xp.parent; + } + if (xp != null) { + xp.red = false; + if (xpp != null) { + xpp.red = true; + root = rotateLeft(root, xpp); + } + } + } + } + } + } + + static TreeNode balanceDeletion(TreeNode root, + TreeNode x) { + for (TreeNode xp, xpl, xpr;;) { + if (x == null || x == root) + return root; + else if ((xp = x.parent) == null) { + x.red = false; + return x; + } + else if (x.red) { + x.red = false; + return root; + } + else if ((xpl = xp.left) == x) { + if ((xpr = xp.right) != null && xpr.red) { + xpr.red = false; + xp.red = true; + root = rotateLeft(root, xp); + xpr = (xp = x.parent) == null ? null : xp.right; + } + if (xpr == null) + x = xp; + else { + TreeNode sl = xpr.left, sr = xpr.right; + if ((sr == null || !sr.red) && + (sl == null || !sl.red)) { + xpr.red = true; + x = xp; + } + else { + if (sr == null || !sr.red) { + if (sl != null) + sl.red = false; + xpr.red = true; + root = rotateRight(root, xpr); + xpr = (xp = x.parent) == null ? + null : xp.right; + } + if (xpr != null) { + xpr.red = (xp == null) ? false : xp.red; + if ((sr = xpr.right) != null) + sr.red = false; + } + if (xp != null) { + xp.red = false; + root = rotateLeft(root, xp); + } + x = root; + } + } + } + else { // symmetric + if (xpl != null && xpl.red) { + xpl.red = false; + xp.red = true; + root = rotateRight(root, xp); + xpl = (xp = x.parent) == null ? null : xp.left; + } + if (xpl == null) + x = xp; + else { + TreeNode sl = xpl.left, sr = xpl.right; + if ((sl == null || !sl.red) && + (sr == null || !sr.red)) { + xpl.red = true; + x = xp; + } + else { + if (sl == null || !sl.red) { + if (sr != null) + sr.red = false; + xpl.red = true; + root = rotateLeft(root, xpl); + xpl = (xp = x.parent) == null ? + null : xp.left; + } + if (xpl != null) { + xpl.red = (xp == null) ? false : xp.red; + if ((sl = xpl.left) != null) + sl.red = false; + } + if (xp != null) { + xp.red = false; + root = rotateRight(root, xp); + } + x = root; + } + } + } + } + } + + /** + * Recursive invariant check + */ + static boolean checkInvariants(TreeNode t) { + TreeNode tp = t.parent, tl = t.left, tr = t.right, + tb = t.prev, tn = (TreeNode)t.next; + if (tb != null && tb.next != t) + return false; + if (tn != null && tn.prev != t) + return false; + if (tp != null && t != tp.left && t != tp.right) + return false; + if (tl != null && (tl.parent != t || tl.hash > t.hash)) + return false; + if (tr != null && (tr.parent != t || tr.hash < t.hash)) + return false; + if (t.red && tl != null && tl.red && tr != null && tr.red) + return false; + if (tl != null && !checkInvariants(tl)) + return false; + if (tr != null && !checkInvariants(tr)) + return false; + return true; + } + } + + // 位于LinkedHashMap中,典型的双向链表节点 + static class Entry extends HashMap.Node { + Entry before, after; + Entry(int hash, K key, V value, Node next) { + super(hash, key, value, next); + } + } +``` + +### HashMap()构造方法 + +空参构造方法,全部使用默认值。 + +``` +public HashMap() { + this.loadFactor = DEFAULT_LOAD_FACTOR; // all other fields defaulted + } +``` + +### HashMap(int initialCapacity)构造方法 + +调用HashMap(int initialCapacity, float loadFactor)构造方法,传入默认装载因子。 + +``` +public HashMap(int initialCapacity) { + this(initialCapacity, DEFAULT_LOAD_FACTOR); + } +``` + +### HashMap(int initialCapacity,float loadFactor)构造方法 + +判断传入的初始容量和装载因子是否合法,并计算扩容门槛,扩容门槛为传入的初始容量往上取最近的2的n次方。 + +``` +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); + } + + static final int tableSizeFor(int cap) { +// 扩容门槛为传入的初始容量往上取最近的2的n次方 + 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; + } +``` + +### put(K key, V value)方法 + +添加元素的入口。 + +``` +public V put(K key, V value) { + // 调用hash(key)计算出key的hash值 + return putVal(hash(key), key, value, false, true); + } + +static final int hash(Object key) { + int h; + // 如果key为null,则hash值为0,否则调用key的hashCode()方法 + // 并让高16位与整个hash异或,这样做是为了使计算出的hash更分散 + return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16); + } + + final V putVal(int hash, K key, V value, boolean onlyIfAbsent, + boolean evict) { + Node[] tab; Node p; int n, i; + // 如果桶的数量为0,则初始化 + if ((tab = table) == null || (n = tab.length) == 0) + // 调用resize()初始化 + n = (tab = resize()).length; + // (n - 1) & hash 计算元素在哪个桶中 + // 如果这个桶中还没有元素,则把这个元素放在桶中的第一个位置 + if ((p = tab[i = (n - 1) & hash]) == null) + // 新建一个节点放在桶中 + tab[i] = newNode(hash, key, value, null); + else { + // 如果桶中已经有元素存在了 + Node e; K k; + // 如果桶中第一个元素的key与待插入元素的key相同,保存到e中用于后续修改value值 + if (p.hash == hash && + ((k = p.key) == key || (key != null && key.equals(k)))) + e = p; + else if (p instanceof TreeNode) + // 如果第一个元素是树节点,则调用树节点的putTreeVal插入元素 + e = ((TreeNode)p).putTreeVal(this, tab, hash, key, value); + else { + // 遍历这个桶对应的链表,binCount用于存储链表中元素的个数 + for (int binCount = 0; ; ++binCount) { + //如果链表遍历完了都没有找到相同key的元素,说明该key对应的元素不存在,则在链表最 //后插入一个新节点 + if ((e = p.next) == null) { + p.next = newNode(hash, key, value, null); + //如果插入新节点后链表长度大于8,则判断是否需要树化,因为第一个元素没有加到 //binCount中,所以这里-1 + if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st + treeifyBin(tab, hash); + break; + } + // 如果待插入的key在链表中找到了,则退出循环 + if (e.hash == hash && + ((k = e.key) == key || (key != null && key.equals(k)))) + break; + p = e; + } + } + // 如果找到了对应key的元素 + if (e != null) { // existing mapping for key + // 记录下旧值 + V oldValue = e.value; + // 判断是否需要替换旧值 + if (!onlyIfAbsent || oldValue == null) + // 替换旧值为新值 + e.value = value; + // 在节点被访问后做点什么事,在LinkedHashMap中用到 + afterNodeAccess(e); + // 返回旧值 + return oldValue; + } + } + // 到这里了说明没有找到元素 + // 修改次数加1 + ++modCount; + // 元素数量加1,判断是否需要扩容 + if (++size > threshold) + // 扩容 + resize(); + afterNodeInsertion(evict); + // 没找到元素返回null + return null; + } + + +``` + +(1)计算key的hash值; + +(2)如果桶(数组)数量为0,则初始化桶; + +(3)如果key所在的桶没有元素,则直接插入; + +(4)如果key所在的桶中的第一个元素的key与待插入的key相同,说明找到了元素,转后续流程(9)处理; + +(5)如果第一个元素是树节点,则调用树节点的putTreeVal()寻找元素或插入树节点; + +(6)如果不是以上三种情况,则遍历桶对应的链表查找key是否存在于链表中; + +(7)如果找到了对应key的元素,则转后续流程(9)处理; + +(8)如果没找到对应key的元素,则在链表最后插入一个新节点并判断是否需要树化; + +(9)如果找到了对应key的元素,则判断是否需要替换旧值,并直接返回旧值; + +(10)如果插入了元素,则数量加1并判断是否需要扩容; + +### resize()方法 + +扩容方法。 + +``` +final Node[] resize() { + // 旧数组 + Node[] 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) + // 如果旧容量的两倍小于最大容量并且旧容量大于默认初始容量(16),则容量扩大为两部,扩容门槛 //也扩大为两倍 + newThr = oldThr << 1; // double threshold + } + else if (oldThr > 0) // initial capacity was placed in threshold + // 使用非默认构造方法创建的map,第一次插入元素会走到这里 + // 如果旧容量为0且旧扩容门槛大于0,则把新容量赋值为旧门槛 + newCap = oldThr; + else { // zero initial threshold signifies using defaults + // 调用默认构造方法创建的map,第一次插入元素会走到这里 +// 如果旧容量旧扩容门槛都是0,说明还未初始化过,则初始化容量为默认容量,扩容门槛为默认容量*默认装载因子 + newCap = DEFAULT_INITIAL_CAPACITY; + newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY); + } + if (newThr == 0) { + float ft = (float)newCap * loadFactor; + newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ? + (int)ft : Integer.MAX_VALUE); + } + // 赋值扩容门槛为新门槛 + threshold = newThr; + // 新建一个新容量的桶数组 + @SuppressWarnings({"rawtypes","unchecked"}) + Node[] newTab = (Node[])new Node[newCap]; + // 把桶赋值为新数组 + table = newTab; + // 如果旧数组不为空,则搬移元素 + if (oldTab != null) { + for (int j = 0; j < oldCap; ++j) { + Node e; + // 如果桶中第一个元素不为空,赋值给e + if ((e = oldTab[j]) != null) { + // 清空旧桶,便于GC回收 + oldTab[j] = null; + // 如果这个桶中只有一个元素,则计算它在新桶中的位置并把它搬移到新桶中 + // 因为每次都扩容两倍,所以这里的第一个元素搬移到新桶的时候新桶肯定还没有元素 + if (e.next == null) + newTab[e.hash & (newCap - 1)] = e; + else if (e instanceof TreeNode) + // 如果第一个元素是树节点,则把这颗树打散成两颗树插入到新桶中去 + ((TreeNode)e).split(this, newTab, j, oldCap); + else { // preserve order + // 如果这个链表不止一个元素且不是一颗树 + // 则分化成两个链表插入到新的桶中去 + // 比如,假如原来容量为4,3、7、11、15这四个元素都在三号桶中 + // 现在扩容到8,则3和11还是在三号桶,7和15要搬移到七号桶中去 + // 也就是分化成了两个链表 + Node loHead = null, loTail = null; + Node hiHead = null, hiTail = null; + Node next; + do { + next = e.next; + // (e.hash & oldCap) == 0的元素放在低位链表中 + // 比如,3 & 4 == 0 + if ((e.hash & oldCap) == 0) { + if (loTail == null) + loHead = e; + else + loTail.next = e; + loTail = e; + } + else { + // (e.hash & oldCap) != 0的元素放在高位链表中 + // 比如,7 & 4 != 0 + if (hiTail == null) + hiHead = e; + else + hiTail.next = e; + hiTail = e; + } + } while ((e = next) != null); + // 遍历完成分化成两个链表了 + // 低位链表在新桶中的位置与旧桶一样(即3和11还在三号桶中) + if (loTail != null) { + loTail.next = null; + newTab[j] = loHead; + } + // 高位链表在新桶中的位置正好是原来的位置加上旧容量(即7和15搬移到七号桶了) + if (hiTail != null) { + hiTail.next = null; + newTab[j + oldCap] = hiHead; + } + } + } + } + } + return newTab; + } +``` + +(1)如果使用是默认构造方法,则第一次插入元素时初始化为默认值,容量为16,扩容门槛为12; + +(2)如果使用的是非默认构造方法,则第一次插入元素时初始化容量等于扩容门槛,扩容门槛在构造方法里等于传入容量向上最近的2的n次方; + +(3)如果旧容量大于0,则新容量等于旧容量的2倍,但不超过最大容量2的30次方,新扩容门槛为旧扩容门槛的2倍; + +(4)创建一个新容量的桶; + +(5)搬移元素,原链表分化成两个链表,低位链表存储在原来桶的位置,高位链表搬移到原来桶的位置加旧容量的位置; + +### TreeNode.putTreeVal(...)方法 + +插入元素到红黑树中的方法。 + +``` +final TreeNode putTreeVal(HashMap map, Node[] tab, + int h, K k, V v) { + Class kc = null; + // 标记是否找到这个key的节点 + boolean searched = false; + // 找到树的根节点 + TreeNode root = (parent != null) ? root() : this; + // 从树的根节点开始遍历 + for (TreeNode p = root;;) { + // dir=direction,标记是在左边还是右边 + // ph=p.hash,当前节点的hash值 + int dir, ph; + // pk=p.key,当前节点的key值 + K pk; + if ((ph = p.hash) > h) + // 当前hash比目标hash大,说明在左边 + dir = -1; + else if (ph < h) + // 当前hash比目标hash小,说明在右边 + dir = 1; + else if ((pk = p.key) == k || (k != null && k.equals(pk))) + // 两者hash相同且key相等,说明找到了节点,直接返回该节点 + // 回到putVal()中判断是否需要修改其value值 + return p; + else if ((kc == null && + // 如果k是Comparable的子类则返回其真实的类,否则返回null + (kc = comparableClassFor(k)) == null) || + // 如果k和pk不是同样的类型则返回0,否则返回两者比较的结果 + (dir = compareComparables(kc, k, pk)) == 0) { + // 这个条件表示两者hash相同但是其中一个不是Comparable类型或者两者类型不同 + // 比如key是Object类型,这时可以传String也可以传Integer,两者hash值可能相同 + // 在红黑树中把同样hash值的元素存储在同一颗子树,这里相当于找到了这颗子树的顶点 + // 从这个顶点分别遍历其左右子树去寻找有没有跟待插入的key相同的元素 + if (!searched) { + TreeNode q, ch; + searched = true; + // 遍历左右子树找到了直接返回 + if (((ch = p.left) != null && + (q = ch.find(h, k, kc)) != null) || + ((ch = p.right) != null && + (q = ch.find(h, k, kc)) != null)) + return q; + } + // 如果两者类型相同,再根据它们的内存地址计算hash值进行比较 + dir = tieBreakOrder(k, pk); + } + + TreeNode xp = p; + if ((p = (dir <= 0) ? p.left : p.right) == null) { + + Node xpn = xp.next; + TreeNode x = map.newTreeNode(h, k, v, xpn); + if (dir <= 0) + xp.left = x; + else + xp.right = x; + xp.next = x; + x.parent = x.prev = xp; + if (xpn != null) + ((TreeNode)xpn).prev = x; + // 插入树节点后平衡 + // 把root节点移动到链表的第一个节点 + moveRootToFront(tab, balanceInsertion(root, x)); + return null; + } + } + } +``` + +(1)寻找根节点; + +(2)从根节点开始查找; + +(3)比较hash值及key值,如果都相同,直接返回,在putVal()方法中决定是否要替换value值; + +(4)根据hash值及key值确定在树的左子树还是右子树查找,找到了直接返回; + +(5)如果最后没有找到则在树的相应位置插入元素,并做平衡; + +### treeifyBin()方法 + +如果插入元素后链表的长度大于等于8则判断是否需要树化。 + +``` +final void treeifyBin(Node[] tab, int hash) { + int n, index; Node e; + if (tab == null || (n = tab.length) < MIN_TREEIFY_CAPACITY) +// 如果桶数量小于64,直接扩容而不用树化 +// 因为扩容之后,链表会分化成两个链表,达到减少元素的作用 +// 当然也不一定,比如容量为4,里面存的全是除以4余数等于3的元素 +// 这样即使扩容也无法减少链表的长度 + resize(); + else if ((e = tab[index = (n - 1) & hash]) != null) { + TreeNode hd = null, tl = null; +// 把所有节点换成树节点 + do { + TreeNode 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); + } + } +``` + +### TreeNode.treeify()方法 + +真正树化的方法。 + +``` +final void treeify(Node[] tab) { + TreeNode root = null; + for (TreeNode x = this, next; x != null; x = next) { + next = (TreeNode)x.next; + x.left = x.right = null; + if (root == null) { + x.parent = null; + x.red = false; + root = x; + } + else { + K k = x.key; + int h = x.hash; + Class kc = null; + for (TreeNode 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 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); + } +``` + +(1)从链表的第一个元素开始遍历; + +(2)将第一个元素作为根节点; + +(3)其它元素依次插入到红黑树中,再做平衡; + +(4)将根节点移到链表第一元素的位置(因为平衡的时候根节点会改变); + +### get(Object key)方法 + +``` +public V get(Object key) { + Node e; + return (e = getNode(hash(key), key)) == null ? null : e.value; + } + +final Node getNode(int hash, Object key) { + Node[] tab; Node first, e; int n; K k; + // 如果桶的数量大于0并且待查找的key所在的桶的第一个元素不为空 + 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)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)计算key的hash值; + +(2)找到key所在的桶及其第一个元素; + +(3)如果第一个元素的key等于待查找的key,直接返回; + +(4)如果第一个元素是树节点就按树的方式来查找,否则按链表方式查找; + +### TreeNode.getTreeNode(int h, Object k)方法 + +``` +final TreeNode getTreeNode(int h, Object k) { +// 从树的根节点开始查找 + return ((parent != null) ? root() : this).find(h, k, null); + } + +final TreeNode find(int h, Object k, Class kc) { + TreeNode p = this; + do { + int ph, dir; K pk; + TreeNode 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; + } +``` + +经典二叉查找树的查找过程,先根据hash值比较,再根据key值比较决定是查左子树还是右子树。 + +### remove(Object key)方法 + +``` +public V remove(Object key) { + Node e; + return (e = removeNode(hash(key), key, null, false, true)) == null ? + null : e.value; + } + +final Node removeNode(int hash, Object key, Object value, + boolean matchValue, boolean movable) { + Node[] tab; Node p; int n, index; + // 如果桶的数量大于0且待删除的元素所在的桶的第一个元素不为空 + if ((tab = table) != null && (n = tab.length) > 0 && + (p = tab[index = (n - 1) & hash]) != null) { + Node node = null, e; K k; V v; + if (p.hash == hash && + ((k = p.key) == key || (key != null && key.equals(k)))) + // 如果第一个元素正好就是要找的元素,赋值给node变量后续删除使用 + node = p; + else if ((e = p.next) != null) { + if (p instanceof TreeNode) + node = ((TreeNode)p).getTreeNode(hash, key); + else { + do { + if (e.hash == hash && + ((k = e.key) == key || + (key != null && key.equals(k)))) { + node = e; + break; + } + p = e; + } while ((e = e.next) != null); + } + } + if (node != null && (!matchValue || (v = node.value) == value || + (value != null && value.equals(v)))) { + if (node instanceof TreeNode) + ((TreeNode)node).removeTreeNode(this, tab, movable); + else if (node == p) + tab[index] = node.next; + else + p.next = node.next; + ++modCount; + --size; + afterNodeRemoval(node); + return node; + } + } + return null; + } +``` + +(1)先查找元素所在的节点; + +(2)如果找到的节点是树节点,则按树的移除节点处理; + +(3)如果找到的节点是桶中的第一个节点,则把第二个节点移到第一的位置; + +(4)否则按链表删除节点处理; + +(5)修改size,调用移除节点后置处理等; + +### TreeNode.removeTreeNode(...)方法 + +``` +final void removeTreeNode(HashMap map, Node[] tab, boolean movable) { int n; // 如果桶的数量为0直接返回 if (tab == null || (n = tab.length) == 0) return; // 节点在桶中的索引 int index = (n - 1) & hash; // 第一个节点,根节点,根左子节点 TreeNode first = (TreeNode) tab[index], root = first, rl; // 后继节点,前置节点 TreeNode succ = (TreeNode) next, pred = prev; if (pred == null) // 如果前置节点为空,说明当前节点是根节点,则把后继节点赋值到第一个节点的位置,相当于删除了当前节点 tab[index] = first = succ; else // 否则把前置节点的下个节点设置为当前节点的后继节点,相当于删除了当前节点 pred.next = succ; // 如果后继节点不为空,则让后继节点的前置节点指向当前节点的前置节点,相当于删除了当前节点 if (succ != null) succ.prev = pred; // 如果第一个节点为空,说明没有后继节点了,直接返回 if (first == null) return; // 如果根节点的父节点不为空,则重新查找父节点 if (root.parent != null) root = root.root(); // 如果根节点为空,则需要反树化(将树转化为链表) // 如果需要移动节点且树的高度比较小,则需要反树化 if (root == null || (movable && (root.right == null || (rl = root.left) == null || rl.left == null))) { tab[index] = first.untreeify(map); // too small return; } // 分割线,以上都是删除链表中的节点,下面才是直接删除红黑树的节点(因为TreeNode本身即是链表节点又是树节点) // 删除红黑树节点的大致过程是寻找右子树中最小的节点放到删除节点的位置,然后做平衡,此处不过多注释 TreeNode p = this, pl = left, pr = right, replacement; if (pl != null && pr != null) { TreeNode s = pr, sl; while ((sl = s.left) != null) // find successor s = sl; boolean c = s.red; s.red = p.red; p.red = c; // swap colors TreeNode sr = s.right; TreeNode pp = p.parent; if (s == pr) { // p was s's direct parent p.parent = s; s.right = p; } else { TreeNode sp = s.parent; if ((p.parent = sp) != null) { if (s == sp.left) sp.left = p; else sp.right = p; } if ((s.right = pr) != null) pr.parent = s; } p.left = null; if ((p.right = sr) != null) sr.parent = p; if ((s.left = pl) != null) pl.parent = s; if ((s.parent = pp) == null) root = s; else if (p == pp.left) pp.left = s; else pp.right = s; if (sr != null) replacement = sr; else replacement = p; } else if (pl != null) replacement = pl; else if (pr != null) replacement = pr; else replacement = p; if (replacement != p) { TreeNode pp = replacement.parent = p.parent; if (pp == null) root = replacement; else if (p == pp.left) pp.left = replacement; else pp.right = replacement; p.left = p.right = p.parent = null; } TreeNode r = p.red ? root : balanceDeletion(root, replacement); if (replacement == p) { // detach TreeNode pp = p.parent; p.parent = null; if (pp != null) { if (p == pp.left) pp.left = null; else if (p == pp.right) pp.right = null; } } if (movable) moveRootToFront(tab, r);} +``` + +(1)TreeNode本身既是链表节点也是红黑树节点; + +(2)先删除链表节点; + +(3)再删除红黑树节点并做平衡; + +## 总结 + +(1)HashMap是一种散列表,采用(数组 + 链表 + 红黑树)的存储结构; + +(2)HashMap的默认初始容量为16(1<<4),默认装载因子为0.75f,容量总是2的n次方; + +(3)HashMap扩容时每次容量变为原来的两倍; + +(4)当桶的数量小于64时不会进行树化,只会扩容; + +(5)当桶的数量大于64且单个桶中元素的数量大于8时,进行树化; + +(6)当单个桶中元素数量小于6时,进行反树化; + +(7)HashMap是非线程安全的容器; + +(8)HashMap查找添加元素的时间复杂度都为O(1); + +## 带详细注释的源码地址 + +https://gitee.com/alan-tang-tt/yuan/blob/master/%E6%AD%BB%E7%A3%95%20java%E9%9B%86%E5%90%88%E7%B3%BB%E5%88%97/code/HashMap.java + +## 彩蛋 + +红黑树知多少? + +红黑树具有以下5种性质: + +(1)节点是红色或黑色。 + +(2)根节点是黑色。 + +(3)每个叶节点(NIL节点,空节点)是黑色的。 + +(4)每个红色节点的两个子节点都是黑色。(从每个叶子到根的所有路径上不能有两个连续的红色节点) + +(5)从任一节点到其每个叶子的所有路径都包含相同数目的黑色节点。 + +红黑树的时间复杂度为O(log n),与树的高度成正比。 + +红黑树每次的插入、删除操作都需要做平衡,平衡时有可能会改变根节点的位置,颜色转换,左旋,右旋等。 \ No newline at end of file diff --git a/week_01/51/HashMap_51.xmind b/week_01/51/HashMap_51.xmind new file mode 100644 index 0000000..e78b0ef Binary files /dev/null and b/week_01/51/HashMap_51.xmind differ diff --git a/week_01/51/LinkedList_51.md b/week_01/51/LinkedList_51.md new file mode 100644 index 0000000..a1f8fcf --- /dev/null +++ b/week_01/51/LinkedList_51.md @@ -0,0 +1,327 @@ +## 问题 + +(1)LinkedList只是一个List吗? + +(2)LinkedList还有其它什么特性吗? + +(3)LinkedList为啥经常拿出来跟ArrayList比较? + +(4)我为什么把LinkedList放在最后一章来讲? + +## 简介 + +LinkedList是一个以双向链表实现的List,它除了作为List使用,还可以作为队列或者栈来使用,它是怎么实现的呢?让我们一起来学习吧。 + +## 继承体系 + +![img](https://mmbiz.qpic.cn/mmbiz_png/C91PV9BDK3xemg4rYaDUe3KGQCUwXXIjcBp4R5xyicibc9yCbNyUEUGUhia8McrTQINQXZ7uT6l7gD0MmzOZwG0Uw/640?wx_fmt=png&tp=webp&wxfrom=5&wx_lazy=1&wx_co=1) + +通过继承体系,我们可以看到LinkedList不仅实现了List接口,还实现了Queue和Deque接口,所以它既能作为List使用,也能作为双端队列使用,当然也可以作为栈使用。 + +## 源码分析 + +### 主要属性 + +``` +// 元素个数 +transient int size = 0; +// 链表首节点 +transient Node first; +// 链表尾节点 +transient Node last; +``` + +属性很简单,定义了元素个数size和链表的首尾节点。 + +### 主要内部类 + +典型的双链表结构。 + +``` +private static class Node { + E item; + Node next; + Node prev; + + Node(Node prev, E element, Node next) { + this.item = element; + this.next = next; + this.prev = prev; + } + } +``` + +### 主要构造方法 + +``` +public LinkedList() { + } + + public LinkedList(Collection c) { + this(); + addAll(c); + } +``` + +两个构造方法也很简单,可以看出是一个无界的队列。 + +### 添加元素 + +作为一个双端队列,添加元素主要有两种,一种是在队列尾部添加元素,一种是在队列首部添加元素,这两种形式在LinkedList中主要是通过下面两个方法来实现的。 + +``` + +public void addFirst(E e) { + linkFirst(e); + } + +// 从队列首添加元素 +private void linkFirst(E e) { + // 首节点 + final Node f = first; + // 创建新节点,新节点的next是首节点 + final Node newNode = new Node<>(null, e, f); + // 让新节点作为新的首节点 + first = newNode; + // 判断是不是第一个添加的元素 + // 如果是就把last也置为新节点 + // 否则把原首节点的prev指针置为新节点 + if (f == null) + last = newNode; + else + f.prev = newNode; + // 元素个数加1 + size++; + // 修改次数加1,说明这是一个支持fail-fast的集合 + modCount++; + } + + public void addLast(E e) { + linkLast(e); + } + +void linkLast(E e) { + // 队列尾节点 + final Node l = last; + // 创建新节点,新节点的prev是尾节点 + final Node newNode = new Node<>(l, e, null); + // 让新节点成为新的尾节点 + last = newNode; +// 判断是不是第一个添加的元素 +// 如果是就把first也置为新节点 +// 否则把原尾节点的next指针置为新节点 + if (l == null) + first = newNode; + else + l.next = newNode; + size++; + modCount++; + } + + // 作为无界队列,添加元素总是会成功的 + public boolean offerFirst(E e) { + addFirst(e); + return true; + } + + public boolean offerLast(E e) { + addLast(e); + return true; + } +``` + +典型的双链表在首尾添加元素的方法,代码比较简单,这里不作详细描述了。 + +上面是作为双端队列来看,它的添加元素分为首尾添加元素,那么,作为List呢? + +作为List,是要支持在中间添加元素的,主要是通过下面这个方法实现的。 + +``` +// 在节点succ之前添加元素 +void linkBefore(E e, Node succ) { + // assert succ != null; + final Node pred = succ.prev; + final Node newNode = new Node<>(pred, e, succ); + succ.prev = newNode; + if (pred == null) + first = newNode; + else + pred.next = newNode; + size++; + modCount++; + } + +// 寻找index位置的节点 +Node node(int index) { + // assert isElementIndex(index); + if (index < (size >> 1)) { + Node x = first; + for (int i = 0; i < index; i++) + x = x.next; + return x; + } else { + Node x = last; + for (int i = size - 1; i > index; i--) + x = x.prev; + return x; + } + } + +public void add(int index, E element) { + checkPositionIndex(index); + + if (index == size) + linkLast(element); + else + linkBefore(element, node(index)); + } +``` + +在中间添加元素的方法也很简单,典型的双链表在中间添加元素的方法。 + +添加元素的三种方式大致如下图所示: + +![img](https://mmbiz.qpic.cn/mmbiz_png/C91PV9BDK3xemg4rYaDUe3KGQCUwXXIjaTB8ria2iclWo4kOB61kVExcBCYxaPXXpuQib9YU9TemopQ6YibAQWyhQA/640?wx_fmt=png&tp=webp&wxfrom=5&wx_lazy=1&wx_co=1) + +在队列首尾添加元素很高效,时间复杂度为O(1)。 + +在中间添加元素比较低效,首先要先找到插入位置的节点,再修改前后节点的指针,时间复杂度为O(n)。 + +### 删除元素 + +作为双端队列,删除元素也有两种方式,一种是队列首删除元素,一种是队列尾删除元素。 + +作为List,又要支持中间删除元素,所以删除元素一个有三个方法,分别如下。 + +``` +// 删除首节点 +private E unlinkFirst(Node f) { + // assert f == first && f != null; + final E element = f.item; + final Node next = f.next; + f.item = null; + f.next = null; // help GC + first = next; + if (next == null) + last = null; + else + next.prev = null; + size--; + modCount++; + return element; + } + +// 删除尾节点 + private E unlinkLast(Node l) { + // assert l == last && l != null; + final E element = l.item; + final Node prev = l.prev; + l.item = null; + l.prev = null; // help GC + last = prev; + if (prev == null) + first = null; + else + prev.next = null; + size--; + modCount++; + return element; + } + +// 删除指定节点x +E unlink(Node x) { + // assert x != null; + final E element = x.item; + final Node next = x.next; + final Node prev = x.prev; + + if (prev == null) { + first = next; + } else { + prev.next = next; + x.prev = null; + } + + if (next == null) { + last = prev; + } else { + next.prev = prev; + x.next = null; + } + + x.item = null; + size--; + modCount++; + return element; + } + +public E removeFirst() { + final Node f = first; + if (f == null) + throw new NoSuchElementException(); + return unlinkFirst(f); + } + +public E removeLast() { + final Node l = last; + if (l == null) + throw new NoSuchElementException(); + return unlinkLast(l); + } + +public E remove(int index) { + checkElementIndex(index); + return unlink(node(index)); + } + +public E pollFirst() { + final Node f = first; + return (f == null) ? null : unlinkFirst(f); + } + public E pollLast() { + final Node l = last; + return (l == null) ? null : unlinkLast(l); + } + +``` + +删除元素的三种方法都是典型的双链表删除元素的方法,大致流程如下图所示。 + +![img](https://mmbiz.qpic.cn/mmbiz_png/C91PV9BDK3xemg4rYaDUe3KGQCUwXXIj2yqSBibVvtmG1yiaqcb6o32GIgsVb55q9wBea8xQECmSIIUkkXCVRDSQ/640?wx_fmt=png&tp=webp&wxfrom=5&wx_lazy=1&wx_co=1) + +在队列首尾删除元素很高效,时间复杂度为O(1)。 + +在中间删除元素比较低效,首先要找到删除位置的节点,再修改前后指针,时间复杂度为O(n)。 + +## 栈 + +前面我们说了,LinkedList是双端队列,还记得双端队列可以作为栈使用吗? + +``` +public void push(E e) { + addFirst(e); + } +public E pop() { + return removeFirst(); + } +``` + +栈的特性是LIFO(Last In First Out),所以作为栈使用也很简单,添加删除元素都只操作队列首节点即可。 + +## 总结 + +(1)LinkedList是一个以双链表实现的List; + +(2)LinkedList还是一个双端队列,具有队列、双端队列、栈的特性; + +(3)LinkedList在队列首尾添加、删除元素非常高效,时间复杂度为O(1); + +(4)LinkedList在中间添加、删除元素比较低效,时间复杂度为O(n); + +(5)LinkedList不支持随机访问,所以访问非队列首尾的元素比较低效; + +(6)LinkedList在功能上等于ArrayList + ArrayDeque; + +## 彩蛋 + +java集合部分的源码分析全部完结,整个专题以ArrayList开头,以LinkedList结尾,我觉得非常合适,因为ArrayList代表了List的典型实现,LInkedList代表了Deque的典型实现,同时LinkedList也实现了List,通过这两个类一首一尾正好可以把整个集合贯穿起来。 \ No newline at end of file diff --git a/week_01/54/ArrayList-054.md b/week_01/54/ArrayList-054.md new file mode 100644 index 0000000..be6a1f3 --- /dev/null +++ b/week_01/54/ArrayList-054.md @@ -0,0 +1,464 @@ +ArrayList底层是基于数组实现的 + +**成员** +/** +* Default initial capacity. 默认的初始容量大小 +*/ +private static final int DEFAULT_CAPACITY = 10; + +/** +* Shared empty array instance used for empty instances.(用于空实例的共享空数组实例)实际上就是空数组对象 +*/ +private static final Object[] EMPTY_ELEMENTDATA = {}; + +/** + * Shared empty array instance used for default sized empty instances. We + * distinguish this from EMPTY_ELEMENTDATA to know how much to inflate when + * first element is added. + * 用于默认大小的空实例的共享空数组实例。我们将其与空的元素数据区分开来,以了解何时 + * 添加第一个元素 + */ +private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {}; + +transient Object[] elementData; //用于增删改查的数组 + +/** + * The size of the ArrayList (the number of elements it contains). + * + * @serial + */ +private int size;//元素的大小 默认为0 + + +/** + * The maximum size of array to allocate. + * Some VMs reserve some header words in an array. + * Attempts to allocate larger arrays may result in + * OutOfMemoryError: Requested array size exceeds VM limit + */ +private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;//最大数组容量,如果数组过长,会抛出OOM错误 + +**构造方法** + + /** + * Constructs an empty list with the specified initial capacity. + * + * @param initialCapacity the initial capacity of the list + * @throws IllegalArgumentException if the specified initial capacity + * is negative + */ +public ArrayList(int initialCapacity) { +//带初始容量的构造方法 + if (initialCapacity > 0) { + //大于0 新建一个Object数组 + this.elementData = new Object[initialCapacity]; + } else if (initialCapacity == 0) { + //等于0 使用上面的空数组对象 + this.elementData = EMPTY_ELEMENTDATA; + } else { + //其他的 抛出IllegalArgumentException + throw new IllegalArgumentException("Illegal Capacity: "+ + initialCapacity); + } +} + + /** + * Constructs an empty list with an initial capacity of ten. + */ +public ArrayList() { +//无参构造方法,默认上面的数组对象 + this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA; +} + +/** + * Constructs a list containing the elements of the specified + * collection, in the order they are returned by the collection's + * iterator. + * + * @param c the collection whose elements are to be placed into this list + * @throws NullPointerException if the specified collection is null + */ +public ArrayList(Collection c) { +//集合的构造方法 + elementData = c.toArray();//把集合转换为数组 + if ((size = elementData.length) != 0) { + //先把集合大小给size,如果集合大小不为0,就会去判断是否为Object[].class + // c.toArray might (incorrectly) not return Object[] (see 6260652) + if (elementData.getClass() != Object[].class) + elementData = Arrays.copyOf(elementData, size, Object[].class);//复制所有元素 + } else { + //集合大小为0 使用上面的空数组对象 + // replace with empty array. + this.elementData = EMPTY_ELEMENTDATA; + } +} +**主要方法** + +add方法 +/** + * Appends the specified element to the end of this list.(添加一个特定的元素到list的末尾) + * + * @param e element to be appended to this list + * @return true (as specified by {@link Collection#add}) + */ +public boolean add(E e) { +//向数组中添加元素 + ensureCapacityInternal(size + 1); // Increments modCount!! 确定内部容量是否足够 + //在数据中size++位置放入元素 + elementData[size++] = e; + return true; +} + +private void ensureCapacityInternal(int minCapacity) { + if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) { + //判断传入进来的数组是否为空,因为如果是空的话,minCapacity=size+1; + //其实就是等于1,空的数组没有长度就存放不了,所以就将minCapacity变成10,也就是默认大小, + //但是在这里,还没有真正的初始化这个elementData的大小 + minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity); + } + + ensureExplicitCapacity(minCapacity); +} + +private void ensureExplicitCapacity(int minCapacity) { + modCount++; + + // overflow-conscious code + if (minCapacity - elementData.length > 0) + grow(minCapacity);//ArrayList自动扩展大小 +} + +/** + * Increases the capacity to ensure that it can hold at least the + * number of elements specified by the minimum capacity argument. + * + * @param minCapacity the desired minimum capacity + */ +private void grow(int minCapacity) { + // overflow-conscious code + int oldCapacity = elementData.length;//先定义一个oldCapacity 存放扩充前的elementData大小 + int newCapacity = oldCapacity + (oldCapacity >> 1);//新的容量为1.5倍的oldCapacity + if (newCapacity - minCapacity < 0) + newCapacity = minCapacity;//当所有为0时,默认为10 + if (newCapacity - MAX_ARRAY_SIZE > 0) + newCapacity = hugeCapacity(minCapacity);//超出最大容量限制,调用hugeCapacity,就是将能给的最大值给newCapacity + // minCapacity is usually close to size, so this is a win: + elementData = Arrays.copyOf(elementData, newCapacity);//改变elementData大小 +} + +private static int hugeCapacity(int minCapacity) { + if (minCapacity < 0) // overflow + throw new OutOfMemoryError();//传入的参数必须大于0,否则报OOM错误 + //如果minCapacity 大于MAX_ARRAY_SIZE(Integer.MAX_VALUE - 8)(2147483639)就返回Integer.MAX_VALUE( 2的31次方减一)(2147483647) 否则就直接给MAX_ARRAY_SIZE + return (minCapacity > MAX_ARRAY_SIZE) ? + Integer.MAX_VALUE : + MAX_ARRAY_SIZE; + } + +/** + * Inserts the specified element at the specified position in this + * list. Shifts the element currently at that position (if any) and + * any subsequent elements to the right (adds one to their indices). + * + * @param index index at which the specified element is to be inserted + * @param element element to be inserted + * @throws IndexOutOfBoundsException {@inheritDoc} + */ +public void add(int index, E element) { +//在指定位置插入元素 + + //检查index位置是否合理 + rangeCheckForAdd(index); + + ensureCapacityInternal(size + 1); // Increments modCount!! + + //插入元素之前需要把index之后的元素往后移动一位 + System.arraycopy(elementData, index, elementData, index + 1, + size - index); + //index位置元素复制 + elementData[index] = element; + //size增加1 + size++; +} + +/** + * A version of rangeCheck used by add and addAll. + */ +private void rangeCheckForAdd(int index) { + if (index > size || index < 0) + throw new IndexOutOfBoundsException(outOfBoundsMsg(index));//判断index位置是否超过size和小于0 抛出数组越界异常 +} + +/** + * Appends all of the elements in the specified collection to the end of + * this list, in the order that they are returned by the + * specified collection's Iterator. The behavior of this operation is + * undefined if the specified collection is modified while the operation + * is in progress. (This implies that the behavior of this call is + * undefined if the specified collection is this list, and this + * list is nonempty.) + * + * @param c collection containing elements to be added to this list + * @return true if this list changed as a result of the call + * @throws NullPointerException if the specified collection is null + */ +public boolean addAll(Collection c) { + //向当前集合添加一个集合 + Object[] a = c.toArray(); + int numNew = a.length; + ensureCapacityInternal(size + numNew); // Increments modCount //确定容量 + System.arraycopy(a, 0, elementData, size, numNew);//扩容数组长度 + size += numNew; + return numNew != 0;//返回新长度和size是否相等的boolean +} + +/** + * Inserts all of the elements in the specified collection into this + * list, starting at the specified position. Shifts the element + * currently at that position (if any) and any subsequent elements to + * the right (increases their indices). The new elements will appear + * in the list in the order that they are returned by the + * specified collection's iterator. + * + * @param index index at which to insert the first element from the + * specified collection + * @param c collection containing elements to be added to this list + * @return true if this list changed as a result of the call + * @throws IndexOutOfBoundsException {@inheritDoc} + * @throws NullPointerException if the specified collection is null + */ +public boolean addAll(int index, Collection c) { +//将指定集合中的所有元素插入集合,从指定位置开始。移动元素目前处于该位置(如有)以及 +//右边(增加他们的指数)。新的元素将出现在集合中按指定集合的迭代器。 + rangeCheckForAdd(index); + + Object[] a = c.toArray(); + int numNew = a.length; + ensureCapacityInternal(size + numNew); // Increments modCount + + int numMoved = size - index; + if (numMoved > 0) + System.arraycopy(elementData, index, elementData, index + numNew, + numMoved); + + System.arraycopy(a, 0, elementData, index, numNew); + size += numNew; + return numNew != 0; +} + +remove方法 + +/** + * Removes the element at the specified position in this list. + * Shifts any subsequent elements to the left (subtracts one from their + * indices). + * + * @param index the index of the element to be removed + * @return the element that was removed from the list + * @throws IndexOutOfBoundsException {@inheritDoc} + */ +public E remove(int index) { +//移除指定位置元素 + + //判断index是否合理 + rangeCheck(index); + + modCount++; + E oldValue = elementData(index);//直接通过索引找到该元素 + + int numMoved = size - index - 1; + if (numMoved > 0) + System.arraycopy(elementData, index+1, elementData, index, + numMoved);//移动元素 + 赋值为null 让GC更快回收 + elementData[--size] = null; // clear to let GC do its work + + return oldValue; +} + +@SuppressWarnings("unchecked") +E elementData(int index) { + //通过index查找所在对应位置元素 + return (E) elementData[index]; +} + + /** + * Removes the first occurrence of the specified element from this list, + * if it is present. If the list does not contain the element, it is + * unchanged. More formally, removes the element with the lowest index + * i such that + * (o==null ? get(i)==null : o.equals(get(i))) + * (if such an element exists). Returns true if this list + * contained the specified element (or equivalently, if this list + * changed as a result of the call). + * + * @param o element to be removed from this list, if present + * @return true if this list contained the specified element + */ +public boolean remove(Object o) { +//循环所有找到对应的元素 + if (o == null) { + for (int index = 0; index < size; index++) + if (elementData[index] == null) { + fastRemove(index); + return true; + } + } else { + for (int index = 0; index < size; index++) + if (o.equals(elementData[index])) { + fastRemove(index); + return true; + } + } + return false; +} + +/* + * Private remove method that skips bounds checking and does not + * return the value removed. + */ +private void fastRemove(int index) { + //其实就跟remove(int index)方法一样 + modCount++; + int numMoved = size - index - 1; + if (numMoved > 0) + System.arraycopy(elementData, index+1, elementData, index, + numMoved); + elementData[--size] = null; // clear to let GC do its work +} + +/** + * Removes all of the elements from this list. The list will + * be empty after this call returns. + */ +public void clear() { +//把所有元素置换成null 让GC快速回收 + modCount++; + + // clear to let GC do its work + for (int i = 0; i < size; i++) + elementData[i] = null; + + size = 0; +} + +/** + * Removes from this list all of the elements whose index is between + * {@code fromIndex}, inclusive, and {@code toIndex}, exclusive. + * Shifts any succeeding elements to the left (reduces their index). + * This call shortens the list by {@code (toIndex - fromIndex)} elements. + * (If {@code toIndex==fromIndex}, this operation has no effect.) + * + * @throws IndexOutOfBoundsException if {@code fromIndex} or + * {@code toIndex} is out of range + * ({@code fromIndex < 0 || + * fromIndex >= size() || + * toIndex > size() || + * toIndex < fromIndex}) + */ +protected void removeRange(int fromIndex, int toIndex) { +//移除一个区间内的元素 + modCount++; + int numMoved = size - toIndex; + System.arraycopy(elementData, toIndex, elementData, fromIndex, + numMoved); + // clear to let GC do its work + int newSize = size - (toIndex-fromIndex); + for (int i = newSize; i < size; i++) { + elementData[i] = null; + } + size = newSize; +} + +/** + * Removes from this list all of its elements that are contained in the + * specified collection. + * + * @param c collection containing elements to be removed from this list + * @return {@code true} if this list changed as a result of the call + * @throws ClassCastException if the class of an element of this list + * is incompatible with the specified collection + * (optional) + * @throws NullPointerException if this list contains a null element and the + * specified collection does not permit null elements + * (optional), + * or if the specified collection is null + * @see Collection#contains(Object) + */ +public boolean removeAll(Collection c) { + Objects.requireNonNull(c);//判断是否为Null + return batchRemove(c, false); +} + +//Objects里面的方法 +public static T requireNonNull(T obj) { + if (obj == null) + throw new NullPointerException(); + return obj; +} + +private boolean batchRemove(Collection c, boolean complement) { + final Object[] elementData = this.elementData;//记录原集合为elementData + int r = 0, w = 0; + boolean modified = false; + try { + for (; r < size; r++) + if (c.contains(elementData[r]) == complement) + elementData[w++] = elementData[r];//判断集合是否有,有就给elementData + } finally { + // Preserve behavioral compatibility with AbstractCollection, + // even if c.contains() throws. + if (r != size) { + //如果数量不相等 则把剩下的元素给elementData + System.arraycopy(elementData, r, + elementData, w, + size - r); + w += size - r; + } + if (w != size) { + // clear to let GC do its work + for (int i = w; i < size; i++) + elementData[i] = null; + modCount += size - w; + size = w; + modified = true; + } + } + return modified; +} + +修改 + +/** + * Replaces the element at the specified position in this list with + * the specified element. + * + * @param index index of the element to replace + * @param element element to be stored at the specified position + * @return the element previously at the specified position + * @throws IndexOutOfBoundsException {@inheritDoc} + */ +public E set(int index, E element) { +//修改指定index位置下的元素 + rangeCheck(index); + + E oldValue = elementData(index); + elementData[index] = element; + return oldValue;//返回旧的元素 +} + +查询 + +/** + * Returns the element at the specified position in this list. + * + * @param index index of the element to return + * @return the element at the specified position in this list + * @throws IndexOutOfBoundsException {@inheritDoc} + */ +public E get(int index) { +//查询指定位置下的元素 + rangeCheck(index); + + return elementData(index); +} diff --git a/week_01/54/HashMap-054.md b/week_01/54/HashMap-054.md new file mode 100644 index 0000000..9114760 --- /dev/null +++ b/week_01/54/HashMap-054.md @@ -0,0 +1,569 @@ +###### 基于jdk1.8的HashMap源码分析 + +**成员** +/** + * The default initial capacity - MUST be a power of two. + */ +static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16 初始容量默认16 必须为2的n次方 + +/** + * The maximum capacity, used if a higher value is implicitly specified + * by either of the constructors with arguments. + * MUST be a power of two <= 1<<30. + */ +static final int MAXIMUM_CAPACITY = 1 << 30; 最大容量 2的30次方 + +/** + * The load factor used when none specified in constructor. + */ +static final float DEFAULT_LOAD_FACTOR = 0.75f; 加载因子默认0.75 + +/** + * The bin count threshold for using a tree rather than list for a + * bin. Bins are converted to trees when adding an element to a + * bin with at least this many nodes. The value must be greater + * than 2 and should be at least 8 to mesh with assumptions in + * tree removal about conversion back to plain bins upon + * shrinkage. + */ +static final int TREEIFY_THRESHOLD = 8;//链表转换成红黑树的阈值为8 + +/** + * The bin count threshold for untreeifying a (split) bin during a + * resize operation. Should be less than TREEIFY_THRESHOLD, and at + * most 6 to mesh with shrinkage detection under removal. + */ +static final int UNTREEIFY_THRESHOLD = 6;//当红黑树数量小于6会转换为链表 + +/** + * The smallest table capacity for which bins may be treeified. + * (Otherwise the table is resized if too many nodes in a bin.) + * Should be at least 4 * TREEIFY_THRESHOLD to avoid conflicts + * between resizing and treeification thresholds. + */ +static final int MIN_TREEIFY_CAPACITY = 64;//当hashmap元素数量大于64也会转换为红黑树 + +/** + * The table, initialized on first use, and resized as + * necessary. When allocated, length is always a power of two. + * (We also tolerate length zero in some operations to allow + * bootstrapping mechanics that are currently not needed.) + */ +transient Node[] table;//不能被序列化的数组 + +/** + * Holds cached entrySet(). Note that AbstractMap fields are used + * for keySet() and values(). + */ +transient Set> entrySet;//数据转为set的存储方式,主要用于迭代功能 + +/** + * The number of key-value mappings contained in this map. + */ +transient int size;//元素数量 + + +/** + * The number of times this HashMap has been structurally modified + * Structural modifications are those that change the number of mappings in + * the HashMap or otherwise modify its internal structure (e.g., + * rehash). This field is used to make iterators on Collection-views of + * the HashMap fail-fast. (See ConcurrentModificationException). + */ +transient int modCount;//修改的次数 + +/** + * The next size value at which to resize (capacity * load factor). + * + * @serial + */ +// (The javadoc description is true upon serialization. +// Additionally, if the table array has not been allocated, this +// field holds the initial array capacity, or zero signifying +// DEFAULT_INITIAL_CAPACITY.) +int threshold;//临界值 + +/** + * The load factor for the hash table. + * + * @serial + */ +final float loadFactor;//加载因子(变量) + +**构造方法** + +/** + * Constructs an empty HashMap with the specified initial + * capacity and load factor. + * + * @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) + //初始容量小于0 抛出IllegalArgumentException + throw new IllegalArgumentException("Illegal initial capacity: " + + initialCapacity); + if (initialCapacity > MAXIMUM_CAPACITY) + //初始容量大于最大值,就设定为MAXIMUM_CAPACITY + initialCapacity = MAXIMUM_CAPACITY; + if (loadFactor <= 0 || Float.isNaN(loadFactor)) + //加载因子小于等于0 或者为NaN 抛出IllegalArgumentException + throw new IllegalArgumentException("Illegal load factor: " + + loadFactor); + this.loadFactor = loadFactor; + this.threshold = tableSizeFor(initialCapacity); +} + +/** + * Returns a power of two size for the given target capacity.返回给定目标容量的二次幂 + */ +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; +} + +/** + * Constructs an empty HashMap with the specified initial + * capacity and the default load factor (0.75). + * + * @param initialCapacity the initial capacity. + * @throws IllegalArgumentException if the initial capacity is negative. + */ +public HashMap(int initialCapacity) { +//传入初始容量 使用0.75默认加载因子 + this(initialCapacity, DEFAULT_LOAD_FACTOR); +} + +/** + * Constructs an empty HashMap with the default initial capacity + * (16) and the default load factor (0.75). + */ +public HashMap() { + this.loadFactor = DEFAULT_LOAD_FACTOR; // all other fields defaulted +} + +/** + * Constructs a new HashMap with the same mappings as the + * specified Map. The HashMap is created with + * default load factor (0.75) and an initial capacity sufficient to + * hold the mappings in the specified Map. + * + * @param m the map whose mappings are to be placed in this map + * @throws NullPointerException if the specified map is null + */ +public HashMap(Map m) { + // 把一个Map集合转换为HashMap集合 + this.loadFactor = DEFAULT_LOAD_FACTOR; + putMapEntries(m, false); +} + +/** + * Implements Map.putAll and Map constructor 实现Map.putAll和Map构造函数 + * + * @param m the map + * @param evict false when initially constructing this map, else + * true (relayed to method afterNodeInsertion). + */ +final void putMapEntries(Map 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) + //如果table进行了初始化 则进行扩容 + resize(); + //遍历元素通过putval方法放入map里面 + for (Map.Entry e : m.entrySet()) { + K key = e.getKey(); + V value = e.getValue(); + putVal(hash(key), key, value, false, evict); + } + } +} + +**核心方法** + +hash计算 + +/** + * Computes key.hashCode() and spreads (XORs) higher bits of hash + * to lower. Because the table uses power-of-two masking, sets of + * hashes that vary only in bits above the current mask will + * always collide. (Among known examples are sets of Float keys + * holding consecutive whole numbers in small tables.) So we + * apply a transform that spreads the impact of higher bits + * downward. There is a tradeoff between speed, utility, and + * quality of bit-spreading. Because many common sets of hashes + * are already reasonably distributed (so don't benefit from + * spreading), and because we use trees to handle large sets of + * collisions in bins, we just XOR some shifted bits in the + * cheapest possible way to reduce systematic lossage, as well as + * to incorporate impact of the highest bits that would otherwise + * never be used in index calculations because of table bounds. + */ +static final int hash(Object key) { + int h; + return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);//根据传入的key的hashcode,进行一次异或运算,为了减少hash冲突 +} + +put方法 + +/** + * Associates the specified value with the specified key in this map. + * If the map previously contained a mapping for the key, the old + * value is replaced. + * + * @param key key with which the specified value is to be associated + * @param value value to be associated with the specified key + * @return the previous value associated with key, or + * null if there was no mapping for key. + * (A null return can also indicate that the map + * previously associated null with key.) + */ +public V put(K key, V value) { + return putVal(hash(key), key, value, false, true);//实际上使用putVal方法 +} + +/** + * Implements Map.put and related methods + * + * @param hash hash for key + * @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 + */ +final V putVal(int hash, K key, V value, boolean onlyIfAbsent, + boolean evict) { + Node[] tab; Node p; int n, i; + if ((tab = table) == null || (n = tab.length) == 0) + //判断table是否初始化,没有就进行扩容 + n = (tab = resize()).length; + if ((p = tab[i = (n - 1) & hash]) == null) + //判断当前hash有没有值,没有值就新建一个node + tab[i] = newNode(hash, key, value, null); + + //hash冲突的情况 + else { + Node e; K k; + if (p.hash == hash && + ((k = p.key) == key || (key != null && key.equals(k)))) + //第一种情况key与当前节点相等 + e = p; + else if (p instanceof TreeNode) + //第二种情况 判断是否为红黑树的节点 + //如果为红黑树的节点,则在红黑树里面进行添加,若存在则返回null + e = ((TreeNode)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); + 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 + //如果有重复的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; +} + +/** + * Copies all of the mappings from the specified map to this map. + * These mappings will replace any mappings that this map had for + * any of the keys currently in the specified map. + * + * @param m mappings to be stored in this map + * @throws NullPointerException if the specified map is null + */ +public void putAll(Map m) { + //插入map集合直接调用putMapEntries方法 + putMapEntries(m, true); +} + +扩容 + +/** + * Initializes or doubles table size. If null, allocates in + * accord with initial capacity target held in field threshold. + * Otherwise, because we are using power-of-two expansion, the + * elements from each bin must either stay at same index, or move + * with a power of two offset in the new table. + * + * @return the table + */ +final Node[] resize() { + Node[] oldTab = table; + //old长度 + int oldCap = (oldTab == null) ? 0 : oldTab.length; + //old临界值 + int oldThr = threshold; + //初始化new的长度和临界值 + int newCap, newThr = 0; + if (oldCap > 0) { + //不是首次初始化 + if (oldCap >= MAXIMUM_CAPACITY) { + //如果超出最大容量 设置阈值为int的最大 + threshold = Integer.MAX_VALUE; + return oldTab; + } + else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY && + oldCap >= DEFAULT_INITIAL_CAPACITY) + //扩容为原来的2倍 + newThr = oldThr << 1; // double threshold + } + else if (oldThr > 0) // initial capacity was placed in threshold + newCap = oldThr;//已经初始化 判断临界值>0 newCap为以前的临界值 + else { // zero initial threshold signifies using defaults + //其他情况 + newCap = DEFAULT_INITIAL_CAPACITY;//newCap使用默认的初始化容量 + newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);//默认的加载因子*默认的初始化容量 + } + if (newThr == 0) { + //如果传入cap为小于16 newThr没有值 + //新的临界值 + float ft = (float)newCap * loadFactor; + //判断新的容量是否小于最大值和临界值是否小于最大容量,否则返回Int最大值 + newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ? + (int)ft : Integer.MAX_VALUE); + } + threshold = newThr; + @SuppressWarnings({"rawtypes","unchecked"}) + Node[] newTab = (Node[])new Node[newCap]; + table = newTab; + if (oldTab != null) { + for (int j = 0; j < oldCap; ++j) { + //遍历oldCap元素 + Node e; + //当前数组下标有值 + if ((e = oldTab[j]) != null) { + //方便gc回收 + oldTab[j] = null; + if (e.next == null) + //没有下一个元素,把值放入newTab中 + newTab[e.hash & (newCap - 1)] = e; + else if (e instanceof TreeNode) + //红黑树情况,转移到newTab中 + ((TreeNode)e).split(this, newTab, j, oldCap); + else { // preserve order + //链表结构,遍历值转到newTab中 + Node loHead = null, loTail = null; + Node hiHead = null, hiTail = null; + Node 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; +} + +删除 + +/** + * Removes the mapping for the specified key from this map if present. + * + * @param key key whose mapping is to be removed from the map + * @return the previous value associated with key, or + * null if there was no mapping for key. + * (A null return can also indicate that the map + * previously associated null with key.) + */ +public V remove(Object key) { +//根据key删除元素,调用removeNode方法,返回null 或者被删除的value + Node e; + return (e = removeNode(hash(key), key, null, false, true)) == null ? + null : e.value; +} + +/** + * Implements Map.remove and related methods + * + * @param hash hash for key + * @param key the key + * @param value the value to match if matchValue, else ignored + * @param matchValue if true only remove if value is equal + * @param movable if false do not move other nodes while removing + * @return the node, or null if none + */ +final Node removeNode(int hash, Object key, Object value, + boolean matchValue, boolean movable) { + Node[] tab; Node p; int n, index; + if ((tab = table) != null && (n = tab.length) > 0 && + (p = tab[index = (n - 1) & hash]) != null) { + //数组不为null tab的长度大于0 删除的节点在数组的下标位置 + Node node = null, e; K k; V v; + if (p.hash == hash && + ((k = p.key) == key || (key != null && key.equals(k)))) + //如果数组下面的变量刚好是要删除的元素,赋值给临时变量node + node = p; + else if ((e = p.next) != null) {//要删除的key在链表或者红黑树上面 + if (p instanceof TreeNode) + //从红黑树里面获取值 + node = ((TreeNode)p).getTreeNode(hash, key); + else { + //循环链表 + do { + if (e.hash == hash && + ((k = e.key) == key || + (key != null && key.equals(k)))) { + //找到值并且复制给临时node + node = e; + break; + } + p = e; + } while ((e = e.next) != null); + } + } + if (node != null && (!matchValue || (v = node.value) == value || + (value != null && value.equals(v)))) { + if (node instanceof TreeNode) + //如果实在红黑树里面 removeTreeNode方法 + ((TreeNode)node).removeTreeNode(this, tab, movable); + else if (node == p) + //为链表且为头节点直接把node.next赋值给tab + tab[index] = node.next; + else + //如果为链表某一节点,复制给上一个p.next为node.next + p.next = node.next; + ++modCount;//修改次数++ + --size;//数量-- + afterNodeRemoval(node); + return node; + } + } + return null; +} + +/** + * Removes all of the mappings from this map. + * The map will be empty after this call returns. + */ +public void clear() { +//清空 + Node[] tab; + modCount++; + if ((tab = table) != null && size > 0) { + //循环table所有置为null + size = 0; + for (int i = 0; i < tab.length; ++i) + tab[i] = null; + } +} + +查询 + +/** + * Returns the value to which the specified key is mapped, + * or {@code null} if this map contains no mapping for the key. + * + *

More formally, if this map contains a mapping from a key + * {@code k} to a value {@code v} such that {@code (key==null ? k==null : + * key.equals(k))}, then this method returns {@code v}; otherwise + * it returns {@code null}. (There can be at most one such mapping.) + * + *

A return value of {@code null} does not necessarily + * indicate that the map contains no mapping for the key; it's also + * possible that the map explicitly maps the key to {@code null}. + * The {@link #containsKey containsKey} operation may be used to + * distinguish these two cases. + * + * @see #put(Object, Object) + */ +public V get(Object key) { +//获取相应key的value通过getNode方法实现 + Node e; + return (e = getNode(hash(key), key)) == null ? null : e.value; +} + +/** + * Implements Map.get and related methods + * + * @param hash hash for key + * @param key the key + * @return the node, or null if none + */ +final Node getNode(int hash, Object key) { + Node[] tab; Node 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)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); + } + } + //没有则返回null + return null; +} \ No newline at end of file diff --git a/week_01/54/LinkedList-054.md b/week_01/54/LinkedList-054.md new file mode 100644 index 0000000..3a51d0f --- /dev/null +++ b/week_01/54/LinkedList-054.md @@ -0,0 +1,265 @@ +###### LinkedList基于jdk1.8源码分析 + +**成员** + +transient int size = 0;//list节点个数 + +/** + * Pointer to first node.指向头节点 + * Invariant: (first == null && last == null) || + * (first.prev == null && first.item != null) + */ +transient Node first; + +/** + * Pointer to last node.//指向尾节点 + * Invariant: (first == null && last == null) || + * (last.next == null && last.item != null) + */ +transient Node last; + +**构造函数** + +/** + * Constructs an empty list.//创建一个空list + */ +public LinkedList() { +} + +/** + * Constructs a list containing the elements of the specified + * collection, in the order they are returned by the collection's + * iterator. + * + * @param c the collection whose elements are to be placed into this list + * @throws NullPointerException if the specified collection is null + */ +public LinkedList(Collection c) { +//把一个Collection变为LinkedList + this(); + addAll(c); +} + +**核心方法** + +添加 + +/** + * Appends the specified element to the end of this list. + * + *

This method is equivalent to {@link #addLast}. + * + * @param e element to be appended to this list + * @return {@code true} (as specified by {@link Collection#add}) + */ +public boolean add(E e) { +//添加一个元素,通过linkLast方法 + linkLast(e); + return true; +} + +/** + * Links e as last element.向尾部添加一个元素 + */ +void linkLast(E e) { + final Node l = last; + final Node newNode = new Node<>(l, e, null);以尾部元素为前面节点next创建一个新节点 + last = newNode; + if (l == null) + //判断是否为空链表 + first = newNode;//头节点为newNode + else + l.next = newNode;//将原来的尾部next插入需要插入的节点 + //更新链表的次数以及大小 + size++; + modCount++; +} + +/** + * Inserts the specified element at the specified position in this list. + * Shifts the element currently at that position (if any) and any + * subsequent elements to the right (adds one to their indices). + * + * @param index index at which the specified element is to be inserted + * @param element element to be inserted + * @throws IndexOutOfBoundsException {@inheritDoc} + */ +public void add(int index, E element) { +//指定位置插入元素 + checkPositionIndex(index);//检查位置是否合理 + + if (index == size) + linkLast(element);//如果位置是在最后一位采用尾插法 + else + linkBefore(element, node(index)); +} + +/** + * Inserts element e before non-null Node succ. + */ +void linkBefore(E e, Node succ) { + // assert succ != null; + final Node pred = succ.prev;//插入位置的前节点 + final Node newNode = new Node<>(pred, e, succ);//新建节点为succ的前节点 + succ.prev = newNode;succ的前节点为newNode + if (pred == null) + //前节点为null 表示为头节点 ,直接first为newNode + first = newNode; + else + //插入位置的前节点的next为newNode + pred.next = newNode; + size++; + modCount++; +} + +/** + * Appends all of the elements in the specified collection to the end of + * this list, in the order that they are returned by the specified + * collection's iterator. The behavior of this operation is undefined if + * the specified collection is modified while the operation is in + * progress. (Note that this will occur if the specified collection is + * this list, and it's nonempty.) + * + * @param c collection containing elements to be added to this list + * @return {@code true} if this list changed as a result of the call + * @throws NullPointerException if the specified collection is null + */ +public boolean addAll(Collection c) { +//在size后面添加一个Collection集合 + return addAll(size, c); +} + +/** + * Inserts all of the elements in the specified collection into this + * list, starting at the specified position. Shifts the element + * currently at that position (if any) and any subsequent elements to + * the right (increases their indices). The new elements will appear + * in the list in the order that they are returned by the + * specified collection's iterator. + * + * @param index index at which to insert the first element + * from the specified collection + * @param c collection containing elements to be added to this list + * @return {@code true} if this list changed as a result of the call + * @throws IndexOutOfBoundsException {@inheritDoc} + * @throws NullPointerException if the specified collection is null + */ +public boolean addAll(int index, Collection c) { + checkPositionIndex(index);//检查位置是否合理 + + Object[] a = c.toArray();//转换为数组 + int numNew = a.length; + if (numNew == 0)//数组大小为0 + return false; + + Node pred, succ; + if (index == size) {//如果size和index相等,初始化succ为null,pred为尾节点 + succ = null; + pred = last; + } else { + //否则 采用折半查找index元素,并设置succ为当前index的数据,pred为当前元素的prev + succ = node(index); + pred = succ.prev; + } + + for (Object o : a) { + //循环object数组,链表循环添加 + @SuppressWarnings("unchecked") E e = (E) o; + Node newNode = new Node<>(pred, e, null); + if (pred == null) + first = newNode; + else + pred.next = newNode; + pred = newNode; + } + //循环结束后,判断如果succ是null, 说明此时是在队尾添加的,设置一下队列尾节点last, + //如果不是在队尾,则更新之前插入位置节点的前节点和当前要插入节点的尾节点 + if (succ == null) { + last = pred; + } else { + pred.next = succ; + succ.prev = pred; + } + + size += numNew; + modCount++; + return true; +} + +删除 +/** + * Removes the element at the specified position in this list. Shifts any + * subsequent elements to the left (subtracts one from their indices). + * Returns the element that was removed from the list. + * + * @param index the index of the element to be removed + * @return the element previously at the specified position + * @throws IndexOutOfBoundsException {@inheritDoc} + */ +public E remove(int index) { +//删除指定位置的元素 + checkElementIndex(index); + return unlink(node(index)); +} + +/** + * Unlinks non-null node x. + */ +E unlink(Node x) { + // assert x != null; + final E element = x.item; + final Node next = x.next;//获取当前next赋值给临时node next + final Node prev = x.prev;//获取当前prev赋值给临时node prev + + if (prev == null) { + //头节点 + first = next;//直接赋值 + } else { + prev.next = next;//把next给prev的next + x.prev = null;//设置为null + } + + if (next == null) { + //尾节点 + last = prev; + } else { + next.prev = prev; + x.next = null; + } + + x.item = null; + size--; + modCount++; + return element; +} + +查询 +/** + * Returns the element at the specified position in this list. + * + * @param index index of the element to return + * @return the element at the specified position in this list + * @throws IndexOutOfBoundsException {@inheritDoc} + */ +public E get(int index) { + checkElementIndex(index);//判断位置是否合理 + return node(index).item;//折半查找 +} + +修改 +/** + * Replaces the element at the specified position in this list with the + * specified element. + * + * @param index index of the element to replace + * @param element element to be stored at the specified position + * @return the element previously at the specified position + * @throws IndexOutOfBoundsException {@inheritDoc} + */ +public E set(int index, E element) { + checkElementIndex(index);//判断位置是否合理 + Node x = node(index);//折半查找 + E oldVal = x.item;//获取旧元素 + x.item = element;//替换成新的元素 + return oldVal;//返回旧元素 +} \ No newline at end of file diff --git a/week_01/55/ArrayList-55.md b/week_01/55/ArrayList-55.md new file mode 100644 index 0000000..7dd4011 --- /dev/null +++ b/week_01/55/ArrayList-55.md @@ -0,0 +1,230 @@ +ArrayList +定义 +1 public class ArrayList extends AbstractList +2 implements List, RandomAccess, Cloneable, java.io.Serializable +ArrayList实际上是一个动态数组,容量可以动态的增长,其继承了AbstractList,实现了List, RandomAccess, Cloneable, java.io.Serializable这些接口。 + +RandomAccess接口,被List实现之后,为List提供了随机访问功能,也就是通过下标获取元素对象的功能。 + +实现了Cloneable, java.io.Serializable意味着可以被克隆和序列化。 + +初始化 +在使用中我们经常需要new出来各种泛型的ArrayList,那么在初始化过程是怎样的呢? + +如下一行代码,创建一个ArrayList对象 + +List list = new ArrayList<>(); + + +我们来看源码,是如何初始化的,找到构造方法 + +//无参构造方法 +public ArrayList() { + super(); + this.elementData = EMPTY_ELEMENTDATA; +} + + +看到这些代码的时候,我也是不解的,elementData和EMPTY_ELEMENTDATA是什么啊?但是很明显EMPTY_ELEMENTDATA是一个常量,追本溯源我们去看一下成员属性。 + +//如果是无参构造方法创建对象的话,ArrayList的初始化长度为10,这是一个静态常量 +private static final int DEFAULT_CAPACITY = 10; +​ +//在这里可以看到我们不解的EMPTY_ELEMENTDATA实际上是一个空的对象数组 + private static final Object[] EMPTY_ELEMENTDATA = {}; +​ +//保存ArrayList数据的对象数组缓冲区 空的ArrayList的elementData = EMPTY_ELEMENTDATA 这就是为什么说ArrayList底层是数组实现的了。elementData的初始容量为10,大小会根据ArrayList容量的增长而动态的增长。 + private transient Object[] elementData; +//集合的长度 + private int size; + + + +执行完构造方法,如下图 + + + +可以说ArrayList的作者真的是很贴心,连缓存都处理好了,多次new出来的新对象,都指向同一个引用。 + +添加方法add +add(E e) + + /** + * Appends the specified element to the end of this list. + */ +//增加元素到集合的最后 +public boolean add(E e) { + ensureCapacityInternal(size + 1); // Increments modCount!! + //因为++运算符的特点 先使用后运算 这里实际上是 + //elementData[size] = e + //size+1 + elementData[size++] = e; + return true; +} + + + +先不管ensureCapacityInternal的话,这个方法就是将一个元素增加到数组的size++位置上。 + +再说回ensureCapacityInternal,它是用来扩容的,准确说是用来进行扩容检查的。下面我们来看一下整个扩容的过程 + + +//初始长度是10,size默认值0,假定添加的是第一个元素,那么minCapacity=1 这是最小容量 如果小于这个容量就会报错 +//如果底层数组就是默认数组,那么选一个大的值,就是10 +private void ensureCapacityInternal(int minCapacity) { + if (elementData == EMPTY_ELEMENTDATA) { + minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity); + } + //调用另一个方法ensureExplicitCapacity + ensureExplicitCapacity(minCapacity); + } +​ + private void ensureExplicitCapacity(int minCapacity) { + //记录修改的次数 + modCount++; +​ + // overflow-conscious code + //如果传过来的值大于初始长度 执行grow方法(参数为传过来的值)扩容 + if (minCapacity - elementData.length > 0) + grow(minCapacity); + } +//真正的扩容 + private void grow(int minCapacity) { + // overflow-conscious code + int oldCapacity = elementData.length; + //新的容量是在原有的容量基础上+50% 右移一位就是二分之一 + int newCapacity = oldCapacity + (oldCapacity >> 1); + //如果新容量小于最小容量,按照最小容量进行扩容 + if (newCapacity - minCapacity < 0) + newCapacity = minCapacity; + if (newCapacity - MAX_ARRAY_SIZE > 0) + newCapacity = hugeCapacity(minCapacity); + // minCapacity is usually close to size, so this is a win: + //这里是重点 调用工具类Arrays的copyOf扩容 + elementData = Arrays.copyOf(elementData, newCapacity); + } +​ +//Arrays +public static T[] copyOf(U[] original, int newLength, Class newType) { + T[] copy = ((Object)newType == (Object)Object[].class) + ? (T[]) new Object[newLength] + : (T[]) Array.newInstance(newType.getComponentType(), newLength); + System.arraycopy(original, 0, copy, 0, + Math.min(original.length, newLength)); + return copy; +} +​ + + + +add(int index, E element) +添加到指定的位置 + + +public void add(int index, E element) { + //判断索引是否越界,如果越界就会抛出下标越界异常 + rangeCheckForAdd(index); +//扩容检查 + ensureCapacityInternal(size + 1); // Increments modCount!! + //将指定下标空出 具体作法就是index及其后的所有元素后移一位 + System.arraycopy(elementData, index, elementData, index + 1,size - index); + //将要添加元素赋值到空出来的指定下标处 + elementData[index] = element; + //长度加1 + size++; +} +//判断是否出现下标是否越界 +private void rangeCheckForAdd(int index) { + //如果下标超过了集合的尺寸 或者 小于0就是越界 + if (index > size || index < 0) + throw new IndexOutOfBoundsException(outOfBoundsMsg(index)); +} + + + +remove(int index) +ArrayList支持两种删除元素的方式 + +remove(int index) 按照下标删除 常用 + +remove(Object o) 按照元素删除 会删除和参数匹配的第一个元素 + +下面我们看一下ArrayList的实现 + + + /** + 移除list中指定位置的元素 + * Removes the element at the specified position in this list. + 所有后续元素左移 下标减1 + * Shifts any subsequent elements to the left (subtracts one from their + * indices). + *参数是要移除元素的下标 + * @param index the index of the element to be removed + 返回值是被移除的元素 + * @return the element that was removed from the list + * @throws IndexOutOfBoundsException {@inheritDoc} + */ +public E remove(int index) { + //下标越界检查 + rangeCheck(index); +//修改次数统计 + modCount++; + //获取这个下标上的值 + E oldValue = elementData(index); +//计算出需要移动的元素个数 (因为需要将后续元素左移一位 此处计算出来的是后续元素的位数) + int numMoved = size - index - 1; + //如果这个值大于0 说明后续有元素需要左移 是0说明被移除的对象就是最后一位元素 + if (numMoved > 0) + //索引index只有的所有元素左移一位 覆盖掉index位置上的元素 + System.arraycopy(elementData, index+1, elementData, index,numMoved); + // 将最后一个元素赋值为null 这样就可以被gc回收了 + elementData[--size] = null; // clear to let GC do its work +//返回index位置上的元素 + return oldValue; +} +​ +//移除特定元素 +public boolean remove(Object o) { + //如果元素是null 遍历数组移除第一个null + if (o == null) { + for (int index = 0; index < size; index++) + if (elementData[index] == null) { + //遍历找到第一个null元素的下标 调用下标移除元素的方法 + fastRemove(index); + return true; + } + } else { + //找到元素对应的下标 调用下标移除元素的方法 + for (int index = 0; index < size; index++) + if (o.equals(elementData[index])) { + fastRemove(index); + return true; + } + } + return false; +} +​ +//按照下标移除元素 +private void fastRemove(int index) { + modCount++; + int numMoved = size - index - 1; + if (numMoved > 0) + System.arraycopy(elementData, index+1, elementData, index, + numMoved); + elementData[--size] = null; // clear to let GC do its work +} + + + +ArrayList总结 +底层数组实现,使用默认构造方法初始化出来的容量是10 + +扩容的长度是在原长度基础上加二分之一 + +实现了RandomAccess接口,底层又是数组,get读取元素性能很好 + +线程不安全,所有的方法均不是同步方法也没有加锁,因此多线程下慎用 + +顺序添加很方便 + +删除和插入需要复制数组 性能很差(可以使用LinkindList) \ No newline at end of file diff --git a/week_01/55/HashMap-55.md b/week_01/55/HashMap-55.md new file mode 100644 index 0000000..7a8d7a9 --- /dev/null +++ b/week_01/55/HashMap-55.md @@ -0,0 +1,258 @@ +HashMap +定义 +public class HashMap extends AbstractMap implements Map, Cloneable, Serializable +HashMap没有什么要说的,直接切入正题,初始化一个HashMap。 + +初始化 +HashMap map = new HashMap(); +通过这个方法会调用HashMap的无参构造方法。 + +//两个常量 向下追踪 +public HashMap() { + this(DEFAULT_INITIAL_CAPACITY, DEFAULT_LOAD_FACTOR); +} + +//无参构造创建对象之后 会有两个常量 +//DEFAULT_INITIAL_CAPACITY 默认初始化容量 16 这里值得借鉴的是位运算 +static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16 +//DEFAULT_LOAD_FACTOR 负载因子默认为0.75f 负载因子和扩容有关 后文详谈 +static final float DEFAULT_LOAD_FACTOR = 0.75f; + +//最大容量为2的30次方 +static final int MAXIMUM_CAPACITY = 1 << 30; + +//以Node为元素的数组,长度必须为2的n次幂 +transient Node[] table; + +//已经储存的Node的数量,包括数组中的和链表中的,逻辑长度 +transient int size; + +threshold 决定能放入的数据量,一般情况下等于 Capacity * LoadFactor +通过上述代码我们不难发现,HashMap的底层还是数组(注意,数组会在第一次put的时候通过 resize() 函数进行分配),数组的长度为2的N次幂。 + +在HashMap中,哈希桶数组table的长度length大小必须为2的n次方(一定是合数),这是一种非常规的设计,常规的设计是把桶的大小设计为素数。相对来说素数导致冲突的概率要小于合数,Hashtable初始化桶大小为11,就是桶大小设计为素数的应用(Hashtable扩容后不能保证还是素数)。HashMap采用这种非常规设计,主要是为了在取模和扩容时做优化,同时为了减少冲突,HashMap定位哈希桶索引位置时,也加入了高位参与运算的过程。 + +那么Node是什么呢? + +//一个静态内部类 其实就是Map中元素的具体存储对象 +static class Node implements Map.Entry { + //每个储存元素key的哈希值 + final int hash; + //这就是key-value + final K key; + V value; + //next 追加的时候使用,标记链表的下一个node地址 + Node next; + + Node(int hash, K key, V value, Node next) { + this.hash = hash; + this.key = key; + this.value = value; + this.next = next; + } +此时我们就拥有了一个空的HashMap,下面我们看一下put + +put +JDK8 HashMap put的基本思路: + +对key的hashCode()进行hash后计算数组下标index; +如果当前数组table为null,进行resize()初始化; +如果没碰撞直接放到对应下标的位置上; +如果碰撞了,且节点已经存在,就替换掉 value; +如果碰撞后发现为树结构,挂载到树上。 +如果碰撞后为链表,添加到链表尾,并判断链表如果过长(大于等于TREEIFY_THRESHOLD,默认8),就把链表转换成树结构; +数据 put 后,如果数据量超过threshold,就要resize。 +public V put(K key, V value) { + //调用putVal方法 在此之前会对key做hash处理 + return putVal(hash(key), key, value, false, true); +} +//hash +static final int hash(Object key) { + int h; + // h = key.hashCode() 为第一步 取hashCode值 + // h ^ (h >>> 16) 为第二步 高位参与运算 + //具体的算法就不解释了 作用就是性能更加优良 + return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16); +} + +//进行添加操作 +final V putVal(int hash, K key, V value, boolean onlyIfAbsent, + boolean evict) { + Node[] tab; Node p; int n, i; + //如果当前数组table为null,进行resize()初始化 + if ((tab = table) == null || (n = tab.length) == 0) + n = (tab = resize()).length; + //(n - 1) & hash 计算出下标 如果该位置为null 说明没有碰撞就赋值到此位置 + if ((p = tab[i = (n - 1) & hash]) == null) + tab[i] = newNode(hash, key, value, null); + else { + //反之 说明碰撞了 + Node e; K k; + //判断 key是否存在 如果存在就覆盖原来的value + if (p.hash == hash && + ((k = p.key) == key || (key != null && key.equals(k)))) + e = p; + //没有存在 判断是不是红黑树 + else if (p instanceof TreeNode) + //红黑树是为了防止哈希表碰撞攻击,当链表链长度为8时,及时转成红黑树,提高map的效率 + e = ((TreeNode)p).putTreeVal(this, tab, hash, key, value); + //都不是 就是链表 + else { + for (int binCount = 0; ; ++binCount) { + if ((e = p.next) == null) { + //将next指向新的节点 + p.next = newNode(hash, key, value, null); + //这个判断是用来判断是否要转化为红黑树结构 + if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st + treeifyBin(tab, hash); + break; + } + // key已经存在直接覆盖value + 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; +} +在刚才的代码中我们提到了红黑树是为了防止**哈希表碰撞攻击,当链表链长度为8时,及时转成红黑树,提高map的效率。**那么接下来说一说什么是哈希表碰撞攻击。 + +现在做web开发RESTful风格的接口相当的普及,因此很多的数据都是通过json来进行传递的,而json数据收到之后会被转为json对象,通常都是哈希表结构的,就是Map。 + +我们知道理想情况下哈希表插入和查找操作的时间复杂度均为O(1),任何一个数据项可以在一个与哈希表长度无关的时间内计算出一个哈希值(key),从而得到下标。但是难免出现不同的数据被定位到了同一个位置,这就导致了插入和查找操作的时间复杂度不为O(1),这就是哈希碰撞。 + +java的中解决哈希碰撞的思路是单向链表和黑红树,上文提到红黑树是JDK8之后添加,为了防止哈希表碰撞攻击,为什么?。 + +不知道你有没有设想过这样一种场景,添加的所有数据都碰撞在一起,那么这些数据就会被组织到一个链表中,随着链表越来越长,哈希表会退化为单链表。哈希表碰撞攻击就是通过精心构造数据,使得所有数据全部碰撞,人为将哈希表变成一个退化的单链表,此时哈希表各种操作的时间均提升了一个数量级,因此会消耗大量CPU资源,导致系统无法快速响应请求,从而达到拒绝服务攻击(DoS)的目的。 + +我们需要注意的是红黑树实际上并不能解决哈希表攻击问题,只是减轻影响,防护该种攻击还需要其他的手段,譬如控制POST数据的数量。 + +扩容resize() +不管是list还是map,都会遇到容量不足需要扩容的时候,但是不同于list,HashMap的扩容设计的非常巧妙,首先在上文提到过数组的长度为2的N次方,也就是说初始为16,扩容一次为32... 好处呢?就是上文提到的扩容是性能优化和减少碰撞,就是体现在此处。 + +数组下标计算: index = (table.length - 1) & hash ,由于 table.length 也就是capacity 肯定是2的N次方,使用 & 位运算意味着只是多了最高位,这样就不用重新计算 index,元素要么在原位置,要么在原位置+ oldCapacity. + +如果增加的高位为0,resize 后 index 不变;高位为1在原位置+ oldCapacity。resize 的过程中原来碰撞的节点有一部分会被分开。 + +扩容简单说有两步: + +1.扩容 + +创建一个新的Entry空数组,长度是原数组的2倍。 + +2.ReHash + +遍历原Entry数组,把所有的Entry重新Hash到新数组。 + +//HashMap的源码真的长 0.0 这段改天补上 +final Node[] resize() { + Node[] 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) + newThr = oldThr << 1; // double threshold + } + else if (oldThr > 0) // initial capacity was placed in threshold + newCap = oldThr; + else { // zero initial threshold signifies using defaults + newCap = DEFAULT_INITIAL_CAPACITY; + newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY); + } + if (newThr == 0) { + float ft = (float)newCap * loadFactor; + newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ? + (int)ft : Integer.MAX_VALUE); + } + threshold = newThr; + @SuppressWarnings({"rawtypes","unchecked"}) + Node[] newTab = (Node[])new Node[newCap]; + table = newTab; + if (oldTab != null) { + for (int j = 0; j < oldCap; ++j) { + Node e; + if ((e = oldTab[j]) != null) { + oldTab[j] = null; + if (e.next == null) + newTab[e.hash & (newCap - 1)] = e; + else if (e instanceof TreeNode) + ((TreeNode)e).split(this, newTab, j, oldCap); + else { // preserve order + Node loHead = null, loTail = null; + Node hiHead = null, hiTail = null; + Node 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; +} +为什么HashMap是线程不安全的 +由于源码过长,HashMap的其他方法就不写了。下面说一下关于HashMap的一些问题 + +1.如果多个线程同时使用put方法添加元素会丢失元素 + +假设正好存在两个put的key发生了碰撞,那么根据HashMap的实现,这两个key会添加到数组的同一个位置,这样最终就会发生其中一个线程的put的数据被覆盖。 + +2.多线程同时扩容会造成死循环 + +多线程同时检查到扩容,并且执行扩容操作,在进行rehash的时候会造成闭环链表,从而在get该位置元素的时候,程序将会进入死循环。【证明HashMap高并发下问题会在以后的文章中出现】 + +如何让HashMap实现线程安全? + +直接使用Hashtable +Collections.synchronizeMap方法 +使用ConcurrentHashMap 下篇文章就是分析ConcurrentHashMap是如何实现线程安全的 +总结 +HashMap 在第一次 put 时初始化,类似 ArrayList 在第一次 add 时分配空间。 +HashMap 的 bucket 数组大小一定是2的n次方 +HashMap 在 put 的元素数量大于 Capacity * LoadFactor(默认16 * 0.75) 之后会进行扩容 +负载因子是可以修改的,也可以大于1,但是建议不要轻易修改,除非情况非常特殊 +JDK8处于提升性能的考虑,在哈希碰撞的链表长度达到TREEIFY_THRESHOLD(默认8)后,会把该链表转变成树结构 +JDK8在 resize 的时候,通过巧妙的设计,减少了 rehash 的性能消耗 +扩容是一个特别耗性能的操作,所以当在使用HashMap的时候,估算map的大小,初始化的时候给一个大致的数值,避免map进行频繁的扩容 \ No newline at end of file diff --git a/week_01/57/ArrayList-057.txt b/week_01/57/ArrayList-057.txt new file mode 100644 index 0000000..05d656b --- /dev/null +++ b/week_01/57/ArrayList-057.txt @@ -0,0 +1,71 @@ +1. +初始化大小为10 + /** + * Default initial capacity. + */ + private static final int DEFAULT_CAPACITY = 10; + +2.构造方法: + ①:无参默认创建一个空集合 + /** + * Constructs an empty list with an initial capacity of ten. + */ + public ArrayList() { + this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA; + } + ②:有参数的情况下 根据参数大小初始化集合 + public ArrayList(int initialCapacity) { + if (initialCapacity > 0) { + this.elementData = new Object[initialCapacity]; + } else if (initialCapacity == 0) { + this.elementData = EMPTY_ELEMENTDATA; + } else { + throw new IllegalArgumentException("Illegal Capacity: "+ + initialCapacity); + } + } + ③:也可以直接在构造方法中传一个集合 + public ArrayList(Collection c) { + elementData = c.toArray(); + if ((size = elementData.length) != 0) { + // c.toArray might (incorrectly) not return Object[] (see 6260652) + if (elementData.getClass() != Object[].class) + elementData = Arrays.copyOf(elementData, size, Object[].class); + } else { + // replace with empty array. + this.elementData = EMPTY_ELEMENTDATA; + } + } + +3.集合最大容量 int类型最大值减去本身储存需要8的大小 + /** + * The maximum size of array to allocate. + * Some VMs reserve some header words in an array. + * Attempts to allocate larger arrays may result in + * OutOfMemoryError: Requested array size exceeds VM limit + */ + private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8; + +4.add时注意ArrayList为非线程安全的集合,多线程访问时需要注意并发修改异常(modCount不一致导致)。 + 解决方案:CopyOnWriteArrayList 写时复制、读写分离 + +5. /** + * Removes all of the elements from this list. The list will + * be empty after this call returns. + */ + public void clear() { + modCount++; + + // clear to let GC do its work + for (int i = 0; i < size; i++) + elementData[i] = null; + + size = 0; + } + 循环遍历,置空每一位 + + +其他为常见方法以及不常用方法。 + +特点:ArrayList 对比 LinkedList +底层为数组结构,查询快,增上慢 底层为链表结构,查询慢,增删快 \ No newline at end of file diff --git a/week_01/57/HashMap-057.txt b/week_01/57/HashMap-057.txt new file mode 100644 index 0000000..a894eb7 --- /dev/null +++ b/week_01/57/HashMap-057.txt @@ -0,0 +1,126 @@ +1.底层为链表+数组+红黑树 +为何用红黑树而不用其他结构? 红黑树的查询及增删效率介于链表及二叉搜索树之间,综合考虑用红黑树。 + +2.初始化大小 16 +/** + * The default initial capacity - MUST be a power of two. + */ + static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16 + +3.最大容量 +static final int MAXIMUM_CAPACITY = 1 << 30; +3.扩容因子 +static final float DEFAULT_LOAD_FACTOR = 0.75f; + +4./** + * The bin count threshold for using a tree rather than list for a + * bin. Bins are converted to trees when adding an element to a + * bin with at least this many nodes. The value must be greater + * than 2 and should be at least 8 to mesh with assumptions in + * tree removal about conversion back to plain bins upon + * shrinkage. + */ + static final int TREEIFY_THRESHOLD = 8; + + /** + * The bin count threshold for untreeifying a (split) bin during a + * resize operation. Should be less than TREEIFY_THRESHOLD, and at + * most 6 to mesh with shrinkage detection under removal. + */ + static final int UNTREEIFY_THRESHOLD = 6; + +当容量大于8时底层为数组加红黑树结构,小于6时又会转为数组加链表结构 +5.hash运算 +static final int hash(Object key) { + int h; + return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16); + } + +6.四个构造方法 + ①: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 m) { + this.loadFactor = DEFAULT_LOAD_FACTOR; + putMapEntries(m, false); + } + +7.put()方法 +final V putVal(int hash, K key, V value, boolean onlyIfAbsent, + boolean evict) { + Node[] tab; Node p; int n, i; + if ((tab = table) == null || (n = tab.length) == 0) + n = (tab = resize()).length; + if ((p = tab[i = (n - 1) & hash]) == null) + tab[i] = newNode(hash, key, value, null); + else { + Node e; K k; + if (p.hash == hash && + ((k = p.key) == key || (key != null && key.equals(k)))) + e = p; + else if (p instanceof TreeNode) + e = ((TreeNode)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); + 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; + } + +8.清除元素:循环置空 +public void clear() { + Node[] tab; + modCount++; + if ((tab = table) != null && size > 0) { + size = 0; + for (int i = 0; i < tab.length; ++i) + tab[i] = null; + } + } +9.非线程安全,多线程操作可能出现并发修改异常 +解决方法:ConcurrentHashMap + +其他get put 遍历等为常用方法 \ No newline at end of file diff --git a/week_01/59/week01/ArrayList.md b/week_01/59/week01/ArrayList.md new file mode 100644 index 0000000..f9bc358 --- /dev/null +++ b/week_01/59/week01/ArrayList.md @@ -0,0 +1,103 @@ +一.ArrayList概述 + 1.ArrayList是可以动态增长和缩减的索引序列,它是基于数组实现的List类 + 2.ArrayList封装了一个动态分配的Object[]数组(elementData)初始化长度是10,和一个size属性代表当前ArrayList内元素的数量。 + 3.ArrayList是线程不安全的,当多条线程访问同一个ArrayList集合时,程序需要手动保证该集合的同步性。 +二.ArrayList继承结构 + 1.ArrayList先继承AbstractList类,让AbstractList去实现下面几个类,从而减少ArrayList的代码量。 + 1.AbstractList实现List,提供一些基本的属性和方法,方便对ArrayList进行元素的操作和判断。 + 2.AbstractList实现RandomAccess,可以随机访问List元素。 + 3.AbstractList实现Cloneable,可以克隆。 + 4.AbstractList实现Serializable,可以被序列化,能够从类变成字节流传输,然后还能从字节流变成原来的类。 +三.构造方法 + 1.初始化一个长度的构造方法 + public ArrayList(int initialCapacity) { + if (initialCapacity > 0) { + this.elementData = new Object[initialCapacity]; + } else if (initialCapacity == 0) { + this.elementData = EMPTY_ELEMENTDATA; + } else { + throw new IllegalArgumentException("Illegal Capacity: "+ + initialCapacity); + } + } + 2.无参构造方法会初始化一个长度为10的list + public ArrayList() { + this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA; + } + 3.传入一个集合的构造方法 + public ArrayList(Collection c) { + elementData = c.toArray();//转化成数组赋值 + if ((size = elementData.length) != 0) { + // c.toArray might (incorrectly) not return Object[] (see 6260652) + if (elementData.getClass() != Object[].class) + //每个集合的toarray()的实现方法不一样,所以需要判断一下, + //如果不是Object[].class类型,那么久需要使用ArrayList中的方法去改造一下。 + elementData = Arrays.copyOf(elementData, size, Object[].class); + } else { + // replace with empty array. + this.elementData = EMPTY_ELEMENTDATA; + } + } +四.核心方法 + 1.4个add方法 + public boolean add(E e) { //数组内加一个元素,size熟悉加1 + ensureCapacityInternal(size + 1); // Increments modCount!! + elementData[size++] = e; + return true; + } + + public void add(int index, E element) {//在一个特定的位置加一个元素 + + // 检查是否越界 + rangeCheckForAdd(index); + //检查是否需要扩容 + ensureCapacityInternal(size + 1); // Increments modCount!! + //把当前elementData的index位置到结尾长度的值,移到index+1往后的位置。空出index位置 + System.arraycopy(elementData, index, elementData, index + 1, + size - index); + elementData[index] = element; + size++; + } + public boolean addAll(Collection c) {//在当前list末尾加上一个集合 + Object[] a = c.toArray(); + int numNew = a.length; + ensureCapacityInternal(size + numNew); // Increments modCount + System.arraycopy(a, 0, elementData, size, numNew); + size += numNew; + return numNew != 0; + } + public boolean addAll(int index, Collection c) { + rangeCheckForAdd(index);//检查是否超出范围 + + Object[] a = c.toArray(); + int numNew = a.length; + //检查是否需要扩容 + ensureCapacityInternal(size + numNew); // Increments modCount + + int numMoved = size - index; + if (numMoved > 0) + System.arraycopy(elementData, index, elementData, index + numNew, + numMoved);//同理拷贝数组 + //拷贝中间数组 + System.arraycopy(a, 0, elementData, index, numNew); + size += numNew; + return numNew != 0; + } + 2.扩容数组 + private void grow(int minCapacity) { + // overflow-conscious code + int oldCapacity = elementData.length; + //右移相当于 除以2,扩大1.5倍 + int newCapacity = oldCapacity + (oldCapacity >> 1); + if (newCapacity - minCapacity < 0)//当list为空时,满足条件 + newCapacity = minCapacity; + 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);//copy数组,并改变容量 + } + 3.其他核心方法类似 +五.总结 + 1.arrayList区别于数组的地方在于能够自动扩展大小,其中关键的方法就是gorw()方法,每次扩容可以扩大1.5倍。 + 2.arrayList由于本质是数组,所以它在数据的查询方面会很快,而在插入删除这些方面,性能下降很多,有移动很多数据才能达到应有的效果。 + 3.arrayList实现了RandomAccess,所以在遍历它的时候推荐使用for循环。 \ No newline at end of file diff --git a/week_01/59/week01/HashMap.md b/week_01/59/week01/HashMap.md new file mode 100644 index 0000000..34fda08 --- /dev/null +++ b/week_01/59/week01/HashMap.md @@ -0,0 +1,26 @@ +一.HashMap 概述 + HashMap是一个键对应一个值的Map集合,它在查找和插入时的效率极高,理论上可以达到O(1). + HashMap是利用hash函数对所需要存入的值进行计算存储位置。 + Hash函数会计算不通值之间的时候,会发生冲突,就叫hash冲突。 + 哈希冲突的解决方案有多种:开放定址法(发生冲突,继续寻找下一块未被占用的存储地址)(顺序寻找下一个未被占用地址,效率低), + 再散列函数法(利用值的平方,再散列寻找未被占用的地址),链地址法,而HashMap即是采用了链地址法,也就是数组+链表的方式。 +二.HashMap 继承结构 + 1.HashMap继承了AbstractMap + 2.HashMap实现了Map,(可以使用一些Map的操作) + 3.HashMAp实现了 Cloneable, (可被克隆) + 4.HashMap实现了Serializable,(可序列化) +三.构造方法 + // 1.无参构造方法、 + // 构造一个空的HashMap,初始容量为16,负载因子为0.75 + public HashMap() { + this.loadFactor = DEFAULT_LOAD_FACTOR; // all other fields defaulted + } +// 2.构造一个初始容量为initialCapacity,负载因子为0.75的空的HashMap, + public HashMap(int initialCapacity) { + this(initialCapacity, DEFAULT_LOAD_FACTOR); + } + /// 等待更新 +四.核心方法 + +五.总结 + \ No newline at end of file diff --git a/week_01/59/week01/LinkedList.md b/week_01/59/week01/LinkedList.md new file mode 100644 index 0000000..dee990d --- /dev/null +++ b/week_01/59/week01/LinkedList.md @@ -0,0 +1,111 @@ +一.LinkedList概述 + 1.LinkedList基于双向链表适用于增删频繁且查询不频繁的场景. + 2.线程不安全的且适用于单线程(这点和ArrayList很像)。 + 3.可以用LinkedList来实现栈和队列. +二.LinkedList继承结构 + 1.LinkedList继承了AbstractSequentialList + 2.LinkedList实现了List,(集合功能) + 3.LinkedList实现了Deque (双端队列功能,模拟栈和队列) + 4.LinkedList实现了Cloneable(克隆数组) + 5.LinkedList实现了Serializable(序列化) +三.构造方法 + transient int size = 0; + transient Node first; + transient Node last; + private static class Node { //内部类,定义一个节点 + E item;//当前元素的value + Node next;//下一个元素。null代表尾元素 + Node prev;//上一个元素。null代表首原素 + //定义一个节点 + Node(Node prev, E element, Node next) { + this.item = element; + this.next = next; + this.prev = prev; + } + } + //无参构造函数 + public LinkedList() { + } + //添加一个集合 + public LinkedList(Collection c) { + this(); + addAll(c); + } + +四.核心方法 + //在头部增加一个节点 + private void linkFirst(E e) { + final Node f = first; + final Node newNode = new Node<>(null, e, f); + first = newNode; + if (f == null) + last = newNode; + else + f.prev = newNode; + size++; + modCount++; + } + //在尾部增加一个节点 + void linkLast(E e) { + final Node l = last; + final Node newNode = new Node<>(l, e, null); + last = newNode; + if (l == null) + first = newNode; + else + l.next = newNode; + size++; + modCount++; + } + //在节点succ之前加入一个节点 + void linkBefore(E e, Node succ) { + // assert succ != null; + final Node pred = succ.prev; + //在pred和succ之间插入newNode + final Node newNode = new Node<>(pred, e, succ); + succ.prev = newNode; + if (pred == null) //是否是头元素 + first = newNode; + else + pred.next = newNode; + size++; //计数加1 + modCount++; + } + //删除头元素,并返回头元素的值 + private E unlinkFirst(Node f) { + // assert f == first && f != null; + final E element = f.item; + final Node next = f.next; + f.item = null; + f.next = null; // help GC + first = next; + if (next == null) + last = null; + else + next.prev = null; + size--; + modCount++; + return element; + } + //索引第index个节点 + Node node(int index) { + //assert isElementIndex(index); + if (index < (size >> 1)) { + Node x = first; + for (int i = 0; i < index; i++) + x = x.next; + return x; + } else { + Node x = last; + for (int i = size - 1; i > index; i--) + x = x.prev; + return x; + } + } + 2.其他方法类似 +五.总结 + 1.LinkedList方法内部实现是链表,且内部有fist与last指针操作数据 + 2.LinkedList线程不安全的,因为其内部添加、删除、等操作,没有进行同步操作。 + 3.LinkedList增删元素速度较快。 + 4.遍历效率(快-慢): + Iterator迭代 > for循环 \ No newline at end of file diff --git "a/week_01/60/01_ArrayList/01-ArrayList\345\205\263\347\263\273\347\261\273\345\233\276.png" "b/week_01/60/01_ArrayList/01-ArrayList\345\205\263\347\263\273\347\261\273\345\233\276.png" new file mode 100644 index 0000000..2471721 Binary files /dev/null and "b/week_01/60/01_ArrayList/01-ArrayList\345\205\263\347\263\273\347\261\273\345\233\276.png" differ diff --git "a/week_01/60/01_ArrayList/02_ArrayList\347\232\204grow\346\226\271\346\263\225\346\211\251\345\256\271\346\265\201\347\250\213.png" "b/week_01/60/01_ArrayList/02_ArrayList\347\232\204grow\346\226\271\346\263\225\346\211\251\345\256\271\346\265\201\347\250\213.png" new file mode 100644 index 0000000..881664f Binary files /dev/null and "b/week_01/60/01_ArrayList/02_ArrayList\347\232\204grow\346\226\271\346\263\225\346\211\251\345\256\271\346\265\201\347\250\213.png" differ diff --git a/week_01/60/01_ArrayList/ArrayList-060.md b/week_01/60/01_ArrayList/ArrayList-060.md new file mode 100644 index 0000000..359fa3e --- /dev/null +++ b/week_01/60/01_ArrayList/ArrayList-060.md @@ -0,0 +1,497 @@ +[TOC] + +- *本文对于`ArrayList`的源码阅读记录基于`JDK1.8`,个人浅见,如有不妥,敬请指正。* +- *文中 "`//TODO`"处代表个人暂时未懂或标示之后会学习的地方,如有高见,敬请指教。* + +# 1.ArrayList底层原理 + +## 1.1 ArrayList的UML + +![01-ArrayList关系类图](01-ArrayList关系类图.png) + +```java +public class ArrayList extends AbstractList + implements List, RandomAccess, Cloneable, java.io.Serializable{...} +``` + +- 继承了AbstractList; +- 实现了List接口; +- 实现了RandomAccess接口,支持快速随机访问,通过下标序号进行快速访问; +- 实现了Cloneable接口,支持克隆; +- 实现了Serializable接口,支持序列化; + +## 1.2 ArrayList的数据结构 + +```java +/** + * Resizable-array implementation of the List interface. Implements + * all optional list operations, and permits all elements, including + * null. +``` + +​ 如上,通过ArrayList的源码文档可知,ArrayList是可动态调整容量的数组(`Resizable-array`),ArrayList实现了List的所有操作并允许包括null在内的所有元素(ArrayList中存放的是Object[]数组,意味着ArrayList可以存放任何继承自Object的对象)。 + +## 1.3 ArrayList中定义的常量、变量以及构造函数 + +```java + //序列版本号 + private static final long serialVersionUID = 8683452581122892189L; + + /** + * 默认初始化容量 + */ + private static final int DEFAULT_CAPACITY = 10; + + /** + * 用于空实例的共享空数组实例。 + */ + private static final Object[] EMPTY_ELEMENTDATA = {}; + + /** + * JDK 1.8中添加的,也是一个空数组,与EMPTY_ELEMENTDATA区分开来 + * (当调用无参构造方法时默认复制这个空数组) + */ + private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {}; + + /** + * 保存添加到数组中的元素; + * elementData是一个动态数组; + * 通过public ArrayList(int initialCapacity){}构造函数时elementData=initialCapacity; + * 通过public ArrayList(){}构造函数时elementData=DEFAULT_CAPACITY=10; + */ + transient Object[] elementData; // 非私有以简化嵌套类访问 + + /** + * 数组中元素的大小 + * + * @serial + */ + private int size; + + /** + * 指定初始化容量大小的构造函数 + * + * @param initialCapacity 数组的初始化容量大小 + * @throws IllegalArgumentException 输入参数为负数时抛出异常 + */ + public ArrayList(int initialCapacity) { + if (initialCapacity > 0) { + this.elementData = new Object[initialCapacity]; + } else if (initialCapacity == 0) { + this.elementData = EMPTY_ELEMENTDATA; + } else { + throw new IllegalArgumentException("Illegal Capacity: "+ + initialCapacity); + } + } + + /** + * 无参构造函数构造,初始化容量为0 + */ + public ArrayList() { + this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA; + } + + /** + * 构造一个包含指定集合的元素的列表,其顺序由集合的迭代器返回 + * + * @param c 将其元素放入此列表的集合 + * @throws NullPointerException 传入集合参数为null时抛出异常 + */ + public ArrayList(Collection c) { + elementData = c.toArray(); + if ((size = elementData.length) != 0) { + // c.toArray might (incorrectly) not return Object[] (see 6260652) + if (elementData.getClass() != Object[].class) + elementData = Arrays.copyOf(elementData, size, Object[].class); + } else { + // replace with empty array. + this.elementData = EMPTY_ELEMENTDATA; + } + } + + /** + * 数组最大容量限定; + * 预留容量空间,避免内存溢出;为什么要预留?后面会说 + */ + private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8; +``` + +### 1.3.1 ArrayList的默认容量是多少? + +```java + private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {}; + + public ArrayList() { + this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA; + } +``` + +​ 通过ArrayList的无参构造函数可以看出,初始化时ArrayList的默认容量是一个空数组,相当于默认容量为0; + +注:JDK1.8之前默认容量是10. + +### 1.3.2 elementData为什么要用transient修饰? + +​ 首先说一下transient关键字:Java的serialization提供了一种持久化对象的机制,当持久化对象时,可能有一个特殊的对象数据成员,不想用serialization机制来保存,为了在一个特定对象的一个域上关闭serialization,可以在这个域前加上关键字transient。当一个对象被序列化时,transient型变量的值不包括在序列化的表示中,非transient型的变量是被包括进去的。(HashMap、ArrayList中都使用了transient关键字) + +- transient修饰的变量不能被序列化; + +- transient只作用于实现 Serializable 接口; + +- transient只能用来修饰普通成员变量字段; + +- 不管有没有 transient 修饰,静态变量都不能被序列化; + +​ ArrayList是可序列化的类,elementdata是ArrayList用来存储元素的成员,用transient关键字修饰elementdata,真的就意味着elementdata不能序列化了吗?而且这样反序列化后的ArrayList也会把原来存储的元素弄丢? + + 如果继续看ArrayList的源码,会发现ArrayList会调用自己实现的writeObject()和readObject()方法进行序列化和反序列化,之所以这样,是因为elementdata是一个缓存数组,通常会预留一些容量,等到容量不足时再扩容,这些预留的容量空间里没有实际存储元素。所以采用transient关键字保证elementdata不会被serialization提供的持久化机制保存(序列化),再加上ArrayList自己实现的序列化和反序列方法,这样就可以保证ArrayList序列化时只会序列化实际存储的那些元素,而不包含预留容量中空的存储空间,从而节省序列化反序列化的时间和空间。 + +参考:[ArrayList中elementData为什么被transient修饰?](https://blog.csdn.net/zero__007/article/details/52166306) + +### 1.3.3 DEFAULTCAPACITY_EMPTY_ELEMENTDATA和EMPTY_ELEMENTDATA的区别? + +//TODO + +### 1.3.4 ArrayList三种构造函数 + +1.无参构造函数:无参构造函数时,创建一个容量为0的数组; + +```java + public ArrayList() { + this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA; + } +``` + +```java + ArrayList arrayList = new ArrayList(); +``` + +2.指定容量构造函数 + +```java + public ArrayList(int initialCapacity) { + if (initialCapacity > 0) { + this.elementData = new Object[initialCapacity]; + } else if (initialCapacity == 0) { + this.elementData = EMPTY_ELEMENTDATA; + } else { + throw new IllegalArgumentException("Illegal Capacity: "+ + initialCapacity); + } + } +``` + +``` + ArrayList arrayList = new ArrayList(20); +``` + +- `initialCapacity > 0`时,ArrayList容量为给定的`initialCapacity`; +- `initialCapacity == 0` 时,ArrayList容量为`EMPTY_ELEMENTDATA = 0`; +- `initialCapacity < 0` 时,抛出异常:`"Illegal Capacity: "+ initialCapacity`; + +3.构造一个包含指定集合的元素的列表,按集合的迭代器返回元素的顺序排列。 + +```java +public ArrayList(Collection c) { + elementData = c.toArray(); + if ((size = elementData.length) != 0) { + // c.toArray might (incorrectly) not return Object[] (see 6260652) + if (elementData.getClass() != Object[].class) + elementData = Arrays.copyOf(elementData, size, Object[].class); + } else { + // replace with empty array. + this.elementData = EMPTY_ELEMENTDATA; + } + } +``` + +``` + ArrayList arrayList = new ArrayList(Arrays.asList(new String[]{"1","2","3"})); +``` + +1. 将传入集合`c`拷贝给`elementData`。 + +```java + elementData = c.toArray(); + | + ↓ + 根据toArray()进入Arrays查看该方法源码 + @Override + public Object[] toArray() { + return a.clone(); + } +``` + +​ 2. 对得到 `c` 拷贝后的`elementData`进行判断: + +​ 2.1传入集合为空时:指定ArrayList容量为`EMPTY_ELEMENTDATA = 0`; + +​ 2.2传入集合不为空时且类型与Object[]不相同时,进行一次`Arrays.copyOf()`,将源数组中的元素类型向上转型后将复制的新数组返回给elementData; + +### 1.3.5 ArrayList为什么规定MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8 ? + +```java + /** + * The maximum size of array to allocate. + * Some VMs reserve some header words in an array. + * Attempts to allocate larger arrays may result in + * OutOfMemoryError: Requested array size exceeds VM limit + */ + private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8; +``` + +​ 数组对象也是标准的JAVA对象,在JVM(HotSpot)的堆内存中的实际存储方式和格式也满足oop-class二分模型。之所以`MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8` 是因为需要预留一定长度来保存对象元数据的信息,在ArrayList中预留的8字节是用来存储数组长度,因为ArrayList不能计算自己的长度。 + +​ 那为什么是`Integer.MAX_VALUE - 8`呢?是因为JVM要求对象的大小必须是8字节的整数倍,因此当“对象头+实例数据”大小不满足8字节的整数倍时,就需要补齐满足前述条件。 + +> “ +> +> 按照8字节对齐,是底层CPU数据总线读取内存数据的要求。 +> +> 通常CPU按照字长来读取数据,一个数据若不对齐则可能需要CPU读取两次; +> +> 若进行了对齐,则一次性即可读出目标数据,这将会大大节省CPU资源,因此对象大小需要补齐。 +> +> ” + +//TODO *JAVA对象在堆内存中的详情?* + +参考: + +- [Why the maximum array size of ArrayList is Integer.MAX_VALUE - 8?](https://stackoverflow.com/questions/35756277/why-the-maximum-array-size-of-arraylist-is-integer-max-value-8) +- [如何计算Java对象占堆内存中的大小 ](http://www.sohu.com/a/306845134_505779) + +# 2.ArrayList如何添加元素? + +​ 向ArrayList中添加元素,在所难免会涉及到ArrayList扩容的情况,在记录添加元素方法前先看看ArrayList的扩容机制。 + +## 2.1 ArrayList的扩容机制? + +#### 2.1.1 初始判断扩容 + +```java + /** + * 比较传入容量的大小 + * 这个方法只有在首次调用时会用到 + **/ + private static int calculateCapacity(Object[] elementData, int minCapacity) { + // 判断ArrayList是否刚初始化 + if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) { + return Math.max(DEFAULT_CAPACITY, minCapacity); + } + return minCapacity; + } + // 校验容量的大小 + private void ensureCapacityInternal(int minCapacity) { + ensureExplicitCapacity(calculateCapacity(elementData, minCapacity)); + } + + private void ensureExplicitCapacity(int minCapacity) { + modCount++;//记录修改,modCount的作用是在List迭代器遍历时用作线程安全检查 + + // 如果传入的值大于初始长度,则调用grow()方法进行扩容 + if (minCapacity - elementData.length > 0) + grow(minCapacity); + } +``` + +- 初始化判断会判断ArrayList是否刚初始化,如果成立,则将ArrayList扩容为默认容量`DEFAULT_CAPACITY = 10`; +- 若判断 `minCapacity` 和 `DEFAULT_CAPACITY` 的大小,在二者的最大值内还有可用空间,则取二者中的最大值为ArrayList的容量; +- 当`minCapacity` 和`DEFAULT_CAPACITY`的最大值容量都没有可用空间了,则调用`grow()`进行扩容; + +#### 2.1.2 调用`grow()`扩容 + +```java + /** + * 对列表进行扩容,以确保它至少可以容纳最小容量参数指定的元素。 + * + * @param minCapacity 传入所需的最小容量 + */ + private void grow(int minCapacity) { + // 记录原容量的大小 + int oldCapacity = elementData.length; + // 设置新容量,新容量大小为原容量的1.5倍(右移一位就是1/2) + int newCapacity = oldCapacity + (oldCapacity >> 1); + // 判断新容量是否满足需求,若扩容后仍不够用,将传入最小容量大小复制给新容量 + if (newCapacity - minCapacity < 0) + newCapacity = minCapacity; + // 判断新容量是否超出最大长度限制,如果超出,进入hugeCapacity()处理 + if (newCapacity - MAX_ARRAY_SIZE > 0) + newCapacity = hugeCapacity(minCapacity); + // 将原数组的数据复制至新数组, ArrayList的底层数组引用指向新数组 + elementData = Arrays.copyOf(elementData, newCapacity); + } + + private static int hugeCapacity(int minCapacity) { + if (minCapacity < 0) // overflow + throw new OutOfMemoryError(); + return (minCapacity > MAX_ARRAY_SIZE) ? + Integer.MAX_VALUE : + MAX_ARRAY_SIZE; + } +``` + +- ArrayList调用grow()扩容后容量变为原来1.5倍; + +![02_ArrayList的grow方法扩容流程](02_ArrayList的grow方法扩容流程.png) + +//TODO *`Arrays.copyOf()`详情?* + +## 2.2 将指定元素添加到列表的尾部 + +```java + /** + * Appends the specified element to the end of this list. + * + * @param e element to be appended to this list + * @return true (as specified by {@link Collection#add}) + */ + public boolean add(E e) { + ensureCapacityInternal(size + 1); // Increments modCount!! + elementData[size++] = e; + return true; + } +``` + +​ 按照前文的ArrayList初始化扩容,的到一个容量为10的ArrayList集合。 + +## 2.3 将指定元素添加到列表的指定位置 + +```java + /** + * 将指定的元素插入指定位置中。 + * 将当前在该位置的元素(如果有的话)和任何后续元素向右移动(向其索引添加一个) + * @param index 指定元素插入的索引 + * @param element 指定元素 + * @throws IndexOutOfBoundsException {@inheritDoc} + */ + public void add(int index, E element) { + rangeCheckForAdd(index);//判断索引是否异常 + + ensureCapacityInternal(size + 1); // 判断是否需要扩容 + //将指定下标空出 具体作法就是index及其后的所有元素后移一位 + System.arraycopy(elementData, index, elementData, index + 1, + size - index); + // 将要添加元素赋值到空出来的指定索引处 + elementData[index] = element; + // 长度+1 + size++; + } + + /** + * A version of rangeCheck used by add and addAll. + * add和addAll使用的rangeCheck版本。 + */ + private void rangeCheckForAdd(int index) { + if (index > size || index < 0) + throw new IndexOutOfBoundsException(outOfBoundsMsg(index)); + } +``` + +​ 值得注意的是,对索引处及其后的元素使用了`System.arraycopy()`方法,该方法源码如下: + +```java + /** + * 从指定的源数组开始复制一个数组,该数组从指定位置开始复制到目标数组的指定位置。 + * Object src 源数组对象 + * int srcPos 源数组的起始位置 + * Object dest 目标数组对象 + * int destPos 目标数组的起始位置 + * int length 要复制的元素数量 + **/ + public static native void arraycopy(Object src, int srcPos, + Object dest, int destPos, + int length); +``` + +​ 该方法是一个数组拷贝的方法,使用`native`修饰,一般是由其它语言实现的(如C、C++)。 + +# 3.ArrayList的数组拷贝是怎么实现的? + +```java + /** + * Returns a shallow copy of this ArrayList instance. (The + * elements themselves are not copied.) + * + * @return a clone of this ArrayList instance + */ + public Object clone() { + try { + ArrayList v = (ArrayList) super.clone(); + v.elementData = Arrays.copyOf(elementData, size);//传入源数组对象、目标数组长度 + v.modCount = 0; + return v; + } catch (CloneNotSupportedException e) { + // this shouldn't happen, since we are Cloneable + throw new InternalError(e); + } + } +``` + +​ 从源码可看出其实ArrayList的拷贝最关键是调用了`Arrays.copyOf()`方法,该方法源码如下: + +```java + public static T[] copyOf(T[] original, int newLength) { + return (T[]) copyOf(original, newLength, original.getClass()); + } + + public static T[] copyOf(U[] original, int newLength, Class newType) { + @SuppressWarnings("unchecked") + T[] copy = ((Object)newType == (Object)Object[].class) + ? (T[]) new Object[newLength] + : (T[]) Array.newInstance(newType.getComponentType(), newLength); + System.arraycopy(original, 0, copy, 0, + Math.min(original.length, newLength)); + return copy; + } +``` + +- 首先,创建一个和**源数组元素**相同类型的新数组; +- 然后调用`System.arraycopy()`,并且在该方法最后一个参数(要复制的元素数量)中对**源数组长度**和**目标数组长度**进行取最小值判断; + +## 3.1 ArrayList中的拷贝是深拷贝还是浅拷贝呢? + +```java + ArrayList v = (ArrayList) super.clone(); +``` + +​ 是浅拷贝:在ArrayList的clone()方法中,第一句可以看到调用了`super.clone()`方法复制了一个全新的对象并将其赋值给`V`。由于`java.lang.Object.clone()`只是一种浅拷贝,所以V的`elementData`引用的还是当前ArrayList的`elementData`引用,这就意味着在`V`上进行操作,会影响原来的ArrayList值。 + +## 3.2 如何复制某个ArrayList到另一个ArrayList中去? + +- 浅拷贝 + - 调用`ArrayList.clone()`; + - 使用ArrayList构造方法; + +```java + ArrayList newArray = oldArray.clone(); + + ArrayList newArray = new ArrayList(oldArray); +``` + +- 深拷贝 + - 使用`Collection.copy()`; + + + + + + + + + + + + + + + + + + + + + + + diff --git "a/week_01/60/02_HashMap/02-HashMap\345\205\263\347\263\273\347\261\273\345\233\276.png" "b/week_01/60/02_HashMap/02-HashMap\345\205\263\347\263\273\347\261\273\345\233\276.png" new file mode 100644 index 0000000..4d76ab6 Binary files /dev/null and "b/week_01/60/02_HashMap/02-HashMap\345\205\263\347\263\273\347\261\273\345\233\276.png" differ diff --git "a/week_01/60/02_HashMap/02-HashMap\346\225\260\346\215\256\347\273\223\346\236\204.png" "b/week_01/60/02_HashMap/02-HashMap\346\225\260\346\215\256\347\273\223\346\236\204.png" new file mode 100644 index 0000000..b4f3c9e Binary files /dev/null and "b/week_01/60/02_HashMap/02-HashMap\346\225\260\346\215\256\347\273\223\346\236\204.png" differ diff --git a/week_01/60/02_HashMap/HashMap-060.md b/week_01/60/02_HashMap/HashMap-060.md new file mode 100644 index 0000000..a74d2fe --- /dev/null +++ b/week_01/60/02_HashMap/HashMap-060.md @@ -0,0 +1,188 @@ +[TOC] + + + +# 1.HashMap底层原理 + +## 1.1 HashMap的UML + +![HashMap的UML](02-HashMap关系类图.png) + +- HashMap继承了AbstractMap; +- HashMap实现了Cloneable接口,支持克隆; +- HashMap实现了Serializable接口,支持序列化; + +## 1.2 HashMap的数据结构 + +​ HashMap底层的数据是数组,被称为哈希桶,每个桶存放的是链表,链表中的每个节点,就是HashMap中的每个元素。综合起来HashMap数据结构=**数组+链表+红黑树**。 + +![02-HashMap数据结构](02-HashMap数据结构.png) + +### 1.2.1 为什么用数组+链表? + +- **数组**用来确定元素所在桶位置,利用元素的key的hash值对数组长度取模得到。 +- **链表**用来解决hash冲突,当出现hash值一样的情形时,在数组对应位置形成一条链表。 + +## 1.3 HashMap源码中的常量、变量 + +```java +/** + * 默认初始容量(必须是2的幂) + */ + static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16 + + /** + * 最大容量(2的30次方) + */ + static final int MAXIMUM_CAPACITY = 1 << 30; + + /** + * 默认加载因子 + */ + static final float DEFAULT_LOAD_FACTOR = 0.75f; + + /** + * 链表转换为红黑树的阈值 + */ + static final int TREEIFY_THRESHOLD = 8; + + /** + * 树的链表还原阈值 + */ + static final int UNTREEIFY_THRESHOLD = 6; + + /** + * 哈希表的最小树形化容量 + */ + static final int MIN_TREEIFY_CAPACITY = 64; +``` + +### 1.3.1 HashMap默认初始化容量(DEFAULT_INITIAL_CAPACITY)为什么是2的幂? + +​ 在HashMap的存取方法中,需要对元素所在数组索引位置进行定位,在`put()`方法中有`i = (n - 1) & hash`,在`get()`方法中有`(n - 1) & hash`(put()、get()详情在后面),索引公式为`(n - 1) & hash`,当初始化容量n大小是2的幂时,索引公式等价于`n%hash`。定位下标一般用取余法,这里为什么不用取余呢? + +- 与运算(&)比取余(%)运算效率高; + +- 求余运算: a % b就相当与a-(a / b)*b 的运算; + +- 与运算: 一个指令就搞定 + + 所以,默认初始化容量大小定义为16,也就是2的幂,是为了使用更高效的与运算。 + +​ 还有一个原因就是在进行hash之后的位运算时,2的幂能保证计算的位数都是1,这样最终的下标就完全由key的hash值来决定也就更好的解决了hash碰撞。 + +### 1.3.2 HashMap默认初始化容量(DEFAULT_INITIAL_CAPACITY)为什么是16? + +​ 未论证:之所以取16是对效率和内存的一个平衡,如果太小,扩容会更频繁,如果太大,又会多占用内存空间。 + +参考:[Choosing the hash map's capacity](https://pzemtsov.github.io/2015/12/14/choosing-the-hash-maps-capacity.html) + +### 1.3.3 HashMap的最大容量(MAXIMUM_CAPACITY)为什么是1 << 30 ? + +​ int占4字节*一个字节占8位=32位整型,在二进制数中,最左边一位是符号位,用来表示正负,所以HashMap最大容量是1 << 30,而不是31次方或32次方。 + +### 1.3.4 HashMap的默认加载因子(DEFAULT_LOAD_FACTOR)为什么是0.75f ? + +```java + *

As a general rule, the default load factor (.75) offers a good + * tradeoff between time and space costs. Higher values decrease the + * space overhead but increase the lookup cost (reflected in most of + * the operations of the HashMap class, including + * get and put). The expected number of entries in + * the map and its load factor should be taken into account when + * setting its initial capacity, so as to minimize the number of + * rehash operations. If the initial capacity is greater than the + * maximum number of entries divided by the load factor, no rehash + * operations will ever occur. + * +译文:通常,默认负载因子(.75)在时间和空间成本之间提供了一个很好的折衷方案。 较高的值会减少空间开销,但会增加查找成本(在HashMap类的大多数操作中都得到体现,包括get和put)。 设置映射表的初始容量时,应考虑映射中的预期条目数及其负载因子,以最大程度地减少重新哈希操作的数量。 如果初始容量大于最大条目数除以负载因子,则将不会进行任何哈希操作。 + +``` + +​ 加载因子表示哈希表的填满程度,和HashMap的扩容紧密相关,根据HashMap源码中的文档(如上)可知,之所以选择0.75f作为默认加载因子,是`between time and space`在时间成本和空间成本上取的一个相对折中的方案。 + +​ 有一种解释是根据数学公式来推测的,一个桶空和非空的概率为0.5,通过牛顿二项式等数学计算,得到这个加载因子的值为`log(2)≈0.693`,而最后选择0.75也许是因为0.75是较为接近这个结果且和默认容量相乘为一个整数12(1`6*0.75=12`) + +​ 下面这个假设推导大佬们可以看看: + +`让s代表大小,n代表增加的键数。使用二项式定理,存储桶为空的概率为:` +$$ +P(0) = C(n, 0) * (1/s)^0 * (1 - 1/s)^(n - 0) +$$ +`因此,如果少于` +$$ +log(2)/log(s/(s - 1)) keys +$$ +`随着s达到无穷大,并且如果添加的键数达到P(0)= .5,则n / s迅速接近log(2):` +$$ +lim (log(2)/log(s/(s - 1)))/s as s -> infinity = log(2) ~ 0.693... +$$ +参考:[What is the significance of load factor in HashMap?](https://stackoverflow.com/questions/10901752/what-is-the-significance-of-load-factor-in-hashmap) + +### 1.3.5 链表转换红黑树的阀值(TREEIFY_THRESHOLD)为什么是8? + +```java + * Because TreeNodes are about twice the size of regular nodes, we + * use them only when bins contain enough nodes to warrant use + * (see TREEIFY_THRESHOLD). And when they become too small (due to + * removal or resizing) they are converted back to plain bins. In + * usages with well-distributed user hashCodes, tree bins are + * rarely used. Ideally, under random hashCodes, the frequency of + * nodes in bins follows a Poisson distribution + * (http://en.wikipedia.org/wiki/Poisson_distribution) with a + * parameter of about 0.5 on average for the default resizing + * threshold of 0.75, although with a large variance because of + * resizing granularity. Ignoring variance, the expected + * occurrences of list size k are (exp(-0.5) * pow(0.5, k) / + * factorial(k)). The first values are: + * + * 0: 0.60653066 + * 1: 0.30326533 + * 2: 0.07581633 + * 3: 0.01263606 + * 4: 0.00157952 + * 5: 0.00015795 + * 6: 0.00001316 + * 7: 0.00000094 + * 8: 0.00000006 + * more: less than 1 in ten million +``` + +`“` + +​ 理想情况下,在随机哈希码情况下,对于随机0.75的加载因子,桶中节点的分布频率服从参数为0.5的泊松分布,即使粒度调整会产生较大方差。 + +​ 由源码中给出如上的参照表可知,当链表中元素个数为8时概率已经非常小了,所以链表转换红黑树的阈值选择了8。 + +`”` + +### 1.3.6 一个树的链表还原阈值(UNTREEIFY_THRESHOLD)为什么是6? + +​ 为了防止链表和树之间频繁的转换,如果是7的话,假设一个HashMap在临界点7频繁的插入和删除元素,链表中元素个数始终在`[7,8]`中徘徊,就会造成树和链表频繁的转换,降低HashMap操作的效率。 + +### 1.3.7 哈希表的最小树形化容量(MIN_TREEIFY_CAPACITY)为什么是64? + +​ 容量低于64时,哈希碰撞的几率比较大,而这个时候出现长链表的可能性会稍微大一些,这种原因下产生的长链表,应该优先选择扩容而避免不必要的数化。 + +参考:[面试加分项-HashMap源码中这些常量的设计目的-FROM掘金-Jay_huaxiao](https://juejin.im/post/5d7195f9f265da03a6533942#heading-0) + + + + + + + + + + + + + + + + + + + + + diff --git "a/week_01/61/ArrayList\346\272\220\347\240\201\351\230\205\350\257\273.md" "b/week_01/61/ArrayList\346\272\220\347\240\201\351\230\205\350\257\273.md" new file mode 100644 index 0000000..8684250 --- /dev/null +++ "b/week_01/61/ArrayList\346\272\220\347\240\201\351\230\205\350\257\273.md" @@ -0,0 +1,244 @@ +#

ArrayList源码分析
+ +- 以下分析基于JDK1.8 + +## ArrayList 简介 + + ArrayList是可以动态扩容和动态删除冗余容量的索引序列,基于数组实现的集合,是常用的Java集合之一。 + +``` + ArrayLis继承自抽象类AbstractList实现了List接口等 +``` + +和Vector不同,ArrayList中的操作不是线程安全的!所以,建议在单线程中才使用ArrayList,而在多线程中可以选择Vector或者CopyOnWriteArrayList,或者使用Collections工具类的synchronizedList方法将其包装 + +## ArrayList分析 + +1、属性分析 + +- ```java + // 序列号ID,ArrayList实现了Serializable接口因此是可在网络中传输 + private static final long serialVersionUID = 8683452581122892189L; + ``` + +- ```java + // //默认初始容量 + private static final int DEFAULT_CAPACITY = 10; + ``` + +- ```java + // 一个空对,当用户指定ArrayList容量为0时,返回该数组 + private static final Object[] EMPTY_ELEMENTDATA = {}; + ``` + +- ```java + // 一个空对象,如果使用默认构造函数创建,则默认对象内容默认是该值 + private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {}; + ``` + +- ```java + // 动态数组的实际大小 ,默认为0 + private int size; + ``` + +- ```java + // 最大数组容量Integer -8 + private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8; + ``` + + + +2、构造方法分析 + +```java +// 传入初始容量 initialCapacity +public ArrayList(int initialCapacity) { + if (initialCapacity > 0) { + this.elementData = new Object[initialCapacity]; + } else if (initialCapacity == 0) { + this.elementData = EMPTY_ELEMENTDATA; + } else { + throw new IllegalArgumentException("Illegal Capacity: "+ + initialCapacity); + } + } + + //无参构造 DEFAULTCAPACITY_EMPTY_ELEMENTDATA=={} 注意此时初始容量是0,而不是的 10 +//当元素第一次被加入时,扩容至默认容量 10 + public ArrayList() { + this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA; + } + + // 创建一个包含collection或其子类的ArrayList 要放入 ArrayList 中的集合,其内元素将会全部添加到新建的 ArrayList 实例中 + public ArrayList(Collection c) { + // 集合传化成Object[]数组 + elementData = c.toArray(); + // 转化后的数组长度赋给当前ArrayList的size,并判断是否为0 + if ((size = elementData.length) != 0) { + // c.toArray might (incorrectly) not return Object[] (see 6260652) + if (elementData.getClass() != Object[].class) + // 若 c.toArray() 返回的数组类型不是 Object[],则利用 Arrays.copyOf(); 来构造一个大小为 size 的 Object[] 数组 + elementData = Arrays.copyOf(elementData, size, Object[].class); + } else { + // replace with empty array. + // 替换空数组 + this.elementData = EMPTY_ELEMENTDATA; + } + } + + +``` + +3、常见方法分析 + +- add方法 + +```java +public boolean add(E e) { + // 赋值初始长度或者扩容,新增元素,当前实际size+1的长度 + ensureCapacityInternal(size + 1); // Increments modCount!! + elementData[size++] = e; + return true; + } +// 确保elemenData数组有合适的大小- 如果元素为空,则复制长度默认为10 或者更大 + private void ensureCapacityInternal(int minCapacity) { + if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) { + minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity); + } + + ensureExplicitCapacity(minCapacity); + } +// 保证elemenData数组有合适的大小 +private void ensureExplicitCapacity(int minCapacity) { + //记录修改次数,迭代中不一致会触发fail-fast机制,因此在遍历中删除元素的正确做法应该是使用Iterator.remove() + modCount++; + + // overflow-conscious code + if (minCapacity - elementData.length > 0) + grow(minCapacity); + } +// 扩容 + private void grow(int minCapacity) { + // overflow-conscious code + int oldCapacity = elementData.length; + int newCapacity = oldCapacity + (oldCapacity >> 1); + if (newCapacity - minCapacity < 0) + newCapacity = minCapacity; + if (newCapacity - MAX_ARRAY_SIZE > 0) + newCapacity = hugeCapacity(minCapacity); + //拷贝扩容 + elementData = Arrays.copyOf(elementData, newCapacity); + } + // 如果小于0 就报错,如果大于最大值 则取最大值 + private static int hugeCapacity(int minCapacity) { + if (minCapacity < 0) // overflow + throw new OutOfMemoryError(); + return (minCapacity > MAX_ARRAY_SIZE) ? + Integer.MAX_VALUE : + MAX_ARRAY_SIZE; + } + + + // 给指定下标,添加元素 + public void add(int index, E element) { + rangeCheckForAdd(index);//检查是否越界 + //赋值初始长度 或者扩容 + ensureCapacityInternal(size + 1); // Increments modCount!! + System.arraycopy(elementData, index, elementData, index + 1, + size - index); + elementData[index] = element; + size++; + } + +``` + +- remove方法 + + ```java + //根据指定下标 删除元素 + public E remove(int index) { + rangeCheck(index);//检查越界 + + modCount++; + E oldValue = elementData(index); + //将数组elementData中index位置之后的所有元素向前移一位 + int numMoved = size - index - 1; + if (numMoved > 0) + System.arraycopy(elementData, index+1, elementData, index, + numMoved); + elementData[--size] = null; // clear to let GC do its work 将原数组最后一个位置置为null,由GC回收 + + return oldValue; + } + + // 根据指定元素 删除元素  + public boolean remove(Object o) { + // ArrayList中允许存放null,因此下面通过两种情况来分别处理。 + if (o == null) { + for (int index = 0; index < size; index++) + if (elementData[index] == null) { + // 私有的移除方法,跳过index参数的边界检查以及不返回任何值 + fastRemove(index); + return true; + } + } else { + for (int index = 0; index < size; index++) + if (o.equals(elementData[index])) { + fastRemove(index); + return true; + } + } + return false; + } + + // 根据下标快速删除元素 + private void fastRemove(int index) { + modCount++; + //将数组elementData中index位置之后的所有元素向前移一位 + int numMoved = size - index - 1; + if (numMoved > 0) + System.arraycopy(elementData, index+1, elementData, index, + numMoved); + elementData[--size] = null; // clear to let GC do its work + } + ``` + +- set方法 + + ```java + public E set(int index, E element) { + rangeCheck(index);//越界检查 + + E oldValue = elementData(index); + elementData[index] = element; // 指定位置替换 + return oldValue; + } + ``` + +- get方法 + + ```java + public E get(int index) { + rangeCheck(index); + + return elementData(index); + } + E elementData(int index) { + return (E) elementData[index];//取数组指定位置并返回 + } + ``` + +## 总结 + +和hashmap一样 扩容都会增加时耗初始化的时候指定合适的长度。 + +arrayList由于本质是数组,所以它在数据的查询方面会很快,而在插入删除这些方面,性能下降很多,有移动很多数据才能达到应有的效果,而LinkedList则相反 + + + + + + + + + diff --git "a/week_01/61/HashMap\346\272\220\347\240\201\351\230\205\350\257\273.md" "b/week_01/61/HashMap\346\272\220\347\240\201\351\230\205\350\257\273.md" new file mode 100644 index 0000000..c88bc3a --- /dev/null +++ "b/week_01/61/HashMap\346\272\220\347\240\201\351\230\205\350\257\273.md" @@ -0,0 +1,312 @@ +#
HashMap源码分析
+ +- 以下分析基于JDK1.8 + +## HashMap 简介 + +HashMap 主要用来存放键值对,它基于哈希表的Map接口实现,是常用的Java集合之一。 + +``` +hashMap继承自抽象类AbstractMap实现了Map接口等 +``` + +HashMap 最多允许一条记录的键为null,允许多条记录的值为null。HashMap 是非线程安全的,即任一时刻有多个线程同时写 HashMap ,可能会导致数据不一致,。如果要满足线程安全,可以使用 Collections 的 SynchronizedMap 方法 或者使用 ConcurrentHashMap。 + +## HashMap分析 + +1、属性分析 + +- ```java + // 序列号ID,hashmap实现了Serializable接口因此是可在网络中传输 + private static final long serialVersionUID = 362498820763181265L; + ``` + +- ```java + // 默认的初始容量是16 + static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16 + ``` + +- ```java + // 最大容量 + static final int MAXIMUM_CAPACITY = 1 << 30; + ``` + +- ``` + // 默认的负载率 75% + static final float DEFAULT_LOAD_FACTOR = 0.75f; + ``` + +- ```java + // 当node上的结点数大于这个值时会由链表结构转成红黑树 + static final int TREEIFY_THRESHOLD = 8; + ``` + +- ```java + // 当node上的结点数小于这个值时树转链表 + static final int UNTREEIFY_THRESHOLD = 6; + ``` + +- ```java + // 桶中结构转化为红黑树对应的table的最小大小 + static final int MIN_TREEIFY_CAPACITY = 64; + ``` + +2、构造方法分析 + +```java +// 无参构造函数 + public HashMap() { + this.loadFactor = DEFAULT_LOAD_FACTOR; // all other fields defaulted + } +// 指定“容量大小”的构造函数 + public HashMap(int initialCapacity) { + this(initialCapacity, DEFAULT_LOAD_FACTOR); + } +// 含另一个“Map”的构造函数 + public HashMap(Map m) { + this.loadFactor = DEFAULT_LOAD_FACTOR; + putMapEntries(m, false); + } +// 指定“容量大小”和“加载因子”的构造函数 + public HashMap(int initialCapacity, float loadFactor) { + // 初始容量小于0,则抛出异常 + if (initialCapacity < 0) + throw new IllegalArgumentException("Illegal initial capacity: " + + initialCapacity); + // 始容量大于容量最大值,则使用最大值作为初始容量 + if (initialCapacity > MAXIMUM_CAPACITY) + initialCapacity = MAXIMUM_CAPACITY; + // 果负载率小于等于0或负载率不是浮点数,则抛出异常 + if (loadFactor <= 0 || Float.isNaN(loadFactor)) + throw new IllegalArgumentException("Illegal load factor: " + + loadFactor); + this.loadFactor = loadFactor; + // 设置阀值为初始容量 + this.threshold = tableSizeFor(initialCapacity); + + } +// 回大于输入参数且最近的2的整数次幂的数。比如10,则返回16 相比于1.7提升效率 + 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; + } + + +``` + +3、常见方法分析 + +- get方法 + +```java +public V get(Object key) { + Node e; + + return (e = getNode(hash(key), key)) == null ? null : e.value; +} +//计算key的hash值 +static final int hash(Object key) { + int h; + return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16); + } + +final Node getNode(int hash, Object key) { + Node[] tab; Node 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)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; + } +``` + +- put方法 + + ```java + public V put(K key, V value) { + return putVal(hash(key), key, value, false, true); + } + + /** + * Implements Map.put and related methods + * + * @param hash hash for key + * @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 + */ + final V putVal(int hash, K key, V value, boolean onlyIfAbsent, + boolean evict) { + Node[] tab; Node p; int n, i; + // table未初始化或者长度为0,进行扩容创建table + if ((tab = table) == null || (n = tab.length) == 0) + n = (tab = resize()).length;// 桶中已经存在元素 + // (n - 1) & hash 确定元素存放在哪个桶中,桶为空,新生成结点放入桶里 + if ((p = tab[i = (n - 1) & hash]) == null) + //此位置没有对象创建新的node + tab[i] = newNode(hash, key, value, null); + else {// 桶中已经存在元素 + Node e; K k; + // 比较桶中第一个元素(数组中的结点)的hash值相等,key相等 + if (p.hash == hash && + ((k = p.key) == key || (key != null && key.equals(k)))) + e = p; + // hash值不相等,即key不相等;为红黑树结点 + else if (p instanceof TreeNode) + e = ((TreeNode)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); + // 判断是否需要转换为红黑树 + if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st + treeifyBin(tab, hash); + break; + } + // 判断链表中结点的key值与插入的元素的key值是否相等 + if (e.hash == hash && + ((k = e.key) == key || (key != null && key.equals(k)))) + break; + p = e; + } + } + // 表示在桶中找到key值hash值与插入元素相等的结点 + 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; + } + + + // 扩容 + final Node[] resize() { + Node[] 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; + } + // 没超过最大值,就扩充为原来的2倍 + else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY && + oldCap >= DEFAULT_INITIAL_CAPACITY) + newThr = oldThr << 1; // double threshold + } + else if (oldThr > 0) // initial capacity was placed in threshold + //旧数组无长度,长度设置为旧的容量的大小 + newCap = oldThr; + else { // zero initial threshold signifies using defaults + newCap = DEFAULT_INITIAL_CAPACITY; + newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY); + } + // 计算新的resize上限 + if (newThr == 0) { + float ft = (float)newCap * loadFactor; + newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ? + (int)ft : Integer.MAX_VALUE); + } + threshold = newThr; + @SuppressWarnings({"rawtypes","unchecked"}) + Node[] newTab = (Node[])new Node[newCap]; + table = newTab; + if (oldTab != null) { + // 把每个bucket都移动到新的buckets中 + for (int j = 0; j < oldCap; ++j) { + Node e; + if ((e = oldTab[j]) != null) { + oldTab[j] = null; + if (e.next == null) + newTab[e.hash & (newCap - 1)] = e; + else if (e instanceof TreeNode) + ((TreeNode)e).split(this, newTab, j, oldCap); + else { // preserve order + Node loHead = null, loTail = null; + Node hiHead = null, hiTail = null; + Node next; + do { + next = e.next; + // 原索引 + if ((e.hash & oldCap) == 0) { + if (loTail == null) + loHead = e; + else + loTail.next = e; + loTail = e; + } + // 原索引+oldCap + else { + if (hiTail == null) + hiHead = e; + else + hiTail.next = e; + hiTail = e; + } + } while ((e = next) != null); + // 原索引放到bucket里 + if (loTail != null) { + loTail.next = null; + newTab[j] = loHead; + } + // 原索引+oldCap放到bucket里 + if (hiTail != null) { + hiTail.next = null; + newTab[j + oldCap] = hiHead; + } + } + } + } + } + return newTab; + } + ``` + +## 总结 + +进行扩容,会伴随着一次重新hash分配,并且会遍历hash表中所有的元素,是非常耗时的。在编写程序中,合理给定初始容量,就算适当浪费空间也要尽量避免resize。 + + + + + + + diff --git a/week_01/62/ArrayList-062.md b/week_01/62/ArrayList-062.md new file mode 100644 index 0000000..fe6779f --- /dev/null +++ b/week_01/62/ArrayList-062.md @@ -0,0 +1,20 @@ +ArrayList基于1.8源码学习 +默认容量大小是 DEFAULT_CAPACITY = 10; +1、扩容 +private void grow(int minCapacity) { + // overflow-conscious code + int oldCapacity = elementData.length; + int newCapacity = oldCapacity + (oldCapacity >> 1); + if (newCapacity - minCapacity < 0) + newCapacity = minCapacity; + 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); + } +如果容器默认大小是10 ,当两个线程在容器有9个值得时候add数据,可能会造成数组下标越界。 +2、在HashMap中也存在modCount,起作用就是在遍历容器时,如果有add(put),remove操作会快速报错 +final void checkForComodification() { + if (modCount != expectedModCount) + throw new ConcurrentModificationException(); + } \ No newline at end of file diff --git a/week_01/62/HashMap-062.md b/week_01/62/HashMap-062.md new file mode 100644 index 0000000..b648fbc --- /dev/null +++ b/week_01/62/HashMap-062.md @@ -0,0 +1,135 @@ +基于jdk8的HashMap源码分析: +周末才有空写,写之前看了大家写的内容,有些源码写的详细的我就略过了,主要写一下自己的一些见解; +1、关于容量和负载因子,如果负载因子太小,会造成resize()频繁调用,造成新能消耗 +2、如果负载因子过大,会对容器空间的利用更加充分,但是会增加查找时间 +3、生成2的幂次方的容量,可以采用这种方法 +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; + } +4、添加元素 +final V putVal(int hash, K key, V value, boolean onlyIfAbsent, + boolean evict) { + Node[] tab; Node p; int n, i; + if ((tab = table) == null || (n = tab.length) == 0) //判断是否是没出初始化的容器 + n = (tab = resize()).length; //对容器初始化 + if ((p = tab[i = (n - 1) & hash]) == null) //判断队列所在的地址是否有值,没有则生成一个 node + tab[i] = newNode(hash, key, value, null); + else { + Node e; K k; + if (p.hash == hash && + ((k = p.key) == key || (key != null && key.equals(k)))) //判断key值是否相同。如果hash值不一样,则key值肯定不一样 + e = p; + else if (p instanceof TreeNode) //判断是否是红黑树结构 + e = ((TreeNode)p).putTreeVal(this, tab, hash, key, value); + else { + for (int binCount = 0; ; ++binCount) { //遍历node 下的链表,直到next没有值,在链表的最后一个添加 node + if ((e = p.next) == null) { + p.next = newNode(hash, key, value, null); + 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)))) //对于node 中可能会出现第一个key值不相同,而后面的值相同的情况 + 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) //当前size大于下次预设值,就会触发resize() + resize(); + afterNodeInsertion(evict); + return null; + } + +5、resize()方法 +final Node[] resize() { + Node[] 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) + newThr = oldThr << 1; // double threshold + } + else if (oldThr > 0) // initial capacity was placed in threshold + newCap = oldThr; + else { // zero initial threshold signifies using defaults + newCap = DEFAULT_INITIAL_CAPACITY; + newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY); + } + if (newThr == 0) { + float ft = (float)newCap * loadFactor; + newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ? + (int)ft : Integer.MAX_VALUE); + } + threshold = newThr; + @SuppressWarnings({"rawtypes","unchecked"}) + Node[] newTab = (Node[])new Node[newCap]; + table = newTab; + if (oldTab != null) { + for (int j = 0; j < oldCap; ++j) { //遍历原来的容器 + Node e; + if ((e = oldTab[j]) != null) { //对数组节点下有值得才进行处理 + oldTab[j] = null; + if (e.next == null) + newTab[e.hash & (newCap - 1)] = e; //使用新的容器(数组的下标与hash进行与运算,确定新容器(table)的数组下标) + else if (e instanceof TreeNode) + ((TreeNode)e).split(this, newTab, j, oldCap); //为红黑树处理 + else { // preserve order //对于所在数组节点有链表的情况 + Node loHead = null, loTail = null; + Node hiHead = null, hiTail = null; + Node 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; + } +6、对于HashMap不是安全的,主要是发生在多线程put元素的时候,可能引起 死循环,可以参考 +https://blog.csdn.net/bjwfm2011/article/details/81076736 描述的很清楚 \ No newline at end of file diff --git a/week_01/62/LinkedList-062.md b/week_01/62/LinkedList-062.md new file mode 100644 index 0000000..3ab6879 --- /dev/null +++ b/week_01/62/LinkedList-062.md @@ -0,0 +1,14 @@ +LinkedList基于jdk1.8源码分析 +我觉得主要就是在节点上使用双向队列的方式,添加nexe,prev实现提高添加,以及删除的效率,是以空间换时间的方式。在redis中是以调表的方式, +对节点上下的引用都添加进来,实现快速查找,是以空间换时间的更加具体实现 +private static class Node { + E item; + Node next; + Node prev; + + Node(Node prev, E element, Node next) { + this.item = element; + this.next = next; + this.prev = prev; + } + } \ No newline at end of file diff --git a/week_01/65/ArrayList-065.md b/week_01/65/ArrayList-065.md new file mode 100644 index 0000000..ca01c1b --- /dev/null +++ b/week_01/65/ArrayList-065.md @@ -0,0 +1,368 @@ +# ArrayList 源码阅读笔记 + +## 一. 简介 + +ArrayList是日常编码中使用频率最高的数据结构,也是实现最简单的数据结构。它和数组一样有较高的随机访问效率,又实现了动态扩容,相当于动态数组。ArrayList是非线程安全容器。 + +## 二. 实现接口 + +ArrayList实现了List接口,是一个有序的线性集合,具有添加、删除、插入、遍历等操作。 +ArrayList实现了Cloneable接口,实现为浅拷贝。 +ArrayList实现了序列化接口Serializable,可以被序列化。 +ArrayList实现了随机访问接口RandomAccess,这是一个标记接口,实现了这个接口的集合for循环遍历效率高于iterator迭代器遍历。 + +## 三. 核心源码 + +### 1. 类属性 + +```java +/** + * 默认初始化容量 + */ +private static final int DEFAULT_CAPACITY = 10; + +/** + * 为所有空集合实例共用的空数组 + */ +private static final Object[] EMPTY_ELEMENTDATA = {}; + +/** + * 用于以无参构造方法进行初始化的空实例的共享空数组。区分它和 + * EMPTY_ELEMENTDATA 以了解添加第一个元素时需要扩容 + */ +private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {}; + +/** + * 存储ArrayList元素的数组缓冲区。 + * ArrayList的容量是这个数组的长度.每个空ArrayList的elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA时 + * 都会被扩容到DEFAULT_CAPACITY。 + */ +transient Object[] elementData; + +/** + * ArrayList中元素的个数 + * + */ +private int size; + +/** + * 数组最大长度。Java中的数组是由JVM构造的类,根据OOP-Klass二分模型,对象有对象头,而数组不能自己计算自己的长度,需要8字节 + * 存储长度信息,所以是Integer.MAX_VALUE - 8 + */ +private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8; + +/** + * 这是一个从父类AbstractList中继承而来的属性,记录了ArrayList被结构性修改的次数 + * java.util包下的集合类都是快速失败(fail—fast)的,不能在多线程下发生并发修改(迭代过程中被修改) + */ +protected transient int modCount = 0; +``` + +### 2. 核心方法 + +#### 构造方法 + +初始容量为参数,如果大于0就初始化elementData为对应大小,如果等于0就使用EMPTY_ELEMENTDATA空数组,如果小于0抛出异常。 + +```java +public ArrayList(int initialCapacity) { + if (initialCapacity > 0) { + this.elementData = new Object[initialCapacity]; + } else if (initialCapacity == 0) { + this.elementData = EMPTY_ELEMENTDATA; + } else { + throw new IllegalArgumentException("Illegal Capacity: "+ + initialCapacity); + } +} +``` + +无参构造方法,elementData初始化为DEFAULTCAPACITY_EMPTY_ELEMENTDATA。 + +```java +public ArrayList() { + this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA; +} +``` + +传入一个集合初始化,如果传入集合元素个数为0,则初始化为EMPTY_ELEMENTDATA空数组,否则使用Arrays.copyOf()拷贝到elementData。 + +```java +public ArrayList(Collection c) { + elementData = c.toArray(); + if ((size = elementData.length) != 0) { + // c.toArray might (incorrectly) not return Object[] (see 6260652) + if (elementData.getClass() != Object[].class) + elementData = Arrays.copyOf(elementData, size, Object[].class); + } else { + // replace with empty array. + this.elementData = EMPTY_ELEMENTDATA; + } +} +``` + +#### 添加元素 + +add(E e)方法将单个元素添加到ArrayList的尾部,其中涉及容量检查和扩容处理,平均时间复杂度O(1)。 + +```java +public boolean add(E e) { + ensureCapacityInternal(size + 1); // 检查是否需要扩容 + elementData[size++] = e; + return true; +} + +private void ensureCapacityInternal(int minCapacity) { + ensureExplicitCapacity(calculateCapacity(elementData, minCapacity)); +} + +private static int calculateCapacity(Object[] elementData, int minCapacity) { + /*如果elementData是由无参构造方法初始化为DEFAULTCAPACITY_EMPTY_ELEMENTDATA的,则需要扩容到DEFAULT_CAPACITY + */ + if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) { + return Math.max(DEFAULT_CAPACITY, minCapacity); + } + return minCapacity; +} + +private void ensureExplicitCapacity(int minCapacity) { + // 增加modCount,添加元素是结构性修改 + modCount++; + + // 扩容 + if (minCapacity - elementData.length > 0) + grow(minCapacity); +} + +private void grow(int minCapacity) { + int oldCapacity = elementData.length; + // 先将新容量设为旧容量的1.5倍。 >> 1 有符号右移一位,相当于除以2 + int newCapacity = oldCapacity + (oldCapacity >> 1); + if (newCapacity - minCapacity < 0) + // 如果新容量达不到需求,那容量就以minCapacity为准 + newCapacity = minCapacity; + if (newCapacity - MAX_ARRAY_SIZE > 0) + /**当传入容量参数太大,大到超过了数组的容量限定值Integer.MAX_VALUE-8却又小于整数限定值Integer.MAX_VALUE,那么新 + * 的数组容量以整数限定值Integer.MAX_VALUE为准,但是当传入的容量参数不大于数组的容量限定值时,以容量限定值 + * Integer.MAX_VALUE-8为准。 + */ + newCapacity = hugeCapacity(minCapacity); + // 拷贝元素到新扩容的数组 + elementData = Arrays.copyOf(elementData, newCapacity); +} + +private static int hugeCapacity(int minCapacity) { + if (minCapacity < 0) // 溢出报错 + throw new OutOfMemoryError(); + return (minCapacity > MAX_ARRAY_SIZE) ? + Integer.MAX_VALUE : + MAX_ARRAY_SIZE; +} +``` + +add(int index, E element)方法可以将element添加到指定的index上。 + +```java +public void add(int index, E element) { + rangeCheckForAdd(index); + + // 检查是否需要扩容 + ensureCapacityInternal(size + 1); + // 复制并调整数组内元素位置 + System.arraycopy(elementData, index, elementData, index + 1, + size - index); + elementData[index] = element; + size++; +} + +private void rangeCheckForAdd(int index) { + // 检查插入index是否合法,不合法抛出异常 + if (index > size || index < 0) + throw new IndexOutOfBoundsException(outOfBoundsMsg(index)); +} +``` + +addAll(Collection c)方法可以将一个集合中的所有元素全部有序添加到ArrayList尾部。 +addAll(int index, Collection c)方法可以将一个集合插入到ArrayList指定index上。 + +```java +public boolean addAll(Collection c) { + // 集合转数组 + Object[] a = c.toArray(); + int numNew = a.length; + // 扩容处理 + ensureCapacityInternal(size + numNew); + // 元素复制 + System.arraycopy(a, 0, elementData, size, numNew); + size += numNew; + return numNew != 0; +} + +public boolean addAll(int index, Collection c) { + // 检查index合法性 + rangeCheckForAdd(index); + + Object[] a = c.toArray(); + int numNew = a.length; + // 扩容 + ensureCapacityInternal(size + numNew); + + // 元素复制 + int numMoved = size - index; + if (numMoved > 0) + System.arraycopy(elementData, index, elementData, index + numNew, + numMoved); + + System.arraycopy(a, 0, elementData, index, numNew); + size += numNew; + return numNew != 0; +} +``` + +#### 删除元素 + +remove(int index)方法将删除指定index上的元素,并返回该元素。删除时需要进行遍历调整index,平均时间复杂度O(n)。 + +```java +public E remove(int index) { + // 检查index是否合法 + rangeCheck(index); + + // 增加modCount,删除元素也是结构性修改 + modCount++; + // 获取待删除index上的元素 + E oldValue = elementData(index); + + // 如果index不是最后一位,则将index之后的元素往前挪一位 + int numMoved = size - index - 1; + if (numMoved > 0) + System.arraycopy(elementData, index+1, elementData, index, + numMoved); + elementData[--size] = null; // 最后一个元素的位置置为null,有利于GC + + return oldValue; +} + +private void rangeCheck(int index) { + // 检查index是否越界 + if (index >= size) + throw new IndexOutOfBoundsException(outOfBoundsMsg(index)); +} + +E elementData(int index) { + return (E) elementData[index]; +} +``` + +remove(Object o)方法删除指定元素值的方法(通过equals()方法判断),平均时间复杂度O(n)。 + +```java +public boolean remove(Object o) { + if (o == null) { + // 遍历寻找index并删除null元素 + for (int index = 0; index < size; index++) + if (elementData[index] == null) { + fastRemove(index); + return true; + } + } else { + // 遍历寻找index并删除普通元素 + for (int index = 0; index < size; index++) + if (o.equals(elementData[index])) { + fastRemove(index); + return true; + } + } + return false; +} + +private void fastRemove(int index) { + // fastRemove(int index)相比remove(int index)方法,少了index越界检查。 + // 增加modCount + modCount++; + + // 如果index不是最后一位,则将index之后的元素往前挪一位 + int numMoved = size - index - 1; + if (numMoved > 0) + System.arraycopy(elementData, index+1, elementData, index, + numMoved); + elementData[--size] = null; // 最后一个元素的位置置为null,有利于GC +} +``` + +removeAll(Collection c)方法可以从ArrayList中删除一个指定集合. + +```java +public boolean removeAll(Collection c) { + // 指定集合不能为null + Objects.requireNonNull(c); + // 批量删除 + return batchRemove(c, false); +} + +/** + * 批量删除元素 + * complement为true表示删除c中不包含的元素 + * complement为false表示删除c中包含的元素 + */ +private boolean batchRemove(Collection c, boolean complement) { + final Object[] elementData = this.elementData; + // 读写分别用两个index表示 + int r = 0, w = 0; + boolean modified = false; + try { + // 遍历整个数组,根据complement把该元素放到写index的位置 + for (; r < size; r++) + if (c.contains(elementData[r]) == complement) + elementData[w++] = elementData[r]; + } finally { + // 如果contains()抛出异常,则把未读的元素都拷贝到写index之后 + if (r != size) { + System.arraycopy(elementData, r, + elementData, w, + size - r); + w += size - r; + } + if (w != size) { + // 将写index之后的元素置为null,有利于GC + for (int i = w; i < size; i++) + elementData[i] = null; + // 修改modCount + modCount += size - w; + // 新大小等于写指针的位置(因为每写一次写指针就加1,所以新大小正好等于写指针的位置) + size = w; + modified = true; + } + } + return modified; +} +``` + +#### 获取元素 + +get(int index)方法很简单,获取指定索引位置的元素,时间复杂度为O(1)。 + +```java +public E get(int index) { + // index越界检查 + rangeCheck(index); + + return elementData(index); +} +``` + +#### 设置元素 + +set(int index, E element)方法可以将指定index的元素设置为指定element,并返回旧元素,时间复杂度O(1)。 + +```java +public E set(int index, E element) { + // inde越界检查 + rangeCheck(index); + + // 获取旧元素 + E oldValue = elementData(index); + elementData[index] = element; + return oldValue; +} +``` diff --git a/week_01/65/HashMap-065.md b/week_01/65/HashMap-065.md new file mode 100644 index 0000000..7653978 --- /dev/null +++ b/week_01/65/HashMap-065.md @@ -0,0 +1,282 @@ +# HashMap源码阅读笔记 + +## 一. 简介 + +HashMap是一种key-value形式的数据结构,key和value一一对应,通过key可以快速定位到value进行操作。在HashMap中是通过key的hashcode定位value的,因此key不能为null而且不能为基本数据类型。在JDK1.7及以前,HashMap底层采用数组+链表的数据结构存储数据,这导致了一些情况下会出现性能问题,而且面对哈希洪水攻击无抵抗能力,在JDK1.8及之后,底层采用数组+链表+红黑树的数据结构存储数据来解决这些问题。HashMap是线程不安全的数据结构,且不能保证存储顺序。 + +## 二. 实现接口 + +HashMap实现了Map接口,是一个key-value数据结构,具有添加、删除、插入、遍历等操作。 +HashMap实现了Cloneable接口,实现为浅拷贝。 +HashMap实现了序列化接口Serializable,可以被序列化。 + +## 三. 核心源码 + +### 1. 底层存储结构 + +HashMap存储结构如下图所示。最基本的存储结构是数组,数组的一个元素又称作桶,当出现hash冲突时转为链表,当链表长度大于8时链表又会转为红黑树。 + +![1HashMap底层存储结构][p1] + +[p1]:data:image/png;base64,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 + +### 2. 类属性 + +#### 内部类 + +Node静态内部类是HashMap最基本的节点,重写了equals()方法和hashcode()方法。Node数组中存放着key-value,根据key的hashcode决定存放在数组中的位置。当遇到hashcode冲突时,Node节点会组成链表以解决冲突。 + +```java +static class Node implements Map.Entry { + final int hash; + final K key; + V value; + Node next; + + Node(int hash, K key, V value, Node 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; + } +} +``` + +TreeNode静态内部类是HashMap中的红黑树节点,当一条链表中节点大于8且HashMap中桶的数量达到64的时候,链表会转化为红黑树。 + +```java +static final class TreeNode extends LinkedHashMap.Entry +``` + +#### 属性 + +```java + +/** + * 默认初始化容量16 + */ +static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16 + +/** + * 最大容量2^30 + */ +static final int MAXIMUM_CAPACITY = 1 << 30; + +/** + * 默认装载因子0.75 + */ +static final float DEFAULT_LOAD_FACTOR = 0.75f; + +/** + * 当一个桶中的元素个数大于等于8时进行树化 + */ +static final int TREEIFY_THRESHOLD = 8; + +/** + * 当一个桶中的元素个数小于等于6时把树转化为链表 + */ +static final int UNTREEIFY_THRESHOLD = 6; + +/** + * 当桶的个数达到64的时候才进行树化 + */ +static final int MIN_TREEIFY_CAPACITY = 64; + +/** + * 桶数组 + */ +transient Node[] table; + +/** + * entrySet()方法返回的集合 + */ +transient Set> entrySet; + +/* + * 元素的数量 + */ +transient int size; + +/** + * 结构性修改次数,同ArrayList + */ +transient int modCount; + +/** + * 当桶的使用数量达到多少时进行扩容,threshold = capacity * loadFactor + */ +int threshold; + +/** + * 装载因子是表示HsahMap中元素的填满的程度。装载因子用来计算容量达到多少时才进行扩容,默认装载因子为0.75。 + */ +final float loadFactor; +``` + +### 3. 核心方法 + +#### 构造方法 + +无参构造方法 + +```java +public HashMap() { + this.loadFactor = DEFAULT_LOAD_FACTOR; // all other fields defaulted +} +``` + +传入初始容量和装载因子的构造方法,根据判断传入参数是否合法构造HashMap。 + +```java +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); +} + +// 计算当桶的数量达到多少时扩容,这里计算为初始容量向上取最接近的2的n次方 +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; +} +``` + +传入初始容量的构造方法,会采用默认装载因子。 + +```java +public HashMap(int initialCapacity) { + this(initialCapacity, DEFAULT_LOAD_FACTOR); +} +``` + +#### 插入元素 + +```java +public V put(K key, V value) { + return putVal(hash(key), key, value, false, true); +} + +final V putVal(int hash, K key, V value, boolean onlyIfAbsent, + boolean evict) { + Node[] tab; Node p; int n, i; + if ((tab = table) == null || (n = tab.length) == 0) + n = (tab = resize()).length; + if ((p = tab[i = (n - 1) & hash]) == null) + // 新建一个节点放在桶中 + tab[i] = newNode(hash, key, value, null); + else { + Node e; K k; + if (p.hash == hash && + ((k = p.key) == key || (key != null && key.equals(k)))) + e = p; + else if (p instanceof TreeNode) + // 如果第一个元素是树节点,则调用树节点的putTreeVal插入元素 + e = ((TreeNode)p).putTreeVal(this, tab, hash, key, value); + else { + // 遍历这个桶对应的链表,binCount用于存储链表中元素的个数 + for (int binCount = 0; ; ++binCount) { + if ((e = p.next) == null) { + p.next = newNode(hash, key, value, null); + 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; + // 节点访问后的回调方法,在LinkedHashMap中用到 + afterNodeAccess(e); + return oldValue; + } + } + ++modCount; + if (++size > threshold) + resize(); + // 节点插入后的回调方法,在LinkedHashMap中用到 + afterNodeInsertion(evict); + return null; +} + +static final int hash(Object key) { + int h; + // 如果key为null,则hash值为0,否则调用key的hashCode()方法 + // 并让高16位与整个hash异或,这样做是为了使计算出的hash更分散 + return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16); +} +``` + +#### 获取元素 + +```java +public V get(Object key) { + Node e; + return (e = getNode(hash(key), key)) == null ? null : e.value; +} + +final Node getNode(int hash, Object key) { + Node[] tab; Node 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)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; +} +``` + +# 未完待续... diff --git a/week_01/65/LinkedList-065.md b/week_01/65/LinkedList-065.md new file mode 100644 index 0000000..faeed7b --- /dev/null +++ b/week_01/65/LinkedList-065.md @@ -0,0 +1,448 @@ +# LinkedList 源码阅读笔记 + +## 一. 简介 + +LinkedList是链表实现的List,内部维护了一个双向链表,插入、修改或删除元素效率较高,访问元素效率低于ArrayList。LinkedList也是一个非线程安全的集合。 + +## 二. 实现接口 + +LinkedList实现了List接口,是一个有序的线性集合,具有添加、删除、插入、遍历等操作。 +LinkedList实现了Cloneable接口,实现为浅拷贝。 +LinkedList实现了序列化接口Serializable,可以被序列化。 +LinkedList实现了双端队列接口Deque,说明LinkedList是一种具有队列和栈的性质的数据结构。 + +## 三. 核心源码 + +### 1. 类属性 + +#### 内部类 + +双链表节点Node + +```java +private static class Node { + E item; + Node next; + Node prev; + + Node(Node prev, E element, Node next) { + this.item = element; + this.next = next; + this.prev = prev; + } +} +``` + +#### 属性 + +```java +/** + * 指向双向链表第一个节点的指针 + */ +transient Node first; + +/** + * 指向双向链表最后一个节点的指针 + */ +transient Node last; + +/** + * LinkedList中元素的个数 + * + */ +transient int size; +``` + +### 2. 核心方法 + +#### 构造方法 + +无参构造方法,构造一个空List。 + +```java +public LinkedList() { +} +``` + +传入一个集合初始化,将集合中的元素添加到链表中。 + +```java +public LinkedList(Collection c) { + this(); + addAll(c); +} +``` + +#### 添加元素 + +LinkedList既可作为List,又可作为双端队列,因此它除了可以在任意位置添加元素外,还应该具有双端队列的性质,即在队列头或队列尾添加元素。 + +add(E e)方法、addLast(E e)方法、offer(E e)和offerLast(E e)都将在LinkedList尾部添加元素,平均时间复杂度O(1)。 + +```java +public boolean add(E e) { + linkLast(e); + return true; +} + +public void addLast(E e) { + linkLast(e); +} + +public boolean offer(E e) { + return add(e); +} + +public boolean offerLast(E e) { + addLast(e); + return true; +} + +void linkLast(E e) { + // 尾节点引用 + final Node l = last; + // 创建新节点,prev指向尾节点,next指向null + final Node newNode = new Node<>(l, e, null); + // 新节点成为尾节点 + last = newNode; + if (l == null) + // last节点为null说明之前LinkedList为空,则把首节点也指向新节点 + first = newNode; + else + l.next = newNode; + size++; + modCount++; +} +``` + +addFirst(E e)方法、offerFirst(E e)方法和push(E e)方法都将在LinkedList首部添加元素,平均时间复杂度O(1)。 + +```java +public void addFirst(E e) { + linkFirst(e); +} + +public boolean offerFirst(E e) { + addFirst(e); + return true; +} + +public void push(E e) { + addFirst(e); +} + +private void linkFirst(E e) { + // 首节点引用 + final Node f = first; + // 创建新节点,prev指向null,next指向首节点 + final Node newNode = new Node<>(null, e, f); + // 新节点成为首节点 + first = newNode; + if (f == null) + // first节点为null说明之前LinkedList为空,则把尾节点也指向新节点 + last = newNode; + else + f.prev = newNode; + size++; + modCount++; +} +``` + +add(int index, E element)方法可以将element添加到指定的index上,平均时间复杂度O(n)。 + +```java +public void add(int index, E element) { + // 检查index是否越界 + checkPositionIndex(index); + + if (index == size) + // 如果index == size,则添加元素到尾部 + linkLast(element); + else + // 将元素插入到index处 + linkBefore(element, node(index)); +} + +Node node(int index) { + // assert isElementIndex(index); + + // 根据index获取节点,思路为如果index < size / 2则从首节点开始往后遍历 + // 否则从尾节点往前遍历 + if (index < (size >> 1)) { + Node x = first; + for (int i = 0; i < index; i++) + x = x.next; + return x; + } else { + Node x = last; + for (int i = size - 1; i > index; i--) + x = x.prev; + return x; + } +} + +/** + * e是待插入节点,succ是待添加节点的后继节点 + */ +void linkBefore(E e, Node succ) { + // assert succ != null; + + // succ的前驱节点 + final Node pred = succ.prev; + // 创建新节点,prev指向succ的前驱节点,next指向succ + final Node newNode = new Node<>(pred, e, succ); + succ.prev = newNode; + if (pred == null) + first = newNode; + else + pred.next = newNode; + size++; + modCount++; +} +``` + +addAll(Collection c)方法可以将一个集合中的所有元素全部有序添加到LinkedList尾部。 +addAll(int index, Collection c)方法可以将一个集合插入到LinkedList指定index上。 + +```java +public boolean addAll(Collection c) { + return addAll(size, c); +} + +public boolean addAll(int index, Collection c) { + // 检查index是否越界 + checkPositionIndex(index); + + Object[] a = c.toArray(); + int numNew = a.length; + if (numNew == 0) + return false; + + Node pred, succ; + if (index == size) { + succ = null; + pred = last; + } else { + succ = node(index); + pred = succ.prev; + } + + // 迭代器遍历复制 + for (Object o : a) { + @SuppressWarnings("unchecked") E e = (E) o; + Node newNode = new Node<>(pred, e, null); + if (pred == null) + first = newNode; + else + pred.next = newNode; + pred = newNode; + } + + if (succ == null) { + last = pred; + } else { + pred.next = succ; + succ.prev = pred; + } + + size += numNew; + modCount++; + return true; +} +``` + +#### 删除元素 + +remove(int index)方法将删除指定index上的元素,并返回该元素。删除时需要进行遍历找到删除节点的位置,平均时间复杂度O(n)。 + +```java +public E remove(int index) { + // index越界检查 + checkElementIndex(index); + // 找到index位置上的元素,从链表中删除 + return unlink(node(index)); +} + +E unlink(Node x) { + // assert x != null; + final E element = x.item; + final Node next = x.next; + final Node prev = x.prev; + + if (prev == null) { + // prev == null说明待删除节点为首节点,则把待删除节点后继节点设为首节点 + first = next; + } else { + prev.next = next; + x.prev = null; + } + + if (next == null) { + // next == null说明待删除节点为尾节点,则把待删除节点前驱节点设为尾节点 + last = prev; + } else { + next.prev = prev; + x.next = null; + } + + // 有利于GC + x.item = null; + size--; + modCount++; + return element; +} +``` + +remove(Object o)方法删除指定元素值的方法(通过equals()方法判断),平均时间复杂度O(n)。 + +```java +public boolean remove(Object o) { + if (o == null) { + for (Node x = first; x != null; x = x.next) { + if (x.item == null) { + unlink(x); + return true; + } + } + } else { + for (Node x = first; x != null; x = x.next) { + if (o.equals(x.item)) { + unlink(x); + return true; + } + } + } + return false; +} +``` + +removeFirst()方法和pop()方法删除首节点元素并返回该元素,如果首节点为null则抛出异常; +poll()和pollFirst()也是删除首节点元素并返回该元素,不同的是如果首节点为null则返回null,它们的时间复杂度都为O(1)。 + +```java +public E removeFirst() { + final Node f = first; + if (f == null) + throw new NoSuchElementException(); + return unlinkFirst(f); +} + +public E poll() { + final Node f = first; + return (f == null) ? null : unlinkFirst(f); +} + +public E pollFirst() { + final Node f = first; + return (f == null) ? null : unlinkFirst(f); +} + +public E pop() { + return removeFirst(); +} + +private E unlinkFirst(Node f) { + // assert f == first && f != null; + final E element = f.item; + final Node next = f.next; + f.item = null; + f.next = null; // help GC + first = next; + if (next == null) + last = null; + else + next.prev = null; + size--; + modCount++; + return element; +} +``` + +removeLast()方法删除首节点元素并返回该元素,如果首节点为null则抛出异常; +pollLast()也是删除首节点元素并返回该元素,不同的是如果首节点为null则返回null,它们的时间复杂度都为O(1)。 + +```java +public E removeLast() { + final Node f = last; + if (f == null) + throw new NoSuchElementException(); + return unlinkLast(f); +} + +public E pollLast() { + final Node l = last; + return (l == null) ? null : unlinkLast(l); +} + +private E unlinkLast(Node f) { + // assert l == last && l != null; + final E element = l.item; + final Node prev = l.prev; + l.item = null; + l.prev = null; // help GC + last = prev; + if (prev == null) + first = null; + else + prev.next = null; + size--; + modCount++; + return element; +} +``` + +#### 获取元素 + +get(int index)方法很简单,遍历获取指定索引位置的元素,时间复杂度为O(n)。 + +```java +public E get(int index) { + checkElementIndex(index); + return node(index).item; +} +``` + +getFirst()、element()和peek()方法都返回链表的首节点,getFirst()方法和element()方法在首节点为null时将抛出异常,而peek()方法则会返回null,时间复杂度为O(1)。 + +```java +public E getFirst() { + final Node f = first; + if (f == null) + throw new NoSuchElementException(); + return f.item; +} + +public E element() { + return getFirst(); +} + +public E peek() { + final Node f = first; + return (f == null) ? null : f.item; +} +``` + +getLast()方法返回链表的尾节点,在尾节点为null时将抛出异常,时间复杂度为O(1)。 + +```java +public E getLast() { + final Node l = last; + if (l == null) + throw new NoSuchElementException(); + return l.item; +} +``` + +#### 设置元素 + +set(int index, E element)方法可以将指定index的元素设置为指定element,并返回旧元素,时间复杂度O(1)。 + +```java +public E set(int index, E element) { + checkElementIndex(index); + Node x = node(index); + E oldVal = x.item; + x.item = element; + return oldVal; +} +``` diff --git a/week_01/ArrayList.emmx b/week_01/ArrayList.emmx new file mode 100644 index 0000000..63d9ef6 Binary files /dev/null and b/week_01/ArrayList.emmx differ