Map接口的和HashMap

(1)Map接口

public interface Map {

/**
 * 获取元素的个数
 */
int size();
​
/**
 * 判断map是否为空
 */
boolean isEmpty();
​
/**
 * 判断是否包含键
 */
boolean containsKey(Object key);
​
/**
 * 判断是否包含值
 */
boolean containsValue(Object value);
​
/**
 * 根据key获取value
 */
V get(Object key);
​
/**
 * 存放键值对
 */
V put(K key, V value);
​
/**
 * 根据key移除键值对
 */
V remove(Object key);
​
/**
 * 在map中添加一个map
 */
void putAll(Map m);
​
/**
 * 清空Map
 */
void clear();
// Views
​
/**
 * 获取所有的键
 */
Set keySet();
​
/**
 * 获取所有的值
 */
Collection values();
​
/**
 * 获取所有的键值对
 */
Set> entrySet();
​
/**
 * 内部接口规定了Entry行为
 *
 * @see Map#entrySet()
 * @since 1.2
 */
interface Entry {
    /**
     * 获取键值对中的key
     */
    K getKey();
​
    /**
     * 获取键值对中的value
     */
    V getValue();
​
    /**
     * 修改键值对中的值
     */
    V setValue(V value);
​
    /**
     * 
     */
    boolean equals(Object o);
​
    /**
     *
     * @see #equals(Object)
     */
    int hashCode();
}

boolean equals(Object o);

int hashCode();

/**
 * 根据键值对移除元素
 * @since 1.8
 */
default boolean remove(Object key, Object value) {
    Object curValue = get(key);
    if (!Objects.equals(curValue, value) ||
        (curValue == null && !containsKey(key))) {
        return false;
    }
    remove(key);
    return true;
}
​
/**
 * 替换value
 * @since 1.8
 */
default boolean replace(K key, V oldValue, V newValue) {
    Object curValue = get(key);
    if (!Objects.equals(curValue, oldValue) ||
        (curValue == null && !containsKey(key))) {
        return false;
    }
    put(key, newValue);
    return true;
}
​
/**
 * 通过key替换值
 * @since 1.8
 */
default V replace(K key, V value) {
    V curValue;
    if (((curValue = get(key)) != null) || containsKey(key)) {
        curValue = put(key, value);
    }
    return curValue;
}

}

(2)HasMap源码

成员变量:
​
//hash表的初始化容量 16
static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16
​
//hash表最大容量 30
static final int MAXIMUM_CAPACITY = 1 << 30;
​
//负载因子 0.75f
static final float DEFAULT_LOAD_FACTOR = 0.75f;
​
//链表转红黑树阈值 8
static final int TREEIFY_THRESHOLD = 8;
​
//红黑树转链表的阈值 6
static final int UNTREEIFY_THRESHOLD = 6;
​
//树化的容量 64
static final int MIN_TREEIFY_CAPACITY = 64;
​
//hash表
transient Node[] table;
​
//HashMap中存放的单元
static class Node implements Map.Entry {
    //key的hashCode
    final int hash;
    final K key;
    V value;
    //保存下一个节点
    Node next;
​
    Node(int hash, K key, V value, Node 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;
    }
​
    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;
    }
}
​
构造方法:
​
调用无参构造方法创建HashMap对象时,只初始化负荷因子loadFactor的值为0.75,并不初始化哈希表的容量
​
public HashMap() {
    this.loadFactor = DEFAULT_LOAD_FACTOR; // all other fields defaulted
}
​
public HashMap(int initialCapacity, float loadFactor) {
    //判断初始容量是否小于0,如果小于0则抛出异常
    if (initialCapacity < 0)
        throw new IllegalArgumentException("Illegal initial capacity: " +
                                           initialCapacity);
    //如果初始容量大于最大容量,则Map容量就是最大容量
    if (initialCapacity > MAXIMUM_CAPACITY)
        initialCapacity = MAXIMUM_CAPACITY;
    //判断负载因子是否小于等于0,以及负载因子不是一个数
    if (loadFactor <= 0 || Float.isNaN(loadFactor))
        throw new IllegalArgumentException("Illegal load factor: " +
                                           loadFactor);
    this.loadFactor = loadFactor;
    this.threshold = tableSizeFor(initialCapacity);
}
​
HashMap常用API
​
put(K key,V value)  添加键值对
get(Object key) 根据键获取值
keySet()    获取keySet
entrySet()  获取entrySet
clear() 清空
containsKey(Object key) 判断是否存在key
remove(Object key)  根据key删除键值对
remover(Object key,Object value)    根据key和value删除键值对
size()  获取元素个数
isEmpty()   判断map是否为空

