Map集合_HashMap

HashMap

一、简介

无序
键值都可以为空,但是只能有一个键为空

1.1、jdk1.8版本前后

1、在jdk1.8之前, 主要存储的方式是数组+链表的结构 ,数组作为主体。链表主要解决hash值冲突问题(两个对象在调用hashcode()方法计算出一个相同的hash值,导致数组的索引一样,这时就要用到链表存储)
2、在调用构造方法时就会创建一个16长度的数组,Entry[]table 存储键值对数据

在jdk1.8之后 ,引入了红黑树存储 (当链表长度超过阈值【或者红黑树的边界值:默认为8】,并且数组长度超过64时,数组索引的位置将会变成黑红树存储)【如果超过阈值但是长度没有超过64就会扩容存储】
2、创建数据长度是在第一次调用put方法是创建一个Node[]table数组

  • 数组没有超过64,进行扩容,主要是数组长度小于64还是数组搜索快一些
  • 使用红黑树效率会变低,因为红黑树为了平衡会 左旋 ,右旋等操作

1.2、存储数据的原理

1、存储第一个数据(eg:轻松 24)时先调用String重写后的hashcode()方法,计算出hash值,再根据Node数组的长度采用某种算法计算出向数组中存放位置的索引值(假若是“2”)

2、存入第二个数据(eg:星星 “25”)如果计算的索引值也是(“2”),就会比较这两个hash值,如果不一样就会划出一个节点,以链表的方式来存储第二个数据

3、存入第三个数据(eg:“轻松 33”)这时索引值也是(‘2’),所以判断和之前存的两对象key 的hash值,如果相等,就是发生hash碰撞, 再调用equels ,判断两个key是否相等如果不相等就会继续在链表下继续存储,相等就覆盖vlue值

1.3、集合容量

A:初始容量(必须是二的n次幂)

默认开始扩容为1左移4位=》 16 也就是1*2的4次方
在这里插入图片描述
是二的n的n次幂
按位与运算(hash &(数组长度-1))
在这里插入图片描述

这样能跟好的均匀的分配数组空间

不是二的n次幂
在这里插入图片描述

非常容易使得索引相同,和哈希碰撞,使得存储的不均匀

B:如果传的长度不是2的n次幂

回走这个方法,找到大于传的数的最小2的幂次方
通过右移再或运算得到

    /**
     * Returns a power of two size for the given target capacity.
     */
    static final int tableSizeFor(int cap) {
        int n = cap - 1;
        n |= n >>> 1;
        n |= n >>> 2;
        n |= n >>> 4;
        n |= n >>> 8;
        n |= n >>> 16;
        return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1;
    }

(eg:传10)计算如下:

第一次计算(将1001 相邻的0变为1 1101)
n=9;
在这里插入图片描述

第二次计算 (再次将 1001 高位的0变为1 1111 )
在这里插入图片描述

第三次计算 (基本没有变化了 )

在这里插入图片描述

1.4、红黑树和链表之间的转化

开始使用链表的原因:因为红黑树的节点比较大,而且红黑树的空间是链表的两倍 所以开始使用链表效率会高一些

当节点高于8的时候,会自动转为红黑树,小于6时,会自动变为链表

主要是泊松分布原理

1.5、加载因子

 /**
     * The load factor used when none specified in constructor.
     */
    static final float DEFAULT_LOAD_FACTOR = 0.75f;

不能太小,太小会导致空间使用率低,会扩容多(扩容很不好,会开辟一个新的数组,然后将旧的数组里面的数据放入新的数组里面 ,使得效率降低)

也不能太大,太大会导致产生更多的链表,红黑树会很多,导致查询等效率大大降低

1.5、扩容机制

扩容之后的索引的位置要么是原来的索引位置,要么是原来索引位置+就数组的容器
在这里插入图片描述

1.6、删除元素

实际是调用removeNode()方法
1、先判断是否是数组结构,如果是就直接按照数组删除
2、判断是否有节点 如果有节点,再判断是红黑树还是链表结构
3、如果是红黑树,就按照红黑树方式删除,链表就按链表结构删除

public V remove(Object key) {
        Node<K,V> e;
        return (e = removeNode(hash(key), key, null, false, true)) == null ?
            null : e.value;
    }
    final Node<K,V> removeNode(int hash, Object key, Object value,
                               boolean matchValue, boolean movable) {
        Node<K,V>[] tab; Node<K,V> p; int n, index;
        if ((tab = table) != null && (n = tab.length) > 0 &&
            (p = tab[index = (n - 1) & hash]) != null) {
            Node<K,V> node = null, e; K k; V v;
            if (p.hash == hash &&
                ((k = p.key) == key || (key != null && key.equals(k))))
                node = p;
            else if ((e = p.next) != null) {
                if (p instanceof TreeNode)
                    node = ((TreeNode<K,V>)p).getTreeNode(hash, key);
                else {
                    do {
                        if (e.hash == hash &&
                            ((k = e.key) == key ||
                             (key != null && key.equals(k)))) {
                            node = e;
                            break;
                        }
                        p = e;
                    } while ((e = e.next) != null);
                }
            }
            if (node != null && (!matchValue || (v = node.value) == value ||
                                 (value != null && value.equals(v)))) {
                if (node instanceof TreeNode)
                    ((TreeNode<K,V>)node).removeTreeNode(this, tab, movable);
                else if (node == p)
                    tab[index] = node.next;
                else
                    p.next = node.next;
                ++modCount;
                --size;
                afterNodeRemoval(node);
                return node;
            }
        }
        return null;
    }

1.6、遍历元素

第一种
//先假设有一个HashMap集合 ->map ,有值
	Map<String,String> map=new HashMap<String,String>
//先得到所有的key值
	String keys=map.keySet();//然后遍历得值
//得到所有的Vule值
	Collection<String> values=map.values();//然后再便利得值
第二种

		HashMap<String,String> map=new HashMap<String, String>();
		//先得到一个set集合
        Set<Map.Entry<String, String>> entries = map.entrySet();
        //再用迭代器,迭代集合
        for (Iterator<Map.Entry<String, String>> it=entries.iterator(); it.hasNext()) {
            Map.Entry<String, String> next = it.next();
            System.out.println(next.getKey()+next.getValue());
        }
第三种(不建议使用)迭代了两次
		HashMap<String,String> map=new HashMap<String, String>();
		//得到所有的key   keys
        Set<String> keys = map.keySet();
        for (String key : keys) {
        	//遍历keys 通过key找到value
            String value = map.get(key);
            System.out.println(key + value);
        }
第四种 JDK8之后默认方法
		HashMap<String,String> map=new HashMap<String, String>(10);
		//JDK8 新特性Lambda表达式
        map.forEach((key,value)->{
            System.out.println(key+"-----"+value);
        });

1.7、知道map的容量,合理设计map集合

也就是初始化容器,尽量避免扩容

容量 initialCapacity=(需要存储的元素个数 / 负载因子)+1

在这里插入图片描述

一般建议使用第二种构造方法,设计好初始容量
1、如果使用第一种,默认为第一次调用put扩容16,如果数据很多,就会多次扩容,扩容很消耗性能
2、一般加载因子不建议修改,原因如上面解释

容量设计正解: 容量 initialCapacity=(需要存储的元素个数 / 负载因子)+1


eg:要存7个数据,如果容器刚好设计为7,因为通过jdk处理,会被设计成8,因为HashMap元素达到80.75=6就会扩容
当按照公式算得为10 因为通过jdk会自动升为16 ,而
0.75=12(*负载因子)刚好合适 ,所以不能直接按照数据大小设计,

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值