(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 extends K, ? extends V> m); /** * 清空Map */ void clear();
// Views /** * 获取所有的键 */ SetkeySet(); /** * 获取所有的值 */ 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集合中添加键值对
map.put(1,"田福军");
//在这里进行了自动装箱
map.put(2,"孙玉厚");
map.put(3,"田福堂");
//通过key获取value
String value = map.get(1);
System.out.println(value);
//获取所有的value
Collection
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
//获取所有的key Setkeys = 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
//判断哈希表是否为空 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
//判断和已有节点的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
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中链表采用尾插法以及红黑树,避免了出现链表死循环的问题。