HashMap

一 HashMap使用
1) 概述:HashMap是基于哈希表的Map接口的非同步实现(Hashtable跟HashMap很像,唯一的区别是Hashtalbe中的方法是线程安全的,也就是同步的)。此实现提供所有可选的映射操作,并允许使用null值和null键。此类不保证映射的顺序,特别是它不保证该顺序恒久不变。
2)数据结构:
链表+数组实现 (底层结构)
jdk1.8开始 常采用数组加链表加红黑树
用链表是为了解决hash冲突
hash冲突:当我们对某个元素进行哈希运算,得到一个存储地址,然后要进行插入的时候,发现已经被其他元素占用了,其实这就是所谓的哈希冲突,也叫哈希碰撞。
解决hash冲突的方法:HashMap即是采用了链地址法,也就是数组+链表的方式
3)实现方法:put(K key, V value), get(K key), remove(K key), resize()
4)hashmap常用方法:clear()
从此映射中移除所有映射关系。
containsKey(Object key)
如果此映射包含对于指定键的映射关系,则返回 true。
containsValue(Object value)
如果此映射将一个或多个键映射到指定值,则返回 true。
entrySet()
返回此映射所包含的映射关系的 Set 视图。
get(Object key)
返回指定键所映射的值;如果对于该键来说,此映射不包含任何映射关系,则返回 null。
remove(Object key)
从此映射中移除指定键的映射关系(如果存在)。
size()
返回此映射中的键-值映射关系数。
HashMap还有一个实现接口是Map.Entry<K,V>:
二 hashmap底层分析
1)散列表结构:数组+链表的结构
2)hash:Hash也称散列、哈希,对应的英文单词Hash,基本原理就是把任意长度的输入,通过Hash算法变成固定长度的输出 不同的数据它对应的哈希码值是不一样的 ,哈希算法的效率非常高。
3)底层储存结构:当链表长度到达8时,升级成红黑树结构
4)put数据原理分析:
首先put进去一个key----value
根据key值会计算出一个hash值
经过扰动使数据更散列
构造出一个node对象
最后在通过路由算法得出一个对应的index
自定义put方法:

  • 自定义put方法
  • 1)key-> hash(key) 散列码 -> hash & table.length-1 index
  • 2)table[index] == null 是否存在节点
  • 3)不存在 直接将key-value键值对封装成为一个Node 直接放到index位置
  • 4)存在 key不允许重复
  • 5)存在 key重复 考虑新值去覆盖旧值
  • 6)存在 key不重复 尾插法 将key-value键值对封装成为一个Node 插入新节点
    5 扩容过程:
    1)table进行扩容
    2)table原先节点进行重哈希
    a.HashMap的扩容指的是数组的扩容,因为数组的空间是连续的,所以需要数组的扩容即开辟一个更大空间的数组,将老数组上的元素全部转移到新数组上来
  • b.在HashMap中扩容,先新建一个2倍原数组大小的新数组,然后遍历原数组的每一个位置,如果这个位置存在节点,则将该位置的链表转移到新数组
  • c.在jdk1.8中,因为涉及到红黑树,jdk1.8实际上还会用到一个双向链表去维护一个红黑树
    中,所以jdk1.8在转移某个位置的元素时,首先会判断该节点是否是一个红黑树节点,然后遍历该红黑树所对应的双向链表,将链表中的节点放到新数组的位置当中
  • d.最后元素转移完之后,会将新数组的对象赋值给HashMap中table属性,老数组将会被回收
    3)扩容时机:table==null或者table需要扩容的时候
    6 HashMap迭代器实现
    1)由于哈希表数据分布是不连续的,所以在迭代器初始化的过程中需要找到第一个非空的位置点,避免无效的迭代
    2)当迭代器的游标到达某一个桶链表的末尾,迭代器的游标需要跳转到下一个非空的位置点