/**

  • Map集合常用Api */

public class MapDemo1 {

        public static void main(String[] args) {

                        //创建Map集合对象

                        Map map = new HashMap<>();

                        //向Map集合中添加键值对

                        map.put(1,"田福军");

                        //在这里进行了自动装箱

                        map.put(2,"孙玉厚");

                        map.put(3,"田福堂");

                        //通过key获取value

                        String value = map.get(1);

                        System.out.println(value);

                        //获取所有的value

                        Collection values =map.values();

                        for(String s: values){

                                 System.out.println("所有的value值"+s);

                        }

                        //获取键值对数量

                        System.out.println("键值对的数量:"+map.size());

                        //通过key删除

                        key-value map.remove(2);

                        System.out.println("键值对的数量:"+map.size());

                        //判断是否包含某个key

                        //contains 底层调用的是equals方法进行比较的,所以自定义的类型需要

                        重写equals方法 重写equals方法比较的是内容

                        System.out.println(map.containsKey(4));//false

                        //判断是否包含某个value

                        System.out.println(map.containsValue("田福军"));//true

                        //清空map map.clear();

                        System.out.println("键值对的数量:"+map.size());//0

                        //判断是否为空

                        System.out.println(map.isEmpty());//true

                   }

                }

  • 遍历方式:

/** *HashMap方式测试类 */ public class MapDemo { public static void main(String[] args) { HashMap map = new HashMap<>(); //添加键值对 map.put(1,"孙少平"); map.put(2,"孙少安"); map.put(3,"田晓霞"); map.put(4,"田润叶"); //获取指定key的值 System.out.println(map.get(2));

    //获取所有的key
    Set keys = map.keySet();
​
    //使用getkey遍历map集合
    for(Integer key:keys){
        System.out.println("key="+key+" value="+map.get(key));
    }
​
    //通过迭代器获取节点的键值对
    Iterator it = keys.iterator();
    while (it.hasNext()){
        Integer key = it.next();
        String value = map.get(key);
        System.out.println("key="+key+" value="+value);
    }
​
    //获取map集合的entry,其中包含key和value
    Set> set = map.entrySet();
​
    //使用foreach遍历Map集合 这种方式比较适合大数据量
    for(Map.Entry entry:set){
        System.out.println("key="+entry.getKey()+" value"+entry.getValue());
    }
​
    //使用迭代器遍历集合
    Iterator> entryIterator = set.iterator();
    while (entryIterator.hasNext()){
        Map.Entry Itentry = entryIterator.next();
        System.out.println("key="+Itentry.getKey()+" value="+Itentry.getValue());
    }
​
    //lamba表达式
    map.forEach((key,value)->System.out.println("key="+key+" value="+value));
}

}

(3)HashMap存放元素流程

HashMap添加元素会调用put(K key,V value)方法,从源码可以看出在put()方法内部调用了putVal()方法。

public V put(K key, V value) {

                //调用putVal方法

                return putVal(hash(key), key, value, false, true);

}

继续查看putVal()方法,这个方法即HashMap存放元素的核心流程。

