HashMap知识点

特点

collection(存放单值的最大接口)和map(存储双值)是集合框架库两个顶级接口。

  • Map接口下特点:以Map<Key,Value>形式存储;(Key是不重复的,代表元素的存储位置,也就是可以通过key去寻找key.value的位置,从而得到value的值)适合做查找工作

证明key不可重复:

public class HashmapTest {
    public static void main(String[]args){
//        HashMap map=new HashMap();//没有指定泛型,存储的数据就是任意的了
        HashMap<String,Integer>map=new HashMap<>();//key:String;value:Integer
        map.put("Tom",12);
        map.put("Jack",15);
        map.put("Jim",29);
        map.put("Tom",69);//存储同样的key,新的value会替换老的value
    //迭代器遍历  通过entrySet方法调用接口,map.EntrySet().Iterator();原因:数据实际上是存储在entry结构中
         Iterator<Map.Entry<String, Integer>> iterator = map.entrySet().iterator();
        while (iterator.hasNext()){
            //iterator.next();next获取的值是Entry的结构
            Map.Entry<String, Integer> next = iterator.next();
            System.out.println("key:"+next.getKey()+"value:"+next.getValue());
        }
    }
}

运行结果截图如下
在这里插入图片描述证明可以为null的方法同上
运行结果截图如下
在这里插入图片描述这也说明了HashMap中元素的顺序,不是插入顺序,因为key-value的存储位置,是由key决定的

如果想单独遍历key:

//只想遍历key时 map.keySet().iterator();
        Iterator<String> iterator1 = map.keySet().iterator();
        while (iterator1.hasNext()){
            //iterator1.next();
           // String next = iterator1.next();
            String key = iterator1.next();
            System.out.println("key:"+key);
        }

运行结果截图如下
在这里插入图片描述

如果只想要value时:

//只需要values时
        Iterator<Integer> iterator2 = map.values().iterator();
        while (iterator2.hasNext()){
            Integer value=iterator2.next();
            System.out.println("value:"+value);
        }

运行结果
在这里插入图片描述

通过foreach遍历:

//通过foreach遍历HashMap集合
        for (Map.Entry<String,Integer>entry:map.entrySet()){
            System.out.println("key:"+entry.getKey()+"value:"+entry.getValue());
        }

其他的方法

// map.get();//通过key查询对应的value的值
        //查询
        System.out.println(map.get("Tom"));//应该返回69;注意执行此方法时把下边的全屏蔽

        //删除
        map.remove("Tom");
        //修改
        map.replace("Jack",105);

  • Cloneable:可以使用clone方法。
  • Serializable:可以被序列化。
  • Map.Entry:可以存储key-value的具体数值。
  • Iterator:迭代器接口(只能从前往后遍历,只有list接口下集合能实现ListInterator)

使用场景

例:存储一个班的人名和成绩,如果用list没办法将成绩与人名相对应 ,而HashMap查询效率比较高,并且可以通过key查询到对应的value。

源码分析(源码的特点)

HashMap底层数据结构:数组+链表的结构(1.7版本);

链表类型的数组:数组下标下面存储的是一个个链表的头节点
为什么要使用这种结构:通过哈希算法计算key的存储位置(hash(key)----->index:存储位置);哈希算法要保证计算出的index一定要小于数组的length如何保证? :在算法最后X%table.length=index),可能会产生多个key对应一个index的问题(哈希冲突:多个X取余以后的值相同),解决方案:链地址法。
链表没有下标,就需要遍历,有了数组以后,就可以通过下标快速找到。

public class MyHashMap<K,V> {
    //HashMap特点1:初始容量是0,当添加第一个元素时容量变为了16
    private Entry[]table;
    Entry<?,?>[]EMPTY_TABLE={};
    private int DEFAULT_CAPCITY=16;
    int size;//存储数据的数量
    float threshold=0.75f;//加载因子;加载因子越大,扩容时机越晚,哈希冲突发生的概率越大,空间利用率越高;加载因子越小,扩容时机越早,哈希冲突发生的概率越小,空间利用率越低
    MyHashMap(){
        table=EMPTY_TABLE;
    }
    public int hash(K key){//key对象
       // return key.hashCode()%(table.length);//如果是null,空指针异常
        //key.hashCode()是一个int类型 这个int类型有可能会造成数组下标越界
        //我们将余数作为index
        if (key==null){
            return 0;
        }
        return key.hashCode();
    }
    public int indexOf(int hash){//保证最后的index一定不会越界
        return hash &(table.length);//table.length扩容了
    }

HashMap的添加方法:

1)判断是否是第一次添加数据,对存储元素的数组进行初始化;
2)通过key来得到index,table[index]访问的是 存储链表 然后使用链表头插法插入数据value,插入之前必须判断是否需要扩容,如果扩容了,带插入数据的index需要重新计算
3)判断key是否为空,若为空则采取putForNullKey,对于key为null的情况下 特殊处理 会把key为空的数据存储在 index=0的位置
4)是否需要扩容?:从集合容量是否够用的角度出发,index的重复率越高,hash冲突越高,(如何降低hash冲突:在某个条件成立时对数组进行扩容,扩容之后重新计算每一个key-value结构存储的位置)所以需要扩容(扩容条件是:size >= table.length * threshold && (null != table[0]))

