hashmap初探

HashMap是一种基于键值对存储的数据结构,具有插入无序、键不可重复等特点。它使用数组+链表的方式解决冲突,内部通过Entry或Node类存储元素。默认容量为16,负载因子为0.75,当元素数量达到扩容阈值时,进行二倍扩容。添加、获取和删除元素时,HashMap会根据键的hashCode重新计算下标,处理冲突。在遍历HashMap时,可通过entrySet、KeySet和Values方法。HashMap在JDK1.8中引入了红黑树优化。要实现线程安全,可使用Collections工具类。
摘要由CSDN通过智能技术生成

1、特点
(1)、插入无序
(2)、 以键值对<k,v>的形式储存
(3)、键不能重复,如果重复,新的值会覆盖旧的值;
如果是自定义元素,必须重写hashcode方法和equals方法
(4)、键可以为有一个为null,值可以多个为null;
特定:键为null的元素放在数组0号位置;
(5)、底层数组的容量为2的指数级
2、 数据结构:
数组+链表
核心就是使用了数组的存储方式,然后将冲突的key的对象放入链表中,一旦发现冲突就在链表中做进一步的对比。
3、底层源码分析
(1)构造函数:

  //指定大小和加载因子
        public HashMap(int initialCapacity, float loadFactor)    
        public HashMap(int initialCapacity) {
        public HashMap() 
        public HashMap(Map<? extends K, ? extends V> m) 

(2)基本属性:

 Node<K,V>[] table;      //hashMap底层是数组实现   
 Set<Map.Entry<K,V>> entrySet;
 int size;               //元素的个数
 static final int DEFAULT_INITIAL_CAPACITY = 16;   //默认容量
 float DEFAULT_LOAD_FACTOR = 0.75f;  //负载因子
 int  MAXIMUM_CAPACITY = 1 << 30;  //哈希表中数组容量的最大值
 int TREEIFY_THRESHOLD = 8;   //  扩容阈(yu 2)值  ,
     //如果capacity*loadfactor >= threshld, 引起扩容(resize);
 

内部类:Entry<K,V> -------------------------------------《1.7中是Entry,1.8中是Node》

final K key;
V value;
Entry<K,V> next;
final int hash;    //key的hash值

(3)默认值: 默认初始容量16,默认负载因子0.75;
(4)扩容方式: 二倍扩容(仍满足2的指数级关系)
(1) 扩容临界: 当前size >= 扩容阈值(当前容量 * 负载因子)
(2)然后重新计算已存入map的元素的hash值并且重新存放;

  /*<jdk:1.7>
   *   会在元素个数超过阈值时(当前容量*负载因子)二倍扩容数组,然后重新计算已
   *   存入map的元素的hash值并且使之均匀存储;  
  */
  Entry<K,V>[]  resize()   {
        Entry[] oldTable = table;
        int oldCapacity = oldTable.length;
        if (oldCapacity == MAXIMUM_CAPACITY) {
            threshold = Integer.MAX_VALUE;
            return;
        }
        Entry[] newTable = new Entry[newCapacity];
        transfer(newTable);     //重新计算已存入元素的hash值,重新存储,
        table = newTable;
        threshold = (int)(newCapacity * loadFactor);
    }

(5)增删改查方法:
A、添加元素:***********************************
首先判断数组是否是null?是则初始化数组,则下一步
然后判断是不是key==null ?是则放置到数组下标为0的位置;否则下一步
利用key的hashCode重新hash计算出当前对象的元素在数组中的下标
存储时,此时有两种情况(用equals比较)。
(1)如果key相同,则覆盖原始值;
(2)如果key不同(出现冲突),则将当前的key-value放入链表头部;
如果数组当前下标为空,直接新建Entry放在此处;

null没有哈希值;但是map中可以储存null;

<jdk:1.7>
//put方法源码分析:
public V put(K key, V value) {
     //第一次插入元素,需要对数组进行初始化:注意:数组大小为2的指数关系
    if (table == EMPTY_TABLE) {
        inflateTable(threshold);
    }
    // 当key为null时,调用putForNullKey方法,将value放置在数组第一个位置。
    if (key == null)
        return putForNullKey(value); 
    //key不为null 
    //通过key来哈希找到该数据所存在的索引位置:key相关的哈希值   
    int hash = hash(key); //根据key的keyCode重新计算hash值。
    int i = indexFor(hash, table.length); //找到位置索引
    //遍历该位置i下面的链表:(判断key是否存在,是则替换旧value)
    for (Entry<K,V> e = table[i]; e != null; e = e.next) {
        Object k;
        if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
            // 如果发现已有该键值,则存储新的值,并返回原始值
            V oldValue = e.value;
            e.value = value;
            e.recordAccess(this);
            return oldValue;
        }
    }
    // 如果i索引处的Entry为null,表明此处还没有Entry。 
    modCount++;
    // 将key、value添加到i索引处。
    addEntry(hash, key, value, i);
    return null;
} 

