Map和Collection没有继承关系
Map集合以key和value的方式存储数据时:键值对
key和value都是引用数据类型。
key和value都是存储对象的内存地址。
key起到主导的地位,value是key的附属品
常用方法
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集合是否为空
V remove(Object key) 通过key删除键值对
int size() 获取Map集合的键值对的个数
Collection values() 获取Map集合中所有的value 返回一个Collection集合
//创建Map集合对象 Map<Integer,String> map = new HashMap<>(); //向Map集合中添加键值对 map.put(1,"zhangsan"); map.put(2,"lisi"); map.put(3,"wangwu"); map.put(4,"zhaoliu"); //通过key获取value String value = map.get(2); System.out.println(value);//lisi //获取键值对的数量 System.out.println(map.size());//4 //判断Map集合是否含有某个key System.out.println(map.containsKey(1));//true //判断Map集合是否含有某个value System.out.println(map.containsValue("lisi"));//true //获取所有的value Collection<String> values = map.values(); for (String s : values) { System.out.println(s); }/* zhangsan lisi wangwu zhaoliu */ //通过key删除value System.out.println(map.remove(2));//lisi //清空map集合 map.clear(); //判断Map集合是否为空 System.out.println(map.isEmpty());//trueSet keySet() 获取Map集合所有的key(多有的键是一个Set集合)
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中的静态内部类
获取所有的key,通过遍历key,来获取value
//创建集合 Map<Integer,String> map = new HashMap<>(); //添加元素 map.put(1,"zhangsan"); map.put(2,"lisi"); map.put(3,"wangwu"); map.put(4,"zhaoliu"); //获取set集合 Set<Integer> set = map.keySet(); //通过遍历key来获取value for (Integer a : set) { System.out.println(a + "=" + map.get(a)); } Iterator<Integer> it = set.iterator(); while (it.hasNext()){ Integer key = it.next(); System.out.println(key + "=" + map.get(key)); }Set<Map.Entry<K,V>> entrySet() 遍历推荐使用这种方法
在源码中,它并不是存成了key=value这种形式,而是在一个集合元素中存储了两个属性,可以通过getKey和getValue来获取
Set<Map.Entry<Integer,String>> sets = map.entrySet(); for(Map.Entry<Integer,String> newSet : sets){ /*System.out.println(newSet);*///两种方法都可以 System.out.println(newSet.getKey() + "=" + newSet.getValue()); } Iterator<Map.Entry<Integer,String>> iterator = sets.iterator(); while (iterator.hasNext()){ System.out.println(iterator.next()); }HashMap集合底层是哈希表/散列表的数据结构。
哈希表是一个数据和单向链表的结合体。
数据:在查询方面效率很高,随机增删方面效率很低。
单向链表:在随机增删方面效率很高,在查询方面效率很低。
哈希表将以上两种数据结构融合在一起,充分发挥他们各自的优点。
集合底层源代码
public class HashMap{ //Hashmap底层实际上就是一个数组。(一维数组) Node<K,V>[] table; //静态的内部类HashMap.Node static class Node<K,V>{ final int hash;//哈希值(哈希值是key的hashcode()方法的执行结果。哈希值可以通过哈希函数/算法,可以转换成数组的下标。) final K key;//存储到map集合中的key V value;//存储到map集合中的value Node<K,V> next;//下一个节点的内存地址。 }哈希表/散列表:一维数组,这个数组中每一个元素是一个单向链表。(数组和链表的集合体)
获取以及存储方法的实现原理
map.put(k,v)实现原理
先将k,v封装到Node对象中。
底层会调用k的hashCode()方法得出hash值
通过哈希函数/哈希算法,将hash值转换成数组的下标。
如果Node数组的这个下标对应的位置上没有任何元素,就把Node添加到这个位置上,如果对应位置上有元素/链表,此时会拿这个元素和这个链表上的每一个节点的k进行equals比较
如果所有的equals方法返回的都是false,那么这个新节点将被添加到链表的末尾,如果其中有一个equals方法返回了true,那么这个节点的value将会被覆盖
v = map.get(k)实现原理
调用k的hashCode()方法得出hash值。通过哈希函数/哈希算法,将hash值转换成数组的下标。如果Node数组的这个下标对应的位置上没有任何元素,就返回null。如果这个位置上有单向链表,那么会拿着这个参数k和单项链表上的每个节点的k进行equals比较。如果所有equals方法返回false,那么get方法返回null,但只要其中一个节点返回true,那么这个节点的value就是要找的value。get方法将这个value返回。总结:为什么哈希表的随机增删和查询效率都很高
增删是在链表上完成的。查询也不需要完全扫描,只需要进行部分扫描但哈希表没有纯数组的查询效率高,随机增删也没有链表的效率高,但都不弱在hashMap集合中同一个单项链表上的所有元素的hash值是相同的,因为他们的数组下标是相同的,但同一个链表的k和k的值肯定都不相等重点:hashMap集合的key会先后调用两个方法,第一个是hashCode()方法,第二个是equals()方法,那么这连个方法就都需要重写,因为equals方法默认比较的是两个对象的内存地址HashMap集合key部分的特点:无序,不可重复
无序:通过hashCode()方法以后不一定放到哪一个链表上
不可重复:equals()方法保证了hashMap集合的key不可重复。如果key重复了,会把旧的value覆盖。
放到HashMap集合key部分的元素其实就是放到HashSet集合中了。所以hashSet集合中的元素也需要同时重写hashCode()+equals()方法。
当重写hashCode()方法不当,导致hashCode方法只返回一个固定值时,那这个hashMap底层就会变成一个纯单向链表,那就无法发挥hashMap集合的查询的优势,称为散列分布不均匀。如100个元素,分为10个单向链表,每个链表平均10个元素,就是分布均匀的。
如果hashCode返回的hash值太多或全部不一样,会导致HashMap底层无限接近于数组,那就无法发挥hashMap的随机增删的优势,也是散列分布不均匀。
HashMap集合的初始化容量( capacity )为16,默认加载因子为0.75
默认加载因子是当hashMap集合底层数组的容量达到75%的时候,数组开始扩容。扩容量为2倍 左移一位
官方建议hashMap集合的初始化容量必须是2的幂,因为这是达到散列分布,提高HashMap集合的存取效率所必须的,不过用户也不用在意 因为Z在源码中已经写好了,当用户输入一个初始化容量时(传进来会先减一,如果用户输入4,他的代码会把4变成8),hashMap会选择一个刚刚好大于它的2的幂作为初始化容量
思考题:equals方法什么情况下不调用
put(k,v)
hashCode所对应的数组下标位置没有元素时
k.hashCode()方法返回哈希值,
哈希值经过哈希算法转换成数组下标。
数组下标位置上如果时null,equals不需要执行
get(k,v)
hashCode所对应的数组下标位置没有元素时
k.hashCode()方法返回哈希值,
哈希值经过哈希算法转换成数组下标。
数组下标位置上如果时null,equals不需要执行
注意点:
如果一个类的equals方法重写了,那么hashCode()方法必须重写。
并且equals方法返回的如果时true,hashCode()方法返回的值必须一样。
euqals方法返回true表示两个对象相同;在同一个单项链表上比较。那么对于同一个单向链表上的节点来说,他们的哈希值也必须都是相同的。所以hashCode()方法的返回值也应该相同
放在hashMap集合key部分的,以及放在HashSet集合中的元素,需要同时重写hashCode方法和equals方法。
对于哈希表数据结构来说:
如果o1 和 o2 的hash值相同,一定时放在同一个单向链表上。
当然如果o1 和 o2 的hash值不同,但由于哈希算法执行结束之后转换的数组下标可能相同,此时会发生“哈希碰撞”
只要任意一条链表上有两个元素,那这两个元素一定发生了哈希碰撞
Set<Student> students = new HashSet<>(); students.add(new Student("柳柴")); students.add(new Student("柴柕")); //往map集合中随机添加5个元素 Random random = new Random(); for (int i = 0; i < 10000 ; i++) { students.add(new Student(String.valueOf(random.nextInt(5)))); } System.out.println(students.size()); Object[] objects = students.toArray(); for (int i = 0; i < objects.length; i++) { int x = objects[i].hashCode(); for (int j = 0; j < objects.length; j++) { if (objects[j].hashCode() == x && !(i == j)){ System.out.println(objects[i] + "=" + objects[j]); System.out.println("相同"); } } }/* 这两个元素的hashCode值相同 Student{name='柳柴'}=Student{name='柴柕'} 相同 Student{name='柴柕'}=Student{name='柳柴'} 相同 */map集合允许key为空
hashMap集合的key null值只能有一个
//创建一个没有泛型的map集合 Map map = new HashMap(); //map集合允许key为null map.put(null,null); //key重复value覆盖 map.put(null,100); System.out.println(map.size()); //通过key获取value System.out.println(map.get(null));//100不同点:
HashTable的key和value都不允许为空,会抛出一个空指针异常
Map map = new Hashtable(); //key不允许为空 map.put(null,"123"); //Exception in thread "main" java.lang.NullPointerException //value也不允许为空 map.put(123,null); //Exception in thread "main" java.lang.NullPointerExceptionHashMap的key和value都可以
HashTable方法都带有synchronized:线程安全的。
线程安全有其他的方案,这个HashTable对线程的处理导致效率较低,使用较少了
HashTable初始化容量为11 默认加载因子0.75 扩容是老容量的2倍再加1
HashMap初始化容量16 默认加载因子0.75 扩容为2倍
相同点:底层都是哈希表
只需要掌握Properties属性类的相关方法即可
Properties是一个Map集合,继承HashTable,Properties的key和value都是String类型。
Properties被称为属性类对象
Properties是线程安全的
Properties的两个方法
String getProperty(String key, String defaultValue)
String getProperty(String key)
//创建一个属性类对象 Properties pro = new Properties(); //需要掌握Properties中两个方法 pro.setProperty("url","jdbc:mysql:localhost:3306"); pro.setProperty("driver","com.mysql.jdbc.Driver"); pro.setProperty("username","root"); pro.setProperty("password","123"); //通过key获取value String url = pro.getProperty("url"); String driver = pro.getProperty("driver"); String username = pro.getProperty("username"); String password = pro.getProperty("password"); System.out.println(url); System.out.println(driver); System.out.println(username); System.out.println(password);