【学习笔记】集合(四)
Map集合
-
Map接口特点:
-
用于存储任意键值对(KEY-Value)
-
键:无序、无下标、不允许重复
-
值:无序、无下标、允许重复
-
-
Map接口的方法:
-
V put(K key,V value) //将对象存入集合中,关联键值,key重复则覆盖原值
-
Object get(Object key) //根据键获取对应的值
-
Set< K > keySet () //返回所有的key
-
Collection< V > values() //返回包含所有值的Collection集合
-
<Map.Entry<K,V>> entrySet () //键值匹配的set集合
-
-
创建集合以及添加元素
package com.collection.map.mapDemo;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
public class Demo01 {
public static void main(String[] args) {
//创建Map集合
Map<String,String> map = new HashMap<>();
//添加元素
map.put("cn","中国");
map.put("uk","英国");
map.put("usa","美国");
System.out.println("元素个数:"+map.size());
System.out.println(map);
}
}
当我们去添加一个重复的 key 的时候,会发现 key相同的 value 被覆盖掉了
map.put("cn","zhongguo");
-
删除元素
删除是通过 key 来删除,因为key 不能重复
map.remove("usa");
System.out.println(map);
-
遍历
-
使用 keySet ,它是所有key 的 Set集合,然后用迭代器或foreach来遍历,
如果也想获取 value 我们可以使用 map中的get方法,通过key去获取value
//遍历集合 Set<String> keySet = map.keySet(); for (String key: keySet) { System.out.println(key+"--------"+map.get(key)); }
-
使用 entrySet 方法
这个方法的返回值也是Set , 但是里面的类型是 Entry<K,Y>, Entry 就是映射对,或键值对
如果想要单独获取 键或者值,可以调用 entry.getKey() 或 entry.getValue()
Set<Map.Entry<String,String>> entries = map.entrySet(); for (Map.Entry<String,String> str: entries) { System.out.println(str); System.out.println(str.getKey() + "----------" + str.getValue()); }
keySet() 和 entrySet() 的图示如下:
-
entrySet() 的效率比 keySet() 高,原因是它一下把key 和 value全都找了出来,而 keySet 只找出了key,想要value 还要在遍历一次。
-
判断
//判断
System.out.println(map.containsKey("cn"));
System.out.println(map.containsValue("中国"));
Map 集合的实现类
HashMap
-
存储结构:哈希表(数组+链表+红黑树)
-
JDK1.2版本加入到java中,线程不安全,运行效率快
-
允许用null 作为key 或value
-
它的默认初始容量是16,默认加载因子0.75
-
默认加载因子是到容量0.75时,进行扩容
-
-
也可以通过构造方法指定容量和加载因子
-
它的方法与 map 接口的方法一样
package com.collection.map.hashMap;
import java.util.HashMap;
public class Demo01 {
public static void main(String[] args) {
//创建一个类型为 Student + String 的集合
HashMap<Student,String> students = new HashMap<>();
Student s1 = new Student("孙悟空",120);
Student s2 = new Student("猪八戒",230);
Student s3 = new Student("沙悟净",231);
students.put(s1,"北京");
students.put(s2,"上海");
students.put(s3,"广州");
System.out.println(students);
}
}
在这里,如果我们用实例化的方式添加 key 相同元素也会加进去,所以也需要重写 hashCode 和equals 方法
students.put(new Student("沙悟净",231),"广州");
重写 hashCode 和equals 方法,再次运行
@Override
public 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 && name.equals(student.name);
}
@Override
public int hashCode() {
return Objects.hash(name, age);
}
-
其他操作
package com.collection.map.hashMap;
import java.util.HashMap;
import java.util.Map;
public class Demo01 {
public static void main(String[] args) {
//创建一个类型为 Student + String 的集合
HashMap<Student,String> students = new HashMap<>();
Student s1 = new Student("孙悟空",120);
Student s2 = new Student("猪八戒",230);
Student s3 = new Student("沙悟净",231);
//添加元素
students.put(s1,"北京");
students.put(s2,"上海");
students.put(s3,"广州");
System.out.println(students);
//删除元素
//students.remove(s1);
//遍历
//1.使用 ketSet
System.out.println("-------ketSet-------");
for (Student stu:
students.keySet()) {
System.out.println(stu);
}
//2.使用 entrySet
System.out.println("-------entrySet-------");
for (Map.Entry<Student,String> stu:
students.entrySet()) {
System.out.println(stu);
}
//判断
System.out.println(students.containsKey(new Student("孙悟空", 100)));
System.out.println(students.containsValue("北京"));
}
}
HashMap 源码
-
常量:
-
默认初始容量 1<< 4 = 16 是数组的初始容量大小
static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16
-
最大的容量大小
static final int MAXIMUM_CAPACITY = 1 << 30;
-
默认加载因子
static final float DEFAULT_LOAD_FACTOR = 0.75f;
加入我们数组中的容量是100,当数组元素的数量大于75 即最大容量的75% ,就要进行扩容
-
红黑树与链表数组的转换
static final int TREEIFY_THRESHOLD = 8;
static final int UNTREEIFY_THRESHOLD = 6;
static final int MIN_TREEIFY_CAPACITY = 64;
-
在JDK1.8之后,存储结构中加入了红黑树,一开始是没有用红黑树的结构,还是使用数组加链表的结构。当链表的长度大于8,并且数组的长度大于等于64,就把数组链表结构转换成红黑树,这时候查找效率会高一些。当红黑树的元素个数小于6时,在把红黑树转换成数组链表
-
Node 类
static class Node<K,V> implements Map.Entry<K,V> {
final int hash;
final K key;
V value;
Node<K,V> next;
我们往集合中添加的元素都是键值对,其实就是一个个的Node
它有 key ,value 还有next 用来在链表中指向下一个元素
transient Node<K,V>[] table;
table 是我们创建HashMap 之后,在table 里面保存的数组,就是数组加链表中的数组
transient int size;
元素的个数
当我们刚创建 HashMap 时,table = null size = 0;
-
构造方法
public HashMap() {
this.loadFactor = DEFAULT_LOAD_FACTOR; // all other fields defaulted
}
一创建 HashMap ,只是把默认加载因子赋值给了loadFactor,没有对数组进行初始化,原因是为了节省空间。
-
put 方法
public V put(K key, V value) {
return putVal(hash(key), key, value, false, true);
}
put 方法调用 putVal
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;
if ((p = tab[i = (n - 1) & hash]) == null)
tab[i] = newNode(hash, key, value, null);
首先用Node 创建数组 tab 以及对象p
把table 赋值给tab ,并且判断是否为空,为空就调用resize() 并且将返回值赋值给tab,在获取tab的长度
Node<K,V>[] oldTab = table;
int oldCap = (oldTab == null) ? 0 : oldTab.length;
int oldThr = threshold;
int newCap, newThr = 0;
else { // zero initial threshold signifies using defaults
newCap = DEFAULT_INITIAL_CAPACITY;
newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);
}
Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap];
table = newTab;
创建新的变量newCap,把默认初始容量(16)赋值给newCap,
创建新的Node 数组 长度为newCap,在把新数组赋值给table
if ((p = tab[i = (n - 1) & hash]) == null)
tab[i] = newNode(hash, key, value, null);
这句话就是把元素放到数组里
总结:
1.HashMap刚刚创建时,table是null,为了节省空间,当添加第一个元素时,table容量调整为16
2.当元素个数大于阈值(16*0.75=12)时,会进行扩容,扩容后大小为原来的二倍,目的是减少调整元素的个数
3.jdk1.8 当每个链表长度大于8 并且数组元素个数大于等于64时,会调整为红黑树,目的提高执行效率
4.jdk1.8 当链表长度小于6时,调整成链表
5.jdk1.8以前,链表是头插入,jdk1.8以后链表是尾插入
HashSet 与 HashMap 的关系:
HashSet 里面用的就是 HashMap
HashSet 的add方法调用的是HashMap 的put方法
public HashSet() {
map = new HashMap<>();
}
public boolean add(E e) {
return map.put(e, PRESENT)==null;
}
HsahMap
-
JDK1.0版本加入,现在较少使用这个集合
-
线程安全,运行效率慢,不允许使用null作为key或value
子类 Properties
-
Hashtable 的子类,要求key 和 value 都是String 。通常用于配置文件的读取
-
使用较多
TreeMap
-
存储结构:红黑树
-
实现了SortedMap接口(是Map的子接口),可以对key自动排序
添加元素时,如果要添加的对象是自己创建的类,也需要实现comparable接口,重写comparator接口,给他定义一个比较的规则,否则就会报类型转换异常
package com.collection.map.treeMap;
import com.collection.map.hashMap.Student;
import java.util.TreeMap;
public class Demo01 {
public static void main(String[] args) {
//创建集合
TreeMap<Student,String> students = new TreeMap();
Student s1 = new Student("孙悟空",120);
Student s2 = new Student("猪八戒",230);
Student s3 = new Student("沙悟净",231);
//添加元素
students.put(s1,"北京");
students.put(s2,"上海");
students.put(s3,"广州");
System.out.println(students);
}
}
实现comparable接口,重写compareTo方法,定义先根据年龄排,再根据姓名排
@Override
public int compareTo(Student s) {
int n1 = this.age - s.getAge();
int n2 = this.name.compareTo(s.getName());
return n1 == 0 ? n2:n1;
}
在运行添加元素的代码
如果通过实例化对象的方式添加属性相同的元素时,不会添加进来,key相同就会覆盖其对应的value
students.put(new Student("沙悟净",231),"深圳");
-
其他操作
package com.collection.map.treeMap;
import com.collection.map.hashMap.Student;
import java.util.Map;
import java.util.TreeMap;
public class Demo01 {
public static void main(String[] args) {
//创建集合
TreeMap<Student,String> students = new TreeMap();
Student s1 = new Student("孙悟空",120);
Student s2 = new Student("猪八戒",230);
Student s3 = new Student("沙悟净",231);
//添加元素
students.put(s1,"北京");
students.put(s2,"上海");
students.put(s3,"广州");
System.out.println(students);
//删除
//students.remove(s1);
//遍历
//1.keySet
System.out.println("-------keySet------");
for (Student stu:
students.keySet()) {
System.out.println(stu+"-------"+ students.get(stu));
}
//2.entrySet
System.out.println("-------entrySet------");
for (Map.Entry<Student,String> stu:
students.entrySet()) {
System.out.println(stu);
}
//判断
System.out.println(students.containsKey(new Student("孙悟空", 120)));
System.out.println(students.containsValue("广州"));
}
}
如果没有实现comparable 接口的话,我们可以采用比较器的方式
TreeMap<Student,String> students = new TreeMap(new Comparator<Student>() {
@Override
public int compare(Student o1, Student o2) {
int n1 = o1.getAge() - o2.getAge();
int n2 = o1.getName().compareTo(o2.getName());
return n1 == 0 ? n2:n1;
}
});
TreeSet 与 TreeMap 的关系
在TreeSet 里面用的就是TreeMap
Collections 工具类
-
概念:集合工具类,定义了除了存取以外的集合常用方法
-
方法:
-
public static void reverse(List<?> list) //反转集合中元素的顺序
-
public static void shuffle(List<?> list) //随机重置集合元素的顺序
-
public static void sort(List<?> list) //升序排序(元素类型必须实现Comparable 接口)
-
package com.collection.comllections;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
public class Demo01 {
public static void main(String[] args) {
List<Integer> list = new ArrayList<>();
list.add(56);
list.add(23);
list.add(78);
list.add(12);
list.add(6);
//sort排序
System.out.println("排序之前:"+list);
Collections.sort(list);
System.out.println("排序之后:"+list);
}
}
-
binarySearch 方法 二分查找,找到返回元素位置,找不到返回小于0的数
System.out.println(Collections.binarySearch(list, 12));
System.out.println(Collections.binarySearch(list, 13));
-
copy 复制
//copy
List<Integer> dest = new ArrayList<>();
Collections.copy(dest,list);
System.out.println(dest);
如果直接复制的话,会报下标越界异常,因为它要求目的集合与源集合的长度必须相等,我们可以先把目标数组里面放上东西,再去复制
//copy
List<Integer> dest = new ArrayList<>();
for (int i = 0; i < list.size(); i++) {
dest.add(0);
}
Collections.copy(dest,list);
System.out.println(dest);
-
reverse 反转
//reverse 反转
Collections.reverse(list);
System.out.println("反转之后:"+list);
-
shuffle 打乱
//reverse 反转
Collections.reverse(list);
System.out.println("反转之后:"+list);
-
集合与数组的转换
//集合转数组
System.out.println("----------集合转数组---------");
Integer[] arr = list.toArray(new Integer[10]);
System.out.println(Arrays.toString(arr));
//数组转集合
System.out.println("----------数组转集合---------");
String[] names = {"张三","李四","王五"};
List<String> list2 = Arrays.asList(names);
System.out.println(list2);
需要注意的是,由集合转成的数组是受限的,无法进行添加和删除的操作。