Java基础-HashMap集合

Map接口的常用方法

Map接口中常用的方法
1.Map和Collection没有继承关系
2.Map集合以key和value的方式存储数据:键值对
  key和value都是引用类型
  key和value都是存储对象的地址。
  key起到主导的地位,value是key的一个附属品。

Map接口常用的方法
V put(k key,v value); 向Map集合中添加键值对
V get(Object key) 通过key获取value
void clear(); 清空Map集合
boolean containsKey(Object key) 判断Map中是否包含某个key
boolean containsValue(Object value) 判断Map中是否包含某个value
boolean isEmpty() 判断Map集合中元素个数是否为0
Set keySet() 获取Map集合中所有的key(所有键是一个set集合)
v remove(Object key) 通过key来删除键值对
int size() 获取Map集合中键值对的个数
Collection values() 获取Map集合中所有的value,返回一个collection集合
set<Map,Entry<K,V>> entrySet() 将Map集合转换成set集合

对于最后一个方法的解释:
假设现在有一个Map集合,如下所示

map1集合

key value
1 zhangsan
2 lisi
3 wangwu
4 zhaoliu

Set set=map1.entrySet();
set集合对象

1=zhangsan
2=lisi
3=wangwu
4=zhaoliu

注意:Map集合通过entrySet()方法转换成的这个set集合,set集合中元素的类型是Map.Entry<K,V>
Map.Entry和String一样,都是一种类型的名字,只不过Map.entry是静态内部类,是Map中的

静态内部类的理解

public class MyClass {
    
    
    //声明一个静态内部类
    private static class InnerClass{
    
    
        //静态方法
        public static void m1(){
    
    
            System.out.println("静态内部类的m1方法执行");
        }
        //实例方法
        public void m2(){
    
    
            System.out.println("静态内部类的m2方法执行");
        }
    }
    public static void main(String[] args) {
    
    
        //类名叫做MyClass.InnerClass
        MyClass.InnerClass.m1();
        MyClass.InnerClass mi=new MyClass.InnerClass();
        mi.m2();

        //给一个Set集合
        //该set集合中存储的对象是MyClass.InnerClass类型
        Set<MyClass.InnerClass> =new HashSet<>();
        //该set集合中存储的对象是String类型
        Set<String> set2=new HashSet<>();
    }
public class MapTest01 {
    
    
    public static void main(String[] args) {
    
    
        //创建Map集合对象
        Map<Integer,String> map=new HashMap<>();
        //向Map集合中添加键值对
        map.put(1,"zhangsan");
        map.put(1,"lisi");
        map.put(1,"wangwu");
        map.put(1,"wangliu");
        //通过key获取value
        String value=map.get(2);
        System.out.println(value);
        //获取键值对的数量
        System.out.println("键值对的数量"+map.size());
        //通过key删除key-value
        map.remove(2);
        System.out.println("删除后键值对的数量"+map.size());
        //contains方法底层调用的都是equals方法,所以自定义类型需要重写equals方法
        //判断是否包含某个key
        System.out.println(map.containsKey(4));
        //判断是否包含某个value
        System.out.println(map.containsValue("wangwu"));
        //获取所有的value
        Collection<String> values=map.values();
        for(String s:values){
    
    
            System.out.println(s);
        }
        //清空map集合
        map.clear();
        //判断是否为空
        System.out.println(map.isEmpty()); //true
    }
}

2.Map集合的遍历

第一种方式:获取所有的key,通过遍历key,来遍历value

public class MapTest02 {
    
    
        public static void main(String[] args) {
    
    
            //第一种方式:获取所有的key,通过遍历key,来遍历value
            Map<Integer,String> map=new HashMap<>();
            map.put(1,"zhangsan");
            map.put(2,"lisi");
            map.put(3,"wangwu");
            map.put(4,"wangliu");
            map.put(1,"zhouqi");
            //遍历map集合
            //获取所有的key,所有的key其实是一个set集合
            Set<Integer> keys=map.keySet();
            //遍历key,通过key获取value
            //迭代器可以
//    Iterator<Integer> it=keys.iterator();
//    while(it.hasNext()){
    
    
//       //取出其中的一个key
//       Integer key=it.next();
//       //通过ke'y获取value
//       String value=map.get(key);
//       System.out.println(key+"="+value);
//    }
            //foreach也可以
            for(Integer key:keys){
    
    
                System.out.println(key+"="+map.get(key));
            }
        }
    }

输出
1=zhouqi
2=lisi
3=wangwu
4=wangliu

第二种方式:Set<Map,Entry<K,V>> entrySet();
以上这个方法是把Map集合直接全部转化为set集合
//set集合中的元素类型是Map.Entry
//Map.Entry有Integer key属性和String value属性

Set<Map.Entry<Integer, String>> set=map.entrySet();
  //遍历set集合,每一次取出一个node
  //迭代器
  Iterator <Map.Entry<Integer, String>> it2=set.iterator();
while(it2.hasNext()){
    
    
      Map.Entry<Integer, String> node=it2.next();
      Integer key=node.getKey();
      String value=node.getValue();
      System.out.println(key+"="+value);
  }

如果用foreach怎么做呢?

3.哈希表数据结构

数组和单向链表的结合体。

1.hashMap集合底层是哈希表/散列表的数据结构

2.HashMap是一个怎样的数据结构呢?

哈希表是一个数组和单向链表的集合体
数组:在查询方面效率很高,随机增删方面效率很低
单向链表:在随机增删方面效率较高,在查询方面效率较低。
哈希表将以上的两种数据结构融合在一起,充分发挥他们各自的长处。
类似于

Node[] nodes;
 	class Node{
    
    
 		Object k;
 		Object v;
 		Node next;
 	}

3.HashMap集合底层的源代码

