韩顺平 零基础30天学会Java 2021,整理1:HashMap源码,ArrayList,LinkedList,HashSet,LinkedHashSet和LinkedHashMap

1、体系

Collection

  • List
    • ArrayList
    • LinkedList
    • Vector
  • Set
    • HashSet
      • Linked HashSet
    • Tree Set

Map

  • HashMap
    • Linked HashMap
  • TreeMap
  • Hashtable
    • Properties

2、ArrayList

1)ArrayList中维护了一个Object类型的数组

2)当创建ArrayList对象时,如果使用的是无参构造器,则初始elementData容量为0第1
次添加,则扩容elementData为10,如需要再次扩容,则扩容elementData为1.5倍

  • 0 10 15 22 33

3)如果使用的是指定大小的构造器容量为指定大小,如果需要扩容,
则直接扩容elementData为1.5倍。

扩容逻辑

首次扩容 10
//首次: DEFAULT_CAPACITY为10,minCapacity=1,肯定取 10
minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
    private void grow(int minCapacity) {

        int oldCapacity = elementData.length;
        //新长度 = 旧长度 + 旧长度/2。所以为:1.5倍
        //旧长度 第一次为 0,1.5 倍依然为 0
        int newCapacity = oldCapacity + (oldCapacity >> 1);
        
        //如果 新长度 - 10(首次) < 0。新长度首次为 0
        if (newCapacity - minCapacity < 0)
            //新长度 赋值为 默认长度
            newCapacity = minCapacity;

        // minCapacity is usually close to size, so this is a win:
        elementData = Arrays.copyOf(elementData, newCapacity);
    }
第11次,扩容为15
		modCount++;		        

		//minCapacity为11 - 10 为1,
		//如果最小的容量(当前容量+1的) - 当前数组实际的大小 > 0,说明不够了,需要扩容了
		if (minCapacity - elementData.length > 0)
            grow(minCapacity);
        //为10
		int oldCapacity = elementData.length;
		//为15
        int newCapacity = oldCapacity + (oldCapacity >> 1);
		//15 - 11 >0
        if (newCapacity - minCapacity < 0)
            newCapacity = minCapacity;//不执行这个逻辑

		//直接 使用15这个长度,复制数组。
拷贝数组底层
        
        //原来为空数组,0,新数组,0。 两个0代表,拷贝的位置
        //从源数组拷贝多少个数据到目标数组,
		//为:原数组实际长度 和 新数组长度的 最小值。主要是防止 新数组 长度太短。
		System.arraycopy(original, 0, copy, 0,
                         Math.min(original.length, newLength));

3、LinkedList

  1. LinkedList底层维护了一个双向链表.
  2. LinkedList中维护了两个属性first和last分别指向首节点和尾节点
    3)每个节点(Node对象),里面又维护了prev、next、item三个属性,其中通过prev指向前一个,通过next指向后一个节点。最终实现双向链表.
  • 删除只需要改变:Node对象里, A B C 。需:A next 改为 C,C的 prev 改为 A。B就被 删除了。

4)所以LinkedList的元素的添加和删除,不是通过数组完成的,相对来说效率较高。

public class LinkedList<E>
    extends AbstractSequentialList<E>
    implements List<E>, Deque<E>, Cloneable, java.io.Serializable
{
    transient int size = 0;

    transient Node<E> first;


    transient Node<E> last;
    
    private static class Node<E> {
        E item;
        Node<E> next;
        Node<E> prev;

        Node(Node<E> prev, E element, Node<E> next) {
            this.item = element;
            this.next = next;
            this.prev = prev;
        }
    }
}

极简模拟链表

        Node n1 = new Node("张三");
        Node n2 = new Node("李四");
        Node n3 = new Node("王五");

        //n1 n2 n3
        n1.next = n2;
        n2.next = n3;

        //n3 n2 n1
        n3.pre = n2;
        n2.pre = n1;

        //测试节点 插入到 n2 和 n3之间
        Node t = new Node("test");
        //指向下一个节点
        t.next = n3;
        //指向前一个 节点
        t.pre = n2;

        //n3的前一个节点,指向t
        n3.pre = t;
        //n2的下一个节点,指向t
        n2.next = t;

        Node first = n1;
        Node last = n3;

        while (true) {
            if (first == null) {
                break;
            }
            System.out.println(first.name);
            first = first.next;
        }


    }

    static class Node {
        Node pre;
        Node next;

        private String name;

        public Node(String name) {
            this.name = name;
        }
    }

