Set（集合）与Map是用得非常多的，有别于有序容器的另外一类容器。集合的概念与数学中的集合概念一致（多值，且不重复），这个需要和List等容器进行区别，List等前面学习的容器可以在一个容器中包含多个相同的值，但是Set集合却不行。Map是一种特殊的容器，或者被称为Key-Value容器，一个记录包含两个值，其中Key是索引，有点像数据库中的表。

## 1. Collection的Set接口

回顾一下上一张的collection类属关系，Collection接口的子接口有三个（Queue、List、Set），前两个已经讲过了，这里我们看看Set接口。

![Alt text](img/image-20230331084818875.png)

> The Set interface extends the Collection interface. It does not introduce new methods or constants, but it stipulates that an instance of Set contains no duplicate elements. The concrete classes that implement Set must ensure that no duplicate elements can be added to the set. That is no two elements e1 and e2 can be in the set such that e1.equals(e2) is true.
>
> Set 接口由Collection接口扩展而来，并没有引入新的方法和常量，只是规定Set中不会保存重复的元素。实现Set接口的类必须保证不出现两个相同的对象，即不能出现 `e1.equals(e2)`为真的情况。

总之一句话，Set中的元素必须是唯一的。

![image-20230402182923594](img/image-20230402182923594.png)

从上图中可以看到类和接口的关系，上图不用去记住，只需要知道HashSet实现了Set接口就可以了。Set还有其他的实现，例如LinkedHashSet，TreeSet等。这里我们只需要掌握HashSet的使用。

```java
import java.util.*;

public class TestHashSet {
	public static void main(String[] args) {
		// Create a hash set
		Set<String> set = new HashSet<String>();			// 变量是Set类型，实际对象是HashSet

		// Add strings to the set
		set.add("London");
		set.add("Paris");
		set.add("New York");
		set.add("San Francisco");
		set.add("Beijing");
		set.add("New York");
		System.out.println(set);

		// Display the elements in the hash set
		for (String s : set) {
			System.out.print(s.toUpperCase() + ":");
		}
	}
}
```

可以看到`New York`虽然被添加了两次，但set中只存在一个。如果使用`set.size()`得到set的大小，值是5（实际添加了6次，一次重复的，被忽略）。

当然，如果写成：

```java
AbstractSet<String> set = new HashSet<String>();
HashSet set = new HashSet<String>();
Collection<String> set = new HashSet<String>();
```

也是可以了，引用变量只是能力声明。

另外需要注意：Set是无序的，没有index的概念，不能使用下标来引用其中的元素，因此无法使用传统的for循环，只能使用如上述的增强for循环。

到现在我们发现，同一种类型的容器，例如List、Queue、Set这些接口，都有很多不同的实现（例如HashSet，TreeSet），其实这些不同的实现都是为了考虑不同的应用场景：多线程安全性，读取或者写入优化，是否排序等等。因此不同的应用场景选择不同的实现是很有必要的，否则会降低程序的运行效率，甚至会出错。

### 1.1. 统计关键词总数量

```java
import java.util.*;
import java.io.*;

public class CountKeywords {
	public static void main(String[] args) throws Exception {
		Scanner input = new Scanner(System.in);
		System.out.print("Enter a Java source file: ");
		String filename = input.nextLine();

		File file = new File(filename);
		if (file.exists()) {
			System.out.println("The number of keywords in " + filename + " is " + countKeywords(file));
		} else {
			System.out.println("File " + filename + " does not exist");
		}
	}

	public static int countKeywords(File file) throws Exception {
		// Array of all Java keywords + true, false and null
		String[] keywordString = { "abstract", "assert", "boolean", "break", "byte", "case", "catch", "char", "class",
				"const", "continue", "default", "do", "double", "else", "enum", "extends", "for", "final", "finally",
				"float", "goto", "if", "implements", "import", "instanceof", "int", "interface", "long", "native",
				"new", "package", "private", "protected", "public", "return", "short", "static", "strictfp", "super",
				"switch", "synchronized", "this", "throw", "throws", "transient", "try", "void", "volatile", "while",
				"true", "false", "null" };

		Set<String> keywordSet = new HashSet<String>(Arrays.asList(keywordString));
		int count = 0;

		Scanner input = new Scanner(file);

		while (input.hasNext()) {
			String word = input.next();
			if (keywordSet.contains(word))
				count++;
		}

		return count;
	}
}
```

