Java之Map集合
1. Map集合的特点
Map是一个无序,键值对的集合,键不可以重复,值可以重复,键重复则后者覆盖前者,Map集合没有继承Collection接口
无序:无序指插入的数据,读取的时候没有按顺序输出
2. Map集合的扩容方式
初始容量16,负载因子0.75,扩容增量1倍
Map集合底层是一个数组
数组的初始容量是16
当长度到数组负载因子0.75长度的时候(16*0.75=12也就是说在12这个长度的时候)
进行一次扩容,扩容后容量是原容量的两倍
当我们已经知道需要的容量大小的时候
就可以指定初始容量跟负载因子的大小
//示例
Map<Integer, String> map = new HashMap<>(16,0.75f);
4. Map的几种遍历方式
- 通过JDK1.8新特性
//方法1示例
map.forEach((k,v)->System.out.println("key="+k+"value="+v));
- 通过KeySet方法拿到所有键,再遍历用所有键拿到对应的所有值
//方法2示例
Set<Integer> keySet = map.keySet();
for (Integer integer : keySet) {
System.out.println("value:"+map.get(integer));
}
- 通过迭代器
//方法3示例
Iterator<Integer> iterator = map.keySet().iterator();
while(iterator.hasNext()) {
int inext = iterator.next();
System.out.println("key:"+inext+"value:"+map.get(inext));
}
- 通过entrySet取出保存所有Entry的Set,再遍历此Set即可
//方法4示例
Set<Entry<Integer, String>> entrySet = map.entrySet();
for (Entry<Integer, String> entry : entrySet) {
System.out.println("key:"+entry.getKey()+"value:"+entry.getValue());
}
5. Map集合的实现
1. HashMap
1、线程安全:指多个线程在执行同du一段代码的时候采用加锁机制,使每次的执zhi行结果和单线程执行的结果都dao是一样的,不存在执行程序时出现意外结果。
2、线程不安全:是指不提供加锁机制保护,有可能出现多个线程先后更改数据造成所得到的数据是脏数据。内部采用数组来存放数据
线程不安全,最常用,速度快
hashMap底层是由数组跟链表组合构成的数据结构
数组里面每个地方存放了Key-Value的实例(节点),在java7叫Entry在java8中叫Node
数组里面的位置刚开始都是空的为null(为什么有长度又都是空的,因为HashMap有一个初始容量),在put插入数据的时候会根据key的hash去计算一个index值也就是说放在指定的数组位置
比如我们在HashMap中put(“码农”,“1024”)插入了码农这个元素,这个时候我们会通过哈希函数计算要出插入的位置,假设计算出来index是2那结果如下。
那链表在HashMap中起什么作用呢?
我们在计算hash值的时候有一定概念会得到相同index值,这时候就会生成一个链表了,就比如说我们put一个码农跟put一个农码得到的结果都是2
- HashMap Node插入到链表1.8之前跟1.8之后的区别
java8之前采取的是"头插法"从上往下
简单来说就是新来的值会把之前的值取代,原来的值就被推到了链表当中,如果有使用到多线程的话,就会造成环形链表进入死循环java8之后采取的是"尾插法"从下往上
就避免了环形链表的发生
这是在网上一位大佬整理的put资料
HashMap put方法执行过程的基本原理
3. HashTable
线程安全的,不常使用的,并行性能慢
HashTable不常使用的原因是因为底层代码部分函数都有使用同一个对象锁
因为部分函数都是使用的同一个对象锁,就会导致有一个人在使用一个函数的时候,其他被这个对象锁加锁的函数其他人也不能使用,直到那个人使用结束
4. ConcurrentHashMap
- 线程安全,采取写时复制的方式
在写入数据的时候写把原集合复制一份在复制的集合上进行操作,然后再赋值给原集合,这样假如在写入数据的时候正好有用户在进行读取数据用户就是读取的原集合,当新集合写入好了再给用户读取
- ConcurrentHashMapJDK1.7跟JDK1.8的区别
1.7采取的方式是分段锁
HashTable采取的是synchronized
在有一个人使用同一个synchronized加锁的方法时,会把这个方法独占起来,其他人都不能使用
好比一个仓库,这个人进去就直接把大门锁了起来
如果其他人想要进入房间的话,就必须等已经进去的那个人出来,这样就会导致大门口外面等了很多人,性能就会很慢而ConcurrentHashMap1.7采取的是分段锁
把一个大仓库分成很多个小房间每个房间都有一把锁,用户a进去a房间,就把a房间锁起来,其他房间别人一样能进去
大大的提高运行效率
1.8采取的是CAS+synchronized实现
CAS:Compare and Swap,先比较再交换。
CAS是一种无锁算法,CAS有3个操作数,内存值V,旧的预期值A,要修改的新值B。并且仅当预期值A和内存值V相同时,将内存值V修改为B,否则什么都不做。
1.7中发生hash碰撞采用链表存储
1.8中先使用链表存储,后面满足条件后(链表长度超过8的时候)会转换为红黑树来优化查询;
-
适合于读多,写少的场景
-
写时复制出一个新的数组,完成插入,修改,或者移除操作后将新数组赋值给array
-
比HashTable性能高
因为保证安全的方式是写时复制,而不是用对象锁加锁函数
-
达成最终一致性
-
使用方式与HashMap类似
5. TreeMap
Key值可以通过Comparator指定按一定的顺序排序,在没有指定排序方式时按自然排序排序,如果key的类型是String则按照ASCII码从小到大排序
//Comparator示例
Map<Integer, String> treemap = new TreeMap<>(new Comparator<Integer>() {
@Override
public int compare(Integer o1, Integer o2) {
// TODO Auto-generated method stub
return -(o1-o2);
}
});
使用Lambda表达式跟Comparator进行排序
//使用Lambda表达式跟Comparator进行排序
Map<Integer, String> treemap = new TreeMap<>((Integer o1, Integer o2) -> -(o1-o2));
int compare(Object o1, Object o2) 返回一个基本类型的整型
如果要按照升序排序,
则o1 小于o2,返回-1(负数),相等返回0,01大于02返回1(正数)
如果要按照降序排序
则o1 小于o2,返回1(正数),相等返回0,01大于02返回-1(负数)
因为需要维护内部的红黑树,用于保证key值的顺序,所以添加或获取元素时性能较HashMap慢
6. LinkedHashMap
继承于HashMap
LinkedHashMap是有序的,且默认为插入顺序,当我们希望有顺序地去存储key-value时,就需要使用LinkedHashMap了
//代码示例
Map<String, String> linkedHashMap = new LinkedHashMap<>();
linkedHashMap.put("name1", "josan1");
linkedHashMap.put("name2", "josan2");
linkedHashMap.put("name3", "josan3");
Set<Entry<String, String>> set = linkedHashMap.entrySet();
Iterator<Entry<String, String>> iterator = set.iterator();
while(iterator.hasNext()) {
Entry entry = iterator.next();
String key = (String) entry.getKey();
String value = (String) entry.getValue();
System.out.println("key:" + key + ",value:" + value);
}