删除首元素源码

        LinkedList linkedList = new LinkedList();
        linkedList.add(1);
        linkedList.add(2);

        linkedList.remove(); // 这里默认删除的是第一个结点
    public E remove() {
        return removeFirst();
    }
    public E removeFirst() {
        final Node<E> f = first;
        if (f == null)
            throw new NoSuchElementException();
        return unlinkFirst(f);
    }
    
    private E unlinkFirst(Node<E> f) {
        //获取元素里的 内容,用于返回
        final E element = f.item;
        
        //获取 首元素的 第二个节点
        final Node<E> next = f.next;
        
        //首元素的 元素内容和 下一个指针都清空。
        f.item = null;
        f.next = null; // help GC
        
        //首指针,现在指向:第二个节点。
        first = next;
        //如果 第二个元素 为null了,说明没了,
        if (next == null)
            last = null;//这 指针 下一个 指为 null
        else
            //否则:就把这个,刚刚升为 首指针的 Node的 上一个指针,指为null
            next.prev = null;
        //长度 -1
        size--;
        //修改数++
        modCount++;
        return element;
    }

1. next = f.next ,取出 原来的 第二个节点(现在作为 第一个节点)。

2. 原来的 首节点 的 next 指向 断开。 f.next = null;    **重要**

3. first 首节点指针,指向第二个节点  first = next;  **重要**

4. 原来的第二个 节点的 prev = null (现在变为 首节点了)  **重要**

增加为尾元素源码

    public boolean add(E e) {
        linkLast(e);
        return true;
    }
    void linkLast(E e) {
        //获取 尾结点
        final Node<E> l = last;
        //创建 一个新节点 为 尾结点。上一个节点为 原尾结点,下一个节点为null
        final Node<E> newNode = new Node<>(l, e, null);
        //尾指针 = 尾结点
        last = newNode;
        if (l == null) //如果原来尾结点 为null,首次添加
            first = newNode;//新节点 也指向 首节点
        else//否则 尾结点为 null
            l.next = newNode;//尾结点的下一个 为新节点
        size++;//长度++
        modCount++;//修改次数++
    }

        Node(Node<E> prev, E element, Node<E> next) {
            this.item = element;
            this.next = next;
            this.prev = prev;
        }

