集合

集合

单列集合

List

  1. List 集合类中元素有序(即添加顺序和取出顺序一致)、且可重复
  2. List 集合中的每个元素都有其对应的顺序索引,即支持索引

Vector

创建源码分析

//底层是一个对象数组
protected Object[] elementData;
//不同于ArrayList  Vector方法都是Synchronized  代表是线程同步的 
public synchronized boolean isEmpty() {
    return elementCount == 0;
}
//有参创建流程
public Vector(int initialCapacity) {
    this(initialCapacity, 0);
}
//无参创建流程
public Vector() {
    //初始容量为10
    this(10);
}

扩容机制是

//当没有指定容量是 初始容量为10+需要增加的容量>0(false)  选择初始化容量  所以初始容量+初始容量
//当指定容量时  初始化容量为指定容量+指定容量是否>0(ture)  选择指定容量  所以指定容量+指定容量
int newCapacity = oldCapacity + ((capacityIncrement > 0) ?
                                 capacityIncrement : oldCapacity);

Array List

  1. Array List中维护了一个 object:类型的数组 element Data
  2. transient object[] element Data;transient表示瞬间短暂的表示该属性不会被序
  3. 当创建 Array List对象时,如果使用的是无参构造器,则初始 element Data容量为0,第一次添加,则扩容 element Data为10,如需要再次扩容,则扩容 element Data为1.5倍
  4. 如果使用的是指定大小的构造器,则初始 element Data容量为指定大小,如果需要扩容则直接扩容 element Data为1.5倍

创建源码分析

//无参创建流程
ArrayList list =new ArrayList();
public ArrayList() {
//创建一个elementData空数组
    this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
//elementData空数组  transient:将不需要序列化的属性前添加关键字transient,序列化对象的时候,这个属性就不会被序列化
transient Object[] elementData; // non-private to simplify nested class access


//有参构造器创建流程
ArrayList list = new ArrayList(16);
public ArrayList(int initialCapacity) {
    if (initialCapacity > 0) {
//创建一个指定大小的elementData数组
        this.elementData = new Object[initialCapacity];
    } else if (initialCapacity == 0) {
//0的话还是空数组
        this.elementData = EMPTY_ELEMENTDATA;
    } else {
//抛出异常初始容量不对
        throw new IllegalArgumentException("Illegal Capacity: "+
                                           initialCapacity);
    }
}

添加流程(add)
img
https://images.cnblogs.com/cnblogs_com/blogs/697861/galleries/2036654/o_210926081035LinkedList.png

//e 添加的数据
public boolean add(E e) {
    	//这里先确定是否需要扩容
        ensureCapacityInternal(minCapacity:size + 1);  // Increments modCount!!
    	//size初始化为0 
        elementData[size++] = e;
        return true;
    }
private void ensureCapacityInternal(int minCapacity) {
    //calculateCapacity判断初始容量大小的封装方法   ensureExplicitCapacity判断是否继续扩容
    ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
}

初始化容量

private static int calculateCapacity(Object[] elementData, int minCapacity) {

    //  private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
    //判断是否为空数组
    if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
        //private static final int DEFAULT_CAPACITY = 10;
        //判断默认大小和最小容量哪个大 并且返回
        return Math.max(DEFAULT_CAPACITY, minCapacity);
    }
    return minCapacity;
    }

判断是否需要扩容

private void ensureExplicitCapacity(int minCapacity) {
    //protected transient int modCount = 0;  
    //为了记录当前集合被修改的次数,防止多线程操作出现的异常
    modCount++;

    // overflow-conscious code
    //这里最小容量-数组实际大小>0就说明 需要扩容
    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);
    //这里因为刚进入方法oldCapacity为0 newCapacity也就为0 
    //这意思是新的容量还没有最小容量大
    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);
}

huge Capacity

private static int hugeCapacity(int minCapacity) {
    if (minCapacity < 0) // overflow
        //内存溢出
        throw new OutOfMemoryError();
    //Integer.MAX_VALUE  
    //private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;  ???对象头的大小不能超过8个字节
    return (minCapacity > MAX_ARRAY_SIZE) ?
        Integer.MAX_VALUE :
        MAX_ARRAY_SIZE;
}

