集合二

LinkedHashMap

在HashMap基础上了增加了如下内容:
每个Entry添加了before和after引用,有个链表的head 和tail ,插入时使用head before after tail 将结点Entry串联起来。Head指向插入顺序第一元素的引用,tail指向按插入顺序最后一个元素的引用。对每一个元素(Entry/Node)增加了俩个属性,before指向按插入顺序前一个元素,after指向插入顺序后一个元素的引用,next指向的是桶里面的链表的下一个

WeakHash Map

通常情况下Map里还有元素在被使用时,都不会被回收,
WeakHash Map当里面的某个元素不再被使用时,可以被垃圾回收器回收

EnumMap

key值必须是枚举类型

IdentityHashMap

通常情况下,哈希值计算方式是key值对象.hasCode()方法,
IdentityHashMap的key值的哈希值计算方式,调用的是System.identityHashCode(key)方法,返回的是地址的十进制数

集合类
Collection
List 列表
Set 集
Queue队列
Map散列 键值对
Set内部使用的是对应的Map来实现的
Map的key值即是存放Set的值,Key值对应的value存放的是一个固定值 static new object
HashSet ->HashMap
TreeSet->TreeMap

TreeSet

不可重复。Map中的key值不可以重复,可以是空,因此Set的值不可以重复可以是空。Set中的值是map的key.

 TreeSet<String> set=new TreeSet<>();
    set.add("str1");
    set.add("str2");
    //set.add(null);
    set.add("str3");
    set.add("str4");
    //set.add(null);
    Iterator<String> iterator=set.iterator();
    while(iterator.hasNext()){
        System.out.println(iterator.next());
    }
    set.remove("str3");

}

结果str1
str2
str3
str4

remove调用的map的remove方法
set的iterator调用的是map.key.iterator()
map的排序优先使用Comparator实现的方法比较key值,默认使用Comparable用自身比较传入的值。
故key值为空报空指针。
TreeSet的值不能为空。所以和TreeMap类似,TreeSet的元素必须实现Comparable接口,如果构造TreeSet指定了比较器Comparator,那么存放元素可以不用实现,则可以存放空。如果未指定时,add需要调用元素的默认比较器Comparable的compareTo方法,去比较元素的大小,如果存放空,则null.compareTo(T object)报错
TreeSet treeSet=new TreeSet(new Comparator(){…};);

HashSet

内部是HashMap来实现的,可以存放空值,但是只能有一个空值
contains()是否包含某个元素

EnumSet

内部使用的是一个枚举类的数组

HashMap的扩容

final Node<K,V>[] resize() {
    Node<K,V>[] oldTab = table;
    // 旧的数组的长度(原桶数)
    int oldCap = (oldTab == null) ? 0 : oldTab.length;
    int oldThr = threshold;
    int newCap, newThr = 0;
    // 数组已经初始化了,进行扩容操作
    if (oldCap > 0) {
        // 如果已经到达最大容量,则不再扩容
        if (oldCap >= MAXIMUM_CAPACITY) {
            // 阀值设置为最大 Integer 值
            threshold = Integer.MAX_VALUE;
            return oldTab;
        }
        // 未到达最大容量
        // 数组容量扩大为原来的2倍:newCap = oldCap << 1
        // 阀值扩大为原来的2倍:newThr = oldThr << 1
        else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&
                    oldCap >= DEFAULT_INITIAL_CAPACITY)
            newThr = oldThr << 1; // double threshold
    }
    // 数组未初始化,且阀值大于0,此处阀值为什么大于0???
    // 当构造 HashMap 时,如果传了容量参数,将根据容量参数计算的离它最近的2的次幂
    // 即数组的容量暂存在阀值变量 threshold 中,详见构造器方法中的语句:
    // this.threshold = tableSizeFor(initialCapacity);
    else if (oldThr > 0) // initial capacity was placed in threshold
        newCap = oldThr;
    // 数组未初始化且阀值为0,说明使用了默认构造方法进行创建对象,即 new HashMap()
    else {               // zero initial threshold signifies using defaults
        newCap = DEFAULT_INITIAL_CAPACITY;
        newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);
    }
    // newCap = oldThr; 语句之后未计算阀值,所以 newThr = 0
    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<K,V>[] newTab = (Node<K,V>[])new Node[newCap];
    table = newTab;
    // 旧数组不为 null 时表示 resize 为扩容操作,否则为第一次初始化数组操作
    if (oldTab != null) {
        // 循环将数组中的每个结点并转移到新的数组中
        for (int j = 0; j < oldCap; ++j) {
            Node<K,V> e;
            // 获取头结点,如果不为空,说明该数组中存放有元素
            if ((e = oldTab[j]) != null) {
                oldTab[j] = null;
                // 头结点 e.next == 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;
                        // (e.hash & oldCap) == 0 时,链表所在桶的索引不变
                        if ((e.hash & oldCap) == 0) {
                            if (loTail == null)
                                loHead = e;
                            else
                                loTail.next = e;
                            loTail = e;
                        }
                        // 否则将链表转移到索引为 index + oldCap 的桶中
                        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;
}

上述代码中,扩容操作使用了左移运算

newCap = oldCap << 1

newThr = oldThr << 1

数组容量和阀值均左移1位,表示原数乘以21,即扩容为原来的2倍。

当桶中存放的为链表,在进行链表的转移时,if判断使用了如下位操作

if ((e.hash & oldCap) == 0)

其中 oldCap 为扩容前数组的长度,为2的次幂,也即它的二进制中最高位为1,其余位都位0。而每次扩容为原来的2倍。

例如原容量为 16,即 oldCap = 10000,扩容后 newCap = 32,即 newCap = 100000。计算链表所在数组的索引表达式 hash & (length - 1):

扩容前,oldCap = 10000

length - 1 = oldCap - 1 = 1111

index = hash & (length - 1) = hash & 1111

数组索引下标 index 依赖于 hash 的低 4 位

扩容后,newCap = 100000

newLength - 1 = newCap - 1 = 11111

newIndex = hash & (newLength - 1) = hash & 11111

新数组索引下标 newIndex 依赖于 hash 的低 5 位

在上述例子中,扩容后,新的数组索引和原索引是否相等取决于 hash 的第5位,如果第5位为0,则新的数组索引和原索引相同;如果第5位为1,则新的数组索引和原索引不同。

如何测试 hash 第5位为0还是为1?因为 oldCap = 10000,刚好第5位为1,其余位都为0,因此 e.hash & oldCap 与操作的结果,hash 第5位为0时,结果为0,hash 第5位为1时,结果为1。

综上所述,扩容后,链表的拆分分两步:

一条链表不需要移动,保存在原索引的桶中,包含原链表中满足 e.hash & oldCap == 0 条件的结点

一条链表需要移动到索引为 index + oldCap 的桶中,包含原链表中不满足 e.hash & oldCap == 0 条件的结点

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值