//添加方法
     public void put(K key,V value){//添加方法
        //第一次添加数据 对存储元素的数组进行初始化
        //如何判断是不是第一次添加数据
        if(table==EMPTY_TABLE){
            table=new Entry[DEFAULT_CAPCITY];
        }
        //将数据往数组中存储
        //通过Key如何得到index(引用转换成int类型)然后进行添加
        int hash=hash(key);//key==null hash=0
        int index=indexOf(hash);//0和任何一个数取余的结果都是0
        if (key==null){
            putForNullKey(hash,value);//专门处理key为空的添加
        }
        //key不为null的情况
        for (Entry<K,V>e=table[index];e!=null;e=e.next){
            //先比较hashCode,如果&&前面的不成立,不会走到后边,直接退出;只有hashCode相等时 这两个对象才有可能是同一个,所以要接着判断
            //如果说引用地址相同,即e.key==key,那么这两个对象一定是相同的--->为了提高效率
            if (e.hash==hash &&(e.key==key||e.key.equals(key))){
                //e.key.equals(key)
                //说明重复,如果一对象重写了equals方法后:比较的就一定是对象底层的数据
                e.value=value;//新值替换老值
                return;
            }
            //如果重写了一个对象的equals方法,一定要重写对象hashCode方法
            //已经遍历过了说明不重复
            if(size>=table.length*threshold&&(null!=table[index])){
                //扩容操作;2倍扩容;要重新计算每个数组的位置
            }
            Entry<K,V>head=table[index];
            Entry<K,V>newEntry=new Entry<>(hash,key,value,head);
            table[0]=newEntry;
            size++;
        }


        //table[index]访问的是 存储链表 然后使用链表头插法插入数据
        //对于key为null的情况下 特殊处理 会把key为空的数据存储在 index=0的位置
        //key重复的时候 用新的value会代替老的value 在map中不会出现重复的key 判断一次key是否相同
        //是否需要扩容 为什么?
    }
    private void putForNullKey(int hash,V value) {
        // table[0];
        //如果重复添加key=null的键值对

        //遍历单链表
        for (Entry<K, V> e = table[0]; e != null; e = e.next) {
            if (e.key == null) {//说明重复
                e.value = value;//新值替换老值
                return;
            }
        }
        //说明没有重复的元素:要真正开始添加元素了(需要将这个key value结构的数据添加到map中
        if (size >= table.length * threshold && (null != table[0])) {
            //扩容操作
        }
            Entry<K, V> head = table[0];
            Entry<K, V> newEntry = new Entry<>(hash,null, value, head);
            table[0] = newEntry;
            size++;
        }

HashMap的remove删除方法:

1)通过key计算出index;

//通过Key如何得到index(引用转换成int类型)然后进行添加
        int hash=hash(key);//key==null hash=0
        int index=indexOf(hash);//0和任何一个数取余的结果都是0

2)得到链表,找到key对应的节点;

for (Entry<K,V>e=table[index];e!=null;e=e.next){
            //先比较hashCode,如果&&前面的不成立,不会走到后边,直接退出;只有hashCode相等时 这两个对象才有可能是同一个,所以要接着判断
            //如果说引用地址相同,即e.key==key,那么这两个对象一定是相同的--->为了提高效率
            if (e.hash==hash &&(e.key==key||e.key.equals(key))){
                //e.key.equals(key)
                //说明重复,如果一对象重写了equals方法后:比较的就一定是对象底层的数据
                e.value=value;//新值替换老值
                return;
            }

3)单链表的删除

  • HashMap中replace方法
    思想与key重复时新值替换老值的思想是一样的
  • HashMap迭代器
 //HashMap迭代器 --->HashMapTest中第一个遍历方法
   class HashIterator implements Iterator<Entry<K,V>>{
    Entry<K,V> next;//保存的是下一个要被遍历的节点
    int index;//当前遍历到的节点所处的下标
    Entry<K,V> current;//保存当前节点
    int exceptedModCount;//在遍历时要抛出异常


    public HashIterator(){
        exceptedModCount=modcount;
        //将next指向下一个要遍历的节点
        if (size>0){
            Entry<K,V>[] t=table;
            while (index<t.length &&(next=t[index+1])==null)
            ;//index开始是0,t[0]=null,index++循环往后走;目的是找第一个不为空的index,直到index越界,或者是找到所需要找的节点
    }
    }

    @Override
    public boolean hasNext() {
        return next!=null;//只要不为空都能继续遍历
    }


    @Override
    public Entry<K, V> next() {
        if (modcount !=exceptedModCount){
            throw  new ConcurrentModificationException();
        }
        //返回当前要遍历的节点都并且让next再指向后一个节点
        Entry<K,V> e=next;
        //让next指向下一个节点
        if (next==null){
            //向后寻准下一个节点位置
            Entry<K,V>[] t=table;
            while (index<t.length &&(next=t[index+1])==null)
                ;
        }
        current=e;
        return current;
    }


    @Override
    public void remove() {//删除当前正在被遍历的节点
        K key=current.key;
        current=null;
        MyHashMap.this.remove(key);
    }
}

保证在进行迭代的时候不允许其他线程进来调用添加或者删除方法:
MyHashMap中 int modcount;
在添加或者删除操作时加上modcount++;
在迭代器中也要加上int exceptedModCount;
在遍历时也要抛出异常。

 public Entry<K, V> next() {
        if (modcount !=exceptedModCount){
            throw  new ConcurrentModificationException();
        }
        //...
        }
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值