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/05/01-ArrayList.md b/week_01/05/01-ArrayList.md new file mode 100644 index 0000000..c8cca2a --- /dev/null +++ b/week_01/05/01-ArrayList.md @@ -0,0 +1,398 @@ +# ArrayList 源码分析 + +## TOP 带着问题看源码 + +1. List list = new ArrayList(20) 扩容了几次 +2. ArrayList 怎么实现数组动态扩容,扩容时机,扩容倍数 +3. ArrayList 怎么实现remove的 +4. 为什么remove具体元素性能差 +5. ArrayList 是怎么序列化的 + +## 1. 继承和实现关系 + + + + + +- *RandomAccess 接口* + + 标记该类具有快速随机访问能力。当一个集合拥有该能力时候,采用for循环遍历会很快;若没有则采用Iterator迭代器最快。参考ArrayList的indexOf(Object o)方法和AbstractList的indexOf(Object o)方法区别。 + +- *Serializable 接口* + + 标记该类是可序列化的。 + +- *Cloneable 接口* + + 标记该类对象能够被Object.clone() + + 根据重写的clone方法实现主要分为如下两种克隆方式 + + 1. 浅克隆 + + 只copy对象本身和对象中的基本变量,不copy包含引用的对象 + + 2. 深克隆 + + 不仅copy对象本身,还copy对象包含的引用对象 + +- *AbstractList 抽象类* + + ​ 提供一些基础方法: IndexOf、clear、addAll、iterator等 + +## 2. 成员变量分析 + +```java +// 默认容量 +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; +// 数组最大容量,减8是因为可能一些VM会在数组保留一些header,防止OOM +private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8; +``` + +## 3. 构造方法分析 + +### 3.1 无参构造方法 + +默认赋值一个空数组实例 + +```java +public ArrayList() { + this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA; +} +``` + +### 3.2 带初始化容量的构造方法 + +可以看到是由参数的大小来创建对应大小的 elementData 数组,回到 **TOP 1** 问题,可以看出来不会发生扩容,也就是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); + } +} +``` + +### 3.3 带集合内容的构造方法 + +把传过来的集合转化为数组赋值给 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; + } +} +``` + +## 4. 核心方法分析 + +### 4.1 获取元素 + +先 check ,再按照 index 取。check也是为了保证工程中不会出现奇奇怪怪的结果 + +```java +public E get(int index) { + rangeCheck(index); + + return elementData(index); +} +``` + +使用 final 修饰的数组来接收存储数组,对其遍历。 modCount 变量和 final 修饰的 expectedModCount 进行对比来判断是否存在并发读写情况 + +```java +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.2 新增元素 + +#### 4.2.1 add(E e) + +把一个元素新增到elementData,主要涉及如下几点 + +1. modCount++ 声明我新增元素了,在并发情况下起到容量是否发生变化作用 +2. 如果容量不足,则扩容数组大小(参考下面grow方法) + +```java +public boolean add(E e) { + ensureCapacityInternal(size + 1); // Increments modCount!! + elementData[size++] = e; + return true; +} +``` + +####4.2.2 add(int index, E element) + +按照index位置来插入元素,和上面方法同理。 + +```java +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++; +} +``` + +####4.2.3 grow(int minCapacity) + +第4行可以看到,使用位运算扩容了 1.5 倍大小空间,至于为啥是1.5倍,我猜是经验值。 + +回到 **TOP 2** 问题,可以明白了扩容机制是通过数组 copy方式,时机就是容量不够的时候,倍数是1.5倍 + +```java +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); +} +``` + +### 4.3 更新元素 + +直接数组下标覆盖,返回旧值,至于为什么返回的是旧值,可能一方面是根据下标查询不是很影响性能索性给查询出来,另一方面下标和新值请求者都清楚也没必要返回。 + +```java +public E set(int index, E element) { + rangeCheck(index); + + E oldValue = elementData(index); + elementData[index] = element; + return oldValue; +} +``` + +### 4.4 删除元素 + +#### 4.4.1 remove(int index) + +计算要删除的下标后一位到数组末尾的长度,然后通过copy这段长度覆盖到原数组的位置,最后把最后一位置null,实现删除。 + +回到 **TOP 3** 问题,可以明白删除机制也是通过数组copy覆盖的思想来实现的 + +```java +public E remove(int index) { + rangeCheck(index); + + modCount++; + E oldValue = elementData(index); + // 计算长度 + int numMoved = size - index - 1; + if (numMoved > 0) + // param1: 源数组 + // param2: 源数组要复制的起始位置 + // param3: 目标数组 + // param4: 目标数组放置的起始位置 + // param5: 复制的长度 + System.arraycopy(elementData, index+1, elementData, index, + numMoved); + elementData[--size] = null; // clear to let GC do its work + + return oldValue; +} +``` + +####4.4.2 remove(Object o) + +首先分为两个场景,第一个是要删除的元素是null,第二个是要删除的是非null的。 + +主要是遍历要找的元素,找到该元素对应的index,然后使用 fastRemove(index) 去快速删除 + +回到 **TOP 4** 问题,可以明白计算某个元素下标的时间复杂度是 O(n) 的,所以性能没有直接根据下标删除好 + +```java +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.4.3 fastRemove(int index) + +因为调用该方法都是内部计算index后调用的,所以不需要再校验index是否越界,也不需要返回oldValue。 + +```java +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 +} +``` + +#### 4.4.4 clear() + +遍历赋值null,size重置为0 + +## 5. 序列化 + +首先我们在最开始就有介绍 ArrayList 类实现的有 Serializable 接口,但是我们在成员变量那一节看到的存储数组 elementData 是有 `transient` 修饰的,也就是elementData不会参与默认序列化,那实现这个 Serializable 接口还有意义么? + +其实仔细观察类里的方法你会发现有两个与序列化的流有关系的方法:`writeObject` 、`readObject` + +在序列化过程中如果有这两个方法,会默认调用这两个方法进行用户自定义的序列化和反序列化,如果没有才走默认序列化。 + +那么我们知道作者的序列化是自定义了,那为什么这样做呢,为什么不直接使用默认序列化呢? + +我们可以想下,每次扩容1.5倍,那这个数组实际会有一些空间扩容后还未被填充,如果使用默认序列化则会将null也给序列化进去。 + +接下来我们来看一下自定义序列化方法具体的实现: + +###5.1 writeObject + +写入数组大小,遍历写入数组元素,检查并发冲突 + +```java +private void writeObject(java.io.ObjectOutputStream s) + throws java.io.IOException{ + // Write out element count, and any hidden stuff + int expectedModCount = modCount; + s.defaultWriteObject(); + + // Write out size as capacity for behavioural compatibility with clone() + s.writeInt(size); + + // Write out all elements in the proper order. + for (int i=0; i

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