From 6a4cc45f761a397ff96ea93db33e4e3e49390824 Mon Sep 17 00:00:00 2001 From: qjwxpz Date: Thu, 19 Dec 2019 15:11:48 +0800 Subject: [PATCH 1/2] =?UTF-8?q?=E8=A1=A5=E5=85=85=E4=B8=8A=E5=91=A8?= =?UTF-8?q?=E5=86=85=E5=AE=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- week_01/32/linkedList.md | 443 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 443 insertions(+) create mode 100644 week_01/32/linkedList.md diff --git a/week_01/32/linkedList.md b/week_01/32/linkedList.md new file mode 100644 index 0000000..4870686 --- /dev/null +++ b/week_01/32/linkedList.md @@ -0,0 +1,443 @@ +# LinkedList源码阅读 + +## 1.1 linkedList继承了AbstractSequentialList,使用listIterator迭代器来实现很多的方法 + +## 1.2 linkedList: + 1.2.1: 实现 List 接口,能对它进行队列操作 + 1.2.2: 实现 Deque 接口,即能将LinkedList当作双端队列使用 + 1.2.3: 实现了Cloneable接口,即覆盖了函数clone(),能克隆 + 1.2.4: 实现java.io.Serializable接口,这意味着LinkedList支持序列化,能通过序列化去传输. + +## 1.3 linkedList属性: + 1.3.1: int size = 0; //默认元素初始数量为0 + 1.3.2: Node first; //定义链表的头节点为first + 1.3.3: Node last; //定义链表尾节点为last +## 1.4 linkedList构造方法: + 1.4.1: +```java + public LinkedList() { + //构造一个空的链表 + } +``` + 1.4.2: +```java + public LinkedList(Collection c) { + this(); //构造一个空的链表 + addAll(c);//将C中的元素添加到上述构造出的链表中 + } +``` +## 1.5 LinkedList底层数据结构: +```java + private static class Node { + E item; //某个元素 + Node next; //item后面关联的节点 + Node prev; //item前面一个节点 + + Node(Node prev, E element, Node next) { + this.item = element; + this.next = next; + this.prev = prev; + } + } +``` +## 1.6 LinkedList方法 + 1.6.1: +```java + private void linkFirst(E e) { //将元素e放在链表头位置 + final Node f = first; //原链表头位置节点 + final Node newNode = new Node<>(null, e, f); //创建新节点,前面一个节点为空,后面关联的节点为原链表的第一个节点 + first = newNode; //更新first节点 + if (f == null) //如果原来链表首节点为空,表示原来链表为空 + last = newNode; //更新last节点为新节点 + else + f.prev = newNode; //将原来链表的first节点的前面一个节点关联为新节点 + size++; //链表节点数+1 + modCount++; //链表节点被修改次数+1 + } +``` + 1.6.2: +```java + void linkLast(E e) { //将元素e放在链表最后 + final Node l = last; //获取链表最后节点 + final Node newNode = new Node<>(l, e, null); //创建新节点,前面一个节点为原链表最后节点,新节点的下一个节点为null + last = newNode; //更新last节点 + if (l == null) //如果原链表last节点为null,表示原链表为null, + first = newNode; //更新first节点为新节点 + else + l.next = newNode; //将原链表的最后一个节点的下一个节点更新为新节点 + size++; //链表节点数+1 + modCount++; //链表节点被修改次数+1 + } +``` + 1.6.3: +```java +``` + 1.6.4: +```java + void linkBefore(E e, Node succ) { //在节点succ前添加一个节点,新节点的下一个节点为succ,新节点的前面一个节点为succ的前一个节点 + // assert succ != null; + final Node pred = succ.prev; + final Node newNode = new Node<>(pred, e, succ); //创建的新节点的前一节点为succ的前一个节点,新节点的下一个节点为succ + succ.prev = newNode; //更新succ的前一节点为创建新节点 + if (pred == null) //如果原来链表的succ节点的前一节点为null,表示succ为链表的首节点 + first = newNode; //更新首节点为新节点 + else + pred.next = newNode; //更新succ的上一节点pred的下一个节点为新节点 + size++; //链表节点数+1 + modCount++; //链表节点被修改次数+1 + } +``` + 1.6.5: +```java + 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; //将next节点的上一节点赋值为空 + size--; //链表节点数-1 + modCount++;//链表节点被修改次数+1 + return element; + } +``` + 1.6.6: +```java + private E unlinkLast(Node l) { //释放链表尾节点 + // assert l == last && l != null; + final E element = l.item; //获取尾节点保存的元素 + final Node prev = l.prev; //获取尾节点的上一节点 + l.item = null; //将尾节点中的元素赋值为空 + l.prev = null; // help GC + last = prev; //将尾节点的上一节点赋值为尾节点 + if (prev == null) //判断原尾节点的上一节点是否为空,如果为空说明原链表为空 + first = null; + else + prev.next = null; //将prev节点的下一节点赋值为空 + size--; //链表节点数-1 + modCount++; //链表节点被修改次数+1 + return element; + } +``` + 1.6.7: +```java + E unlink(Node x) { //释放节点x + // assert x != null; + final E element = x.item; //获取节点x中保存的元素 + final Node next = x.next; //获取节点X的下一个节点 + final Node prev = x.prev; //获取节点x的上一个节点 + + if (prev == null) { //判断prev是否为空,如果为空说明节点x为链表头,此时释放节点x表示需要将节点x的下一个节点next变成链表头 + first = next; + } else { + prev.next = next; //将next节点赋值给prev的下一个节点 + x.prev = null; //将x的上一节点赋值为空 + } + + if (next == null) { //如果next为空,说明原来x节点是尾节点 + last = prev; //将prev赋值给尾节点 + } else { + next.prev = prev; //将prev赋值给next节点的上一节点 + x.next = null; //将空赋值给x的下一个节点 + } + //这个时候x就和整个链表没有连接关系了 + x.item = null; //将x中保存的元素赋值为空 + size--; //链表节点数-1 + modCount++; //链表节点被修改次数+1 + return element; + } +``` + 1.6.8: +```java + public E getFirst() { //获取头节点保存的元素 + final Node f = first; //获取头节点 + if (f == null) //如果头节点为空抛错 + throw new NoSuchElementException(); + return f.item; + } +``` + 1.6.9: +```java + public E getLast() { //获取尾节点保存的元素 + final Node l = last; //获取尾节点 + if (l == null) //如果尾节点为空抛错 + throw new NoSuchElementException(); + return l.item; + } +``` + 1.6.10: +```java + public E removeFirst() { //删除链表头节点 + final Node f = first; //获取链表头节点 + if (f == null) //判断链表头节点是否为空,为空表示整个链表为空,抛错 + throw new NoSuchElementException(); + return unlinkFirst(f); //调用释放头节点方法并返回头节点保存的元素 + } +``` + 1.6.11: +```java + public E removeLast() { //删除链表尾节点 + final Node l = last; //获取链表尾节点 + if (l == null) //判断链表尾节点是否为空,为空表示整个链表为空,怕错 + throw new NoSuchElementException(); + return unlinkLast(l); //电泳释放尾节点方法并返回原尾节点保存的元素 + } +``` + 1.6.12: +```java + public void addFirst(E e) { //添加元素到链表头节点 + linkFirst(e); //调用将元素放到链表头节点方法 + } +``` + 1.6.13: +```java + public void addLast(E e) { //添加元素到链表尾节点 + linkLast(e); //调用将元素放到链表尾节点方法 + } +``` + 1.6.14: +```java + public boolean contains(Object o) { //判断元素o是否在链表中 + return indexOf(o) != -1; //用元素o在链表的位置和-1比较,如果元素o没在链表中返回-1,在链表中返回元素o在链表中的实际位置 + } +``` + 1.6.15: +```java + public int size() { //获取链表节点数,即获取链表中保存的元素个数 + return size; + } +``` + 1.6.16: +```java + public boolean add(E e) { //添加一个元素到链表中 + linkLast(e); //调用将元素放到链表尾节点方法 + return true; + } +``` + 1.6.17: +```java + public boolean remove(Object o) { //删除链表中的节点保存的元素o,该方法如果链表中保存了多个节点中的元素与o相同,则删除距离first节点最近的一个 + if (o == null) { + for (Node x = first; x != null; x = x.next) { //冲链表头节点开始遍历,一直遍历到获取的节点x为空停止 + if (x.item == null) { //判断遍历的节点x中保存的元素是否为null,如果为空就移除该节点 + unlink(x); //释放节点x方法 + return true; + } + } + } else { + for (Node x = first; x != null; x = x.next) { //冲链表头节点开始遍历,一直遍历到获取的节点x为空停止 + if (o.equals(x.item)) { //判断遍历的节点x中保存的元素是否为o,如果是 + unlink(x); //释放节点x方法 + return true; + } + } + } + return false; + } +``` + 1.6.18: +```java + public boolean addAll(Collection c) { //添加集合到链表中 + return addAll(size, c); //调用添加集合到链表中方法,size原链表中节点个数,c添加的集合 + } +``` + 1.6.19: +```java + public boolean addAll(int index, Collection c) { //添加集合中元素到链表中 + checkPositionIndex(index); //判断是否下表越界 + + Object[] a = c.toArray(); //集合转数组 + int numNew = a.length; + if (numNew == 0) //如果集合没有元素,直接返回false + return false; + + Node pred, succ; //定义2个节点等哈用 + if (index == size) { //如果index和size相同,表示冲链表尾开始添加节点,所以succ为空,上一节点为原链表的尾节点 + succ = null; + pred = last; + } else { + succ = node(index); //取index上的节点,使用了折半查询 + pred = succ.prev; //获取index上的节点的上一个节点 + } + //遍历数组插入,首先拆散原先的index-1、index、index+1之间的联系,新建节点插入进去即可 + 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; + } +``` + 1.6.20: +```java + public void clear() { //清空链表节点 + // Clearing all of the links between nodes is "unnecessary", but: + // - helps a generational GC if the discarded nodes inhabit + // more than one generation + // - is sure to free memory even if there is a reachable Iterator + for (Node x = first; x != null; ) { + Node next = x.next; + x.item = null; + x.next = null; + x.prev = null; + x = next; + } + first = last = null; + size = 0; + modCount++; + } +``` + 1.6.21: +```java + public E get(int index) { //获取链表在index位置的元素,先看index下表越界没,然后获取index节点保存的元素 + checkElementIndex(index); + return node(index).item; + } +``` + 1.6.22: +```java + public E set(int index, E element) { //将链表index位置的节点保存的元素赋值为element并返回原来index位置节点保存的元素 + checkElementIndex(index); //先判断是否下表越界 + Node x = node(index); //然后获取index位置的节点 + E oldVal = x.item; // + x.item = element; + return oldVal; + } +``` + 1.6.23: +```java + public void add(int index, E element) { //在链表index位置添加一个节点,该节点保存的元素为element + checkPositionIndex(index); + + if (index == size) //如果index等于链表元素个数,则在链表尾添加节点 + linkLast(element); + else + linkBefore(element, node(index)); //在index位置前添加一个节点 + } +``` + 1.6.25: +```java + public E remove(int index) { //删除链表index位置上的节点 + checkElementIndex(index); //判断是否下标越界 + return unlink(node(index)); //调用释放节点方法 + } +``` + 1.6.26: +```java + Node node(int index) { //根据下标获取节点 + // assert isElementIndex(index); + //此处用到了折半法 + if (index < (size >> 1)) { //判断index是否大于链表个数的一半,来选择是冲头开始还是冲尾倒回 + 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; + } + } +``` + 1.6.27: +```java + public int indexOf(Object o) { //获取链表中第一个保存元素o的节点的位置,如果没有元素o返回-1 + int index = 0; + if (o == null) { + for (Node x = first; x != null; x = x.next) { + if (x.item == null) + return index; + index++; + } + } else { + for (Node x = first; x != null; x = x.next) { + if (o.equals(x.item)) + return index; + index++; + } + } + return -1; + } +``` + 1.6.28: +```java + public int lastIndexOf(Object o) { //获取链表中最后一个保存元素o的节点的位置,如果没有元素o返回-1 + int index = size; + if (o == null) { + for (Node x = last; x != null; x = x.prev) { + index--; + if (x.item == null) + return index; + } + } else { + for (Node x = last; x != null; x = x.prev) { + index--; + if (o.equals(x.item)) + return index; + } + } + return -1; + } +``` +## 1.7 LinkedList实现Deque方法 +```java + //interface Deque方法有: + void addFirst(E e); //插入头部 + void addLast(E e); //插入尾部 + boolean offerFirst(E e); //插入头部 + boolean offerLast(E e); //插入尾部 + E removeFirst(); //移除头部 + E removeLast(); //移除尾部 + E pollFirst(); //移除头部 + E pollLast(); //移除尾部 + E getFirst(); //获取头部 + E getLast(); //获取尾部 + E peekFirst(); //获取头部 + E peekLast(); //获取尾部 + +``` +## 1.8 LinkedList总结 + 1.8.1: linkedList允许null + 1.8.2: 因为双向链表,顺序访问效率高,随机效率低 + 1.8.3: 插入和删除只需要修改index-1,index,index+1之间的关联关系 + + + + + + + + + + + + + + + + + + + + + + -- Gitee From 1eee90f376673e0804489f43af663b0d6e0ac18e Mon Sep 17 00:00:00 2001 From: qjwxpz Date: Fri, 20 Dec 2019 00:28:43 +0800 Subject: [PATCH 2/2] =?UTF-8?q?=E8=A1=A5=E5=85=85=E4=B8=8A=E5=91=A8?= =?UTF-8?q?=E5=86=85=E5=AE=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- week_01/32/HashMap.md | 383 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 383 insertions(+) create mode 100644 week_01/32/HashMap.md diff --git a/week_01/32/HashMap.md b/week_01/32/HashMap.md new file mode 100644 index 0000000..52ae370 --- /dev/null +++ b/week_01/32/HashMap.md @@ -0,0 +1,383 @@ +# HashMap源码阅读1.7 + +## 1.1 HashMap继承了AbstractMap + +## 1.2 HashMap: + 1.2.1: 实现 Map 接口 + 1.2.2: 实现了Cloneable接口,即覆盖了函数clone(),能克隆 + 1.2.3: 实现Serializable接口,这意味着HashMap支持序列化,能通过序列化去传输. + +## 1.3 HashMap属性: + 1.3.1: static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; //默认初始容量(必须是2的幂,2的幂-1后的二进制位全是1) + 1.3.2: static final int MAXIMUM_CAPACITY = 1 << 30; //最大容量(必须是2的幂且小于2的30次方,传入容量过大将被这个值替换) + 1.3.3: static final float DEFAULT_LOAD_FACTOR = 0.75f; //元素占用容量的75%就扩容 + 1.3.4: static final Entry[] EMPTY_TABLE = {}; + 1.3.5: transient Entry[] table = (Entry[]) EMPTY_TABLE; // HashMap的实现方式 = 拉链法,Entry数组上的每个元素本质上是一个单向链表 + 1.3.6: transient int size; // HashMap的大小,即 HashMap中存储的键值对的数量 + 1.3.7: int threshold; // 扩容阈值,当哈希表的大小 ≥ 扩容阈值时,就会扩容哈希表(即扩充HashMap的容量) + 1.3.8: final float loadFactor; // 实际加载因子 + 1.3.9: transient int modCount;//数据结构变化次数 + 1.3.10: static final int ALTERNATIVE_HASHING_THRESHOLD_DEFAULT = Integer.MAX_VALUE; //容量的默认阈值 + 1.3.11: transient int hashSeed = 0; //与此实例关联的随机化值,应用于密钥的哈希代码,使哈希冲突更难 +## 1.4 HashMap构造方法: + 1.4.1: +```java + public HashMap() { //默认构造函数(无参)加载因子 & 容量 = 默认 = 0.75、16 + this(DEFAULT_INITIAL_CAPACITY, DEFAULT_LOAD_FACTOR); + } +``` + 1.4.2: +```java + public HashMap(int initialCapacity) {//指定容量大小的构造函数,加载因子 = 默认 = 0.75 、容量 = 指定大小 + this(initialCapacity, DEFAULT_LOAD_FACTOR); + } +``` +```java + public HashMap(int initialCapacity, float loadFactor) {//指定“容量大小”和“加载因子”的构造函数 + if (initialCapacity < 0) + throw new IllegalArgumentException("Illegal initial capacity: " + + initialCapacity); + // HashMap的最大容量只能是MAXIMUM_CAPACITY,哪怕传入的 > 最大容量 + if (initialCapacity > MAXIMUM_CAPACITY) + initialCapacity = MAXIMUM_CAPACITY; + if (loadFactor <= 0 || Float.isNaN(loadFactor)) + throw new IllegalArgumentException("Illegal load factor: " + + loadFactor); + this.loadFactor = loadFactor; //设置加载因子 + threshold = initialCapacity; //设置 扩容阈值 = 初始容量 + init(); // 一个空方法用于未来的子对象扩展 + } +``` +```java + public HashMap(Map m) { //创建一个包含map的HashMap + this(Math.max((int) (m.size() / DEFAULT_LOAD_FACTOR) + 1, + DEFAULT_INITIAL_CAPACITY), DEFAULT_LOAD_FACTOR); // 设置容量大小 & 加载因子 = 默认 + inflateTable(threshold); // 该方法用于初始化 数组 & 阈值 + + putAllForCreate(m); // 将传入的子Map中的全部元素逐个添加到HashMap中 + } +``` +## 1.5 HashMap底层数据结构: +```java + static class Entry implements Map.Entry { // Entry类实现了Map.Entry接口,即 实现了getKey()、getValue()、equals(Object o)和hashCode()等方法 + final K key; // 键 + V value; // 值 + Entry next; // 指向下一个节点 ,也是一个Entry对象,从而形成解决hash冲突的单链表 + int hash; // hash值 + + /** + * Creates new entry. + */ + Entry(int h, K k, V v, Entry n) { //构造方法,创建一个Entry,参数:哈希值h,键值k,值v、下一个节点n + value = v; + next = n; + key = k; + hash = h; + } + + public final K getKey() { //返回 与 此项 对应的键 + return key; + } + + public final V getValue() { // 返回 与 此项 对应的值 + return value; + } + + public final V setValue(V newValue) { + V oldValue = value; + value = newValue; + return oldValue; + } + public final boolean equals(Object o) { //判断2个Entry是否相等,必须key和value都相等,才返回true + if (!(o instanceof Map.Entry)) + return false; + Map.Entry e = (Map.Entry)o; + Object k1 = getKey(); + Object k2 = e.getKey(); + if (k1 == k2 || (k1 != null && k1.equals(k2))) { + Object v1 = getValue(); + Object v2 = e.getValue(); + if (v1 == v2 || (v1 != null && v1.equals(v2))) + return true; + } + return false; + } + + public final int hashCode() { + return Objects.hashCode(getKey()) ^ Objects.hashCode(getValue()); + } + + public final String toString() { + return getKey() + "=" + getValue(); + } + + /** + * This method is invoked whenever the value in an entry is + * overwritten by an invocation of put(k,v) for a key k that's already + * in the HashMap. + */ + void recordAccess(HashMap m) { + } + + /** + * This method is invoked whenever the entry is + * removed from the table. + */ + void recordRemoval(HashMap m) { + } + } +``` +## 1.6 HashMap主要方法 + 1.6.1: +```java + public V put(K key, V value) { + if (table == EMPTY_TABLE) { //若哈希表未初始化(即 table为空),则使用构造函数时设置的阈值(即初始容量) 初始化 数组table + inflateTable(threshold); + } + if (key == null) //判断key是否为空值null + return putForNullKey(value); //若key == null,则将该键-值 存放到数组table 中的第1个位置,即table [0],该位置永远只有1个value,新传进来的value会覆盖旧的value + int hash = hash(key); //根据键值key计算hash值 + int i = indexFor(hash, table.length); //根据hash值 最终获得 key对应存放的数组Table中位置 + for (Entry e = table[i]; e != null; e = e.next) { //判断该key对应的值是否已存在(通过遍历 以该数组元素为头结点的链表 逐个判断) + Object k; + if (e.hash == hash && ((k = e.key) == key || key.equals(k))) { //若该key已存在(即 key-value已存在 ),则用 新value 替换 旧value + V oldValue = e.value; + e.value = value; + e.recordAccess(this); + return oldValue; + } + } + + modCount++; + addEntry(hash, key, value, i); //该key不存在,则将“key-value”添加到table中 + return null; + } +``` + 1.6.2: +```java + void addEntry(int hash, K key, V value, int bucketIndex) { //添加键值对(Entry )到 HashMap中 + if ((size >= threshold) && (null != table[bucketIndex])) { //插入前,先判断容量是否足够,如果不够扩容2倍 + resize(2 * table.length); + hash = (null != key) ? hash(key) : 0; //重新计算key对应的hash值 + bucketIndex = indexFor(hash, table.length); //重新计算该Key对应的hash值的存储数组下标位置 + } + + createEntry(hash, key, value, bucketIndex); //若容量足够,则创建1个新的数组元素(Entry) 并放入到数组中 + } +``` + 1.6.3: +```java + void createEntry(int hash, K key, V value, int bucketIndex) { //创建1个新的数组元素(Entry) 并放入到数组中 + Entry e = table[bucketIndex]; //获取table中该位置原来的Entry + //在table中该位置新建一个Entry:将原头结点位置(数组上)的键值对 放入到(链表)后1个节点中、将需插入的键值对 放入到头结点中(数组上)-> 从而形成链表 + //即 在插入元素时,是在链表头插入的,table中的每个位置永远只保存最新插入的Entry,旧的Entry则放入到链表中(即 解决Hash冲突) + table[bucketIndex] = new Entry<>(hash, key, value, e); + size++; //哈希表的键值对数量计数增加 + } +``` + 1.6.4: +```java + static int indexFor(int h, int length) { + // assert Integer.bitCount(length) == 1 : "length must be a non-zero power of 2"; + return h & (length-1); //// 将对哈希码扰动处理后的结果 与运算(&) (数组长度-1),最终得到存储在数组table的位置(即数组下标、索引) + } +``` + 1.6.5: +```java + private void inflateTable(int toSize) { + // Find a power of 2 >= toSize + int capacity = roundUpToPowerOf2(toSize); //将传入的容量大小转化为:>传入容量大小的最小的2的次幂 + + threshold = (int) Math.min(capacity * loadFactor, MAXIMUM_CAPACITY + 1); //重新计算阈值 threshold = 容量 * 加载因子 + table = new Entry[capacity]; //使用计算后的初始容量(已经是2的次幂) 初始化数组table(作为数组长度) + initHashSeedAsNeeded(capacity); + } +``` + 1.6.6: +```java + private static int roundUpToPowerOf2(int number) { //将传入的容量大小转化为:>传入容量大小的最小的2的幂 + // assert number >= 0 : "number must be non-negative"; + return number >= MAXIMUM_CAPACITY + ? MAXIMUM_CAPACITY + : (number > 1) ? Integer.highestOneBit((number - 1) << 1) : 1;//若容量超过了最大值,初始化容量设置为最大值 ;否则,设置为:>传入容量大小的最小的2的次幂 + } +``` + 1.6.8: +```java + private V putForNullKey(V value) { + // 遍历以table[0]为首的链表,寻找是否存在key==null 对应的键值对,若有:则用新value 替换 旧value;同时返回旧的value值 + for (Entry e = table[0]; e != null; e = e.next) { + if (e.key == null) { + V oldValue = e.value; + e.value = value; + e.recordAccess(this); + return oldValue; + } + } + modCount++; + //若无key==null的键,那么调用addEntry(),将空键 & 对应的值封装到Entry中,并放到table[0]中 + addEntry(0, null, value, 0); + return null; + } +``` + 1.6.9: +```java + final int hash(Object k) { //计算hash,将键key转换成 哈希码(hash值)操作 + int h = hashSeed; // + if (0 != h && k instanceof String) { + return sun.misc.Hashing.stringHash32((String) k); + } + + h ^= k.hashCode(); + + // This function ensures that hashCodes that differ only by + // constant multiples at each bit position have a bounded + // number of collisions (approximately 8 at default load factor). + h ^= (h >>> 20) ^ (h >>> 12); + return h ^ (h >>> 7) ^ (h >>> 4); + } +``` + 1.6.10: +```java + public V get(Object key) { + if (key == null) //当key为null时,则到 以哈希表数组中的第1个元素(即table[0])为头结点的链表去寻找对应 key == null的键 + return getForNullKey(); + //当key ≠ null时,去获得对应值 + Entry entry = getEntry(key); + + return null == entry ? null : entry.getValue(); + } +``` + 1.6.11: +```java + private V getForNullKey() { //当key == null时,则到 以哈希表数组中的第1个元素(即table[0])为头结点的链表去寻找对应 key == null的键 + if (size == 0) { + return null; + } + for (Entry e = table[0]; e != null; e = e.next) { //遍历以table[0]为头结点的链表,寻找 key==null 对应的值 + if (e.key == null) //从table[0]中取key==null的value值 + return e.value; + } + return null; + } +``` + 1.6.12: +```java + final Entry getEntry(Object key) { //获取key对应的值 + if (size == 0) { + return null; + } + + int hash = (key == null) ? 0 : hash(key); //根据key值,通过hash()计算出对应的hash值 + for (Entry e = table[indexFor(hash, table.length)]; + e != null; + e = e.next) { //遍历 以该数组下标的数组元素为头结点的链表所有节点,寻找该key对应的值 + Object k; + if (e.hash == hash && + ((k = e.key) == key || (key != null && key.equals(k)))) + return e; + } + return null; + } +``` + 1.6.13: +```java + void resize(int newCapacity) { //当容量不够是扩容2倍 + Entry[] oldTable = table; //获取旧数组 + int oldCapacity = oldTable.length; //得到旧数组长度 + if (oldCapacity == MAXIMUM_CAPACITY) { //若旧容量已经是系统默认最大容量了,那么将阈值设置成整型的最大值,退出 + threshold = Integer.MAX_VALUE; + return; + } + + Entry[] newTable = new Entry[newCapacity]; //根据新容量(2倍容量)新建1个数组,即新table + transfer(newTable, initHashSeedAsNeeded(newCapacity)); //将旧数组上的数据(键值对)转移到新table中,从而完成扩容 + table = newTable; //将新数组table引用到HashMap的table属性上 + threshold = (int)Math.min(newCapacity * loadFactor, MAXIMUM_CAPACITY + 1); //重新设置阈值 + } +``` + 1.6.14: +```java + void transfer(Entry[] newTable, boolean rehash) { //将旧数组上的数据(键值对)转移到新table中,从而完成扩容 + int newCapacity = newTable.length; //获取新数组的大小 = 获取新容量大小 + for (Entry e : table) { //通过遍历旧数组,将旧数组上的数据(键值对)转移到新数组中 + while(null != e) { + Entry next = e.next; //转移链表时,因是单链表,故要保存下1个结点,否则转移后链表会断开 + if (rehash) { + e.hash = null == e.key ? 0 : hash(e.key); + } + int i = indexFor(e.hash, newCapacity); //计算保存位置 + e.next = newTable[i]; //将元素放在数组上 + newTable[i] = e; + e = next; //访问下1个Entry链上的元素,如此不断循环,直到遍历完该链表上的所有节点 + } + } + } +``` + 1.6.15: +```java + final Entry removeEntryForKey(Object key) { //删除 + if (size == 0) { + return null; + } + int hash = (key == null) ? 0 : hash(key); //计算hash值 + int i = indexFor(hash, table.length); //计算存储的数组下标位置 + Entry prev = table[i]; + Entry e = prev; + + while (e != null) { + Entry next = e.next; + Object k; + if (e.hash == hash && + ((k = e.key) == key || (key != null && key.equals(k)))) { + modCount++; + size--; + if (prev == e) //若删除的是table数组中的元素(即链表的头结点),则删除操作 = 将头结点的next引用存入table[i]中 + table[i] = next; + else + prev.next = next; //否则 将以table[i]为头结点的链表中,当前Entry的前1个Entry中的next 设置为 当前Entry的next(即删除当前Entry = 直接跳过当前Entry) + e.recordRemoval(this); + return e; + } + prev = e; + e = next; + } + + return e; + } +``` + +## 1.7 HashMap总结 + 1.7.1:HashMap采用的数据结构为数组+单链表--拉链法(1.8采用数组+链表+红黑数(当链表长度>8时))。 + 1.7.2:HashMap的初始容量是16,扩容因子是0.75,当size>16*0.75的时候HashMap扩容到32 + 1.7.3:数组的元素就是键值对(一个链表),数组的下标就是Key的hash值,数组的大小就是HashMap的容量。 + 由于不同的Key计算的hash值可能会相同(hash冲突),链表就是用于解决这种冲突的。即发生冲突时,新元素插入到链表头中,新元素总是添加到数组里,旧元素移到单列表中。 + 1.7.4:HashMap在put的时候,插入的元素超过了容量(由负载因子决定)的范围就会触发扩容操作,就是rehash, + 这个会重新将原数组的内容重新hash到新的扩容数组中,在多线程的环境下,存在同时其他的元素也在进行put操作, + 如果hash值相同,可能出现同时在同一数组下用链表表示,造成闭环,导致在get时会出现死循环,所以HashMap是线程不安全的。 + + + + + + + + + + + + + + + + + + + + + + + + + + -- Gitee