final V putVal(int hash, K key, V value, boolean onlyIfAbsent, boolean evict) {

        Node[] tab; Node p; int n, i;

        //判断哈希表是否为空 if ((tab = table) == null || (n = tab.length) == 0)

        //扩容 n = (tab = resize()).length;

        if ((p = tab[i = (n - 1) & hash]) == null)

        //判断tab[i]是否有元素 //如果没有元素则直接存放

        tab[i] = newNode(hash, key, value, null);

        else {

                 Node e; K k;

                //判断和已有节点的key是否相等

                if (p.hash == hash && ((k = p.key) == key || (key != null && key.equals(k))))

                //如果key相等则替换 e = p; //如果key不相等,判断哈希表已有的节点是不是红黑树 else                         if (p instanceof TreeNode)

                                //如果是红黑树则以树的方式插入元素

                                e = ((TreeNode)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);

                                                               //如果链表长度大于等于7,则进制红黑树转换相关工作

                                                        if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st                                                                  treeifyBin(tab, hash); break;

                                        }

                                        //如果key相等则在链表中替换

                                        if (e.hash == hash && ((k = e.key) == key || (key != null &&                                         key.equals(k)))) break;

            }
        }
        if (e != null) { // existing mapping for key
            V oldValue = e.value;
            if (!onlyIfAbsent || oldValue == null)
                e.value = value;
            afterNodeAccess(e);
            return oldValue;
        }
    }
    ++modCount;
    //如果元素个数大于临界值则扩容
    if (++size > threshold)
        resize();
    afterNodeInsertion(evict);
    return null;

}

过程描述:

HashMap添加元素时调用put(Key k,Value v),put方法调用putVal()方法,该方法是存放元素的核心方法
判断hash表是否为空,若为空,则对哈希表进行扩容。若不为空,则通key值求出新添加的元素在哈希表中的索引位置
判断哈希表索引位置处是否有元素,若没有元素,则直接将节点元素添加到索引位置,添加后要判断hash表容量是否达到扩容阈值,达到要扩容
若对应位置有元素,那么判断key值是否相等,相等直接赋值
若对应位置有元素,并且key值不相等,则判断该位置处是否为红黑树,若为红黑树,则按照红黑树的添加步骤进行添加。
若对应位置key值不相等,并且不是红黑树的结构,则开始遍历链表准备添加操作,如果添加元素与链上的元素的键值相等,则覆盖对应的节点。
若链表中不存在键值相等的节点,则将新添加的节点添加到链表的末尾。
如果链表长度大于8,则判断哈希表的容量是否大于64,若大于,则要将链表转换为红黑树,并以红黑树的方式插入元素。
添加完元素后,要判断哈希表长度是否大于扩容阈值,大于阈值时要进行扩容。

哈希冲突:两个不相等的元素在hash表中的位置相同,解决哈希冲突的方法:1、拉链法    2、开放寻址法
HashMap中使用拉链发解决哈希冲突。
​
注意:
​
HashMap扩容时会存在以下几种情况:
1、调用无参构造方法时,首先初始化的底层哈希表的容量为0,在调用put方法添加元素时,哈希表的容量会初始化为10
2、调用有参构造指定了容量和负载因子时,会根据指定的正整数找到不小于指定容量的2的次幂,将这个数赋值给扩容阈值,当第一次put()方法时,会将阈值赋值给容量,并计算新的阈值,新的阈值等于容量乘以负载因子
3、如果不是第一次扩容,则容量变为原容量的2倍。
​
Java7和Java8中HashMap的区别
Java7中HashMap数据结构采用了数组+链表+红黑树,当链表长度大于8并且容量大于64时,会将链表转换为红黑树(注意,如果此时链表长度已经是8,但是数组长度并没有到64时会先进行扩容)。当红黑树节点个数小于等于6时会转换为链表。
Java7中链表使用的是头插法,头插法在多线程环境下有概率出现链表死循环的问题
在Java8中链表采用尾插法以及红黑树,避免了出现链表死循环的问题。

你可能感兴趣的:(java,哈希算法,开发语言)