在开发里面:Collection集合保存数据的目的是为了输出,Map集合保存数据的目的是为了进行key的查找。
1、Map接口简介
Map接口是进行二元偶对象保存的最大父接口,该接口定义如下:public interface Map<K, V>
在进行接口对象实例化的时候需要设计Key与Value的类型。3个重点操作方法:
- 向集合之中保存数据:
public V put(K key, V value)
- 根据key查询数据:
public V get(Object key)
- 将Map集合转为Set集合:
public Set<Map.Entry<K,V> entrySet()
- 查询指定的key是否存在:
public boolean containsKey(Object key)
- 将Map集合中的key转为Set集合:
public Set<K> keySet()
- 根据key删除掉指定数据:
public V remove(Object key)
【范例】Map使用
package demo;
import java.util.Map;
public class JavaAPIDemo {
public static void main(String[] args) throws Exception {
Map<String, Integer> map = Map.of("a",1,"b",2,"c",12);
System.out.println(map);
}
}
{a=1, b=2, c=12}
在Map集合之中数据的保存就是按照“key = value”的形式存储的,并且使用of()方法里面的数据key值是不能重复的。常用的子类:HashMap、Hashtable、TreeMap、LinkedHashMap。
HashMap子类
该类的主要特点是无序存储。定义:public class HashMap<K,V> extends AbstractMap<K,V> implements Map<K,V>, Cloneable,Serializable
;
【范例】Map集合的使用
package demo;
import java.util.HashMap;
import java.util.Map;
public class JavaAPIDemo {
public static void main(String[] args) throws Exception {
Map<String, Integer> map = new HashMap<String, Integer>();
map.put("one",1);
map.put("two",2);
map.put("one",112);//key重复
map.put(null,1);
map.put("zeor",null);
System.out.println(map.get("zero"));
System.out.println(map.get("one"));//key存在
System.out.println(map.get("null"));//key不存在
System.out.println(map.get(null));//key存在
System.out.println(map.get("ten"));//key不存在
}
}
null
112
null
1
null
通过HashMap实例化的Map接口里面可以通过key或value保存null的数据,即便保存数据的key重复,也不会出现错误,而是出现内容的替换。
但是对于Map接口中提供的put()方法本身是提供有返回值的,那么这个返回值指的是在重复key的情况下返回旧的value。
【范例】put()方法使用
package demo;
import java.util.HashMap;
import java.util.Map;
public class JavaAPIDemo {
public static void main(String[] args) throws Exception {
Map<String, Integer> map = new HashMap<String, Integer>();
System.out.println(map.put("one", 1));//key不重复,返回value
System.out.println(map.put("one", 121));//key重复,返回旧数据
System.out.println(map);
}
}
在设置了相同key的内容的时候put()方法会返回原始的数据内容。
在进行HashMap的put()操作的时候,如何实现容量扩充的?
- 在HashMap类里面提供有一个“DEFAULT_INITIAL_CAPACITY”常量,作为初始化的容量配置,而后这个常量的默认大小为16个元素,也就是默认的保存大小为16;
- 当保存的内容容量超过了某个阈值(DEFAULT_LOAD_FACTOR = 0.75f),相当于“容量*阈值 = 12”保存12个元素的时候就会进行容量的扩充;
- 在进行扩充的时候HashMap采用的是成倍的扩充模式,即:每一次都扩充2倍的容量。
请解释HashMap的工作原理:
- 在HashMap中进行数据存储的依然是利用了Node类完成的,那么这种情况下证明可以使用的数据结构只有两种:链表(时间复杂度O(n))、二叉树(时间复杂度O(logn));
- 从JDK1.8开始,HashMap发生了改变,因为其要适用于大数据时代的海量数据问题,所以对其存储发生了变换,并且在HashMap类的内部提供了一个重要的常量:
static final int TREEIFY_THRESHOLD = 8
;在使用HashMap的时候,如果没有超过阈值8(TREEIFY_THRESHOLD ),那么会按照链表的形式进行存储,而如果超过了这个阈值,则会将链表转为红黑树以实现树的平衡,并且利用左旋与右旋保存数据的查询性能。
LinkedHashMap
HashMap虽然是Map集合最为常用的一个子类,但是其本身所保存的数据都是无序的(有序与否对Map没有影响),如果现在希望Map集合之中保存的数据为其增加顺序,就可以更换子类为LinkedHashMap(基于链表实现的)。.
package demo;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;
public class JavaAPIDemo {
public static void main(String[] args) throws Exception {
Map<String, Integer> map = new LinkedHashMap<String, Integer>();
map.put("w", 12);
map.put("s", 2);
map.put("d", 32);
map.put("e", 42);
map.put("c", 52);
System.out.println(map);
}
}
{w=12, s=2, d=32, e=42, c=52}
Hashtable子类
与Vector、Enumeration属于最早的一批动态数组的实现类。定义如下:public class Hashtable<K,V> extends DIctionary<K,V> implements Map<K,V>,Cloneable, Serializable
。
package demo;
import java.util.Hashtable;
import java.util.Map;
public class JavaAPIDemo {
public static void main(String[] args) throws Exception {
Map<String, Integer> map = new Hashtable<String, Integer>();
map.put("w", 12);
map.put("s", 2);
map.put("d", 32);
map.put("e", 42);
map.put("c", 52);
System.out.println(map);
}
}
{w=12, s=2, e=42, d=32, c=52}
Hashtable里面进行存储的时候设置的key或value都不允许为null,否则报错NullPointerException。
HashMap与Hashtable的区别?
- HashMap中的方法都属于异步操作(非线程安全),HashMap允许保存有null数据;
- Hashtable中的方法都属于同步方法(线程安全),Hashtable不允许保存null,否则会出现NullPointerException。
Map.Entry接口
static class Node<K,V> implements Map.Entry<K,V>{}
所有的key和value的数据都被封装在Map.Entry接口之中,而此接口定义如下:
public static interface Map.Entry<K,V>
并且在这个内部接口里面提供有两个重要的操作方法:
- 获取key:
public K getKey()
; - 获取value:
public V getValue()
。
package demo;
import java.util.Map;
public class JavaAPIDemo {
public static void main(String[] args) throws Exception {
Map.Entry<String , Integer> entry = Map.entry("one", 2);
System.out.println("获取Key:" + entry.getKey());
System.out.println("获取value:" + entry.getValue());
System.out.println(entry.getClass().getName());
}
}
获取Key:one
获取value:2
java.util.KeyValueHolder
整个Map集合里面,Map.Entry的主要作用就说作为一个key和一个value的包装类型使用,大部分情况下进行数据存储的时候都会将key和value包装为一个Map.Entry对象进行使用。
利用Iterator输出Map集合
使用Iterator实现Map集合的输出须按照如下不走处理:
- 利用Map接口中提供的entrySet()方法将Map集合转为Set集合;
- 利用Set接口中的iterator()方法将Set集合转为Iterator接口实例;
- 利用Iterator进行迭代输出获取每一组的Map.Entry对象,随后通过getKey()与getValue()获取数据
【范例】利用Iterator输出Map集合
package demo;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
public class JavaAPIDemo {
public static void main(String[] args) throws Exception {
Map<String , Integer> map= new HashMap<String, Integer>();
map.put("one", 1);
map.put("two", 2);
Set<Map.Entry<String,Integer>> set = map.entrySet();
Iterator<Map.Entry<String,Integer>> iter = set.iterator();
while(iter.hasNext()) {
Map.Entry<String, Integer> me = iter.next();
System.out.println(me.getKey()+ "=" + me.getValue());
}
}
}
虽然Map集合本身支持有迭代输出的支持,但是如果从实际的开发来讲,Map集合最主要的用法在于实现数据key的查找操作,如果现在不使用Iterator而使用foreach语法输出也需要将Map转为Set集合
【范例】使用foreach输出
package demo;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
public class JavaAPIDemo {
public static void main(String[] args) throws Exception {
Map<String , Integer> map= new HashMap<String, Integer>();
map.put("one", 1);
map.put("two", 2);
Set<Map.Entry<String,Integer>> set = map.entrySet();//将Map集合变为Set集合
for(Map.Entry<String, Integer> entry:set) {
System.out.println(entry.getKey() + "=" + entry.getValue());
}
}
}
关于KEY的定义
可以使用自定义的类来进行Key类型的设置。
package demo;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
public class JavaAPIDemo {
public static void main(String[] args) throws Exception {
Map<Person, String> map= new HashMap<Person,String>();
Person p3 = new Person("张三", 12);
map.put(p3,"duck");
map.put(new Person("李四", 22),"dog");
System.out.println(map.get(p3));
System.out.println(map.get(new Person("李四", 22)));
}
}
class Person{
private String name;
private int age;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
@Override
public String toString() {
// TODO Auto-generated method stub
return "name = " + this.name +"|age = " + this.age;
}
}
虽然允许使用自定义的类作为key的类型,但是在实际开发之中常用的类型就三种:String、Long、Integer。尽量使用系统类。
如果在进行HashMap进行数据操作的时候出现了Hash冲突(Hash码相同),HashMap是如何解决的?
当出现了Hash冲突之后,为了保证程序的正常执行,会在冲突的位置上将所有Hash冲突的内容转为链表保存。
参考地址:https://edu.aliyun.com/lesson_1012_9091#_9091