Linked List

  1. Linked List底层实现了双向链表和双端队列特点
  2. 可以添加任意元素(元素可以重复),包括null,线程不安全,没有实现同步

Linked List底层的双向链表

//首节点
transient Node<E> first;
//尾节点
transient Node<E> last;
//node节点
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;
    }
}

模拟简单的双向链表

 public static void main(String[] args) {
        Node J = new Node("J");
        Node Q = new Node("Q");
        Node K = new Node("老韩");
        //连接三个结点,形成双向链表
        //J -> Q -> K
        J.next = Q;
        Q.next = K;
        //K -> Q -> J
        K.pre = Q;
        Q.pre = J;
        //让first引用指向J,就是双向链表的头结点
        Node first = J;
        //让last引用指向K,就是双向链表的尾结点
        Node last = K;
        //从头到尾进行遍历
        System.out.println("===从头到尾进行遍历===");
        while (true) {
            if(first == null) {
                break;
            }
            //输出first 信息
            System.out.println(first);
            first = first.next;
        }

        //从尾到头的遍历
        System.out.println("====从尾到头的遍历====");
        while (true) {
            if(last == null) {
                break;
            }
            //输出last 信息
            System.out.println(last);
            last = last.pre;
        }

        //1. 先创建一个 Node 结点,name 就是 smith
        Node smith = new Node("smith");
        //下面就把 smith 加入到双向链表了
        smith.next = K;
        smith.pre = Q;
        K.pre = smith;
        Q.next = smith;

        //让first引用指向J,就是双向链表的头结点
        first = J;

        System.out.println("===从头到尾进行遍历===");
        while (true) {
            if(first == null) {
                break;
            }
            //输出first 信息
            System.out.println(first);
            first = first.next;
        }
        //让last 重新指向最后一个结点
        last = K; 
        //演示,从尾到头的遍历
        System.out.println("====从尾到头的遍历====");
        while (true) {
            if(last == null) {
                break;
            }
            //输出last 信息
            System.out.println(last);
            last = last.pre;
        }


    }
  private static  class Node {
        /**
         *节点的值
         */
        public Object item;
        /**
         *当前节点的后一个节点的引用
         */
        public Node next;
        /**
         *当前节点的前一个节点的引用
         */
        public Node pre;
        public Node(Object name) {
            this.item = name;
        }
        @Override
        public String toString() {
            return "Node name=" + item;
        }
    }

img

创建源码分析

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

第一步

//无参创建流程
public LinkedList() {
}
//数量
transient int size = 0;
public boolean add(E e) {
    linkLast(e);
    //添加成功
    return true;
}

void linkLast(E e) {
    	//e代表添加的数据 e=linkedList.add(1),此时last为null
        final Node<E> l = last;
    	//创建一个新节点 
        final Node<E> newNode = new Node<>(l, e, null);
    	//last指向新节点new Node<>(l, e, null) 
        last = newNode;
        if (l == null)
            //first也指向新节点new Node<>(l, e, null) 
            first = newNode;
        else
            l.next = newNode;
    	//添加一个元素
        size++;
    	//记录修改一个元素
        modCount++;
    }

	
    //第二次添加
    void linkLast(E e) {
    	//此时e代表第二次添加的数据 e=linkedList.add(2), 此时last不为null  last指向new Node<>(l, e, null) 这个节点
        final Node<E> l = last;
    	//创建一个新节点 此时l=new Node<>(l, e, null) 这个节点 
        final Node<E> newNode = new Node<>(l, e, null);
    	//此时last指向新节点new Node<>(l, 2, null) 
        last = newNode;
        if (l == null)
            //first也指向新节点new Node<>(l, e, null) 
            first = newNode;
        else
            l.next = newNode;
    	//添加一个元素
        size++;
    	//记录修改一个元素
        modCount++;
    }

img

删除源码分析

