【Java】Map和Set
文章目录
- 一.Map
- 1.Map的说明
- 2.Map.Entry<K,V>
- 3.Map的常用接口
- 4.HsahMap和TreeMap的对比
- 二.Set
- 1.Set的接口
- 2.TreeSet和HashSet的对比
- 三.Java中的哈希表
- 1.HashMap的结构
- 2.负载因子
- 3.HashMap的put过程
一.Map
1.Map的说明
由图中可知,Map是一个接口类,且该类未继承自Collection,其存储的是键值对<k,v>结构。其中,k是唯一的,无法重复;
2.Map.Entry<K,V>
Map.Entry<K,V>是Map中的一个内部类,其主要用来存放<K,V>键值对并提供相应的访问方法;
方法 | 说明 |
---|---|
K getKey() | 返回Entry中的key |
V getValue() | 返回Entry中的value |
V setValue(V value) | 将Entry中的value改为指定的value |
注: Map.Entry<K,V>中并未提供能修改key的方法;
3.Map的常用接口
Map虽然有两种实现类,但其接口是基本相同的;
方法 | 说明 |
---|---|
V put(K key,V value) | 将键值对<k,v>放入Map中,如果key已经存在,将原value替换为新的value; |
V get(Object key) | 返回key对应的value |
V remove(Object key) | 删除<key,value>键值对 |
boolean containsKey(Object key) | 判断key是否存在Map中 |
boolean containsValue(Object value) | 判断value是否在Map中 |
Set keySet() | 返回所有的key集合,作为Set返回 |
Set<Map.Entry<K,V>> entrySet() | 返回所有的<K,V>键值对,每个元素是Map.Entry<K,V>类型 |
Collection values() | 返回所有的可重复的values集合 |
V getOrDefault(Object k,V defaultV) | 返回key对应的value,如果key不存在,则返回默认值 |
// 1.创建空的Map
Map<String,Integer> e1=new TreeMap<>(); //使用TreeMap作为实现类
Map<String,Integer> e2=new HashMap<>(); //使用HashMap作为实现类// 2.插入键值对
e.put(key,value);// 3.移除键值对
e.remove(key);// 4.获取key对应的value
e.get(key);// 5.获取所有key的集合
Set<K> t=e.keySet();// 6.遍历Map
Set<Map.Entry<K,V>> s=e.entrySet();
for(Map.Entry<K,V> tmp: s){K key=tmp.getKey();V value=tmp.getValue();//7.修改valuetmp.setValue(val);
}
注意点:
- Map是一个接口,无法实例化对象,只能通过实例化实现类来进行实例化;
- Map中的key和value全部可以分离出来单独访问,key是Set,value是Collection;
- Map中的key是唯一的,value不唯一;
4.HsahMap和TreeMap的对比
TreeMap | HashMap | |
---|---|---|
底层结构 | 红黑树 | 哈希桶 |
增/删/查的时间复杂度 | O(logn) | O(1) |
是否线程安全 | 线程不安全 | 线程不安全 |
元素是否有序 | 有序 | 不有序 |
补充: |
TreeMap的底层是红黑树,是搜索树,因此相关操作会涉及key的比较,因此TreeMap 的key不允许为null;
HashMap的底层是哈希桶,对于自定义类型,必须重写equals或者hashCode方法,其允许key的值为null;
二.Set
set继承自Collection接口类,其只存储了key;
1.Set的接口
方法 | 说明 |
---|---|
boolean add(E e) | 插入元素e,如果已经存在,则无法插入,返回false |
boolean contains(Object o) | 判断元素o是否在Set中 |
boolean remove(Object o) | 删除Set中的元素o |
int size() | 返回Set的元素个数 |
boolean isEmpty() | 判断Set是否为空 |
Object[] toArray() | 将Set的元素转为数组并返回 |
Iterator<E> iterator() | 返回迭代器 |
boolean addAll(Collection< ? extends E> c) | 将集合c添加到Set中,可以达到去重的效果 |
public static void main(String[] args) {Set<Integer> set=new TreeSet();//1.插入-addfor(int i=10;i>=0;i--){set.add(i);}//2.判断元素是否存在-containsSystem.out.println(set.contains(5));//3.移除元素-removeSystem.out.println(set.remove(10));//4.获取迭代器-iteratorIterator<Integer> it=set.iterator();while(it.hasNext()) {System.out.print(it.next() + " ");}//5.去重-addAllSet<String> s=new TreeSet();s.addAll(new ArrayList<>(Arrays.asList("a","d","c","a")));System.out.println(s);
}
Set的注意事项:
- Set是一个接口,其无法实例化出对象,必须使用HashSet或者TreeSet或者LinkedHashSet来进实现该接口
- Set的最大功能是对集合中的元素进行去重
- Set的底层是Map实现的,
- TreeSet中不能插入null作为key,但HashSet可以,这与底层实现有关;
- TreeSet和HashSet的对比与前文Map的基本一致;
2.TreeSet和HashSet的对比
TreeSet | HashSet | |
---|---|---|
底层结构 | 红黑树 | 哈希桶 |
是否有序 | 有序 | 不一定 |
是否线程安全 | 不安全 | 不安全 |
三.Java中的哈希表
1.HashMap的结构
Java中解决哈希冲突的方法是开散列法(链地址法)/哈希桶;故HashMap的数据结构由数组和链表组成,数组中存放的是一个个链表(哈希桶),当发生冲突时,将元素尾插到对应桶中;
2.负载因子
负载因子= 表中元素个数/表的长度;HashMap的默认负载因子为0.75;当表中元素个数超过容量的75%时,就需要进行扩容;
负载因子太低会造成空间浪费,负载因子太高会造成频繁冲突,因此默认值0.75很好的维持了这个平衡;
3.HashMap的put过程
前面说过,HashMap的无参构造并未对容量进行设置,因此可以猜到put极有可能对容量进行了设置;
部分成员:
put过程源码:
put的过程:
- 首先,put方法会调用putVal方法
- 如果是第一次put,会调用reSize方法,将数组的大小改为默认值16
- 通过(n-1)&hash拿到key需要被映射的位置
- 如果该位置为null,则直接进行覆盖,如果非null,则进行尾插法;
- 尾插过程中,记录元素个数,如果元素个数>=8,则会调用treeify方法
- 如果8<=binCount<64,则直接进行扩容,如果binCount>=64,则将链表构造成红黑树,并插入键值对;
- 插入完,判断是否需要reSize,如不需要,结束;
注意点:
1.扩容时需要注意什么?
将原哈希表中的每个元素均进行重新映射(重新哈希); 因为键值对的index是基于oldLength的,而扩容后,length变了,因此键值对的index理应按照新的length来映射;
2.构造的时候如果给了容量,且容量非二次幂数,怎么办?
如果构造时,给定的容量不等于二次幂,则Java会在构造方法中通过tableSizeFor返回一个接近输入的二次幂数作为initcapacity;
3.equals和hashCode的区别?
Java中,计算哈希值实际是通过hashCode方法,进行key比较时是通过equals方法;
在比较元素时,时间先比较hashCode值是否相等,如果相等,则再用equals进行比较;
因此,equals相等的对象,hashCode也一致,hashCode相等的对象,equals不一定一致;
4.HashSet的底层是HashMap;