Javad的容器 Hashmap 的几种基本方法

本部分转载参考【http://www.importnew.com/16301.html】

Java语言中,最基础的数据结构  只有 数组 + 模拟指针 (引用),所有的数据结构底层都是这么构造出来的。

HashMap的本质:链表散列的数据结构(链表+数组),数据每一项是个数组,数组中每一项是链表【链表太长时,用红黑树存,加快查找速度(好开心,知识连起来了)】。以空间换时间。

//HashMap的源码
public class HashMap<K,V>
    extends AbstractMap<K,V>
    implements Map<K,V>, Cloneable, Serializable

HashMap实现了Map接口的非同步实现,不是线程安全的。【resize死循环,抛出异常后的fail-fast策略】

HashMap实现了Cloneable接口,实现了clone( )方法,即clone了一个HashMap对象并返回。

HashMap实现了Serializable接口,实现了串行读取、写入功能。

writeObject( ),将HashMap"总容量、实际容量、所有Entry"写入输出流中。

readObject( ),将HashMap"总容量、实际容量、所有Entry"依次读出。

  • HashMap不是线程安全的,如果使用迭代器过程中,其他线程修改了map,那么会抛出ConcurrentModificationException 异常。

允许使用nulll值,和 null 键。不保证映射顺序的恒久不变。

  • 向HashMap中存储put值时,根据key的hashcode 重新计算hash 值,根据hash值得到元素在数组中的下标,如果数组的该位置中已经有元素了,那么本位置元素开始呈链表 结构存放,新加入的放在链表头部。若没有元素,则直接放置,暂时不涉及链表结构。
  • addEntry(hash,key,value,i)方法根据计算出的hash值,将key-value对放在数组table的bucketIndex索引处。
  • HashMap存储key-value对是,只考虑了key来决定Entry放置的位置。可以把value视为key的附属,系统确定key位置后,顺便把value 保存在那里。
  • HashMap找元素时,根据key的hash值,确定对应数组中的位置。应该尽量优化hash算法,这样每个位置均匀分布(下条说道hashMap长度都是2^n),减少了遍历链表的时间,大大优化查询效率。
  • 任意对象 hashCode() 返回值相同,那么程序调用 hash(int h)方法计算得到的hash码也是相同的。对hash值对数组长度取模运算??元素可以相对均匀分布。大致懂了。而HashMap底层数组的长度总是 2 的 n 次方,这是HashMap在速度上的优化。那么 length总是2的n次方时,hash & (length-1) eg:length=16=2^4时,是hash & 1111 = hash??有意义???等价于对length取模,也就是 hash%length ,方便理解,毕竟&比%有更高的效率。【当数组长度为2的n次幂的时候,不同的key算得得index相同的几率较小,那么数据在数组上分布就比较均匀,也就是说碰撞的几率小,相对的,查询的时候就不用遍历某个位置上的链表,这样查询效率也就较高了。
  • HashMap底层用Entry[]数组保存所有的 key-value对,需要存储Entry对象时,根据hash算法来决定存储位置,根据equals方法决定在数组位置上的链表中的存储位置。取Entry时,用hash找到数组中的位置,再用equals找到位置中存放的链表中的位置。

HashMap的扩容resize

  • 本来数组长度是固定的,但元素越来越多 hash冲突越来越高,为了提高查找效率,需要对hashmap数组扩容。(那么已经存咋的数据必须重新计算扩容后新的位置,再放进入)。
transient Entry<k,v>[] table;//存储(位桶)的数组</k,v>  
http://liyanblog.cn/articles/2012/09/28/1348814751429.html
 static class Entry<K,V> implements Map.Entry<K,V> {
        final K key;
        V value;
        Entry<K,V> next;
        final int hash;}
HashMap<String , Double> map = new HashMap<String , Double>(2);    
  map.put("语文" , 80.0);    
  map.put("数学" , 89.0);    
  map.put("英语" , 78.2);

hashCode( )方法 得到key的hash值

int hash = hash(key.hashCode());

indexFor( )方法 找到对于位置

int i = indexFor(hash, table.length);

HashMap的存储

public V put(K key, V value) {
    // HashMap允许存放null键和null值。
    // 当key为null时,调用putForNullKey方法,将value放置在数组第一个位置。  
    if (key == null)
        return putForNullKey(value);
    // 根据key的hashCode重新计算hash值。
    int hash = hash(key.hashCode());
    // 搜索指定hash值在对应table数组中的索引位置。
    int bucketIndex = indexFor(hash, table.length);
    // 如果 bucketIndex 索引处的 Entry 不为 null,通过循环不断遍历 e 元素的下一个元素。
    for (Entry<K,V> e = table[bucketIndex]; e != null; e = e.next) {
        Object k;
    //判断条件要 hash值相同 && key值相同(用==或equals两种方法判断前者更快,因为只是比较引用)。 因为存在key值不同却得到相同hash值的可能。
    //“==” 比较的是值:变量(栈内存)中存放的对象的堆内存地址
    //“equals”比较两个对象的内容(栈内存中存放的内容,不是比地址)equals(k)的k已经成了e.key了
        if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
            V oldValue = e.value;//若存在相同hash值,key相同,那么直接覆盖value,返回旧value
            e.value = value;
            e.recordAccess(this);
            return oldValue;//??为啥返回oldValue???
        }
    }
    // 如果bucketIndex索引处的Entry为null,表明此处还没有Entry。
    modCount++;//修改次数增加1,用来实现 fail-fast机制???
    // 将key、value添加到i索引处。
    addEntry(hash, key, value, bucketIndex);//这个不管是否Entry为null,都要执行的下面addEntry()方法
    return null;
}