linkedList.remove();
public E remove() {
    return removeFirst();
}
public E removeFirst() {
    //让f指向第一个节点 Node<>(null, 1,下一个元素为2的节点 )
    final Node<E> f = first;
    if (f == null)
        throw new NoSuchElementException();
    return unlinkFirst(f);
}
private E unlinkFirst(Node<E> f) {
    // assert f == first && f != null;
   	//拿出第一个节点的元素
    final E element = f.item;
    //获取到下一个节点
    final Node<E> next = f.next;
    //此时就把第一个节点里面的内容1变成null  f = Node<>(null,null,下一个元素为2的节点 )
    f.item = null;
    //此时就把第一个节点里面的下一个元素为2的节点变成null  f = Node<>(null,null,null )
    //这时候这个就删除了 
    f.next = null; // help GC  Gc就回收了
    //这里又把第一个节点指向第二个节点了  个人理解是前面那个垃圾节点已经被回收了 下一个元素为2的节点就变成了第一个节点 
    first = next;
    //这里判断  此时next应该是 Node<>(上一个节点,2,下一个元素为3的节点 ) 
    //上一个节点===此时节点还没变化是Node<>(null, 1,下一个元素为2的节点 )
    if (next == null)
        last = null;
    else
        //这个它把next变成Node<>(null,2,下一个元素为3的节点 ) 
        next.prev = null;
    //LinkedList里面的元素减少一个 
    size--;
    //LinkedList修改的元素加一个 
    modCount++;
    return element;
}

修改源码分析

linkedList.set(1, 999);
public E set(int index, E element) {
    checkElementIndex(index);
    //获取到对应索引的节点 
    Node<E> x = node(index);
     //获取到对应索引的节点里面的元素
    E oldVal = x.item;
    //把修改的元素赋给要修改的 
    x.item = element;
    return oldVal;
}
//检查元素索引
private void checkElementIndex(int index) {
    //判断没有这个元素索引
    if (!isElementIndex(index))
        throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}

Set

  1. 不能存放重复的元素, 可以添加一个null
  2. 接口对象存放数据是无序(即添加的顺序和取出的顺序不一致)
  3. 注意:取出的顺序的顺序虽然不是添加的顺序,但是他的取出顺序固定
  4. 遍历:迭代器 增强for 接口对象无法使用索引来获取

Hash Set

Hash Set底层就是Hash Map

public HashSet() {
    map = new HashMap<>();
}

在执行add方法后,会返回一个boolean值 remove 指定删除哪个对象

Hash Set 可以存放null ,但是只能有一个null,即元素不能重复

添加元素的底层实现

添加一个元素时,先得到Hash值这个Hash值会转变成索引值,找到存储数据表(节点数组数组里面存放的就是链表),看这个索引位置是否已经存放元素,如果没有直接插入,如果有调用equals方法比较,如果相同则放弃添加,如果不相同,则添加到最后;在JDK8中如果一条链表的元素个数到达8,并且table的大小>=64就会进行树化(红黑树)。

添加源码分析

