文章目录
- 一、Map集合
- 1.1 Map集合概述和特点【理解】
- 1.2 Map集合的基本功能【应用】
- 1.3 Map集合的获取功能【应用】
- 1.4 Map集合的两种遍历方式
- 二、HashMap集合
- 2.1 HashMap集合概述和特点【理解】
- 2.2 HashMap的组成、构造函数
- 2.3 put、查找方法
- 2.4 HashMap集合应用案例【应用】
- 三、TreeMap集合
- 3.1 TreeMap集合概述和特点【理解】
- 3.2 TreeMap集合应用案例【应用】
- 四、可变参数
- 五、Collections类
- 5.1 Collections常用功能
- 5.2 Comparator比较器
- 六、综合练习
- 练习1:随机点名器
- 练习2:带概率的随机
- 练习3:随机不重复
- 练习4:集合的嵌套
还记得 Java集合框架体系、Collection、List、ArrayList、LinkedList、Set、TreeSet、HashSet 吗?如果忘记可以到这里重新温习: Java集合【超详细】
本文我们重点介绍 Map、HahsMap、TreeMap相关集合。Collection、List、Set相关部分可查看 Java集合【超详细】。
一、Map集合
1.1 Map集合概述和特点【理解】
-
Map集合概述
interface Map<K,V> K:键的类型;V:值的类型
-
Map集合的特点
- 双列集合,一个键对应一个值
- 键不可以重复,值可以重复
-
Map集合的基本使用
public class MapDemo01 {public static void main(String[] args) {Map<String, String> map = new HashMap<>();//V put(K key, V value) 将指定的值与该映射中的指定键相关联map.put("student1", "zhangsan");map.put("student2", "lisi");map.put("student3", "wangwu");map.put("student4", "zhaoliu");//输出集合对象//{student2=lisi, student1=zhangsan, student4=zhaoliu, student3=wangwu}System.out.println(map);}
}
1.2 Map集合的基本功能【应用】
- 方法介绍
方法名 | 说明 |
---|---|
V put(K key,V value) | 添加元素 |
V remove(Object key) | 根据键删除键值对元素 |
void clear() | 移除所有的键值对元素 |
boolean containsKey(Object key) | 判断集合是否包含指定的键 |
boolean containsValue(Object value) | 判断集合是否包含指定的值 |
boolean isEmpty() | 判断集合是否为空 |
int size() | 集合的长度,也就是集合中键值对的个数 |
- 示例代码
public class MapDemo02 {public static void main(String[] args) {Map<String, String> map = new HashMap<>();//V put(K key, V value) 将指定的值与该映射中的指定键相关联map.put("student1", "zhangsan");map.put("student2", "lisi");map.put("student3", "wangwu");map.put("student4", "zhaoliu");// System.out.println(map.remove("student1")); //zhangsan
// System.out.println(map.remove("student5")); //null
// //{student2=lisi, student4=zhaoliu, student3=wangwu}
// System.out.println(map);// map.clear();
// System.out.println(map.size() + ", " + map); //0, {}System.out.println(map.containsKey("student1")); //trueSystem.out.println(map.containsKey("student5")); //falseSystem.out.println(map.containsValue("zhangsan")); //trueSystem.out.println(map.containsValue("Jenny")); //falseSystem.out.println(map.isEmpty()); //falseSystem.out.println(map.size()); //4System.out.println(map);}
}
1.3 Map集合的获取功能【应用】
- 方法介绍
方法名 | 说明 |
---|---|
V get(Object key) | 根据键获取值 |
Set keySet() | 获取所有键的集合 |
Collection values() | 获取所有值的集合 |
Set<Map.Entry<K,V>> entrySet() | 获取所有键值对对象的集合 |
- 示例代码
public class MapDemo03 {public static void main(String[] args) {Map<String, String> map = new HashMap<>();map.put("student1", "zhangsan");map.put("student2", "lisi");map.put("student3", "wangwu");map.put("student4", "zhaoliu");System.out.println(map.get("student1"));System.out.println(map.get("student5"));//Set<K> keySet():获取所有键的集合Set<String> keySet = map.keySet();for (String key : keySet) {System.out.println(key);}System.out.println("~~~~~");//Collection<V> values():获取所有值的集合Collection<String> values = map.values();for (String value : values) {System.out.println(value);}System.out.println("~~~~~");Set<Map.Entry<String, String>> entries = map.entrySet();for (Map.Entry<String, String> entry : entries) {System.out.println(entry);}System.out.println(entries);}
}
1.4 Map集合的两种遍历方式
Map集合有两种遍历方式
-
方式一
- 获取所有键的集合。用keySet()方法实现
- 遍历键的集合,获取到每一个键。用增强for实现
- 根据键去找值。用get(Object key)方法实现
-
方式二
- 获取所有键值对对象的集合
- Set<Map.Entry<K,V>> entrySet():获取所有键值对对象的集合
- 遍历键值对对象的集合,得到每一个键值对对象
- 用增强for实现,得到每一个Map.Entry
- 根据键值对对象获取键和值
- 用getKey()得到键
- 用getValue()得到值
- 获取所有键值对对象的集合
public class MapDemo04 {public static void main(String[] args) {Map<String, String> map = new HashMap<>();map.put("student1", "zhangsan");map.put("student2", "lisi");map.put("student3", "wangwu");map.put("student4", "zhaoliu");//方式一:获取所有键的集合。用keySet()方法实现Set<String> keySet = map.keySet();for (String key : keySet) {//根据键去找值。用get(Object key)方法实现String value = map.get(key);System.out.println(key + "," + value);}System.out.println("~~~~~");//方式二:获取所有键值对对象的集合Set<Map.Entry<String, String>> entries = map.entrySet();for (Map.Entry<String, String> entry : entries) {//根据键值对对象获取键和值String key = entry.getKey();String value = entry.getValue();System.out.println(key + "," + value);}}
}
二、HashMap集合
2.1 HashMap集合概述和特点【理解】
- HashMap底层是哈希表结构的
- 依赖hashCode方法和equals方法保证键的唯一
- 如果键要存储的是自定义对象,需要重写hashCode和equals方法
HashMap存放数据的数据是什么呢?代码中存放数据的容器如下:
transient Node<K,V>[] table;
说明了该容器中是一个又一个node组成,而node有三种实现,所以hashMap中存放的node的形式既可以是Node也可以是TreeNode。
2.2 HashMap的组成、构造函数
- HashMap的组成
public class HashMap<K,V> extends AbstractMap<K,V>implements Map<K,V>, Cloneable, Serializable {private static final long serialVersionUID = 362498820763181265L;//是hashMap的最小容量16,容量就是数组的大小也就是变量,transient Node<K,V>[] table。static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16//最大数量,该数组最大值为2^31一次方。static final int MAXIMUM_CAPACITY = 1 << 30;//默认的加载因子,如果构造的时候不传则为0.75static final float DEFAULT_LOAD_FACTOR = 0.75f;//一个位置里存放的节点转化成树的阈值,也就是8,比如数组里有一个node,这个node链表的长度达到该值才会转化为红黑树。static final int TREEIFY_THRESHOLD = 8;//当一个反树化的阈值,当这个node长度减少到该值就会从树转化成链表static final int UNTREEIFY_THRESHOLD = 6;//满足节点变成树的另一个条件,就是存放node的数组长度要达到64static final int MIN_TREEIFY_CAPACITY = 64;//具体存放数据的数组transient Node<K,V>[] table;//entrySet,一个存放k-v缓冲区transient Set<Map.Entry<K,V>> entrySet;//size是指hashMap中存放了多少个键值对transient int size;//对map的修改次数transient int modCount;//The next size value at which to resize (capacity * load factor).int threshold;//加载因子final float loadFactor;//...
}
- HashMap的构造函数
//只有容量,initialCapacity
public HashMap(int initialCapacity) {this(initialCapacity, DEFAULT_LOAD_FACTOR);
}public HashMap() {this.loadFactor = DEFAULT_LOAD_FACTOR; // all other fields defaulted
}public HashMap(Map<? extends K, ? extends V> m) {this.loadFactor = DEFAULT_LOAD_FACTOR;putMapEntries(m, false);
}final void putMapEntries(Map<? extends K, ? extends V> m, boolean evict) {int s = m.size();if (s > 0) {if (table == null) { // pre-sizefloat ft = ((float)s / loadFactor) + 1.0F;int t = ((ft < (float)MAXIMUM_CAPACITY) ?(int)ft : MAXIMUM_CAPACITY);if (t > threshold)threshold = tableSizeFor(t);}else if (s > threshold)resize();for (Map.Entry<? extends K, ? extends V> e : m.entrySet()) {K key = e.getKey();V value = e.getValue();putVal(hash(key), key, value, false, evict);}}
}public HashMap(int initialCapacity, float loadFactor) {if (initialCapacity < 0) // 容量不能为负数throw new IllegalArgumentException("Illegal initial capacity: " +initialCapacity);//当容量大于2^31就取最大值1<<31;if (initialCapacity > MAXIMUM_CAPACITY)initialCapacity = MAXIMUM_CAPACITY;if (loadFactor <= 0 || Float.isNaN(loadFactor))throw new IllegalArgumentException("Illegal load factor: " +loadFactor);this.loadFactor = loadFactor;//当前数组table的大小,一定是是2的幂次方// tableSizeFor保证了数组一定是是2的幂次方,是大于initialCapacity最接近的值。this.threshold = tableSizeFor(initialCapacity);
}
tableSizeFor()方法保证了数组大小一定是是2的幂次方,是如何实现的呢?
static final 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 >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1;
}
该方法将一个二进制数第一位1后边的数字全部变成1,然后再加1,这样这个二进制数就一定是100…这样的形式。此处实现在ArrayDeque的实现中也用到了类似的方法来保证数组长度一定是2的幂次方。
2.3 put、查找方法
- put方法
开发人员使用的put方法
public V put(K key, V value) {return putVal(hash(key), key, value, false, true);
}
HashMap内部使用的put值的方法:
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,boolean evict) {Node<K,V>[] tab; Node<K,V> p; int n, i;if ((tab = table) == null || (n = tab.length) == 0)n = (tab = resize()).length;//当hash到的位置,该位置为null的时候,存放一个新node放入 // 这儿p赋值成了table该位置的node值if ((p = tab[i = (n - 1) & hash]) == null)tab[i] = newNode(hash, key, value, null);else {Node<K,V> e; K k;//该位置第一个就是查找到的值,将p赋给eif (p.hash == hash &&((k = p.key) == key || (key != null && key.equals(k))))e = p;//如果是红黑树,调用红黑树的putTreeVal方法 else if (p instanceof TreeNode)e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);else {//是链表,遍历,注意e = p.next这个一直将下一节点赋值给e,直到尾部,注意开头是++binCountfor (int binCount = 0; ; ++binCount) {if ((e = p.next) == null) {p.next = newNode(hash, key, value, null);//当链表长度大于等于7,插入第8位,树化if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1sttreeifyBin(tab, hash);break;}if (e.hash == hash &&((k = e.key) == key || (key != null && key.equals(k))))break;p = e;}}if (e != null) { // existing mapping for keyV oldValue = e.value;if (!onlyIfAbsent || oldValue == null)e.value = value;afterNodeAccess(e);return oldValue;}}++modCount;if (++size > threshold)resize();afterNodeInsertion(evict);return null;
}
- 查找方法
final Node<K,V> getNode(int hash, Object key) {Node<K,V>[] tab; Node<K,V> first, e; int n; K k;//先判断表不为空if ((tab = table) != null && (n = tab.length) > 0 &&//这一行是找到要查询的Key在table中的位置,table是存放HashMap中每一个Node的数组。(first = tab[(n - 1) & hash]) != null) {//Node可能是一个链表或者树,先判断根节点是否是要查询的key,就是根节点,方便后续遍历Node写法并且//对于只有根节点的Node直接判断if (first.hash == hash && // always check first node((k = first.key) == key || (key != null && key.equals(k))))return first;//有子节点if ((e = first.next) != null) {//红黑树查找if (first instanceof TreeNode)return ((TreeNode<K,V>)first).getTreeNode(hash, key);do {//链表查找if (e.hash == hash &&((k = e.key) == key || (key != null && key.equals(k))))return e;}//遍历链表,当链表后续为null则推出循环while ((e = e.next) != null);}}return null;
}
2.4 HashMap集合应用案例【应用】
-
案例需求
- 创建一个HashMap集合,键是学生对象(Student),值是居住地 (String)。存储多个元素,并遍历。
- 要求保证键的唯一性:如果学生对象的成员变量值相同,我们就认为是同一个对象
-
代码实现
学生类
public class Student {private String name;private int age;public Student() {}public Student(String name, int age) {this.name = name;this.age = age;}public String getName() {return name;}public void setName(String name) {this.name = name;}public int getAge() {return age;}public void setAge(int age) {this.age = age;}@Overridepublic boolean equals(Object o) {if (this == o) return true;if (o == null || getClass() != o.getClass()) return false;Student student = (Student) o;return age == student.age && Objects.equals(name, student.name);}@Overridepublic int hashCode() {return Objects.hash(name, age);}
}
测试类
public class HashMapDemo {public static void main(String[] args) {HashMap<Student, String> hm = new HashMap<Student, String>();Student s1 = new Student("林青霞", 30);Student s2 = new Student("张曼玉", 35);Student s3 = new Student("王祖贤", 33);Student s4 = new Student("王祖贤", 33);//把学生添加到集合hm.put(s1, "西安");hm.put(s2, "武汉");hm.put(s3, "郑州");hm.put(s4, "北京");Set<Student> keySet = hm.keySet();for (Student key : keySet) {String value = hm.get(key);System.out.println(key.getName() + "," + key.getAge() + "," + value);}}
}
三、TreeMap集合
3.1 TreeMap集合概述和特点【理解】
- TreeMap底层是红黑树结构
- 依赖自然排序或者比较器排序,对键进行排序
- 如果键存储的是自定义对象,需要实现Comparable接口或者在创建TreeMap对象时候给出比较器排序规则
3.2 TreeMap集合应用案例【应用】
-
案例需求
- 创建一个TreeMap集合,键是学生对象(Student),值是籍贯(String),学生属性姓名和年龄,按照年龄进行排序并遍历
- 要求按照学生的年龄进行排序,如果年龄相同则按照姓名进行排序
-
代码实现
学生类
public class Student implements Comparable<Student>{private String name;private int age;public Student() {}public Student(String name, int age) {this.name = name;this.age = age;}public String getName() {return name;}public void setName(String name) {this.name = name;}public int getAge() {return age;}public void setAge(int age) {this.age = age;}@Overridepublic String toString() {return "Student{" +"name='" + name + '\'' +", age=" + age +'}';}@Overridepublic int compareTo(Student o) {//按照年龄进行排序int result = o.getAge() - this.getAge();//次要条件,按照姓名排序。result = result == 0 ? o.getName().compareTo(this.getName()) : result;return result;}
}
测试类
public class TreeMapDemo {public static void main(String[] args) {TreeMap<Student,String> tm = new TreeMap<>();Student s1 = new Student("zhangsan",23);Student s2 = new Student("lisi",22);Student s3 = new Student("wangwu",22);Student s4 = new Student("zhangsan",21);tm.put(s1,"江苏");tm.put(s2,"北京");tm.put(s3,"天津");tm.put(s4, "武汉");// 遍历TreeMap集合,打印每个学生的信息tm.forEach((Student key, String value) -> {System.out.println(key + "---" + value);});}
}
四、可变参数
在JDK1.5之后,如果我们定义一个方法需要接受多个参数,并且多个参数类型一致,我们可以对其简化.
- 格式:
修饰符 返回值类型 方法名(参数类型... 形参名){ }
-
**底层:**其实就是一个数组
-
**好处:**在传递数据的时候,省的我们自己创建数组并添加元素了,JDK底层帮我们自动创建数组并添加元素了
-
代码演示:
public class ChangeArgs {public static void main(String[] args) {int sum = getSum(8, 10, 5, 13, 2300);System.out.println(sum);}public static int getSum(int... arr) {int sum = 0;for (int a : arr) {sum += a;}return sum;}
}
- 注意:
1.一个方法只能有一个可变参数
2.如果方法中有多个参数,可变参数要放到最后。
- 应用场景: Collections
在Collections中也提供了添加一些元素方法:
public static <T> boolean addAll(Collection<T> c, T... elements)
:往集合中添加一些元素。
public class CollectionsDemo {public static void main(String[] args) {List<Integer> list = new ArrayList<>();//原来写法list.add(9);list.add(14);list.add(8);list.add(320);System.out.println(list); //[9, 14, 8, 320]//采用工具类 完成 往集合中添加元素Collections.addAll(list, 15, 14, 7, 310);System.out.println(list);}
}
五、Collections类
5.1 Collections常用功能
java.utils.Collections
是集合工具类,用来对集合进行操作。常用方法如下:
public static void shuffle(List<?> list)
:打乱集合顺序。public static <T> void sort(List<T> list)
:将集合中元素按照默认规则排序。public static <T> void sort(List<T> list,Comparator<? super T> )
:将集合中元素按照指定规则排序。
代码演示:
public class CollectionsDemo1 {public static void main(String[] args) {List<Integer> list = new ArrayList<>();//原来写法list.add(9);list.add(8);list.add(320);list.add(14);System.out.println(list); //[9, 8, 320, 14]//将集合中元素按照默认的自然规则排序Collections.sort(list);System.out.println(list); //[8, 9, 14, 320]//打乱集合顺序Collections.shuffle(list);System.out.println(list); //[320, 8, 14, 9]}
}
我们的集合按照默认的自然顺序进行了排列,如果想要指定顺序那该怎么办呢?
5.2 Comparator比较器
如果希望根据指定规则对集合进行排序,可使用public static <T> void sort(List<T> list,Comparator<? super T> )
方法
Student类
public class Student {private String name;private int age;public Student() {}public Student(String name, int age) {this.name = name;this.age = age;}public String getName() {return name;}public void setName(String name) {this.name = name;}public int getAge() {return age;}public void setAge(int age) {this.age = age;}@Overridepublic String toString() {return "Student{" +"name='" + name + '\'' +", age=" + age +'}';}
}
测试类
public class StudentSort {public static void main(String[] args) {List<Student> list = new ArrayList<>();list.add(new Student("Rose",18));list.add(new Student("Jack",16));list.add(new Student("LiHua",20));list.add(new Student("Jenny",18));Collections.sort(list, new Comparator<Student>() {@Overridepublic int compare(Student o1, Student o2) {//以学生的年龄升序,如果年龄一样 就按名字升序int result = o1.getAge() - o2.getAge();result = result==0 ? o1.getName().compareTo(o2.getName()) : result;return result;}});for (Student stu : list) {System.out.println(stu);}}
}
结果输出
Student{name=‘Jack’, age=16}
Student{name=‘Jenny’, age=18}
Student{name=‘Rose’, age=18}
Student{name=‘LiHua’, age=20}
六、综合练习
练习1:随机点名器
需求:班级里有N个学生,实现随机点名器
public class Test1 {public static void main(String[] args) {List<String> list = new ArrayList<>();Collections.addAll(list,"范闲","范建","范统","杜子腾","杜琦燕","宋合泛","侯笼藤","朱益群","朱穆朗玛峰","袁明媛");//法一Random r = new Random();int index = r.nextInt(list.size());String name = list.get(index);System.out.println(name);//法二 打乱顺序Collections.shuffle(list);String name1 = list.get(0);System.out.println(name1);}
}
练习2:带概率的随机
需求:
班级里有N个学生
要求在随机的时候,70%的概率随机到男生,30%的概率随机到女生
public class Test2 {public static void main(String[] args) {List<Integer> list = new ArrayList<>();Collections.addAll(list,1,1,1,1,1,1,1);Collections.addAll(list,0,0,0);//打乱集合中的数据Collections.shuffle(list);//从list集合中随机抽取0或者1Random r = new Random();int index = r.nextInt(list.size());int number = list.get(index);System.out.println(number);//创建两个集合分别存储男生和女生的名字ArrayList<String> boyList = new ArrayList<>();ArrayList<String> girlList = new ArrayList<>();Collections.addAll(boyList,"范闲","范建","范统","杜子腾","宋合泛","侯笼藤","朱益群","朱穆朗玛峰");Collections.addAll(girlList,"杜琦燕","袁明媛","李猜","田蜜蜜");//判断此时是从boyList里面抽取还是从girlList里面抽取if(number == 1){//boyListint boyIndex = r.nextInt(boyList.size());String name = boyList.get(boyIndex);System.out.println(name);}else{//girlListint girlIndex = r.nextInt(girlList.size());String name = girlList.get(girlIndex);System.out.println(name);}}
}
练习3:随机不重复
需求:
班级里有N个学生,被点到的学生不会再被点到。但是如果班级中所有的学生都点完了, 需要重新开启第二轮点名。
public class Test3 {public static void main(String[] args) {List<String> list1 = new ArrayList<>();Collections.addAll(list1, "范闲", "范建", "范统", "杜子腾", "杜琦燕", "宋合泛", "侯笼藤", "朱益群", "朱穆朗玛峰", "袁明媛");//创建一个临时的集合,用来存已经被点到学生的名字List<String> list2 = new ArrayList<>();//外循环:表示轮数for (int i = 0; i < 10; i++) {System.out.println("=========第" + i + "轮点名开始了======================");int count = list1.size();Random r = new Random();//内循环:每一轮中随机循环抽取的过程for (int j = 0; j < count; j++) {int index = r.nextInt(list1.size());String name = list1.remove(index);list2.add(name);System.out.println(name);}//此时表示一轮点名结束//list1 空了 list2 10个学生的名字list1.addAll(list2);list2.clear();}}
}
练习4:集合的嵌套
需求:
定义一个Map集合,键用表示省份名称province,值表示市city,但是市会有多个。
添加完毕后,遍历结果格式如下:
江苏省 = 南京市,扬州市,苏州市,无锡市,常州市
湖北省 = 武汉市,孝感市,十堰市,宜昌市,鄂州市
河北省 = 石家庄市,唐山市,邢台市,保定市,张家口市
public class Test4 {public static void main(String[] args) {Map<String, ArrayList<String>> hm = new HashMap<>();//创建单列集合存储市ArrayList<String> city1 = new ArrayList<>();city1.add("南京市");city1.add("扬州市");city1.add("苏州市");city1.add("无锡市");city1.add("常州市");ArrayList<String> city2 = new ArrayList<>();city2.add("武汉市");city2.add("孝感市");city2.add("十堰市");city2.add("宜昌市");city2.add("鄂州市");ArrayList<String> city3 = new ArrayList<>();city3.add("石家庄市");city3.add("唐山市");city3.add("邢台市");city3.add("保定市");city3.add("张家口市");//把省份和多个市添加到map集合hm.put("江苏省",city1);hm.put("湖北省",city2);hm.put("河北省",city3);Set<Map.Entry<String, ArrayList<String>>> entries = hm.entrySet();for (Map.Entry<String, ArrayList<String>> entry : entries) {//entry依次表示每一个键值对对象String key = entry.getKey();ArrayList<String> value = entry.getValue();StringJoiner sj = new StringJoiner(", ","","");for (String city : value) {sj.add(city);}System.out.println(key + " = " + sj);}}
}
参考黑马程序员相关视频与笔记、【查漏补缺】Java 集合详解!