4、HashSet 和 HashMap 转成树

  • HashSet 实际是 hashMap。是数组 + 链表+红黑树
    public HashSet() {
        map = new HashMap<>();
    }
  • 链表,变成红黑树
    • 链表的数据 添加了第9个(超过8个),数组(也叫table)的大小,>= 64

    • 如果 table表 没到,table 表 会扩容(2倍)

    • 
                              			p.next = newNode(hash, key, value, null);
                    
                    			//即:添加第9个后。下面会说明。
                    			//binCount >= 7。binCount从0开始,上面新添加了一个。
                              if (binCount >= TREEIFY_THRESHOLD - 1)
                                  treeifyBin(tab, hash);
            ```
      
      
    •  		//如果 < 64,依然扩容。不转成树
                if (tab == null || (n = tab.length) < MIN_TREEIFY_CAPACITY)
                    resize();
      

2。添加一个元素时,先得到hash值-会转成->索引值

3.找到存储数据表table,看这个索引位置是否已经存放的有元素

4。如果没有,直接加入

5.如果有,调用equals比较,如果相同,就放弃添加,如果不相同,则添加到最后

整理

1.先获取元素的哈希值( hashCode方法)

2.对哈希值进行运算(hashCode & 最大索引),得出一个索引值即为要存放在哈希表中的位置号

3.如果该位置上没有其他元素,则直接存放
如果该位置上已经有其他元素,则需要进行equals判断

  • 如果相等,则不再添加。
  • 如果不相等,则以链表的方式添加。

HashSet的扩容和转成红黑树机制

1.HashSet底层是HashMap,第一次添加
时,table数组扩容到16,临界值(threshold)是16加载因子
(loadFactor)是0.75 = 12

2.如果table数组使用到了临界值12 (超过12,第13次添加后),就
会扩容到16*2= 32,新的临界值就是32*0.75 = 24,依次类推

3.在Java8中,如果一条链表的元素个数到
达 TREEIFY_THRESHOLD(默认是8,第9次添加后),并且table的大小>=
MIN TREEIFY CAPACITY(默认64),就会进行树化(红黑树),否则仍然采用数组扩容机制

HashSet的添加返回值 布尔

  • 因为返回 旧值,也没有意义,就是 一个空对象而已。
    public boolean add(E e) {
        //hashMap返回的是 null,hashSet 判断了,返回 true
        return map.put(e, PRESENT)==null;
    }

HashMap添加的返回值

  • 添加成功,返回 null
  • 添加添加失败,返回被替换掉的旧值。
    final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
                   boolean evict) {

            if (e != null) {

                //添加失败,返回 旧值,就是 被替换的值。
                V oldValue = e.value;
                
                if (!onlyIfAbsent || oldValue == null)
                    //新值 会进行替换。
                    e.value = value;
                afterNodeAccess(e);
                return oldValue;
            }
        }
        ++modCount;
		
        //添加成功返回null
        return null;
    }
        HashMap h=new HashMap();
        Object put1 = h.put(1, 100);
		//添加成功,返回 null
        System.out.println(put1);

		//添加添加失败,返回被替换掉的旧值。为100
        Object put2 = h.put(1, 200);
        System.out.println(put2);
		//{1=200}
        System.out.println(h);

HashMap的 hash方法解析

    static final int hash(Object key) {
        int h;
        //key如果为 null,就是 0,所以:所有的null值,都放在第一个。
        
        //key 取 hashCode() ^ h无符号 右移动16位。 
        return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
    }

//hash("java") = 3254803, 注意:这个值,不是 hashCode
//因为无符号右移 16位,得到了一个很小的,又和这个 很小的值 异或了。
    
//比如下面:774889 ^ 11 = 774882 ,小了 7
//总之 就是为了 避免碰撞。
  • 注意:这个值,不是 hashCode

  • 2进制 100,为4,右移1为 就是2

左 右 移位
  1. 算术右移 >>:低位溢出,符号位不变,并用符号位补溢出的高位
  • 第一位为1,表示 负数
  1. 算术左移 <<: 符号位不变,低位补 0

  2. 逻辑右移也叫无符号右移,运算规则是: 低位溢出,高位补 0

  3. 特别说明:没有 <<< 符号

按位异或^

1代表 真, 0代表假

  • 两位一个为0,一个为1,结果为1,否则为0 (不同为1,相同为0)
  • 111 ^ 101 结果为:010
  • 当 a 和 b 不同时,则结果为 true
String的 hashCode方法
  • 单字符,为字符的 unicode 编码
        //new一个 char数组。
        char val[] = new char[]{'A'};
        //char数组直接 可以复制为 in
        int unicode = val[0];
        // 大A的 unicode 为 65,也就是 “A".hashCode()
        System.out.println(unicode);

//小a是97,"1" 为 49
  • “Aa”.hashCode() 为:65*31 + 97 = 2112
    • 第一次循环为:65
    • 第二次循环为: 65*31 + 97
  • Aa1.hashCode() 为:(65*31+97 )* 31 + 49 = 65521
    • 第一次循环为: 65
    • 第二次循环为:65*31 + 97 = 2112
    • 第三次循环为:2112 * 31 + 49 = 65472+49=65521
    public int hashCode() {
        int h = hash;
        
        if (h == 0 && value.length > 0) {
            
            char val[] = value;

            for (int i = 0; i < value.length; i++) {
                h = 31 * h + val[i];
            }
            
            hash = h;
        }
        return h;
    }
扰动函数 hashCode 异或 右移
  • (h = key.hashCode()) ^ (h >>> 16)
h.put(new Person("张三"), "1");

		//key.hashCode() 如上 就是调用 Person对象向的 hashCode方法
        @Override
        public int hashCode() {
            return 1;
        }
//二进制 0001 0000 0000 0000 0000,10进制为 65536
//右移动 16位 为 1
//0001 0000 0000 0000 0000 异或 0000 0000 0000 0000 0001
//相同为0,不同为1,结果为:65537
        System.out.println(65536 >>> 16);//1
        System.out.println(65536 ^ 1);//65537

负载因子0.75

  • 在 有参和无参构造时 初始化
    static final float DEFAULT_LOAD_FACTOR = 0.75f;
    
    public HashMap() {
        this.loadFactor = DEFAULT_LOAD_FACTOR; // all other fields defaulted
    }

HashMap的Node

  • Node 是内部类,内部类 模拟的 单向链表
  • Node 构成的数组,称作:table
    transient Node<K,V>[] table;
static class Node<K,V> implements Map.Entry<K,V> {
        final int hash;
        final K key;
        V value;
        Node<K,V> next;

    	//hash,key,value, 下一个 指针
        Node(int hash, K key, V value, Node<K,V> next) {
            this.hash = hash;
            this.key = key;
            this.value = value;
            this.next = next;
        }

        public final 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;
        }
    }

(1)HashMap的put首次执行

主方法 put:最大索引 & hash
    public V put(K key, V value) {
        return putVal(hash(key), key, value, false, true);
    }

	//hash key value,仅仅是缺席的,为false。驱逐为true

	//onlyIfAbsent – if true, don't change existing value 
	//如果为真,不改变现有值。现在传递为 false

	//evict – if false, the table is in creation mode. 
	//如果为false,表处于创建模式。
	final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
                   boolean evict) {
        Node<K,V>[] tab; Node<K,V> p; int n, i;
        
        if ((tab = table) == null || (n = tab.length) == 0)
            //首次 执行初始化。tab进行了,赋值。n赋值为了16
            n = (tab = resize()).length;
        
        //n-1为15,i赋值为了15。 
        //15 & 一个非常大的数,结果最大为 15,即:最大索引。所以:不会越界。
        //从 tab[i] 中 取出值,赋值给 p,首次 肯定为 null
        if ((p = tab[i = (n - 1) & hash]) == null)
            //首次存入,hash,key,value,下个指针为null
            tab[i] = newNode(hash, key, value, null);
        else {
            xx暂时不看
        }
        //修改次数++
        ++modCount;
        //添加后,长度+1 判断是否 大于 阈值12
        if (++size > threshold)
            resize();
        //空的钩子方法。添加成功返回null
        afterNodeInsertion(evict);
        return null;
    }

    void afterNodeInsertion(boolean evict) { }
resize 扩容16和2倍
  • 默认16,每次达到0.75,扩容2倍。下次为32
  • ArrayList 默认是10,满了就扩容1.5倍。下次为15
    final Node<K,V>[] resize() {
        //首次 table 为null,所以 oldTable 也为null
        Node<K,V>[] oldTab = table;
        //所以:oldCap 为 0
        int oldCap = (oldTab == null) ? 0 : oldTab.length;
        //threshold 默认为0,所以 oldThr也为0
        int oldThr = threshold;
        
        int newCap, newThr = 0;
        
        if (oldCap > 0) {
			//本次不执行,这便是扩容2倍的逻辑,详见下面的代码。
            (newCap = oldCap << 1) //总容量扩容2倍
            newThr = oldThr << 1; // double threshold。阈值 原来是12,也扩容2倍。
        }
        else if (oldThr > 0) //不执行
            newCap = oldThr;
        else {
            //0 初始化 零 初始 阈值 表示 使用 默认值
            // zero initial threshold signifies using defaults
            newCap = DEFAULT_INITIAL_CAPACITY;//16
            //0.75 * 16 = 12
            newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);
        }
        if (newThr == 0) {
            //不执行。
        }
        //12 赋值给 threshold,即:扩容阈值。
        threshold = newThr;
        
        //创建了一个 Node 数组
        Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap];
        //赋值给了 table
        table = newTab;
        
		//返回 创建的数组。此数组 长度为 16
        return newTab;
    }

(2)put 第多次执行(非扩容 未冲突)

        HashMap h = new HashMap();
        h.put("A", "B");
	
		//本次断点,这一行代码
        h.put("AA", "BB");
     final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
                   boolean evict) {
        Node<K,V>[] tab; Node<K,V> p; int n, i;

        //tab 已经不为Null,第一个条件为假。
        //执行第二个条件,n赋值为 tab的长度,为 16
        if ((tab = table) == null || (n = tab.length) == 0)
            n = (tab = resize()).length;
         
        //依然是: 15&非常大的值,赋值为i(即为 这个数组的索引)
        //先取一下 赋值为 p,此时p 为 null
        if ((p = tab[i = (n - 1) & hash]) == null)
            //把最新的值,放入数组
            tab[i] = newNode(hash, key, value, null);
        else {}
        
         //下面的逻辑一样
         return null;
     }

(3)put 第多次执行(非扩容 键冲突)

        HashMap h = new HashMap();
        h.put("A", "B");
        h.put("A", "AA");
    final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
                   boolean evict) {
        Node<K,V>[] tab; Node<K,V> p; int n, i;
        
        //n 赋值为 16
        if ((tab = table) == null || (n = tab.length) == 0)
            //不执行这里
        //p 为 取出的值, 冲突时 tab[i] !=null
        if ((p = tab[i = (n - 1) & hash]) == null)
            //不执行这里
        else {
            //执行这里
            Node<K,V> e; K k;
            
            //p为 数组中[i] 存入的值,的 hash (hash都是key) 这里 参数传递的 hash 是相同的。
            // p.ke == key,也是为 true 
            if (p.hash == hash &&
                ((k = p.key) == key || 
                 (key != null && key.equals(k))))
                
                //表中i 位置的 Node 赋值给了 e
                e = p;
            
            else if (p instanceof TreeNode){
            }
            
            //e不为null
            if (e != null) { 
                // existing mapping for key
                //取出旧的值
                V oldValue = e.value;
                
    //onlyIfAbsent – if true, don't change existing value 
	//如果为真,不改变现有值。现在传递为 false
                //不为真 或者 旧值为 null
                if (!onlyIfAbsent || oldValue == null)
                    // 数组里 [i] 的位置,使用新值 替换。
                    e.value = value;
                
                //钩子方法
                afterNodeAccess(e);
                //返回旧值
                return oldValue;
            }
            
            
        }
        
            void afterNodeAccess(Node<K,V> p) { }

(4)hashCode()相同,equals 不同。转成树

        HashMap h = new HashMap();
        h.put(new Person("张三"), "1");
        h.put(new Person("李四"), "2");
		h.put(new Person("李四"), "3");

    @Data
    @AllArgsConstructor
    static
    class Person {
        String name;

        @Override
        public boolean equals(Object o) {
            if (this == o) return true;
            if (o == null || getClass() != o.getClass()) return false;
            Person person = (Person) o;
            return Objects.equals(name, person.name);
        }

        @Override
        public int hashCode() {
            return 1;
        }
    }
    static final int hash(Object key) {
        int h;
        //重写hashCode后,HashMap所有的 hash方法,都返回1
        return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
    }
  • 添加 张三 时
		//15 & 1 依然为 1,会放到 数组为 1的位置,i为1
		if ((p = tab[i = (n - 1) & hash]) == null)
            tab[i] = newNode(hash, key, value, null);
        else {
        }
  • 添加 李四 时
        //p 不为null,p指向了 张三
		if ((p = tab[i = (n - 1) & hash]) == null)
            //这里不会执行
            tab[i] = newNode(hash, key, value, null);
        else {
            
            Node<K,V> e; K k;
            // hash相同
            // p的 key为 person张三  == person李四,条件为假
            //继续执行:key.equals(k),重写过 equals,条件依然为 假
            
            //真 && 假 || 假 ,次判断条件为 假
            if (p.hash == hash &&
                ((k = p.key) == key || (key != null && key.equals(k))))
                e = p;
            else if (p instanceof TreeNode)
                e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
            else {
                //进入到这个逻辑里
                //死循环,从0开始遍历
                for (int binCount = 0; ; ++binCount) {
                    //使用next 指针的逻辑。 如果 p.next 就是 Node[i]取出的person张三,下一个指针为 null
                    //此判断成立
                    if ((e = p.next) == null) {
                        //添加到 person张三后面,
                        p.next = newNode(hash, key, value, null);//添加了第9个。执行: treeifyBin(tab, hash);
                        
                        //判断一下,这个链表的长度是否 >= 7。binCount从0开始。就是是否 >=8。
                        //在加上 上面添加的一个。即:添加第9个后。

                        //binCount=0,上面添加的为 第2个
                        //binCount=7,上面添加的为 第9个后。
                        if (binCount >= TREEIFY_THRESHOLD - 1)
                            treeifyBin(tab, hash);
                        break;
                    }
                    
                    //h.put(new Person("李四"), "3");再次添加 李四,即可进入这个逻辑。
                    
                    //上面的 p = tab[i = (n - 1) & hash],p是元素的首节点。链表的首节点(table里[i]位置第一个元素)
                    //e = p.next。e是 下一个节点。 让 e 和 要插入的新的key(结点) 比较
                    //有相同了,就 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;
            }
        }

(5)第二次多次扩容

        HashMap h = new HashMap();
		
		
		//断点 第13 次循环
        for (int i = 1; i <= 13; i++) {
            String k = "k" + i;
            String v = "v" + i;
            h.put(k, v);
        }
    final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
                   boolean evict) {
                   ...添加完毕后
        
        ++modCount;
        //第12次,size为11,++size为12
        //第13次,size为12,++size为13,> threshold
        if (++size > threshold)
            resize();
        afterNodeInsertion(evict);
        return null;
    }
    final Node<K,V>[] resize() {
        //table,现在放置了 13个元素
        Node<K,V>[] oldTab = table;
        //oldCap 为 16
        int oldCap = (oldTab == null) ? 0 : oldTab.length;
        //老的 阈值,12 赋值给 oldThr
        int oldThr = threshold;
        //
        int newCap, newThr = 0;
        //成立
        if (oldCap > 0) {
            //oldCap 肯定 不大于 1 << 30
            if (oldCap >= MAXIMUM_CAPACITY) {
                threshold = Integer.MAX_VALUE;
                return oldTab;
            }
            //扩容逻辑来了。newCap 扩容原来的 2倍。扩容后32
            //oldCap >= 1 << 4为16
            else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&
                     oldCap >= DEFAULT_INITIAL_CAPACITY)
               	//阈值也扩容2倍
                newThr = oldThr << 1; // double threshold
        }
        else if
        else 都不执行
        
        //新的 阈值 24,赋值给 threshold
        threshold = newThr;
        
        @SuppressWarnings({"rawtypes","unchecked"})
        	//创建一个 32 长度的 Node 数组
            Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap];
        // 新数组,赋值给 table
        table = newTab;
        //如果原来表 存在,执行复制(原表 复制到 新表)
        if (oldTab != null) {
            //从 0 复制 到 15
            for (int j = 0; j < oldCap; ++j) {
                Node<K,V> e;
                //如果 旧的数组的位置上 有值,赋值给 e
                if ((e = oldTab[j]) != null) {
                    //旧数组的 位置置空
                    oldTab[j] = null;
                    //如果 e的 下一个指针为空(说明 这个[j]位置,就存了一个元素,)
                    if (e.next == null)
                        //e.hash & 31,就是存到 0 - 31 索引处。hash & 最大索引,就是 O1 查找的精髓。
                        newTab[e.hash & (newCap - 1)] = e;
                    
                  	//这里 如果为 红黑树,e.next 肯定不为null
                    else if (e instanceof TreeNode)
                        ((TreeNode<K,V>)e).split(this, newTab, j, oldCap);
                    else {// preserve order,i位置是 链表的处理
                     //声明2个头 和 2个 尾结点
                     Node<K,V> loHead = null, loTail = null;
                     Node<K,V> hiHead = null, hiTail = null;
                     
                     Node<K,V> next;
                     do {
                         	//获取这个 位置的下一个节点
                            next = e.next;
                            if ((e.hash & oldCap) == 0) {
                                if (loTail == null)
                                    loHead = e;
                                else
                                    loTail.next = e;
                                loTail = e;
                            }
                     }
                         
               //最终返回 newTab
               return newTab;

HashMap 的内部类 EntrySet

    transient Set<Map.Entry<K,V>> entrySet;
    
    final class EntrySet extends AbstractSet<Map.Entry<K,V>> {
        public final int size()                 { return size; }
        public final void clear()               { HashMap.this.clear(); }
} 
        //1. k-v 最后是 HashMap$Node node = newNode(hash, key, value, null)

        //2. k-v 为了方便程序员的遍历,还会 创建 EntrySet 集合 ,该集合存放的元素的类型 Entry

        //   而一个Entry对象就有k,v EntrySet<Entry<K,V>> 即: 
		transient Set<Map.Entry<K,V>> entrySet;
        
       //3. entrySet 中, 定义的类型是 Map.Entry ,但是实际上存放的还是 HashMap$Node
       //values的方法返回 Collection 集合。 指向HashMap$Node 的Value

       //keySet的方法返回 Set集合。 指向HashMap$Node 的Key
	   //这s是因为 
		static class Node<K,V> implements Map.Entry<K,V>
	   
		//4. 当把 HashMap$Node 对象 存放到 entrySet 就方便我们的遍历, 
            //因为 Map.Entry 提供了重要方法		: K getKey(); V getValue();
    	Set set = map.entrySet();
        System.out.println(set.getClass());
// HashMap$EntrySet,里面放的 Map.Entry,真实为实现类 HashMap$Node
        for (Object obj : set) {

            //System.out.println(obj.getClass()); //HashMap$Node
            
            //为了从 HashMap$Node 取出k-v
            //1. 先做一个向下转型
            Map.Entry entry = (Map.Entry) obj;
            
            System.out.println(entry.getKey() + "-" + entry.getValue() );
        }


		//第二个遍历:
        Set set1 = map.keySet();
        System.out.println(set1.getClass()); //class java.util.HashMap$KeySet
		//使用 map.getValue(key)

        Collection values = map.values();
        System.out.println(values.getClass());//class java.util.HashMap$Values

5、LinkedHashSet 和 LinkedHashMap

基本说明

1)LinkedHashSet是 HashSet的子类

  1. LinkedHashSet底层是一个LinkedHashMap,底层维护了一个数组+双向链表
    3)LinkedHashSet根据元素的hashCode值来决定元素的存储位置,同时使用链表维护元素的次序(图),这使得元素看起来是以插入顺序保存的。
    4)LinkedHashSet不允许添重复元素
  • 加入顺序和取出元素/数据的顺序一致
  • 底层维护的是一个LinkedHashMap(是HashMap的子类)
  • 底层结构 (数组table+双向链表)
tail.next = newElement //结尾的下一个 指向新节点
    
newElement.pre = tail //新节点的前一个,指向结尾
    
tail = newEelment; //新节点 指向 结尾
Set set = new LinkedHashSet();

源码解读

  • LinkedHashSet 调用父类 HashSet,使用的是:LinkedHashMap 继承了:LinkedHashMap
    public LinkedHashSet() {
        super(16, .75f, true);
    }

    HashSet(int initialCapacity, float loadFactor, boolean dummy) {
        map = new LinkedHashMap<>(initialCapacity, loadFactor);
    }


    public LinkedHashMap(int initialCapacity, float loadFactor) {
        super(initialCapacity, loadFactor);
        accessOrder = false;
    }
public class LinkedHashMap<K,V>
    extends HashMap<K,V>
    implements Map<K,V>{
    
    transient LinkedHashMap.Entry<K,V> head;
    transient LinkedHashMap.Entry<K,V> tail;
    
    static class Entry<K,V> extends HashMap.Node<K,V> {
        Entry<K,V> before, after;
        Entry(int hash, K key, V value, Node<K,V> next) {
            super(hash, key, value, next);
        }
    }
}
	//Hash的是 单项链表
    static class Node<K,V> implements Map.Entry<K,V> {
        final int hash;
        final K key;
        V value;
        Node<K,V> next;
    }
  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值