集合笔记部分(下)
LinkHashSet(了解)
唯一、有次序(存储顺序),没有下标
在HashSet的基础上额外添加一个链表,底层基于LinkedHashMap
性能上比HashSet低
使用场景不多(浏览记录)
LinkedHashSet set = new LinkedHashSet();
set.add("hello");
set.add("abc");
set.add("hello");
set.add("bbb");
System.out.println(set);//[hello, abc, bbb] 唯一、有次序(存储顺序)
Map接口
特征:
1:)Map存储的是Key/value键值对,key可以是任意的数据类型,实际开发中key一般是String类型,value可以是任意数据类型.
2:)key唯一,value可以重复
3:)如果往Map中添加新的数据的key已经存在,那么新的数据的value会覆盖该key对应的之前的value值
4:)Map里面的key可以为null,但是只能有一个,多个的时候,后面的会覆盖前面的
实现类
HashMap(线程不安全的), HashTable(老版本,基本不用,线程安全)
TreeMap,
Properties(配置文件,key/value都是String类型)
HashMap
数据结构:数组+链表/红黑树(哈希表)
JDK1.7及之前:数组+链表
JDK1.8及之后,引入红黑树,提高查询效率,数组+链表/红黑树
面试题:三大集合的特征
(1)List:是一个有序,元素可以重复,元素有下标的集合。常用的实现类有 ArrayList、LinkedList等。
(2)Set:是一个无序,不可以存储重复元素,元素没有下标,允许存入的元素为null但是只能存在一个,Set 接口常用实现类是 HashSet、LinkedHashSet 以及 TreeSet。
(3)Map是一个键值对的集合,存储键、值和之间的映射。 Key无序,唯一;value 不要求有序,允许重复。key,value可以是任意数据类型,key一般为String类型。Map没有继承于Collection接口,从Map集合中检索元素时,只要给出键对象,就会返回对应的值对象。Map 的常用实现类:HashMap、TreeMap等。
构造方法
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-n7hsw274-1660188869748)(D:\飞思实训三\博客\我的\7.集合\assets\image-20220808092035662.png)]
HashMap hashMap = new HashMap();//默认初始容量16,加载因子0.75
HashMap hashMap1 = new HashMap(25);//指定初始容量(不是说长度就是25) 容量是2的幂次方,传入容量,调用tableSizeFor方法,经过计算,得出32 不设置加载因子
System.out.println(tableSizeFor(25));//32
static int tableSizeFor(int cap) {
int n = cap - 1;
n |= n >>> 1;
n |= n >>> 2;
n |= n >>> 4;
n |= n >>> 8;
n |= n >>> 16;
return (n < 0) ? 1 : n + 1;
}
容量:
数组的长度,设置为2的幂次方 11–> 16 5–>8
加载因子:
扩容的指标, 扩容的阈值 = 容量**加载因子 例如 16* * 0.75 = 12,也就是说长度为16的数组,当使用到第12个时,自动扩容
例如,1000个元素 1024*0.75 = 768(数组有768个位置已经有元素时扩容)
面试题:为什么默认的加载因子是0.75?
为什么需要加载因子?
HashMap的底层是一个哈希表(散列表),存储的是键值对,需要通过计算得出元素在哈希表中存放的位置。一般的数据结构要么是插入快要么是查询快,而HashMap是一种一种插入慢、查询快的数据结构。
这种数据结构容易产生两种问题:
1:如果空间的利用率高,那么经过计算之后得出的新元素的存储位置可能已经有元素了(哈希冲突)
2:如果为了降低哈希冲突发生的概率而去增大数组的容量,就会导致空间的利用率不高。
加载因子表示的是Hash表中元素的填满程度。加载因子=填入表中的元素个数/散列表的长度。
加载因子越大,填满的元素越多,空间的利用率越高,因此发生哈希冲突的概率也会增大;加载因子越小,填满的元素越少,发生冲突的概率也会降低,但是会浪费更多的空间,而且发生扩容的次数也会增多
那么如何在“哈希冲突”与“空间利用率”之间达到相对的平衡
可以将冲突元素的位置构造成一个链表,如果添加的元素哈希地址与表上的元素冲突了,就把新元素放在这个链表上。(链地址法)
这种做法的优点是:
1.处理冲突的方式简单,没有元素堆集的现象,平均查找长度较短
2.由于各链表的结点空间是动态申请的,所以它很适合不知道具体的表长时来使用
3.删除节点操作简单,只要删除链表上的相应节点即可
缺点:需要额外的存储空间
由于HashMap的底层是由数组+链表/红黑树构成的,当链表的长度大于8时,判断数组的长度是否大于64,如果大于,这个链表就转换成红黑树
为什么默认的加载因子是0.75?而不是0.8, 0.6?
我们知道,HashMap的底层是哈希表,而解决冲突的方法是链地址法。
HashMap的初始容量大小默认是16,为了减少冲突发生的概率,当HashMap的数组长度到达一个临界值的时候,就会触发扩容,把所有元素rehash之后再放在扩容后的容器中,这是一个相当耗时的操作。
而这个临界值就是由加载因子和当前容器的容量大小来确定的:
临界阈值 = 容量*加载因子
即默认情况下是16x0.75=12时,就会触发扩容操作。
那么为什么选择了0.75作为HashMap的加载因子呢?这个跟一个统计学里很重要的原理——泊松分布有关(这里不细说,感兴趣的可以自行百度一下)。
总结就是:HashMap中除了哈希算法之外,有两个参数影响了性能:初始容量和加载因子。将常数0.5作为参数,加载因子0.75作为一个条件代入泊松分布来计算后可以最大限度的减少扩容的操作次数。因此,选择0.75作为默认的加载因子,完全是时间和空间成本上寻求的一种折衷选择。
常用方法
添加元素:put(key,value) 如果key不存在,添加元素;key存在,覆盖value
删除元素:remove(Object key) 根据key删除,返回被删除元素的value值
清空:clear()
查询:get(Object key) 根据key得到value ,如果key不存在,返回null
V getOrDefault(Object key,V defaultValue)//如果key不存在, 返回你设置defaultValue
判断key,value是否存在
boolean containsKey(Object key)
boolean containsValue(Object value)
(重点)遍历Map:
Set keySet()//获取Map所有的key
Collection values()//获取Map所有的value
Set entrySet()//得到Map所有的entry对象(键值对)(JDK1.7及之前–Entry Jdk1.8及之后Node)
1.第一种: 得到hashMap所有的key集合: ketSet(), 遍历key的set集合, 得到value
//第一种遍历方式
Set keys = map.keySet();
//简写forEach循环
for(Object key:keys){
//根据key获取value
Object value = map.get(key);
System.out.println("key:"+key+"-value:"+value);
}
//使用迭代器
Set keys = map.keySet();
Iterator iterator = keys.iterator();
while(iterator.hasNext()){
Object key = iterator.next();
Object val = map.get(key);
}
2.第二种: 得到map所有value的集合, 但是, 无法通过value得到key
Collection values = map.values();
//简写forEach循环
for(Object value:values){
//获取value
System.out.println("-value:"+value);
}
//使用迭代器
Collection values = map.values();
Iterator iterator = values.iterator();
while(iterator.hasNext()){
Object value = iterator.next();
}
得到map的所有key/value 的set集合, entrySet(); 开发中最常用最推荐的方式
//Entry: key/value对
Set entrys = map.entrySet();
//使用迭代器
//简写forEach循环
for(Object entryObj : entrys){
Map