public boolean add(E e) {
    //e是添加的元素   PRESENT就是Obkect对象 private static final Object PRESENT = new Object();
    return map.put(e, PRESENT)==null;
}
//key =e value =private static final Object PRESENT = new Object();
public V put(K key, V value) {
    return putVal(hash(key), key, value, false, true);
}
//计算hashCode值  
static final int hash(Object key) {
    int h;
    //hashcode和无符号右移16位的hashcode后得到的值做异或运算
    //(h = key.hashCode()) ^ (h >>> 16)得到hash值 为了避免重复
    return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
 /**
     * Implements Map.put and related methods
     *
     * @param hash hash过后的key
     * @param key 就是e
     * @param value private static final Object PRESENT = new Object();
     * @param onlyIfAbsent 如果为true,则不更改现有值 
     * @param evict if false, 该表处于创建模式。
     * @return 以前的值,如果没有,则为null
     */
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
               boolean evict) {
    //辅助变量
    Node<K,V>[] tab; Node<K,V> p; int n, i;
    //table就是HashMap的Node<K,V>[]节点数组 if语句表示当前table是null或者大小=0 
   
    if ((tab = table) == null || (n = tab.length) == 0)
         //第一次扩容,到16个空间
        n = (tab = resize()).length;
    //根据key,得到hash 去计算该key应该存放到table表的哪个索引位置并把这个位置的对象,赋给 p
    //判断p 是否为null 如果p 为null, 表示还没有存放元素, 就创建一个Node (key=添加的元素,value=PRESENT)
	//就放在该位置 tab[i] = newNode(hash, key, value, null) 
    //当key相同时此时这里不会为空  
    if ((p = tab[i = (n - 1) & hash]) == null)
        tab[i] = newNode(hash, key, value, null);
    else {
        //相同的key会走这里  
        Node<K,V> e; K k;
        //hash值是否相同
        if (p.hash == hash &&
            ((k = p.key) == key || (key != null && key.equals(k))))
            e = p;	
        //p 是不是一颗红黑树,
        else if (p instanceof TreeNode)
            //是一颗红黑树,就调用 putTreeVal , 来进行添加
            e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
        else {
            // table 对应索引位置,已经是一个链表, 就使用 for 循环比较
            for (int binCount = 0; ; ++binCount) {
                //依次和该链表的每一个元素比较后,都不相同,  则加入到该链表的最后
                if ((e = p.next) == null) {
                    p.next = newNode(hash, key, value, null);
                    //注意在把元素添加到链表后,立即判断 该链表是否已经达到 8 个结点
				  //就调用 treeifyBin() 对当前这个链表进行树化(转成红黑树)
                    if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
                        treeifyBin(tab, hash);
                    break;
                }
                //依次和该链表的每一个元素比较过程中,如果有相同情况,就直接 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;
            //HashMap留给子类的方法  对于HashMap为空 比如可以让LinkedHashSet为有序
            afterNodeAccess(e);
            return oldValue;
        }
    }
    ++modCount;
    if (++size > threshold)
        resize();
    afterNodeInsertion(evict);
    return null;
}
final Node<K,V>[] resize() {
    //让oldTab指向Table
    Node<K,V>[] oldTab = table;
    //此时oldCap=0
    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
        //初始容量  static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16
        newCap = DEFAULT_INITIAL_CAPACITY;
        //static final float DEFAULT_LOAD_FACTOR = 0.75f;
        //临界值: 0.75*16 
        //为啥是加载因子是0.75? 0.75正好是3/4,而capacity又是2的幂。所以,两个数的乘积都是整数
        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节点数组里面有16个空间
        Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap];
    table = newTab;
    if (oldTab != null) {
        for (int j = 0; j < oldCap; ++j) {
            Node<K,V> 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<K,V>)e).split(this, newTab, j, oldCap);
                else { // preserve order
                    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;
                        }
                        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;
}

final void treeifyBin(Node<K,V>[] tab, int hash) {
    int n, index; Node<K,V> e;
    
    if (tab == null || (n = tab.length) < MIN_TREEIFY_CAPACITY)
        //如果成立 table 扩容.
        resize();
    //进行转成红黑树
    else if ((e = tab[index = (n - 1) & hash]) != null) {
        TreeNode<K,V> hd = null, tl = null;
        do {
            TreeNode<K,V> 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);
    }
}

Linked Hash Set

  1. Linked Hash Set是Hash Set的子类
  2. Linked Hash Set底层是一个Linked Hash Map,底层维护了一个数组+双向链表
  3. Linked Hash Set根据元素的hashcode值来决定元素的存储位置,同时使用链表维护元素的次序,使得元素看起来是以插入顺序保存的
  4. Linked Hash Set不允许添重复元素
  5. Linked Hash Set 加入顺序和取出元素/数据的顺序一致
  6. Linked Hash Set 底层维护的是一个Linked Hash Map(是Hash Map的子类)
  7. Linked Hash Set 底层结构 (数组table+双向链表)
  8. 添加第一次时,直接将 数组table 扩容到 16 ,存放的结点类型是 Linked Hash Map$Entry
  9. 数组是 Hash Map$Node[] 存放的元素/数据是 Linked Hash Map$Entry类型
