目录
一、Map接口
-
Map与Collection并列存在的。用于保存具有映射关系的数据 key-value
-
Map中的key和value可以是任何引用数据类型,会封装到HahsMap$Node对象中
-
Map中的key不允许重复
-
Map中的value可以重复
-
Map的key可以为null value也可以为null,注意key为null,只能有一个;value为null,可以有多个
-
常用String类作为Map的key
-
key和value之间存在单向一对一关系,即通过指定的key总能找到对应value
-
Map存放数据的 一对k-v 是放在一个Node中的,又因为Node类实现了Entry接口,所以也可说是k-v就是一个Entry
为了方便遍历Map的实现类都有EntrySet、KeySet 、Values三个内部类
(他们三个其实不存放数据,真正存放数据的地方还是在数组中,只不过他们是使用了迭代器,实现了指向数组table中key value )
public class MapSource {
public static void main(String[]args){
Map map = new HashMap<>();
map.put("no1","张三");
map.put("no2","李四");
/*
1.k-v 最后是通过 newNode(hash, key, value, null); 存放在Node中的
2.为了方便程序员的遍历,还会在底层创建EntrySet集合,
该集合存放元素的类型是Entry,而一个Entry对象就包含了key和value
transient Set<Map.Entry<K,V>> entrySet;
3.entrySet中,定义的类型是Map.Entry,但是实际上存放的还是HahsMap$Node
这是因为Node实现了Map.Entry
static class Node<K,V> implements Map.Entry<K,V>
4.这样当把 HahsMap$Node 存放在entrySet方便我们的遍历
因为Map.Entry提供了两个很重要的方法
getKey() 和 getValue()
*/
//1、包含key和value的 Entry接口 其实放的是Node类
//Set<Map.Entry<K,V>>
Set set = map.entrySet();
//HashMap$EntrySet
System.out.println(set.getClass());
for (Object o : set) {
Map.Entry entry = (Map.Entry)o;
System.out.print("key:"+entry.getKey());
System.out.println(" - value:"+entry.getValue());
}
//2、包含key的set集合
Set set1 = map.keySet();
//3、包含value的Collecion即可
Collection values = map.values();
}
}
1.1 、Map接口的常用方法
-
put 添加
-
remove 根据键值删除映射关系
-
get 根据键获取值
-
size 获取元素个数
-
isEmpty 判断个数是否为0
-
clear清除
-
containsKey查找键是否存在
1.2 、Map接口6种遍历方式
public class HashMapFor {
public static void main(String[]args){
Map map = new HashMap();
map.put("1","张三");
map.put("2","李四");
map.put("3","王五");
map.put("4","黄六");
map.put(null,"黄六");
map.put("科技","信管");
//第一组:keySet 先取出所有的key,通过key求出对应的value
Set set = map.keySet();
System.out.println("======1====");
//(1)增强for循环
for (Object o : set) {
//value
Object o1 = map.get(o);
System.out.println(o+"-"+o1);
}
//(2)迭代器
System.out.println("======2====");
Iterator iterator = set.iterator();
while (iterator.hasNext()){
//key
Object next = iterator.next();
//value
Object o = map.get(next);
System.out.println(next+"-"+o);
}
//第二组 取出values
Collection values = map.values();
//(3)增强for
System.out.println("======3====");
for (Object value : values) {
System.out.println(value);
}
//(4)迭代器
System.out.println("======4====");
Iterator iterator1 = values.iterator();
while (iterator1.hasNext()){
System.out.println(iterator1.next());
}
//第三组 使用entrySet
//(5)增强for
System.out.println("======5====");
Set set1 = map.entrySet();
for (Object o : set1) {
Map.Entry entry =(Map.Entry)o;
Object key = entry.getKey();
Object value = entry.getValue();
System.out.println(key+"-"+value);
}
//(6)迭代器
System.out.println("======6====");
Iterator iterator2 = set1.iterator();
while (iterator2.hasNext()) {
Map.Entry entry =(Map.Entry) iterator2.next();
Object key = entry.getKey();
Object value = entry.getValue();
System.out.println(key+"-"+value);
}
}
}
总结
-
HashMap是Map接口中使用频率最高的实现类
-
HashMap是以k-v键值对的方式来存储数据
-
key不能重复,但是值可以重复,允许使用null键和null值
-
如果添加相同的key,则会覆盖原来的k-val 等同于修改 (key不会替换,vlaue会进行替换,并讲旧得值返回)
if执行完毕后直接执行下面的语句
5.与HashSet一样,不保证映射的顺序,因为底层是以Hash表的方式存储的
6.HashMap没有实现同步,因此是线程不安全的
3 HashMap的底层源码
-
HashMap底层维护了Node类型的数组table 默认为null
-
当创建对象的时候,将加载因子(loadfactor)初始化为0.75
-
当添加key-val的时候,通过key的哈希值得到table的索引。然后判断该索引处是否有元素。如果没有元素直接添加。如果该索引处有元素,进行比较,如果equals相等就进行替换value返回旧的值,如果不相等需要判断是否是树结构还是链表结构,如做出响应的处理。如果发现容量超过阈值就进行扩容
-
第一次添加 需要扩容为16 临界值(threshold)为12
-
以后在扩容,则扩容table容量的二倍,临界值也是原来的二倍
-
在jdk8 如果一条链表元素个数大于等于默认8个,并且table数组的大小大于等于64就会转换为红黑树,如果数组长度小于就进行扩容
4、Hashtable(t是小写)
-
存放的元素是键值对
-
Hashtable的键和值都不能是null
-
Hashtable使用方法基本与HashMap是一样的
-
Hashtable是线程安全的,HashMap是线程不安全的
@SuppressWarnings("all")
public class HashTable_ {
public static void main(String[]args){
Map map = new Hashtable();
map.put("jack",100); //ok
// map.put(null,100);// 空指针异常
// map.put("jack",null);//空指针异常
map.put("tom",22);//ok
map.put("lic",33);//ok
map.put("lic",48);//替换
System.out.println(map);
/*
1.底层有数组 Hashtable$Entry[] 初始就是11
public Hashtable() {
this(11, 0.75f);
}
public Hashtable(int initialCapacity, float loadFactor) {
this.loadFactor = loadFactor; //0.75
table = new Entry<?,?>[initialCapacity];
threshold = (int)Math.min(initialCapacity * loadFactor, MAX_ARRAY_SIZE + 1);
}
2. 临界值 threshold为 11*0.75 = 8 容量为11
3. value不能为null 否则抛出空指针异常
key也不能为null 因为调用了key.hashCode() 否则抛出空指针异常
public synchronized V put(K key, V value) {
// Make sure the value is not null
if (value == null) {
throw new NullPointerException();
}
// Makes sure the key is not already in the hashtable.
Entry<?,?> tab[] = table;
int hash = key.hashCode();
int index = (hash & 0x7FFFFFFF) % tab.length;
@SuppressWarnings("unchecked")
Entry<K,V> entry = (Entry<K,V>)tab[index];
for(; entry != null ; entry = entry.next) {
if ((entry.hash == hash) && entry.key.equals(key)) {
V old = entry.value;
entry.value = value; //进行替换的代码
return old;
}
}
addEntry(hash, key, value, index);
return null;
}
//3.扩容 if (count >= threshold) 就调用 rehash();
扩容长度原来二倍+1 //(int)临界值为新的容量*0.75
private void addEntry(int hash, K key, V value, int index) {
modCount++;
Entry<?,?> tab[] = table;
//如果超过 threshold就进行扩容
if (count >= threshold) {
// Rehash the table if the threshold is exceeded
rehash();
tab = table;
hash = key.hashCode();
index = (hash & 0x7FFFFFFF) % tab.length;
}
// Creates the new entry.
@SuppressWarnings("unchecked")
Entry<K,V> e = (Entry<K,V>) tab[index];
tab[index] = new Entry<>(hash, key, value, e);
count++;
}
protected void rehash() {
int oldCapacity = table.length;
Entry<?,?>[] oldMap = table;
// overflow-conscious code
//扩容原来二倍+1
int newCapacity = (oldCapacity << 1) + 1;
//临界值为新的容量*0.75
threshold = (int)Math.min(newCapacity * loadFactor, MAX_ARRAY_SIZE + 1);
*/
}
}
5、 Properties
-
Properties类继承自Hashtable类并且实现Map接口,也是键值对的形式来保存数据
-
它的使用特点和Hashtable类似
-
Properties还可以用于从xxx.properties文件中,加载数据到Properties类对象,并进行读取和修改
-
工作后xxx.properties文件通常作为配置文件
public class Properties_ {
public static void main(String[]args){
/*
1. Properties 继承Hashtable
2.可以通过k-v 存放数据,当然key和value不能为null
*/
Properties pro = new Properties();
pro.put("jack",100);
// pro.put(null,100);// 空指针异常
// pro.put("jack",null);//空指针异常
pro.put("tom",22);//ok
pro.put("lic",33);//ok
pro.put("lic",48);//替换
pro.put("mm","ww");//替换
System.out.println(pro);
//通过key,获取对应的值
System.out.println(pro.get("jack"));
//删除 remove
pro.remove("jack");
System.out.println(pro);
//注意getProperty 得到如果不是String 就返回null
//只有是String才能得到结果
/*
public String getProperty(String key) {
Object oval = super.get(key);
String sval = (oval instanceof String) ? (String)oval : null;
return ((sval == null) && (defaults != null)) ? defaults.getProperty(key) : sval;
}
*/
System.out.println(pro.getProperty("tom"));//null
System.out.println(pro.getProperty("mm"));//ww
}
}
6、TreeSet类(TreeMap等同)
是有序的 底层是红黑树(又名自平衡二叉查找树)
package com.sofwin.controller;
import java.util.Comparator;
import java.util.Set;
import java.util.TreeSet;
/**
* @packageName: com.sofwin.controller
* @author: wentao
* @date: 2022/11/1 22:27
* @version: 1.0
* @email 1660420659@qq.com
* @description: TreeSet
*/
public class TreeSet_ {
public static void main(String[]args){
//当我们使用无参构造器的时候 默认是升序
//如果想要自定义比较的 使用TreeSet提供的构造器,可以传入一个比较器(匿名内部类)
//从大到小
// Set set = new TreeSet();
Set set = new TreeSet(new Comparator() {
@Override
public int compare(Object o1, Object o2) {
//调用String的compareTo方法进行比较
return ((String)o2).compareTo(((String)o1));
}
});
set.add("jack");
set.add("tom");
set.add("sp");
set.add("a");
// set.add(1);
// set.add(10);
// set.add(-1);
// set.add(3);
System.out.println(set);
/*
1.构造器底层 会将写的比较器对象 给到TreeMap的一个属性comparator
public TreeSet(Comparator<? super E> comparator) {
this(new TreeMap<>(comparator));
}
public TreeMap(Comparator<? super K> comparator) {
this.comparator = comparator;
}
2. 在调用TreeSet的add方法的时候 底层会执行
//cpr就是我们匿名内部类
Comparator<? super K> cpr = comparator;
if (cpr != null) {
do {
parent = t;
// 动态绑定我们匿名内部类中写的compare方法
cmp = cpr.compare(key, t.key);
if (cmp < 0)
t = t.left;
else if (cmp > 0)
t = t.right;
else //如果=0 即相等就设置value 也就是加入不了了(替换)
return t.setValue(value);
} while (t != null);
}
*/
}
}
7、开发中如何选择集合实现类
-
先判断存储的类型 是一个对象,还是是一对键值对
-
一组对象 Collection接口
-
允许重复 List
-
增删多:LinkedList 【底层维护了一个双向链表】
-
改查多:ArrayList 【底层维护了Object类型的可变数组】
-
-
不允许重复 Set
-
无序 HashSet 【底层HashMap 维护了一个哈希表 数组+链表+红黑树】
-
排序 TreeSet
-
插入和取出顺序一致 LinkedHashSet 数组+双向链表+红黑树
-
-
-
一组键值对 Map接口
-
键无序:HashMap 【底层是哈希表 jdk7 数组+链表 jdk8 数组+链表+红黑树】
-
键排序 TreeMap
-
键插入和取出顺序一致 LinkedHashMap
-
读取文件 Properties
-
二、Collections工具类
-
Collections是一个操作Set、List、Map等集合的工具类
-
Collections中提供了一系列静态的方法对集合元素进行排序、查询和修改等操作
排序操作 (均为static)
-
reverse: 反转List中元素的顺序
-
shuffle :对List集合元素进行随机排序
-
sort :根据元素的自然排序对指定的List集合按照升序排序
-
sort(List,Comparator)根据指定的Comparator比较器,进行对List集合的排序
-
swap (List,i,j) 将指定List集合中i处元素和j处元素进行交换
public class Collections_ {
public static void main(String[]args){
//创建ArrayList用于测试
List list = new ArrayList();
list.add("to");
list.add("jack");
list.add("swich");
list.add("swicz");
list.add("mki");
System.out.println("反转前元素:"+list);
// 1. reverse: 反转List中元素的顺序
Collections.reverse(list);
System.out.println("反转后元素:"+list);
// 2. shuffle :对List集合元素进行随机排序 每次调用都会进行打乱
/* Collections.shuffle(list);
System.out.println("第一次打乱过后元素:"+list);
Collections.shuffle(list);
System.out.println("第二次打乱过后元素:"+list);*/
// 3. sort :根据元素的自然排序对指定的List集合按照升序排序
Collections.sort(list);
System.out.println("自然排序后"+list);
// 4. sort(List,Comparator)根据指定的Comparator比较器,进行对List集合的排序
//希望按照字符串长度进行排序
Collections.sort(list, new Comparator() {
@Override
public int compare(Object o1, Object o2) {
return ((String)o1).length()-((String)o2).length();
}
});
System.out.println("自定义排序规则:"+list);
// 5. swap (List,i,j) 将指定List集合中i处元素和j处元素进行交换
Collections.swap(list,0,2); //越界会报错
System.out.println("将第一个元素和第三个元素交换后:"+list);
}
}
查找、替换
-
max(Colleciton) 根据元素的自然顺序,返回给定集合中最大的元素
-
max (Collection,Compartor) 根据比较器规则,返回给定集合中的最大元素
-
min 跟最大相同
-
int frequency(Collection,Object) 返回指定集合中指定元素的出现次数
-
copy(List dest ,List src) 将src中内容赋值到dest中
-
replaceAll(List,Object oldVal,Object newVal) 使用新值替换集合中的旧值
//创建ArrayList用于测试
List list = new ArrayList();
list.add("to");
list.add("jack");
list.add("swich");
list.add("swicz");
list.add("mki");
System.out.println("反转前元素:"+list);
// 1. max(Colleciton) 根据元素的自然顺序,返回给定集合中最大的元素
Comparable max = Collections.max(list);
System.out.println("自然顺序的最大元素"+max);
// 2. max (Collection,Compartor) 根据比较器规则,返回给定集合中的最大元素
Object max1 = Collections.max(list, new Comparator() {
@Override
public int compare(Object o1, Object o2) {
return ((String) o1).length() - ((String) o2).length();
}
});
System.out.println("长度最大的元素:"+max1);
// 3. min 跟max相同
// 4. int frequency(Collection,Object) 返回指定集合中指定元素的出现次数
int to = Collections.frequency(list, "to");
System.out.println("to出现的次数"+to);
list.add("to");
int to2 = Collections.frequency(list, "to");
System.out.println("to出现的次数"+to2);
// 5. copy(List dest ,List src) 将src中内容赋值到dest中
ArrayList dest = new ArrayList<>();
System.out.println("拷贝前dest元素"+dest);
//会抛出 IndexOutOfBoundsException
/* 如果源数组list集合的长度 大于填充数组的长度dest就抛出异常
int srcSize = src.size();
if (srcSize > dest.size())
throw new IndexOutOfBoundsException("Source does not fit in dest");
*/
//因此为了完成拷贝,我们要先给dest进行赋值
for (int i =0; i<list.size(); i++){
dest.add(null);
}
Collections.copy(dest,list);
System.out.println("拷贝后desc元素"+dest);
// 6. replaceAll(List,Object oldVal,Object newVal) 使用新值替换集合中的旧值
System.out.println("替换前:"+list);
Collections.replaceAll(list,"to","王五");
System.out.println("替换后"+list);
面试题: HashSet和TreeSet的去重机制
1.HashSet的底层是HashMap 数组+链表+红黑树 set的元素存储在key中,value是set的常量PRESENT 通过hashCode+equals,底层先通过存入对象进行运算,调用hashCode得到hash值,通过hash值计算出在数组table中索引位置,如果发现该索引位置没有元素就加入,如果发现该索引位置存在元素了或者有链表了,就逐个通过equals进行比较,如果都不相同就加入到尾部(尾插法) 如果过程中有一个相等就不加入,只是将value进行替换,但是value都一样也就相当于不改变 2.TreeSet的底层是TreeMap 是数组+红黑树,如果在TreeSet的时候传入了Compartor匿名对象,就使用实现了compare方法去重,如果该方法返回0就进行value值的替换,也就是去重。如果创建TreeSet的时候没有传入Compartor匿名对象,就使用元素本身实现Compareable接口的compareTo方法去重
面试题2
package com.sofwin.controller;
import java.util.TreeSet;
/**
* @author: wentao
* @date: 2022/11/2 13:53
* @version: 1.0
*/
public class Work1 {
public static void main(String[]args){
TreeSet set = new TreeSet();
set.add(new Person()); //此处会出现异常 ClassCastException 类型转换异常
//原因: 当调用无参构造器的时候,没有传入Compartor匿名内部类
//TreeSet底层会使用数据转换成Comparable 使用它的compareTo方法
// put中的这一行 Comparable<? super K> k = (Comparable<? super K>) key;
//但是由于Person没有实现Comparable接口 因此会抛出类型准换异常
}
}
class Person {
}
集合总结
List集合 | 底层结构 | 线程安全 | 扩容机制 |
---|---|---|---|
ArrayList | 可变数组 | 线程不安全 | 无参: 第一次扩容容量为10 第二次按照1.5倍扩容 |
有参: 容量是指定大小 扩容为1.5倍 | |||
Vector | 可变数组 | 线程安全 | 无参: 默认10 每次扩容2倍 |
注意:Vector创建对象的时候就进行初始化数组 | 有参: 容量是指定大小 每次扩容2倍 | ||
LinkedList | 双向链表 | 线程不安全 |
Set集合 | 底层结构 | 线程安全 | 扩容机制 | 阈值 |
---|---|---|---|---|
HashSet | HashMap 数组+链表+红黑树 | 线程不安全 | 有参: 第一次 容量按照指定大小的最接近2^n次 | 有参:第一次 容量的0.75倍 |
有参:第二次 按照原来的二倍 | 有参:第二次 按照原来的二倍 | |||
无参: 第一次默认是16 | 无参:阈值是 容量的0.75倍 12 | |||
无参: 第二次 按照原来的二倍 | 无参: 第二次 按照原来的二倍 | |||
LinkedHashSet(HashSet的子类) | LinkedHashMap 数组+双向链表+红黑树 | 线程不安全的 | 跟HashSet一样 | 跟HashSet一样 |
TreeSet | TreeMap 红黑树 | 线程不安全的 |
Map集合 | 底层结构 | 线程安全 | 扩容机制 | 阈值 |
---|---|---|---|---|
HashMap | 数组+链表+红黑树 | 线程不安全 | 有参: 第一次 容量按照指定大小的最接近2^n次 | 有参:第一次 容量的0.75倍 |
有参:第二次 按照原来的二倍 | 有参:第二次 按照原来的二倍 | |||
无参: 第一次默认是16 | 无参:阈值是 容量的0.75倍 12 | |||
无参: 第二次 按照原来的二倍 | 无参: 第二次 按照原来的二倍 | |||
LinkedHashMap(HashMap的子类) | 数组+双向链表+红黑树 | 线程不安全的 | 跟HashSet一样 | 跟HashSet一样 |
Hashtable (t小写) | 数组+双向链表+红黑树 | 线程安全的 | 有参: 直接是指定的大小 | 有参: //(int)临界值为新的容量*0.75 |
注意:Hashtable创建对象的时候就进行初始化数组 | 无参: 扩容长度原来二倍+1 | 无参: //(int)临界值为新的容量*0.75 | ||
TreeSet | TreeMap 红黑树 | 线程不安全的 |