四,Map
0.概述
Map用于保存具有映射关系的数据,因此Map集合里保存着两组值,一组用于保存Map里的key,另外一组用于保存Map里的value,key和value都可以使任何引用类型的数据。Map的key不允许重复,即同一个Map对象的任何两个key通过equals方法比较总是返回false。
key和value之间存在单向一对一关系,即通过指定的key,总能找到唯一的、确定的value。如果把Map里所有key放在一起,它们就组成了一个Set集合(所有的key没有顺序,key与key之间不能重复),实际上Map确实包含了一个keySet()方法,用于返回Map里所有key组成的Set集合;如果把Map里所有value放在一起,它们又非常类似于一个List,元素与元素之间可以重复,每个元素可以根据索引来查找,只是Map中的索引不是整数值,而是以另一个对象作为索引。
Map有时候也被称为字典,或者关联数组。
HashMap:底层用哈希表数据结构,线程不同步,可以存入null键,null值
LinkedHashMap:基于哈希表又融入了链表,可以Map集合进行增删提高效率
Hashtable:底层是哈希表数据结构,线程同步,不可以存入null键,null值,效率较低
TreeMap:底层是二叉树数据结构,可以对Map中的键进行排序
1.Map接口中的常用方法
(1)void clear(): 删除该Map对象中的所有key-value对
(2)boolean containsKey(Object key): 查询Map中是否包含指定的key,如果包含返回true
(3)boolean containsValue(Object value): 查询Map中是否包含一个或多个value,如果包含返回true
(4)Set entrySet(): 返回Map中包含的key-value对所组成的Set集合
(5)Object get(Object key): 返回指定可以所对应的value;如果该Map中不包含该key,返回null
(6)boolean isEmpty(): 查询Map是否为空,如果为空返回true
(7)Set keySet(): 返回该Map中所有key组成的set集合
(8)Object put(Object key, Object value):添加一个key-value对,如果当前Map中已有一个与该key相等的key-value对,则新的key-value对会覆盖原来的key-value对
(9)void putAll(Map m): 将指定Map中的key-value对复制到本Map中
(10)Object remove(Object key):删除指定key对应的key-value对,返回被删除key所关联的value,如果该key不存在,返回null
(11)boolean remove(Object key, Object value):java8新增的方法,删除指定key、value所对应的key-value对,如果从Map中成功删除,返回true
(12)int size(): 返回该Map里的key-value对的个数
(13)Collection values():返回该Map里所有value组成的Collection
2.遍历Map的方式
(1)使用keySet,将Map转成Set集合,通过Set的迭代器取出Set集合中的每一个元素,就是Map集合中的所有的键,再通过get方法获取键对应的值
public class MapTest {
public static void main(String[] args) {
Map<Integer, String> map = new HashMap<Integer, String>();
map.put(1, "aaaa");
map.put(2, "bbbb");
map.put(3, "cccc");
System.out.println(map);
//
// 获取方法:
// 第一种方式: 使用keySet
// 需要分别获取key和value,没有面向对象的思想
// Set<K> keySet() 返回所有的key对象的Set集合
Set<Integer> ks = map.keySet();
Iterator<Integer> it = ks.iterator();
while (it.hasNext()) {
Integer key = it.next();
String value = map.get(key);
System.out.println("key=" + key + " value=" + value);
}
}
}
/*
{1=aaaa, 2=bbbb, 3=cccc}
key=1 value=aaaa
key=2 value=bbbb
key=3 value=cccc
*/
(2)通过values获取所有值,不能获取key对象
public class MapTest {
public static void main(String[] args) {
Map<Integer, String> map = new HashMap<Integer, String>();
map.put(1, "aaaa");
map.put(2, "bbbb");
map.put(3, "cccc");
System.out.println(map);
// 第二种方式:
// 通过values 获取所有值,不能获取到key对象
// Collection<V> values()
Collection<String> vs = map.values();
Iterator<String> it = vs.iterator();
while (it.hasNext()) {
String value = it.next();
System.out.println(" value=" + value);
}
}
}
/*
{1=aaaa, 2=bbbb, 3=cccc}
value=aaaa
value=bbbb
value=cccc
*/
(3)Map.Entry,面向对象向的思想,将Map集合中的键和值映射关系打包为一个对象,将该对象存入Set集合,所具备的方法有:
Object getKey(): 返回该Entry里包含的key值
Object getValue(): 返回该Entry里包含的value值
Object setValue(V value): 设置该Entry里包含的value值,并返回新设置的value值
public class MapTest {
public static void main(String[] args) {
Map<Integer, String> map = new HashMap<Integer, String>();
map.put(1, "aaaa");
map.put(2, "bbbb");
map.put(3, "cccc");
System.out.println(map);
// 第三种方式: Map.Entry对象 推荐使用 重点
// Set<Map.Entry<K,V>> entrySet()
// 返回的Map.Entry对象的Set集合 Map.Entry包含了key和value对象
Set<Map.Entry<Integer, String>> es = map.entrySet();
Iterator<Map.Entry<Integer, String>> it = es.iterator();
while (it.hasNext()) {
// 返回的是封装了key和value对象的Map.Entry对象
Map.Entry<Integer, String> en = it.next();
// 获取Map.Entry对象中封装的key和value对象
Integer key = en.getKey();
String value = en.getValue();
System.out.println("key=" + key + " value=" + value);
}
}
}
/*
{1=aaaa, 2=bbbb, 3=cccc}
key=1 value=aaaa
key=2 value=bbbb
key=3 value=cccc
*/
3.HashMap和Hashtable
HashMap和Hashtable都是Map接口的典型实现类,它们之间的关系完全类似于ArrayList和Vector的关系。Hashtable是一个线程安全的Map实现,但HashMap是线程不安全的实现,所以HashMap比Hashtable性能高一点,但如果有多个线程访问同一个Map对象时,使用Hashtable实现类会更好。Hashtable不允许使用null作为key和value,如果试图把null值放进Hashtable中,将会引发NullPointerException异常,但HashMap可以使用null作为key或value。
class Person {
private String name;
private int age;
Person() {
}
public Person(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;
}
@Override
public int hashCode() {
return this.name.hashCode() + age * 37;
}
@Override
public boolean equals(Object obj) {
if (obj instanceof Person) {
Person p = (Person) obj;
return this.name.equals(p.name) && this.age == p.age;
} else {
return false;
}
}
@Override
public String toString() {
return "Person@name:" + this.name + " age:" + this.age;
}
}
public class HashMapTest {
public static void main(String[] args) {
// TODO Auto-generated method stub
HashMap<Person, String> hm = new HashMap<Person, String>();
hm.put(new Person("jack", 20), "1001");
hm.put(new Person("rose", 18), "1002");
hm.put(new Person("lucy", 19), "1003");
hm.put(new Person("hmm", 17), "1004");
hm.put(new Person("ll", 25), "1005");
System.out.println(hm);
System.out.println(hm.put(new Person("rose", 18), "1006"));
Set<Entry<Person, String>> entrySet = hm.entrySet();
Iterator<Entry<Person, String>> it = entrySet.iterator();
while (it.hasNext()) {
Entry<Person, String> next = it.next();
Person key = next.getKey();
String value = next.getValue();
System.out.println(key + " = " + value);
}
}
}
/*
{Person@name:jack age:20=1001, Person@name:rose age:18=1002, Person@name:lucy age:19=1003, Person@name:hmm age:17=1004, Person@name:ll age:25=1005}
1002
Person@name:jack age:20 = 1001
Person@name:rose age:18 = 1006
Person@name:lucy age:19 = 1003
Person@name:hmm age:17 = 1004
Person@name:ll age:25 = 1005
*/
4.LinkedHashMap
LinkedHashMap使用双向链表维护key-value对的次序,该链表负责维护Map的迭代顺序,迭代顺序与key-value对的插入顺序保持一致。LinkedHashMap可以避免对HashMap、Hashtable里的key-value对进行排序,同时又可避免使用TreeMap所增加的成本。LinkedHashMap需要维护元素的插入顺序,因此性能略低于HashMap,但因为它以链表来维护内部顺序,所以在迭代访问Map里的全部元素时将有较好的性能。
public class LinkedHashMapTest {
public static void main(String[] args) {
// TODO Auto-generated method stub
LinkedHashMap scores = new LinkedHashMap();
scores.put("语文" , 80);
scores.put("英文" , 82);
scores.put("数学" , 76);
// 调用forEach方法遍历scores里的所有key-value对
scores.forEach((key, value) -> System.out.println(key + "-->" + value));
}
}
/*
语文-->80
英文-->82
数学-->76
*/
5.TreeMap
TreeMap就是一个红黑树结构,每个key-value对即为红黑树的一个节点。TreeMap存储key-value对时,需要根据key对节点进行排序,TreeMap可以保证所有的key-value对处于有状态。
Set和Map的关系十分密切,java源码就是先实现了HashMap、TreeMap等集合,然后通过包装一个所有的value都为null的Map集合实现了Set集合类。
五,Collections,操作集合的工具类
java提供了一个操作Set、List和Map等集合的工具类–Collections,该工具类提供了大量方法对集合元素进行排序、查询和修改等操作,还提供了将集合对象设置为不可变、对集合对象实现同步控制等方法。
以下对常用方法作一简单介绍,具体可见帮助文档。
1.排序操作
(1)void reverse(List list):反转指定List集合中元素的顺序
(2)void shuffle(List list):对List集合元素进行随机排序(shuffle方法模拟了“洗牌”动作)
(3)void sort(List list):根据元素的自然顺序对指定List集合元素按升序进行排序
(4)void sort(List list, Comparator c):根据指定Comparator产生的顺序对List集合元素进行排序
(5)void swap(List list,int i,int j):将指定List集合中的i元素和j元素进行交换
(6)void rotate(List list,int distance):当distance为正数,将list集合的后distance个元素整体移到前面;当distance为负数,将list集合的前distance个元素整体移到后面,该方法不会改变集合的长度
public class SortTest {
public static void main(String[] args)
{
ArrayList nums = new ArrayList();
nums.add(2);
nums.add(-5);
nums.add(3);
nums.add(0);
System.out.println(nums); // 输出:[2, -5, 3, 0]
Collections.reverse(nums); // 将List集合元素的次序反转
System.out.println(nums); // 输出:[0, 3, -5, 2]
Collections.sort(nums); // 将List集合元素的按自然顺序排序
System.out.println(nums); // 输出:[-5, 0, 2, 3]
Collections.shuffle(nums); // 将List集合元素的按随机顺序排序
System.out.println(nums); // 每次输出的次序不固定
}
}
2.查找、替换
(1)int binarySearch(List list, Object key):使用二分搜索法搜索指定的List集合,以获得指定对象在List集合中的索引,如果要使该方法可以正常工作,必须保证List中元素已经处于有序状态
(2)Object max(Collection coll):根据元素的自然排序,返回给定集合中的最大元素
(3)Object max(Collection coll, Comparator comp):根据Comparator指定的顺序,返回给定集合中的最大元素
(4)Object mix(Collection coll):根据元素的自然排序,返回给定集合中的最小元素
(5)Object mix(Collection coll, Comparator comp):根据Comparator指定的顺序,返回给定集合中的最小元素
(6)void fill(List list, Object obj):使用指定元素obj替换指定List集合中的所有元素
(7)int frequency(Collection c, Object o):返回指定集合中指定元素的出现次数
(8)int indexOfSubList(List source, List target):返回子List对象在父List对象中最后一次出现的位置索引,如果父List中没有出现这样的子List,返回-1
(9)boolean replaceAll(List list, Object oldVal, Object newVal):使用一个新值newVal替换List对象的所有旧址值
public class SearchTest
{
public static void main(String[] args)
{
ArrayList nums = new ArrayList();
nums.add(2);
nums.add(-5);
nums.add(3);
nums.add(0);
System.out.println(nums); // 输出:[2, -5, 3, 0]
System.out.println(Collections.max(nums)); // 输出最大元素,将输出3
System.out.println(Collections.min(nums)); // 输出最小元素,将输出-5
Collections.replaceAll(nums , 0 , 1); // 将nums中的0使用1来代替
System.out.println(nums); // 输出:[2, -5, 3, 1]
// 判断-5在List集合中出现的次数,返回1
System.out.println(Collections.frequency(nums , -5));
Collections.sort(nums); // 对nums集合排序
System.out.println(nums); // 输出:[-5, 1, 2, 3]
//只有排序后的List集合才可用二分法查询,输出3
System.out.println(Collections.binarySearch(nums , 3));
}
}
3.同步控制
Collections提供了多个synchronizedXxx()方法,该方法可以将指定集合包装成线程同步的集合,从而可以解决多线程并发访问集合时的线程安全问题。
从前面学习中可以看出,java中常用的集合框架中的实现类HashSet、TreeSet、ArrayList、LinkedList、HashMap、TreeMap都是线程不安全的。Collections提供了多个类方法可以把它们包装成线程同步的集合。
public class SynchronizedTest
{
public static void main(String[] args)
{
// 下面程序创建了四个线程安全的集合对象
Collection c = Collections
.synchronizedCollection(new ArrayList());
List list = Collections.synchronizedList(new ArrayList());
Set s = Collections.synchronizedSet(new HashSet());
Map m = Collections.synchronizedMap(new HashMap());
}
}
4.设置不可变集合
(1)emptyXxx():返回一个空的,不可变的集合对象,此处的集合既可以是List,也可以是sortedSet、Set,还可以是Map、SortMap等
(2)singletonXxx():返回一个只包含指定对象(只有一个或一项元素)的、不可变的集合对象,此处的集合既可以是List,还可以是Map
(3)unmodifiableXxx():返回指定集合对象的不可变视图,此处的集合既可以是List,也可以是Set、SortedSet,还可以是Map、SortedMap
上述三类方法的参数是原有的集合对象,返回值是该集合的只读版本,通过Collections提供的三类方法,可以生成只读的Collection和Map。
Eg:
public class UnmodifiableTest
{
public static void main(String[] args)
{
// 创建一个空的、不可改变的List对象
List unmodifiableList = Collections.emptyList();
// 创建一个只有一个元素,且不可改变的Set对象
Set unmodifiableSet = Collections.singleton("疯狂Java讲义");
// 创建一个普通Map对象
Map scores = new HashMap();
scores.put("语文" , 80);
scores.put("Java" , 82);
// 返回普通Map对象对应的不可变版本
Map unmodifiableMap = Collections.unmodifiableMap(scores);
// 下面任意一行代码都将引发UnsupportedOperationException异常
unmodifiableList.add("测试元素"); //①
unmodifiableSet.add("测试元素"); //②
unmodifiableMap.put("语文" , 90); //③
}
}
上述代码分别定义了一个空的、不可变的List对象,一个只包含一个元素的、不可变的Set对象和一个不可变的Map对象,不可变的集合智能访问集合元素,不可修改集合元素,所以上述①②③都将引发UnsupportedOperationException异常
回忆:Arrays,用于对数组操作的工具类
二分查找,数组需要有序 binarySearch(int[])
binarySearch(double[])
数组排序 sort(int[])
sort(char[])……
将数组变成字符串 toString(int[])
复制数组 copyOf()
复制部分数组 copyOfRange()
比较两个数组是否相同 equals(int[],int[])
将数组变成集合 List asList(T[])
练习:定义一个Person数组,将Person数组中的重复对象剔除
package test;
import java.util.*;
/*
思路:
1. 描述一个Person类
2. 将数组转换为Arrays.asList() List
3. Set addAll( list )
4. hashCode()且equals()
*/
class Person {
public String name;
public int age;
public Person() {
}
public Person(String name, int age) {
this.name = name;
this.age = age;
}
public String toString() {
return getClass().getName() + " : name=" + this.name + " age="
+ this.age;
}
// 重写hashCode和equals()
public int hashCode() {
return this.age;
}
public boolean equals(Object o) {
Person p = null;
if (o instanceof Person)
p = (Person) o;
return this.name.equals(p.name) && (this.age == p.age);
}
}
public class Test {
public static void main(String [] args){
Person[] ps = new Person[] { new Person("jack", 34),
new Person("lucy", 20), new Person("lili", 10),
new Person("jack", 34) };
// 遍历数组
System.out.println(Arrays.toString(ps));
// 2. 将自定义对象数组转换为List集合
List<Person> list = Arrays.asList(ps);
// 3. 将List转换为Set
Set<Person> set = new HashSet<Person>();
set.addAll(list);
System.out.println(set);
}
}
/*
[test.Person : name=jack age=34, test.Person : name=lucy age=20, test.Person : name=lili age=10, test.Person : name=jack age=34]
[test.Person : name=jack age=34, test.Person : name=lucy age=20, test.Person : name=lili age=10]
*/