上面这个例子作为了解，可以分析一下这个程序到底要做什么？

## 2. Map接口

> The Map interface maps keys to the elements. The keys are like indexes. In List, the indexes are integer. In Map, the keys can be any objects. 
>
> Map 接口中，每个元素都是一个key-value的映射。Key如同是索引，在数组中索引是整型（integer），在Map中，Key可以是任何的引用类型（Object及其子对象）。
>
> 

![image-20230402203448937](img/image-20230402203448937.png)

> An instance of Map represents a group of objects, each of which is associated with a key. You can get the object from a map using a key, and you have to use a key to put the object into the map. 
>
> 每一个Map的实例都代表一系列的对象组，每组中有个Key。你可以通过Key值获取一个Value，当添加的时候，必须使用key-value的形式。



![image-20230402203548339](img/image-20230402203548339.png)

引用网上一个比较完整的类图：[容器框架概述](https://blog.csdn.net/WZD2012/article/details/73245493)

![map](img/map.jpeg)

下图是Map接口的基本定义，通过函数名可以知道其基本的含义。

![image-20230402203619454](img/image-20230402203619454.png)

下图是Map接口的其他实现，包括HashMap，LinkedHashMap，TreeMap。每个Map的具体实现的引用场景不太一样，大家只需要掌握HashMap和Map接口的基本使用就可以了。

![image-20230402203630858](img/image-20230402203630858.png)

![image-20230402203649477](img/image-20230402203649477.png)

### 2.1. HashMap

```java
import java.util.*;

public class TestMap {
	public static void main(String[] args) {
		// Create a HashMap
		Map<String, Integer> hashMap = new HashMap<String, Integer>();
		hashMap.put("Smith", 30);
		hashMap.put("Anderson", 31);
		hashMap.put("Lewis", 29);
		hashMap.put("Cook", 29);
        hashMap.put("Cook", 31);					// 修改 Cook 的值为 31，以前Cooke为29的被改写了

		System.out.println("Display entries in HashMap");
		System.out.println(hashMap + "\n");

		// Create a TreeMap from the preceding HashMap
		Map<String, Integer> treeMap = new TreeMap<String, Integer>(hashMap);

		System.out.println("Display entries in ascending order of key");
		System.out.println(treeMap);

		// Create a LinkedHashMap
		Map<String, Integer> linkedHashMap = new LinkedHashMap<String, Integer>(16, 0.75f, true);
		linkedHashMap.put("Smith", 30);
		linkedHashMap.put("Anderson", 31);
		linkedHashMap.put("Lewis", 29);
		linkedHashMap.put("Cook", 29);

		// Display the age for Lewis
		System.out.println("\nThe age for " + "Lewis is " + linkedHashMap.get("Lewis"));

		System.out.println("Display entries in LinkedHashMap");
		System.out.println(linkedHashMap);
	}
}
```

输入结果是：

```java
Display entries in HashMap
{Lewis=29, Smith=30, Cook=31, Anderson=31}

Display entries in ascending order of key
{Anderson=31, Cook=31, Lewis=29, Smith=30}

The age for Lewis is 29
Display entries in LinkedHashMap
{Smith=30, Anderson=31, Cook=29, Lewis=29}
```

这里一共3个Map的实现，HashMap、TreeMap和LinkedHashMap，基本使用都一样。

`HashMap<String, Integer>()`

Map的put方法是向Map中增加一个Key-Value的值，第一个参数是Key，第二个参数是Value。

### 2.2. 统计单词出现次数

```java
import java.util.*;

public class CountOccurrenceOfWords {
	public static void main(String[] args) {
		String text = "Good morning. Have a good class. Afternoon. Have a good time. " + "Have a good visit. Have fun!";
		Map<String, Integer> map = new TreeMap<String, Integer>();
		String[] words = text.split("[ \n\t\r.,;:!?(){}]");
		for (int i = 0; i < words.length; i++) {
			String key = words[i].toLowerCase();
			if (key.length() > 0) {
				if (!map.containsKey(key)) {
					map.put(key, 1);
				} else {
					int value = map.get(key);
					value++;
					map.put(key, value);
				}
			}
		}

		for (String i : map.keySet()) {
			int v = map.get(i);
			System.out.printf("%s\t%d\n", i, v);
		}

	}
}
```

## 3. 本章重点

1. 掌握Set的概念和接口基本使用；
2. 结合HashMap类，掌握Map的概念和基本使用。