 public class HashMap{
    
    
 		//HashMap底层实际上就是一个一维数组
 		Node<K,V> table;
 		//静态的内存类HashMap.Node
 		static class Node<K,V>{
    
    
 			final int hash;		//哈希值(哈希值是key的hashCode()方法的执行结果。hash值经过哈希函数/算法可以转化为数组的下标)
 			final K key;	//存储到Map集合的key
 			V value;		//存储到Map集合中的哪个value
 			Node<K,V> next;		//下一个节点的内存地址
 		}
}

哈希表/散列表:一维数组,这个数组中每一个元素是一个单向链表(数组和链表的结合体)

4.最主要掌握的是map.put(k,v);v=map.get(k);的原理

在这里插入图片描述
map.put(k,v)实现原理
第一步:先将k,v封装到node对象中
第二步:底层会调用key的hashCode()方法得出哈希值,然后通过哈希函数/哈希算法,将hash值转换成数组的下标,下标位置上如果没有任何的元素,就把node添加到这个位置上,如果说下标对应的位置上有数据或者有链表。此时会拿着k和链表中的每一个结点的k进行equals,如果所有的equals方法返回都是false,那么这个新节点将会本地添加到链表的末尾,如果其中有一个equals方法true,那么这个结点的value将会被覆盖。

单向链表:查询效率比较低。
比如,从字典中找到 中 这个字
第一种方式从第一页开始一页一页的找,直到找到为止
第二种方式:从目录中找zhong汉语拼音对应的页码,假设这个页码对应的页数是360页,从360页挨着一页一页的找,最终找了5页,在365页找到了。
第二种方式的效率比较高,因为缩小了扫描范围。

v=map.get(k)实现原理
先调用k的hashcode()方法得出哈希值,通过哈希算法转换成数组下标,通过数组下标快速定位某个位置上。如果这个位置上什么都没有,返回null,如果这个位置上有单向链表,那么会拿着参数k和单向链表上的每个结点中的k进行equals,如果所有的方法返回false,那么get方法返回null,只要有一个结点的k和参数k的equals方法返回true,那么此时这个结点的value就是我们要找的value,get方法最终返回我们要找的value.

为什么哈希表的随机增删,自己查询效率都很高
增删是在链表上完成。

查询也不需要都扫描,只需要部分扫描即可。
重点:通过讲解可以得出HashMap集合的key,会先后调动两个方法,一个方法是hashcode(),一个方法是equals,那么这两个方法都需要重写。
为什么放在HashMap集合key部分的元素需要重写equals方法呢?
equals默认比较的是两个对象的内存地址。我们应该比较内容。

5.HashMap的key部分

无序不可重复
为什么无序?因为不一定挂到哪个单向链表上。
不可重复如何保证?equals方法来保证HashMap集合的key不可重复。如果key重复了,value会覆盖。
放在HashMap集合key部分的元素其实就是放在HashSet集合中了。

所以HashSet集合中的元素也需要同时重写hashcode()和equals方法()

注意:同一个单向链表上,所有结点的哈希相同,因为他们的数组下标是一样的。在同一个链表上k和k的equals方法肯定返回的是false.都不相等。

6.哈希表HashMap使用不当时无法发挥其性能。

假设将所有的hashcode()方法的返回值固定为某个值,那么会导致底层哈希表变成了单向链表,这种情况我们称为:散列分布均匀
假设有100个元素,10个单向链表,那么每个单向链表上有10个结点,这是最好的,是散列分布均匀的
假设所有的hashcode()方法返回值都设定为不一样的值,可以吗,有什么问题?
不行,因为这样的话导致哈希链表就成为一维数组了,没有链表的概念了。
这种情况也称为散列分布不均匀。

7.重点:放在HashMap集合key部分的元素,以及放在HashSet集合中的元素,需要同时重写hashcode和equals方法

同时重写hashcode和equals方法

8.HashMap集合的默认初始化容量是16,默认加载因为是0.75,这个默认因子是当HashMap集合底层数组的容量达到75%的时候,数组开始扩容。
HashMap集合初始化容量必须是2的倍数,这也是官方推荐的
这是因为达到散列均匀,为了提高hashMap集合的存取效率,所必须的。

public static void main(String[] args) {
    
    
    //测试HashMap集合key部分元素的特点
    //Integer是key,重写了hashCode和equals方法
    Map<Integer,String> map=new HashMap<>();
    map.put(1111,"zhangsan");
    map.put(6666,"lisi");
    map.put(7777,"wangwu");
    map.put(2222,"zhaoliu");
    map.put(2222,"zhangsan");     //key重复的时候,value会自动覆盖
    System.out.println(map.size());       //输出4

    //遍历map集合
    Set<Map.Entry<Integer,String>> set=map.entrySet();
    for(Map.Entry<Integer,String> entry:set){
    
    
        //验证结果:HashMap集合元素无序不可重复
        System.out.println(entry.getKey()+"="+entry.getValue());
    }
}

4
7777=wangwu
1111=zhangsan
6666=lisi
2222=zhangsan

向Map集合中存,以及从Map集合中取,都是先调用key的hashcode方法,然后再调用equals方法
equals方法可能调用,也有可能不调用
拿put(k,v)举例,什么时候equals不会调用
k.hashcode()方法返回哈希值
哈希值经过哈希算法转换成数组下标。
数组下标位置上如果是null,equals不需要执行
拿get(k)举例,什么时候equals不会调用
数组下标位置上如果是null,equals不需要执行

class Student{
    
    
    private String name;
    public Student(){
    
    

    }
    public String getName() {
    
    
        return name;
    }
    public void setName(String name) {
    
    
        this.name = name;
    }
    public Student(String name) {
    
    
        super();
        this.name = name;
    }