B、获取元素:*****************************
先判断 key == null? 是则调用getForNullKey()在数组0号下标查找
获取时,直接找到hash值对应的下标,再遍历链表判断key是否相同,从而找到对应值。

public V get(Object key) {
        if (key == null)
            return getForNullKey();
        int hash = hash(key.hashCode());
        for (Entry<K,V> e = table[indexFor(hash, table.length)];
             e != null;
             e = e.next) {
            Object k;
            if (e.hash == hash && ((k = e.key) == key || key.equals(k)))
                return e.value;
        }
        return null;
    }

C、删除元素:******************************
首先检查k == null ? 是则使其 hash值 为0;
获取其hash值,在数组指定下标处开始遍历(key==nulltable[0]处遍历查找);
遍历判断 入参 key equals (entry.key)? 是则返回此元素,然后返回其value。

public V remove(Object key) {
        Entry<K,V> e = removeEntryForKey(key);
        return (e == null ? null : e.value);
    }
final Entry<K,V> removeEntryForKey(Object key) {
        int hash = (key == null) ? 0 : hash(key.hashCode());
        int i = indexFor(hash, table.length);
        Entry<K,V> prev = table[i];
        Entry<K,V> e = prev;
        while (e != null) {
            Entry<K,V> next = e.next;
            Object k;
            if (e.hash == hash &&
                ((k = e.key) == key || (key != null && key.equals(k)))) {
                modCount++;
                size--;
                if (prev == e)
                    table[i] = next;
                else
                    prev.next = next;
                e.recordRemoval(this);
                return e;
            }
            prev = e;
            e = next;
        }

        return e;
    }

(6)继承关系

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

4、方法探究
V put(K key,V val)添加键值对;返回的类型是值的类型的数据(还不知道返回的是什么)
V replace(K key, V val);替换key所对应的值为val,并且返回那个被替换的值;
V remove(K key);删除入参键对应的映射,(如果存在)
boolean remove(K key, V val); 如果map中有此映射而且值不为null时,删除并返回true;
boolean replace (K key, V oldVal , V newVal ); 只有当这对键值对存在时,才能替换旧的值;

5、遍历map
1、遍历键值对 entrySet();返回的是一个键值对的 set

<jdk:1.8>
Iterator <Map.Entry<Integer,String >>iter = map.entrySet().iterator();
while (iter.hasNext()) {
    Map.Entry<Integer,String> e = iter.next();
    Integer k = e.getKey();
    String v = e.getValue();
    System.out.println(k+"-->"+v);
}

2、遍历键, KeySet();返回的是一个键的 set 集合

Iterator<Integer> iter2 = map.keySet().iterator();
while (iter2.hasNext()) {
   Integer k = iter2.next();
    System.out.println(k);
}

3、遍历值,Values();返回的是一个value的Collection对象;

Iterator <String> iter3 = map.Values().iterator();
while(iter3.hasNext())) {
    String str = iter3.next();
    System.out.println(str);
}

6、思考:
(1)如何使HashMap实现线程安全:
使用集合工具类Collections使hashMap边的安全,同理,也可以让List、Set等具有线程安全性

Map m = Collections.synchronizedMap(new LinkedHashMap(...)); 

(2)hashMap在jdk1.7和jdk1.8中的区别:
jdk1.8中
数组+链表+红黑树
(3)究竟是如何将map中元素的key放到set中去的:
首先使用KeySet()方法返回的只是一个set引用,

    public Set<K> keySet() {
        Set<K> ks = keySet;
        if (ks == null) {
            ks = new KeySet();
            keySet = ks;
        }
        return ks;
    }

然后使用iteator方法得到一个迭代器,
public final Iterator<K> iterator() { return new KeyIterator(); }
但是返回的这个迭代器是KeyIteator,此类继承的是hashIteator抽象类,里面包括hasnext,remove和一个构造器,但是重写了Next(方法,返回的nextNode.key;

final class KeyIterator extends HashIterator
        implements Iterator<K> {
        public final K next() { return nextNode().key;}
    }

然后我们在迭代过程中,调用此next方法,就不返回一个node的key,所以keySet并不是把map里面的值存到set中去了,而是在我们调用next方法的时候,把元素的key返回给了我们而已,同理可得,entrySet()方法和Values()方法,都是使用了同样的道理,在我们调用set的其他方法的时候,不是从set里面取出来返回的,而是直接指向了map里面的元素的值或者调用了返回调用map里面方法的结果而已。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值