//继承关系是在内部类完成.
static class Entry<K,V> extends HashMap.Node<K,V> {
    //定义了before, after属性
    Entry<K,V> before, after;
    Entry(int hash, K key, V value, Node<K,V> next) {
        super(hash, key, value, next);
    }

Tree Set

  1. Tree Set底层是二叉树,可以对对象元素进行排序,但是自定义类需要实现comparable接口,重写comparaTo() 方法。
  2. Tree Set 可以保存对象元素的唯一性(并不是一定保证唯一性,需要根据重写的compaaTo方法来确定)
//构造器把传入的比较器对象,赋给了 TreeSet的底层的 TreeMap的属性this.comparator
public TreeMap(Comparator<? super K> comparator) {
       this.comparator = comparator;
   }
	在调用 treeSet.add(), 在底层会执行到
	////cpr 就是我们的匿名内部类(对象)
    if (cpr != null) {
       do {
           parent = t;
           //动态绑定到我们的匿名内部类(对象)compare
           cmp = cpr.compare(key, t.key);
           if (cmp < 0)
               t = t.left;
           else if (cmp > 0)
               t = t.right;
           else 
               ////如果相等,即返回0,这个Key就没有加入
               return t.setValue(value);
       } while (t != null);
   }

双列集合

Map

  1. Map 与 Collection 并列存在。用于保存具有映射关系的数据:Key-Value(双列元素)
  2. Map 中的 key 和value 可以是任何引用类型的数据,会封装到 Hash Map$Node 对象中
  3. Map 中的 key 不允许重复,原因和 Hash Set 一样,调用Hash算法。
  4. Map 中的 value 可以重复Map 的 key 可以为 null, value 也可以为 null ,注意 key 为 null 只能有一个,value 为 null ,可以多个。
  5. Map的增删改查 put,remove,replace,遍历所有的key,遍历所有的value,遍历所有的key,value
  6. k-v 放在 Hash Map$Node node = new Node(hash, key, value, null) 里面
  7. k-v 为了方便程序员的遍历,还会 创建 Entry Set 集合 ,该集合存放的元素的类型 Entry, 而一个Entry对象就有k,v Entry Set<Entry<K,V>> 即: transient Set<Map. Entry<K,V>> entry Set

Hash Map

  1. Map接口的常用实现类: Hash Map、 Hash Table和 Properties
  2. Hash Map是以 key-val对的方式来存储数据( Hash Map$Node类型)
  3. key不能重复,但是值可以重复允许使用null键建和null值
  4. 如果添加相同的key,则会覆盖原来的key-val,等同于修改.(key不会替换,val会替换)
  5. 与 Hash Set一样,不保证映射的顺序,因为底层是以hash表的方式来存储的.(JDK8的
    Hash Map底层数组+链表+红黑树)
  6. Hash Map没有实现同步,因此是线程不安全的方法没有做同步互斥的操作,没有synchronized
//初始化加载因子0.75f
public HashMap() {
    this.loadFactor = DEFAULT_LOAD_FACTOR; // all other fields defaulted
}
//执行 put 调用 hash 方法,计算 key 的 hash 值 (h = key.hashCode()) ^ (h >>> 16)
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<K,V>[] tab; Node<K,V> p; int n, i;
    ////如果底层的 table 数组为 null, 或者 length =0 , 就扩容到 16
    if ((tab = table) == null || (n = tab.length) == 0)
        n = (tab = resize()).length;
    //取出 hash 值对应的 table 的索引位置的 Node, 如果为 null, 就直接把加入的 k-v创建成一个 Node ,加入该位置即可
    if ((p = tab[i = (n - 1) & hash]) == null)
        tab[i] = newNode(hash, key, value, null);
    else {
        Node<K,V> e; K k;
       // 如果 table 的索引位置的 key 的 hash 相同和新的 key 的 hash 值相同,
       // 并满足(table 现有的结点的 key 和准备添加的  key 是同一个对象|| equals 返回真)就认为不能加入新的 k-v
        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 {
            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
                        //如果 table 为 null ,或者大小还没有到 64,暂时不树化,而是进行扩容.
					  //否则才会真正的树化 -> 剪枝
                        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;
}
  1. Hash Mapl底层维护了Node类型的数组tabe,默认为null
  2. 当创建对象时,将加载因子( load factor)初始化为075
  3. 当添加 key-val时,通过key的哈希值得到在tabel的索引。然后判断该索引处是否有元素,
    如果没有元素直接添加。如果该索引处有元素,继续判断该元素的key和准备加入的key相
    是否等,如果相等,则直接替换val;如果不相等需要判断是树结构还是链表结构,做出相
    应处理。如果添加时发现容量不够,则需要扩容。
  4. 第1次添加,则需要扩容 table容量为16,临界值( threshold)为12(16*0.75)
  5. 以后再扩容,则需要扩容 table容量为原来的2倍(32),临界值为原来的2倍即24依次类推
  6. 在Java8中如果一条链表的元素个数超过 TREEIFY THRESHOLD(默认是8),
  7. 并且table的大小>= MIN TREEIFY CAPACITY(默认64)就会进行树化红黑树)

Linked Hash Map

Tree Map

