Map集合
1、概念
集是一个集合,它可以快速地寻找现有的元素,但是要查看元素,就需要查看的元素的精确副本。这不是一种非常通用的查找方式。通常,我们知道某些键的信息,并想要查找与之相对应的元素。映射表(map)数据结构就是为此设计的。映射表用来存放键值对。如果提供了键就能查找到相应的值。例如,有一张关于员工信息的记录表,键为员工ID,值为Employee对象。
Java 类库为映射表提供了两种通用的实现:HashMap和TreeMap。这两个类都实现了Map接口。
public class App {
public static void main(String[] args) {
Map<String,String> Student = new HashMap<>();
// MyMap<String,String> Student = new MyHashMap<String,String>();
Student.put("19041211910","张三");
Student.put("19041211911","李四");
Student.put("19041211912","王麻子");
Student.put("19041211913","牛二");
Student.put("19041211914","马五");
// Student.put("19041211932","牛魔王");
System.out.println(Student.get("19041211910"));
System.out.println(Student.get("19041211911"));
}
}
数组:采用连续的存储单元来存放数据,对于指定的下标查找,时间复杂度为O(1),平均复杂度为O(n)。
链表:链表的新增,删除等操作时间复杂度为O(1),查找为O(n)
2、编写自己的Map(映射集合)
/**
* 需求分析:一些方法
* 1、V put(K key, V value);
* 2、V getVlaue(K key);
*
* 内部接口:
* interface Entry<K,V>
*/
public interface MyMap<K,V> {
V put(K key, V value);
V getValue(K key);
int size();
interface Entry<K,V>{
public K getKey();
public V getValue();
}
}
class MyHashMap<K,V> {
public static int DEFAULTLEN=16;//默认数组容量大小
public int put(K key,V value){
int index = key.hashCode();
System.out.println(value+":"+index+" :"+hash(index));
return index;
}
public int hash(int hc){
int hash=hc%(DEFAULTLEN-1);
return hash;
}
}
3、使自己的Map集合实现真正的功能
/**
* @param <K>
* @param <V>
* @Author JosephLee
* 数据结构:数组+链表
* 数组:里面存放的是Entry对象
* 方法:
* int size()
* V get(K key)
* V put(K key, V value)
* int hash(Object key)
*
* 内部类中的方法
* K getKey()
* void put(K key,V value)
*/
public class MyHashMap<K,V> implements MyMap<K,V> {
Node<K,V> node = new Node<K,V>();
public static int DEFAULTLEN=16; //默认数组容量大小
private Node<K,V>[] table=null;
int size=0;
public MyHashMap(int length){
DEFAULTLEN=length;
table = new Node[DEFAULTLEN];
}
public MyHashMap(){
this(DEFAULTLEN);
}
@Override
public V put(K key, V value) {
int index = hash(key);
Node<K,V>[] tab;
int n;
if((tab=table)==null||tab[index]==null){
table[index]=addEntry(index,key,value,null);
size++;
}
else{
Node<K,V> p = table[index];
table[index]=addEntry(index,key,value,p);
}
return table[index].value;
}
public Node addEntry(int h,Object k, Object v, Node next){
return new Node(h,k,v,next);
}
@Override
public V getValue(K key){
return getNode(key);
}
private V getNode(K key){
if(size==0){
return null;
}
int index = hash(key);
if(table[index]==null){
return null;
}
Node<K,V> p;
for(p=table[index]; p!=null;p=p.next){
if(p.getKey().hashCode()==key.hashCode()){
return p.value;
}
}
return null;
}
@Override
public int size() {
return size;
}
public int hash(K key){
int index= key.hashCode()%(DEFAULTLEN-1);
return Math.abs(index);
}
class Node<K,V> implements MyMap.Entry<K,V>{
private Node<K,V> next;
private int hash;
private K key;
private V value;
public Node(int hash, K key, V value, Node<K,V> next) {
this.hash = hash;
this.key = key;
this.value = value;
this.next = next;
}
public Node(){
}
@Override
public K getKey() {
return key;
}
@Override
public V getValue() {
return value;
}
}
}
3、HashMap的一些其他特性
1)扩容机制
首先我们需要了解一下,构造 hash 表时,如果不指明初始大小,默认大小为 16(即 Node 数组大小 16 ),如果 Node[]数组中的元素达到(填充比* Node.length )重新调整 HashMap 大小 变为原来 2 倍大小,需要注意的是,扩容很耗时。
其中默认的填充比为 0.75
2)底层数据构:
HashMap 是数组+链表+红黑树(JDK1.8 增加了红黑树部分)实现的,当链表长度超过阈值(8)时,将链表转换为红黑树,这样大大减少了查找时间。简单来讲:
首先有一个每个元素都是链表(可能表述不准确)的数组,当添加一个元素(key-value)时,就首先计算元素 key 的 hash 值,以此确定插入数组中的位置,但是可能存在同一 hash 值的元素已经被放在数组同一位置了,这时就添加到同一 hash 值的元素的后面(俗称 hash 冲突,链表结构出现的实际意义也就是为了解决 hash 冲突的问题),他们在数组的同一位置,但是形成了链表,同一各链表上的 Hash 值是相同的,所以说数组存放的是链表。而当链表长度太长时,链表就转换为红黑树,这样大大提高了查找的效率。
4、HashTable
和HashMap的区别:
HashMap是非线程安全的,只是用于单线程环境下,多线程环境下可以采用concurrent并发包下的concurrentHashMap。
HashMap 实现了Serializable接口,因此它支持序列化,实现了Cloneable接口,能被克隆。
HashMap中key和value都允许为null。key为null的键值对永远都放在以table[0]为头结点的链表中。
HashMap把Hashtable的contains方法去掉了
Hashtable也是JDK1.0引入的类,是线程安全的,能用于多线程环境中。
Hashtable同样实现了Serializable接口,它支持序列化,实现了Cloneable接口,能被克隆。
Hashtable中,key和value都不允许出现null值。但是如果在Hashtable中有类似put(null,null)的操作,编译同样可以通过,因为key和value都是Object类型,但运行时会抛出NullPointerException异常,这是JDK的规范规定的。
5、TreeMap的一些特性
TreeMap能够把它保存的记录根据键排序,默认是按升序排序,也可以指定排序的比较器,当用Iterator 遍历TreeMap时,得到的记录是排过序的。
import java.util.*;
public class App {
public static void main(String[] args) {
Map<String,String> Student = new HashMap<>();
// MyMap<String,String> Student = new MyHashMap<String,String>();
Student.put("19041211910","张三");
Student.put("19041211911","李四");
Student.put("19041211912","王麻子");
Iterator ite = Student.keySet().iterator();
while(ite.hasNext()){
Object key=ite.next();
System.out.println("HashMap.get(key) is: "+key);
}
System.out.println("————————————————————————————————————");
Map<String,String> Student2 = new Hashtable<>();
Student2.put("19041211910","张三");
Student2.put("19041211911","李四");
Student2.put("19041211912","王麻子");
Iterator ite2 = Student2.keySet().iterator();
while(ite2.hasNext()){
Object key=ite2.next();
System.out.println("HasTable.get(key) is: "+key);
}
System.out.println("————————————————————————————————————");
Map<String,String> Student3 = new TreeMap<>();
Student3.put("19041211910","张三");
Student3.put("19041211911","李四");
Student3.put("19041211912","王麻子");
Iterator ite3 = Student3.keySet().iterator();
while(ite3.hasNext()){
Object key=ite3.next();
System.out.println("TreeMap.get(key) is: "+key);
}
}
}
Student3.put("19041211910","张三");
Student3.put("19041211911","李四");
Student3.put("19041211912","王麻子");
Iterator ite3 = Student3.keySet().iterator();
while(ite3.hasNext()){
Object key=ite3.next();
System.out.println("TreeMap.get(key) is: "+key);
}
}
}