From 6d83fe0f19595fbae1d3fe58d090c3d44d0c67f3 Mon Sep 17 00:00:00 2001 From: jiangjun huang Date: Sun, 8 Mar 2020 22:00:14 +0800 Subject: [PATCH] week 01 --- ...20\347\240\201\350\247\243\346\236\220.md" | 281 +++++++++++++++++ ...20\347\240\201\350\247\243\346\236\220.md" | 294 ++++++++++++++++++ ...20\347\240\201\350\247\243\346\236\220.md" | 157 ++++++++++ 3 files changed, 732 insertions(+) create mode 100644 "second/week_01/87/ArrayList\346\272\220\347\240\201\350\247\243\346\236\220.md" create mode 100644 "second/week_01/87/HashMap\346\272\220\347\240\201\350\247\243\346\236\220.md" create mode 100644 "second/week_01/87/LinkedList\346\272\220\347\240\201\350\247\243\346\236\220.md" diff --git "a/second/week_01/87/ArrayList\346\272\220\347\240\201\350\247\243\346\236\220.md" "b/second/week_01/87/ArrayList\346\272\220\347\240\201\350\247\243\346\236\220.md" new file mode 100644 index 0000000..cd767c7 --- /dev/null +++ "b/second/week_01/87/ArrayList\346\272\220\347\240\201\350\247\243\346\236\220.md" @@ -0,0 +1,281 @@ +# ArrayList + +## 根据面试题来学习源码: + +**1.new ArrayList的时候底层new了什么?** + +```java +private static final Object[] EMPTY_ELEMENTDATA = {}; + +transient Object[] elementData; + +public ArrayList() { + super(); + this.elementData = 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); + } +} +``` + + + +在new时不一定有创建对象,如果是空参,他是直接使用的静态成员变量。只有指定了initialCapacity(初始化容量) 时才会创建Object数组对象。 + +**2.凡数组都有类型,什么类型?** + +数组的类型是Object + +**3.数组要在内存中占据连续的内存空间,初始值以java8为例是多少?** + +```java +private static final int DEFAULT_CAPACITY = 10; + +private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {}; + +private static int calculateCapacity(Object[] elementData, int minCapacity) { + + if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) { + + return Math.max(DEFAULT_CAPACITY, minCapacity); + + } + + return minCapacity; + +} + +private void ensureExplicitCapacity(int minCapacity) { + + modCount++; + + // overflow-conscious code + + if (minCapacity - elementData.length > 0) + + grow(minCapacity); + +} + +private void grow(int minCapacity) { + + // overflow-conscious code + + int oldCapacity = elementData.length; + + // 1.5倍大小 + + int newCapacity = oldCapacity + (oldCapacity >> 1); + + if (newCapacity - minCapacity < 0) + + newCapacity = minCapacity; + + // 需要扩容 + + if (newCapacity - MAX_ARRAY_SIZE > 0) + + newCapacity = hugeCapacity(minCapacity); + + // minCapacity is usually close to size, so this is a win: + + // 底层通过 copyOf来处理 + + elementData = Arrays.copyOf(elementData, newCapacity); + +} +``` + +如上函数在add函数中调用用以确保 + +默认为10 + +**4.存25个元素进去可以吗?底层发生什么?底层扩容到多少?** + + **空参传递时,第一次add时是变成10个的容量,加到第10个时进行扩容为15个容量再到22 最后扩容到33 最终将元素都加进去了 +** + +**底层使用了 Arrays.copyOf函数** + +**5.搬家的过程用到了什么方法?** + + **Arrays.copyOf** + + **最终通过调用** System.*arraycopy这方法来处理* + + + +```java +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; + +} +``` + +**6.第二次扩容扩到多少?** + + **15 +** + +**7.Array线程安全还是不安全?** + + **非线程安全,可对比 Vector,其大多数操作都是通过** **synchronized关键字来进行修饰的进而保证线程的安全。** + +**8.请写一个线程不安全的例子?** + + **俩条线程去写同一个ArrayList 就会抛出**ConcurrentModificationException 此异常 + +**9.vector线程安全,ArrayList线程不安全,为什么?** + +**vector通过synchronized修饰来保证线程安全** + +**10.ArrayList 中 elementData 为什么使用 transient 修饰?** + +用**transient**关键字标记的成员变量不参与序列化过程,由于数组中部分并没有使用到,就没有必要进行序列化的。可以避免序列化空的元素。 + + + + + +## 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)); + } +``` + diff --git "a/second/week_01/87/HashMap\346\272\220\347\240\201\350\247\243\346\236\220.md" "b/second/week_01/87/HashMap\346\272\220\347\240\201\350\247\243\346\236\220.md" new file mode 100644 index 0000000..bc23982 --- /dev/null +++ "b/second/week_01/87/HashMap\346\272\220\347\240\201\350\247\243\346\236\220.md" @@ -0,0 +1,294 @@ +# HashMap + +> hashMap是Key-value形式的集合 + + + +## 面试题 + +1. 谈一下HashMap的特性? + + Key-value形式的集合,快速的存取,key不可重复,可以为null。线程不安全的集合类,底层是hash表(具体比较复杂)。 + +2. 谈一下HashMap的底层原理是什么? + + jdk8采用数组,链表,红黑树的数据结构。当一个k-v想存入一个map时先要进行hashCode运算其k,得到他在Bucket数组中的位置。而bucket数组内存储的就是Entry对象。 + +3. 谈一下hashMap中put是如何实现的? + + 1. 先计算key的hashCode值 + 2. 如果散列表为空时,调用resize初始化散列表 + 3. 如果没有发生碰撞,直接添加元素的到散列表中 + 4. 如果发生了hash碰撞,进行判断 + 1. 如果key地址相同或equals,就替换掉原来的值 + 2. 如果是红黑树,就调用树的插入方法 + 3. 如果是链表结构,循环遍历直到某个节点为空。尾插法进行插入,插入之后判断链表的个数是否达到红黑树的阈值8。如果equals就进行覆盖 + 5. 如果桶满了大于阈值,进行resize进行扩容 + + + + + + + + +## 源码 + + + +### resize + +```java + ```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; + } + ``` +``` + + + + + +### putVal + +```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; + } +``` + +### treeifyBin + + ```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); + } + } + ``` + + + +### get + +```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; +} +``` \ No newline at end of file diff --git "a/second/week_01/87/LinkedList\346\272\220\347\240\201\350\247\243\346\236\220.md" "b/second/week_01/87/LinkedList\346\272\220\347\240\201\350\247\243\346\236\220.md" new file mode 100644 index 0000000..ff65c6e --- /dev/null +++ "b/second/week_01/87/LinkedList\346\272\220\347\240\201\350\247\243\346\236\220.md" @@ -0,0 +1,157 @@ +# LinkedList + +> LinkedList 底层是通过双向链表实现,保留的头尾指针。通过Node数据结构,链表的插入和删除效率较高,链表插入效率较高。且链表相对ArrayList不存在有扩容的过程。 + +## 面试题: + + 1. 谈谈ArrayList和LinkedList的区别 + + 首先呢 底层的实现不同,ArrayList 底层是由数组来实现的。而LinkedList的底层是通过双向链表来实现的。 + + 由于底层实现的不同,因此他所有的方法都会有的不同的执行效率。简单的来说 ArrayList 并不适合做list中间或前面的元素增加或删除,也不适合于,频繁的增加数据(并不清楚数组内部要多少),由于扩容的关系会产生大量的垃圾,适用于固定大小的列表,或少量增加,查询的速度也相对较快,可以直接通过索引来获取对应的元素。而相对的,LinkedList就更适用于插入和删除,他也没有容量上的限制,理论上可以无限大小。查询其就是按照循环从头到尾的进行遍历来检索。内存上肯定是LinkedList会更占用内存。 + + 2. 初始化的时候做了什么? + + 空参构造时其并没有做什么操作,内置的头尾节点也没有初始化。 + + 在add时进行了属性初始化。 + + 3. 为什么内部要使用双向链表? + + 内部使用双向链表个人觉得优势在于可以前后俩头去找元素,提高了链表的处理效率。如果靠近尾部的可以通过尾部的往前去处理。 + + ```java + 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; + } + } + ``` + + + + + + + + + +## 源码 + +### add方法 + +```java +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; +} +``` + + + +### unlinkFisrt + +```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; +} +``` + + + +### linkFirst + +```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++; +} +``` + + + -- Gitee