  1. Tree Map存储K-V键值对,通过红黑树(R-B tree)实现
  2. Tree Map继承了Navigable Map接口,Navigable Map接口继承了Sorted Map接口,可支持排序方法提供了接口,需要Tree Map自己去实现;
  3. TreeMap实现了Cloneable接口,可被克隆,实现了Serializable接口,可序列化;
  4. Tree Map因为是通过红黑树实现,红黑树结构天然支持排序,默认情况下通过Key值的自然顺序进行排序;
//构造器. 把传入的实现了 Comparator接口的匿名内部类(对象),传给给TreeMap的comparator
public TreeMap(Comparator<? super K> comparator) {
    this.comparator = comparator;
}
//第一次添加, 把k-v 封装到 Entry对象,放入root
Entry<K,V> t = root;
if (t == null) {
    compare(key, key); // type (and possibly null) check

    root = new Entry<>(key, value, null);
    size = 1;
    modCount++;
    return null;
}
Comparator<? super K> cpr = comparator;
if (cpr != null) {
    do { //遍历所有的key , 给当前key找到适当位置
        parent = t;
        //动态绑定到我们的匿名内部类的compare
        cmp = cpr.compare(key, t.key);
        if (cmp < 0)
            t = t.left;
        else if (cmp > 0)
            t = t.right;
        //如果遍历过程中,发现准备添加Key 和当前已有的Key 相等,就不添加
        else  
            return t.setValue(value);
    } while (t != null);
}

Hash Table