    public boolean equals(Object obj){
    
    
        if(obj==null || (obj instanceof Student))
            return false;
        if(obj==this)
            return true;
        Student s=(Student)obj;
        return this.name.equals(s.name);
    }

}
public class HashMapTest04 {
    
    
    public static void main(String[] args) {
    
    
        Student s1=new Student("zhangsan");
        Student s2=new Student("zhangsan");
        //重写equals方法之前是false
        System.out.println(s1.equals(s2));    //false
        //重写equals方法之后是true
        System.out.println(s1.equals(s2));    //true
        System.out.println("s1的hashcode="+s1.hashCode());  //366712642
        System.out.println("s2的hashcode="+s2.hashCode());  //1829164700
        //s1.equals(s2)结果已经使true了,表示s1和s2是一样的,那么往hashset集合中放的话
        //按说只能放一个,(hashset特点,无序不可重复)
        Set<Student> students=new HashSet<>();
        students.add(s1);
        students.add(s2);
        System.out.println(students.size());   //这个结果应该是1
    }
}

输出
false
false
s1的hashcode=517938326
s2的hashcode=914424520
2
这个结果按说是1,但是结果是2,显然显然不符合hashset集合存储的特点。

注意:如果一个类的equals方法重写了,那么hashcode()方法必须重写,并且equals方法返回结果
如果是true,hashcode()方法返回的值必须一致。
equals方法返回true,表示两个对象相同。
在同一个单向链表上比较,那么对于同一个单向链表的结点来说,他们的哈希值是相同的,所以hashcode()方法的返回值也应该相同。

hashcode()方法和equals()方法不需要研究了,可以直接使用eclipse或者idea工具生成,但是这两个方法必须同时生成。

@Override
public int hashCode() {
    
    
    final int prime = 31;
    int result = 1;
    result = prime * result + ((name == null) ? 0 : name.hashCode());
    return result;
}
@Override
public boolean equals(Object obj) {
    
    
    if (this == obj)
        return true;
    if (obj == null)
        return false;
    if (getClass() != obj.getClass())
        return false;
    Student other = (Student) obj;
    if (name == null) {
    
    
        if (other.name != null)
            return false;
    } else if (!name.equals(other.name))
        return false;
    return true;
}

输出
true
true
s1的hashcode=-1432604525
s2的hashcode=-1432604525
1

结论
放在hashMap集合key部分的,以及放在hashset集合中的元素,需要同时重写hashcode方法和equals方法。

4.Java8对HashMap的改进

如果哈希表的单向链表上超过8个元素,再往下面存的时候,单向链表这种数据结构会变成红黑树数据结构,当红黑树上的节点数量小于6,会重新把红黑树变成单向链表数据结构。因为树的查询效率比较高。这种方式也是为了提高检索效率,二叉树的搜索会再次缩小检索范围。提高效率。
初始化容量16,默认加载因子是0.75

5.HashMap和HashTable的区别

HashMap集合的key部分允许为null吗
允许
但是要注意hashMap的key null值只能有一个

public static void main(String[] args) {
    
    
    Map map=new HashMap();
    //HashMap集合允许key为null
    map.put(null,null);
    System.out.println(map.size());       //输出 1
    //key重复的话value会覆盖
    map.put(null,100);
    System.out.println(map.size());       //输出 1
    //通过key获取value
    System.out.println(map.get(null)); //100
}

HashMapkey可以为 null
hashtable的key和value都是不能为null的
hashMap集合的key和value都是可以为null的
Hashtable方法都带有sychronized,是线程安全的
线程安全有其他的方案,这个hashtable对线程的处理导致效率较低,使用较少了
hashtable和hashmap底层都是哈希表数据结构

Map map=new Hashtable();
map.put(null,"124");
map.put(1,null);
Exception in thread "main" java.lang.NullPointerException
	at java.util.Hashtable.put(Hashtable.java:465)
	at testCollection.HashTable.main(HashTable.java:16)

hashset集合初始化容量是16,初始化容量建议是2的倍数,扩容之后是原容量的2倍。

猜你喜欢

转载自blog.csdn.net/qq_39736597/article/details/112055920