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集合,如下所示
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
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
}
}
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 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
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怎么做呢?
数组和单向链表的结合体。
哈希表是一个数组和单向链表的集合体
数组:在查询方面效率很高,随机增删方面效率很低
单向链表:在随机增删方面效率较高,在查询方面效率较低。
哈希表将以上的两种数据结构融合在一起,充分发挥他们各自的长处。
类似于
Node[] nodes;
class Node{
Object k;
Object v;
Node next;
}
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; //下一个节点的内存地址
}
}
哈希表/散列表:一维数组,这个数组中每一个元素是一个单向链表(数组和链表的结合体)
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默认比较的是两个对象的内存地址。我们应该比较内容。
无序不可重复
为什么无序?因为不一定挂到哪个单向链表上。
不可重复如何保证?equals方法来保证HashMap集合的key不可重复。如果key重复了,value会覆盖。
放在HashMap集合key部分的元素其实就是放在HashSet集合中了。
所以HashSet集合中的元素也需要同时重写hashcode()和equals方法()
注意:同一个单向链表上,所有结点的哈希相同,因为他们的数组下标是一样的。在同一个链表上k和k的equals方法肯定返回的是false.都不相等。
假设将所有的hashcode()方法的返回值固定为某个值,那么会导致底层哈希表变成了单向链表,这种情况我们称为:散列分布均匀
假设有100个元素,10个单向链表,那么每个单向链表上有10个结点,这是最好的,是散列分布均匀的
假设所有的hashcode()方法返回值都设定为不一样的值,可以吗,有什么问题?
不行,因为这样的话导致哈希链表就成为一维数组了,没有链表的概念了。
这种情况也称为散列分布不均匀。
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方法。
如果哈希表的单向链表上超过8个元素,再往下面存的时候,单向链表这种数据结构会变成红黑树数据结构,当红黑树上的节点数量小于6,会重新把红黑树变成单向链表数据结构。因为树的查询效率比较高。这种方式也是为了提高检索效率,二叉树的搜索会再次缩小检索范围。提高效率。
初始化容量16,默认加载因子是0.75
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倍。