三 hashmap源码分析
1)类的继承关系

  • public class HashMap<K,V> extends AbstractMap<K,V> implements Map<K,V>, Cloneable, Serializable

  • HashMap允许空值和空键

  • HashMap是非线程安全

  • HashMap元素是无序 LinkedHashMap TreeMap

  • (HashTable不允许为空 线程安全)
    2)类的属性

  • static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; 16 默认初始容量 用来给table初始化

  • static final int MAXIMUM_CAPACITY = 1 << 30;

  • static final float DEFAULT_LOAD_FACTOR = 0.75f; //扩容机制

  • static final int TREEIFY_THRESHOLD = 8; //链表转为红黑树的节点个数

  • static final int UNTREEIFY_THRESHOLD = 6;//红黑树转为链表的节点个数

  • static final int MIN_TREEIFY_CAPACITY = 64;

  • static class Node<K,V> implements Map.Entry<K,V>

  • transient Node<K,V>[] table; //哈希表中的桶

  • transient Set<Map.Entry<K,V>> entrySet; //迭代器遍历的时候

  • transient int size;

  • int threshold;

  • final float loadFactor;

  • 3)类中重要的方法 (构造函数 put remove resize)

  • 构造函数中并未给桶进行初始化

  • put

  • if ((tab = table) == null || (n = tab.length) == 0)

  •         n = (tab = resize()).length; //resize()   初始化(和扩容)
    
  • if ((p = tab[i = (n - 1) & hash]) == null)

  •         tab[i] = newNode(hash, key, value, null);//当前位置不存在节点,创建一个新节点直接放到该位置
    
  • else{

  • //当前位置存在节点 判断key是否重复
    
  • if (p.hash == hash &&
    
  •             ((k = p.key) == key || (key != null && key.equals(k))))
    
  •     e = p;
    
  •     //判断第一个节点的key是否与所要插入的key相等
    
  •     //hashCode 表示将对象的地址转为一个32位的整型返回  不同对象的hashCode有可能相等
    
  •     //比较hash相比于使用equals更加高效
    
  • 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
    
  •                  treeifyBin(tab, hash);
    
  •                  break;
    
  •             }
    
  •             if (e.hash == hash &&
    
  •                 ((k = e.key) == key || (key != null && key.equals(k))))
    
  •                  break;
    
  •                  //判断e是否是key重复的节点
    
  •             p = e;
    
  •         }
    
  •     }
    
  • }
    四 HashMap常见面试题分析
    1)JDK1.7与JDK1.8HashMap有什么区别和联系

  • 最重要的一点是底层结构不一样,1.7是数组+链表,1.8则是数组+链表+红黑树结构;

  • jdk1.7中当哈希表为空时,会先调用inflateTable()初始化一个数组;而1.8则是直接调用resize()扩容;

  • 插入键值对的put方法的区别,1.8中会将节点插入到链表尾部,而1.7中是采用头插;

  • jdk1.7中的hash函数对哈希值的计算直接使用key的hashCode值,而1.8中则是采用key的hashCode异或上key的hashCode进行无符号右移16位的结果,避免了只靠低位数据来计算哈希时导致的冲突,计算结果由高低位结合决定,使元素分布更均匀;

*扩容时1.8会保持原链表的顺序,而1.7会颠倒链表的顺序;而且1.8是在元素插入后检测是否需要扩容,1.7则是在元素插入前;

*jdk1.8是扩容时通过hash&cap==0将链表分散,无需改变hash值,而1.7是通过更新hashSeed来修改hash值达到分散的目的;

*扩容策略:1.7中是只要不小于阈值就直接扩容2倍;而1.8的扩容策略会更优化,当数组容量未达到64时,以2倍进行扩容,超过64之后若桶中元素个数不小于7就将链表转换为红黑树,但如果红黑树中的元素个数小于6就会还原为链表,当红黑树中元素不小于32的时候才会再次扩容