  1. 存放的元素是键值对:即K-V
  2. Hash table的键和值都不能为nu,否则会抛出 Null Pointer Exception
  3. Hash Table使用方法基本上和 Hash Map一样
  4. Hash Table是线程安全的( synchronized), Hash Map是线程不安全的
  5. Hash Table 的元素是头插法,也就是插入到链表的头部,因为Hash Table 是线程安全的,在这个前提下,使用头查法性能更好,否则还有遍历到链表的尾部插入
  6. Hash Table扩容时,将容量变为原来的2倍加1,而Hash Map扩容时,将容量变为原来的2倍。
//底层有数组 Hashtable$Entry[] 初始化大小为 11
//初始化大小11
public Hashtable() {
    this(11, 0.75f);
}
public synchronized V put(K key, V value) {
    // Make sure the value is not null
    if (value == null) {
        throw new NullPointerException();
    }

    // Makes sure the key is not already in the hashtable.
    //初始化的表
    Entry<?,?> tab[] = table;
    //计算出key的hash值
    int hash = key.hashCode();
    // 计算下标 HashMap 是计算key的hash再与tab.length-1进行与运算;
    // HashTable则是key的hash值与0x7FFFFFFF进行与运算,然后再对tab.length取模
    // 先hash&0x7FFFFFFF后,再对length取模,与0x7FFFFFFF的目的是为了将负的hash值转化为正值,因为hash值有可能为负数,而		&0x7FFFFFFF后,只有符号外改变,而后面的位都不变
    int index = (hash & 0x7FFFFFFF) % tab.length;
    @SuppressWarnings("unchecked")
    // 确定 index 位置上的链表头,这里主要是遍历链表找到key 值相等的节点,然后返回old value,这样的话就不用添加新值
    // 也就是不用调用addEntry 方法
    Entry<K,V> entry = (Entry<K,V>)tab[index];
    // 存在key
    for(; entry != null ; entry = entry.next) {
        if ((entry.hash == hash) && entry.key.equals(key)) {
            V old = entry.value;
            entry.value = value;
            return old;
        }
    }
	// 链表中不存在,则添加新值
    addEntry(hash, key, value, index);
    return null;
}
private void addEntry(int hash, K key, V value, int index) {
    Entry<?,?> tab[] = table;
    //判断是否要扩容
    if (count >= threshold) {
        // Rehash the table if the threshold is exceeded
        rehash();
		
        tab = table;
        hash = key.hashCode();
        index = (hash & 0x7FFFFFFF) % tab.length;
    }
   
        Entry<K,V> e = (Entry<K,V>) tab[index];
    	// e 也就是  tab[index] 是这个链表的头结点, tab[index] = new Entry<>(hash, key, value, e); 也就是将元素添加到链表的头部,e 做为new Entry<>(hash, key, value, e)的next 节点
        tab[index] = new Entry<>(hash, key, value, e);
        count++;
        modCount++;
protected void rehash() {
    int oldCapacity = table.length;
    Entry<?,?>[] oldMap = table;
// overflow-conscious code 
// 扩容X2+1
int newCapacity = (oldCapacity << 1) + 1;
// 判断是否超出了容量限制
if (newCapacity - MAX_ARRAY_SIZE > 0) {
    if (oldCapacity == MAX_ARRAY_SIZE)
        // Keep running with MAX_ARRAY_SIZE buckets
        return;
    // 最大容量 MAX_ARRAY_SIZE    
    newCapacity = MAX_ARRAY_SIZE;
}
// 创建新的数组
Entry<?,?>[] newMap = new Entry<?,?>[newCapacity];
modCount++;
// 更新 threshold
threshold = (int)Math.min(newCapacity * loadFactor, MAX_ARRAY_SIZE + 1);
table = newMap;
// 数据迁移,遍历数组
for (int i = oldCapacity ; i-- > 0 ;) {
        // for 循环的方式遍历链表
    for (Entry<K,V> old = (Entry<K,V>)oldMap[i] ; old != null ; ) {
        Entry<K,V> e = old;
        old = old.next;
        int index = (e.hash & 0x7FFFFFFF) % newCapacity;
        e.next = (Entry<K,V>)newMap[index];
        newMap[index] = e;
    }
}
}

泛型

泛型注意事项

  1. interface接口{ }和class类
  2. 其中T,K,V不代表值,而是表示类型。
  3. 任意字母都可以。常用T表示,是Type的缩写
  4. 在给泛型指定具体类型后,可以传入该类型或者其子类类型
  5. 普通成员可以使用泛型(属性、方法)
  6. 使用泛型的数组,不能初始化
  7. 静态方法中不能使用类的泛型
  8. 泛型类的类型,是在创建对象时确定的(因为创建对象时,需要指定确定类型)

泛型的继承和通配符

  1. 泛型不具备继承性
    List list= new Array List();
  2. :支持任意泛型类型
  3. :支持A类以及A类的子类,规定了泛型的上限
  4. :支持A类以及A类的父类,不限于直接父类,规定了泛型的下限
posted @ 2021-11-09 07:47  布卷-  阅读(60)  评论(0)    收藏  举报