[java]  view plain  copy
  1. import java.util.HashMap;  
  2. import java.util.Map;  
  3. public class Test {  
  4.     public static void main(String[] args) {  
  5.         Map<String, String> map = new HashMap<String, String>();  
  6.         String p1 = map.put("11""22");  
  7.         System.out.println("p1:" + p1);  
  8.         String p2 = map.put("33""44");  
  9.         System.out.println("p2:" + p2);  
  10.         String value1 = map.get("11");  
  11.         System.out.println("value1:" + value1);  
  12.         String p3 = map.put("11""44");  
  13.         System.out.println("p3:" + p3);  
  14.         String value2 = map.get("11");  
  15.         System.out.println("value2:" + value2);  
  16.     }  
  17. }  

输出结果:

[java]  view plain  copy
  1. p1:null  
  2. p2:null  
  3. value1:22  
  4. p3:22  
  5. value2:44  

说明:put方法返回值为null或者value;

如果key没有重复,put成功,则返回null,如p1、p2;

如果key重复了,返回的是map.get(key),也就是当前这个key对应的value,如上面的p3,key="11",而p1的key也是"11",p1与p3重复,返回的是p1的value="22",并且将p3覆盖掉p1



HashMap的Entry添加

void addEntry(int hash, K key, V value, int bucketIndex) {
    //已知hashCode值->bucketIndex索引值,key,value。
    // 获取指定 bucketIndex 索引处的 Entry  
    Entry<K,V> e = table[bucketIndex];
    // 将新创建的 Entry 放入 bucketIndex 索引处,本来该位置没有Entry,现在是新建立一个Entry.
    //后面是错的---->并让新的 Entry 指向原来的 Entry,
    //这句话是错的---> 新的Entry做为链表头,e是本Entry的next,本位置记录为链表头
    table[bucketIndex] = new Entry<K,V>(hash, key, value, e);
    // 如果 Map 中的 key-value 对的数量超过了极限
    if (size++ >= threshold)
    // 把 table 对象的长度扩充到原来的2倍。
        resize(2 * table.length);
}

HashMap的读取

public V get(Object key) {
    if (key == null)
        return getForNullKey();
    int hash = hash(key.hashCode()); //hash算法得到hashCode值
    for (Entry<K,V> e = table[indexFor(hash, table.length)]; e != null;e = e.next) {
        Object k
//找到数组中对应的某一个元素(table数组中的hash位),本位存放了好多Entry<K,V>,通过key的equals方法在对应位置的链表上找到需要的元素。
        if (e.hash == hash && ((k = e.key) == key || key.equals(k)))  
            return e.value;
    }
    return null;
}
HashMap的遍历方式
Map map = new HashMap();
  Iterator iter = map.entrySet().iterator();
  while (iter.hasNext()) {
  Map.Entry entry = (Map.Entry) iter.next();
  Object key = entry.getKey();
  Object val = entry.getValue();
  }



补充部分:

接口实现,红黑树代码转载自【http://blog.csdn.net/tuke_tuke/article/details/51588156】

用Entry<k,v>实现Map.Entry接口。

//Node是单向链表,它实现了Map.Entry接口  
static class Node<k,v> implements Map.Entry<k,v> {  
    final int hash;  
    final K key;  
    V value;  
    Node<k,v> next;  
    //构造函数Hash值 键 值 下一个节点  
    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 K getKey()        { return key; }  
    public final V getValue()      { return value; }  
    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;  
    }  
    //判断两个node是否相等,若key和value都相等,返回true。可以与自身比较为true  
    public final boolean equals(Object o) {  
        if (o == this)  
            return true;  
        if (o instanceof Map.Entry) {  
            Map.Entry<!--?,?--> e = (Map.Entry<!--?,?-->)o;  
            if (Objects.equals(key, e.getKey()) &&  
                Objects.equals(value, e.getValue()))  
                return true;  
        }  
        return false;  
    }  

红黑树???没看懂

//红黑树  
static final class TreeNode<k,v> extends LinkedHashMap.Entry<k,v> {  
    TreeNode<k,v> parent;  // 父节点  
    TreeNode<k,v> left; //左子树  
    TreeNode<k,v> right;//右子树  
    TreeNode<k,v> prev;    // needed to unlink next upon deletion  
    boolean red;    //颜色属性  
    TreeNode(int hash, K key, V val, Node<k,v> next) {  
        super(hash, key, val, next);  
    }  
   
    //返回当前节点的根节点  
    final TreeNode<k,v> root() {  
        for (TreeNode<k,v> r = this, p;;) {  
            if ((p = r.parent) == null)  
                return r;  
            r = p;  
        }  
    } 



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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值