Set,Map的底层实现

modCount

 记录当前集合数据被修改的次数。[添加,删除]  >>> 这两个操作影响元素的个数。  Itr内部有expectedModCount预期的修改次数变量。 当我们使用迭代器或foreach遍历时,如果你在foreach遍历时,会自动调用迭代器的迭代方法,此时在遍历过程中调用了集合的add,remove方法时,modCount就会改变。而迭代器记录的modCount是开始迭代之前的,如果两个不一致,就会报异常,说明有两个线程同时操作集合,这种操作有风险,为了保证结果的正确性,避免这样的情况发生,一旦发现modCount与expectedCount不一致,立即报错。

 

Set的底层实现

 1、HashSet  : HashMap
 2、TreeSet  : TreeMap
 3、LinkedHashSet: LinkedHashMap
 
 Set添加元素时用add(元素),而Map添加元素put(key,value)。
 发现添加到Set中的元素,是作为底层的Map的key,那么value它们选用了一个Object类型的常量对象PRESENT。
 所有的HashSet共用同一个PRESENT对象。
 所有的TreeSet共用同一个PRESENT对象。
 所有的LinkedHashSet共用同一个PRESENT对象。

Map底层实现

1、哈希表系列:
      数组 + 链表
  	  数组 + 链表/红黑树
2、TreeMap:红黑树
     
 HashMap的底层实现:
     JDK1.7以及之前:数组 + 链表
     JDK1.8以及之后:数组 + 链表/红黑树

数组的优点:访问速度快,因为可以根据下标直接定位到某个元素
链表的优点:不需要元素是挨着存储,不需要连续空间,在添加和删除元素时不需要移动元素,只需要修改前后元素的引用关系就可以。

HashMap:会根据key的hashCode-->公式/算法-->[index]
    因为不同的hashCode值,可能得到的[index]是相同的,那么此时就冲突了,那么只能把[index]的多个映射关系用链表连接起来
 
二叉树的优点:查找的速度比链表快
    旧版的HashMap,如果key的hashCode算出了[index]相同的话(我们称为冲突)都在一个table[index]下面,如果严重的话,会导致[index]下面的链表很长,就会导致查询速度减慢。当链表长到一定程度时,就需要把链表变为二叉树,以提高我们查询速度。

JDK1.7的HashMap源码追踪

public HashMap(){
    this(DEFAULT_INITIAL_CAPACITY,DEFAULT_LOAD_FACTOR);// 默认容量16,默认加载因子0.75
}
public HashMap(int initialCapacity, float loadFactor) {
    //排除非法值
    this.loadFactor=loadFactor;
    threshold=initialCapcaity;
    init();
}
transient Entry<K,V>[] table=(Entry<K,V>[]) EMPTY_TABLE; // table数组初始化一个长度为0的空数组。

LoadFactor加载因子用来计算threshold=capacity*loadFactor。 数组需要考虑扩容的阈值=threshold。

为什么是0.75。 太小就频繁扩容,太大就可能导致某个table[index]下面的链表会很长,查询速度就低。


put的过程:
1.public void  put(K key , V value){
    if(table==EMPTY_TABLE){ inflateTable(threshold)}; //判断数组是否是空数组。如果是空数组就充气Table

    int hash=hash(key);//根据key的hashCode用异或,无符号右移等各种运算,得到一个int类型的hash值。下面再用hash值来算[index]。它的设计者用户重写的hashCode可能不够均匀。
    int i=indexFor(hash,table.length); // 哈希与 (上数组的长度-1) // table数组的长度意思2^n.   
// table.lengt-1.的二进制,前面都是0,后面都是1, hash&table.lenght-1的结果一定是在0~table.length-1范围内。  //i决定了要存的数组的下标、

    for(Entry<K,V> e=table[i];e!=null;e=e.next){ // 查找table[i]下面的链表中是否有映射关系的key是和我重复的,如果是重复的,就用新的value代替就得value。
        if(e.hash==hash && ((k=e.key)==key || key.equals(k) )){// 哈希值的相等,或者key的地址相等,或者相等,就是key重复了, 
               V oldValue=e.value;
                e.value=value;
                e.recoredAccess(this);
                return oldValue;
        }
    }
    //key没有重复
modCount++;
addEntry(hash,key,value,i);
return null;


}

2.private void inflateTable(int toSize){
    int capacity=runUpToPowerOf2(toSize);  // 一开始是add时,传进来的toSize=16
    threshold=(int)Math.min(capacity*loadFactory,MAXIMUM_CAPACITY+1); // 修改阈值为12.
    table=new Entry[capacity]; // 创建一个大小为16的数组。
    initHashSeedAsNeeded(capacity);
}

3. private static int runUpToPowerOf2(int number){
    return number>=MAXIMUM_CAPACITY? MAXIMUM_CAPACITY:(number>1)? Integer.higestOneBit((number-1)<<1):1;    //离number最近的2^n的数
}


//一开始的threshold的值为16.  




(2) put()
发现数组table为空数组后,会把数组初始化长度为16的Entry类型的数组,并且把threshold计算为12.
如果不是空数组。检查Key==NULL?单独处理。如果Key!=null,算Key的哈希值。

final int hash(Object k){  //按位异或运算
    int h=haseSedd;
    if(0!=h&& k instanceOf String){ return sum,misc.Hashing.stringHash32((String) k)};
    h^=k.hashCode;
    h^=(h>>>20)^ (h>>>12); // 无符号右移,  
    return  h^(h>>>7)^(h>>>4); // 为了扰乱,哈希值

}

public int indexFor(int hash,int lnegth){
    //按位与运算。
}


void  addEntry(int hash,K key,V value,int bucketIndex){ 
   //  当元素的总个数达到阈值 && 新的映射关系要添加的table[index]下面不是空的。
    if((size>=thres   hold)&&(null!=table[bucketIndex])){
        resize(2*table.length);// 数组扩容为原来的2倍。然后重新算哈希+bucketIndex
        hash=(null!=key) ?hash(key):0;
        buckenIndex=indexFor(hash,table.length);
    }
    createEntry(hash,key,value,bucketIndex);
}

void createEntry(int hash,K key,V value,int bucketIndex){
    Entry<K,V> e=table[bucketIndex]; // 原来的节点都添加到我的下面。
    table[bucketIndex]=new Entry<>(hash,key,value,e);
    size++;
}

static class Entry{
    final K key;
    V value;
    Entry<K,V> next;
    int has;
    Entry(int h,K k,V v,Entry<K,V> n){
        value=v;
        next=n;
        key=k;
        hash=h;
    }

}

 

JDK1.8的HashMap源码追踪

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值