通过代码  transient  Entry[]  table ; 可以看出 有一个Entry数组。



static  
  class  
  Entry<K,V>  
  implements  
  Map.Entry<K,V> {
 
 
         
   final  
   K  
   key 
   ;
 
  
        V  
   value 
   ;
 
  
        Entry<K,V>  
   next 
   ;
 
  
         
   final  
   int  
   hash 
   ;
 
  
}



通过上面这段代码可以看出:



      内部类 Entry中有四个属性信息,分别为key,value,next指针,hash值,即每一个Entry对象都有这四个信息。



通过 for  (Entry<K,V> e =  table [i]; e !=  null ; e = e. next  )  这个for循环代码可以看出:



     table数组中,每一个位置都存有一些Entry对象信息,而这些信息用next连接了起来,就形成了一个链表,所以table数组中的每一个位置都存有Entry对象的链表(当然了,也可能是null)。






再看一下put代码:



public  
    V put(K key, V value) {
 
 
         
  if  
  (key ==  
  null 
  )
 
 
             
  return  
  putForNullKey(value);
 
 
         
  //计算用自己的hash方法计算hash值
 
 
         
  int  
  hash = hash(key.hashCode());
 
 
         
   //用计算出的hash值与table的长度进行与运算,计算 
   出要put的键值对存放的table数组的位置i
 
 
         
  int  
  i = indexFor(hash,  
  table  
  . 
  length  
  );
 
 
        //循环遍历table数组位置i下的链表信息
 
 
        
  for  
  (Entry<K,V> e =  
  table 
  [i]; e !=  
  null 
  ; e = e. 
  next 
  ) {
 
 
            Object k;
 
 
            //如果要存储的key的hash值与table数组位置i下的某个链表节点的hash值相等,
 
 
            //并且,key值也相等,那么就执行替换操作
 
 
             
  if  
  (e.  
  hash  
  == hash && ((k = e.  
  key 
  ) == key || key.equals(k))) {
 
 
                V oldValue = e.  
  value 
  ;
 
 
                e.  
  value  
  = value;
 
 
                e.recordAccess(  
  this 
  );
 
 
                 
  return  
  oldValue;
 
 
            }
 
 
        }
 
 
         
  modCount 
  ++;
 
 
      
     //如果循环遍历之后,没有找到hash和key都相同的Entry节点,那么就在table数组的位置i处,
 
 
        //插入一个Entry节点信息,addEntry方法中有四个参数,分别是hash值,key,value,table数组的位置i
 
 
        addEntry(hash, key, value, i);
 
 
         
  return  
  null 
  ;
 
 
    }






再看一下addEntry方法:



void  
   addEntry( 
   int  
   hash, K key, V value,  
   int  
   bucketIndex) {
     //在插入之前,首先将table数组的位置i处的所有Entry节点信息都保存到e对象中,因为table数组的每一个位置都是一个Entry对象。 
  
     Entry<K,V> e =  
   table 
   [bucketIndex];
 
  
     //然后新建一个Entry对象,然后将这个新建的Entry对象再赋值给table数组的位置i处。
 
  
      
   table 
   [bucketIndex] =  
   new  
   Entry<K,V>(hash, key, value, e);
 
  
      
   if  
   ( 
   size 
   ++ >=  
   threshold 
   )
 
  
         resize(2 *  
   table 
   .  
   length 
   );
 
  
    }






单独拿出 new  Entry<K,V>(hash, key, value, e);来说一说:



     新建一个Entry对象,传入的参数有四个,分别是根据key计算出来的hash,key,value,以及table数组的位置i处的所有Entry节点信息。






看一下Entry的构造方法



     

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






根据  next  = n;可以知道,我新建一个Entry对象节点,然后将next指针指向了传入的参数n(即上面保存table[bucketIndex]的e对象节点),我觉得新建的这个Entry对象节点应该会指向原来的Entry对象的头节点处。 此时table[i]的位置的那个Entry对象应该改变了,变成刚刚新创建的那个Entry对象节点了。当再次put的时候,再次执行这个for循环的时候 for  (Entry<K,V> e =  table [i]; e !=  null ; e = e. next  )  只是table[i]的值变化了一下,table[i]处的Entry链表长度变化了一下。









分析完put方法之后,再分析一下get方法:



   

public 
     V get(Object key) {
 
   
         
    if 
     (key ==  
    null 
    )
 
   
             
    return 
     getForNullKey();
 
   
        //计算key值的hash值,与put方法的一样
 
   
         
    int 
     hash = hash(key.hashCode());
 
   
        //用 
    indexFor(hash,  
    table 
    .  
    length 
    )方法进行与运算,然后找到了要在table数组取值的位置
 
   
        //循环遍历table[i]处的Entry链表节点
 
   
         
    for 
     (Entry<K,V> e =  
    table 
    [ indexFor(hash,  
    table 
    .  
    length 
    )];
 
   
             e !=  
    null 
    ;
 
   
             e = e.  
    next 
    ) {
 
   
            Object k;
 
   
            //如果key值的hash与table数组中存储的Entry对象e的hash值相等,同时key值相等,那么,就说明找到了,并返回value值。
 
   
             
    if 
     (e.  
    hash 
     == hash && ((k = e.  
    key 
    ) == key || key.equals(k)))
 
   
                 
    return 
     e.  
    value 
    ;
 
   
        }
 
   
         
    return 
      
    null 
    ;
 
   
    }