2)用过HashMap没?说说HashMap的结构(底层数据结构 + put方法描述 见上文)
3)说说HashMap的扩容过程(见上文)
4)HashMap中可以使用自定义类型作为其key和value吗?
可以,但必须用自定义类作为key,必须重写equals()和hashCode()方法。Object类的hashCode()方法返回这个对象存储的内存地址的编号。而equals()比较的是内存地址是否相等。通过hashCode()能够计算出一个hash值,通过hash值来判断两个对象的值是否相等,如果hash值不相等则说明这两个对象不相等,如果hash值相等则继续用equals()去判断两个对象是否相等。
5)HashMap中table.length为什么需要是2的幂次方
HashMap存储数据时要避免位置碰撞且数据分配均匀,于是采用位移运算的算法计算存储链表的位置,假设HashMap的长度不为2的幂次方则有可能产生碰撞。
例如长度为9时候,3&(9-1)=0 2&(9-1)=0 ,都在0上,碰撞了;
例如长度为8时候,3&(8-1)=3 2&(8-1)=2 ,不同位置上,不碰撞;
6)HashMap与HashTable的区别和联系
父类不一样。HashMap是继承自AbstractMap类,而HashTable是继承自Dictionary类。不过它们都实现了同时实现了map、Cloneable(可复制)、Serializable(可序列化)这三个接口。
联系:
*HashMap是Hashtable的轻量级实现,他们都实现了共同的父接口Map。
*.都采用了Hash方法进行索引,底层都是Hash表结构,具有很快的访问速度。
区别:
*.1HashMap允许空键值(最多只允许一条记录的键为null,不允许多条记录的值为null),Hashtable不允许空键值。
*.Hashtable的方法是线程安全的,HashMap的方法是线程不安全的。

  • Hashtable使用Enumeration进行遍历,HashMap使用Iterator进行遍历。
    *HashMap把Hashtable的contains的方法去掉了改成containsvalue和containsKey。Hashtable继承自Dictionary类,而HashMap是java1.2引进的Map interface的一个实现。
    Hashtable中hash数组默认大小是11,扩容方式为old2+1;HashMap中默认大小是16,扩容一定是2的指数。
    *Hash值的使用不同,Hashtable直接使用对象的HashCode。
    7)HashMap、LinkedHashMap、TreeMap之间的区别和联系?
    a联系:
    HashMap,LinkedHashMap,TreeMap都属于集合Map接口的子类。都是键值对集合。线程都不安全,效率高。
    b 区别:
    *HashMap键是哈希表结构,可以保证键的唯一性,key能null,值也可为null。
  • LinkedHashMap是HashMap的子类,内部依赖哈希表和链表列实现。由hash保证键的唯一性,由LinkedList保证有序性(存取顺序一致)。key能为Null,value也能为null。
    *TreeMap类,键是红黑树结构,可以保证键的排序(自然排序),并且不能重复。key不能为null,value可以为null
    8)HashMap与WeakHashMap的区别和联系
    a .HashMap
    *HashMap是基于Key-Value的散列表(JDK7:数组+链表,JDK8:数组+链表+红黑树bai),采用拉链法实现的。一般用于单线程当中,非线程安全,HashMap的键是"强键"。
    *.继承于抽象类AbstractMap,并且实现Map接口。遍历时,取得的数据完全是随机的。
    *默认容量大小是16,加载因子是0.75。
    *.最多只允许一条key为Null,允许多条value为Null。
    *.HashMap实现了Cloneable和Serializable接口,而WeakHashMap没有。
    *.HashMap实现Cloneable,说明它能通过clone()克隆自己。
    *.HashMap实现Serializable,说明它支持序列化,能通过序列化去传输。
    *.添加、删除操作时间复杂度都是O(1)。
    b weakHashMap
    *.weakHashMap是基于Key-Value的散列表(数组+链表),采用拉链法实现的。一般用于单线程当中,非线程安全,weakHashMap中的键是"弱键"。
    备注:当"弱键"被GC会收时,它对应的键值也会从weakHashMap中删除。
    *继承于抽象类AbstractMap,并且实现Map接口。
    *.默认容量大小是16,加载因子是0.75。
    *.最多只允许一条key为Null,允许多条value为Null。
    9)WeakHashMap中涉及到的强弱软虚四种引用
    Java中四大引用:
  • 强引用
    A a = new A(); //a是强引用
    只要是强引用,GC就不会回收被引用的对象
  • 软引用SoftReference
    一般用户实现Java对象的缓存,缓存可以有可以没有,一般将有用但是非必须的
    对象用软引用关联
    只要是软引用关联的对象,在Java发生内存溢出异常之前,会将这些对象列入要
    回收的范围,如果回收之后发现内存还是不够,才会抛出OOM异常
    map -》 SoftReference -》 SoftReference.get()
  • 弱引用 WeakReference
    弱引用是用来一些非必须的对象,比软引用更弱一些
    只要是被弱引用关联的对象,只能够生存到下一次垃圾回收之前,一旦发生垃圾回收,
    无论当前内存是否够用,都会回收掉被弱饮用关联的对象
  • 虚引用 PhantomReference
    别名幽灵引用 最弱的引用关系,一个对象是否具有虚引用的存在,完全是不会对其生命
    周期产生影响,也无法通过虚引用获取一个对象的实例,它存在的唯一目的就是在对象被
    垃圾回收之后收到一个系统通知
    特殊的HashMap,WeakHashMap的键是弱引用对象,只能存活到下一次垃圾回收之前
    10)HashMap是线程安全的吗?引入HashTable和ConcurrentHashMap(见上文)
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值