Java集合框架概述
1.集合、数组都是对多个数据进行存储操作的结构,检查Java容器。
说明:主要指的是内存层面的存储,不涉及到持久化的存储(.txt,.jpg.avi,数据库中)
2.1 数组在储存多个数据方面的特点:
- 一旦初始化以后,其长度就确定了
- 数组一旦定义好,其元素的类型也就确定了。我们就只能操作指定类型的数据了,比如:String[ ] arr, int[ ] arr等
2.2 数组在存储多个数据方面的缺点:
- 一旦初始化以后,其长度就不可修改。
- 数组中提供的方法非常有限,对于添加、删除、插入数据等操作,非常不便,同时效率不高。
- 获取数组中实际元素的个数的需求,数组没有现成的属性或方法可用
- 数组存储数据的特点:有序、可重复。对于无序、不可重复的需求数组不能满足。
二、集合框架
Collection接口: 单列集合,用来储存一个一个的对象
- List接口:存储有序的、可重复的数据 → “动态”数组
》实现类:ArrayList、LinkedList、Vector等 - Set接口:存储无序的、不可重复的数据 → 高中讲的“集合”
》实现类:HashSet、LinkedHashSet、TreeSet等
Map: 双列集合,用来储存一对(key - value)一对的数据 → 高中函数:y = f (x) 一个key只能对应一个value,但是一个value可能对应多个key,可以多对一,不能一对多。
》实现类:HashMap、LinkedHashMap、TreeMap、Hashtable、Properties等
Collection接口方法
package com.acoffee.java;
import org.junit.Test;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Date;
/**
* @author acoffee
* @create 2020-10-11 21:24
*/
public class CollectionTest {
@Test
public void test1(){
Collection coll = new ArrayList();
//add(Object e):将元素e添加到集合coll中
coll.add("aa");
coll.add("bb");
coll.add(123);//自动装箱
coll.add(new Date());
//size():获取添加的元素个数
System.out.println(coll.size());//4
//addAll(Collection cill1):coll1集合中的元素添加到当前的集合中
Collection coll1 = new ArrayList();
coll1.add(456);
coll1.add("cc");
coll.addAll(coll1);
System.out.println(coll.size());//6
System.out.println(coll);//相当于调实现类ArrayList()的toString方法
//结果:[aa, bb, 123, Sun Oct 11 21:38:53 CST 2020, 456, cc]
//clear():清空集合元素
coll.clear();//清空之后只是数据不要了,还是有对象的,所以
//清空之后isEmpty()还是返回true
//isEmpty():判断当前集合是否为空
System.out.println(coll.isEmpty());//true
System.out.println(coll.size());//0
}
}
contains(Object obj):判断当前集合中是否包含obj
Person类:
package com.acoffee.java;
/**
* @author acoffee
* @create 2020-10-12 17:57
*/
public class Person {
private String name;
private int age;
public Person(){
}
public Person(String name, int age){
this.age = age;
this.name = name;
}
public int getAge(){
return age;
}
public void setAge(){
this.age = age;
}
public String getName(){
return name;
}
public void setName(){
this.name = name;
}
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
// @Override
// public boolean equals(Object o) {
// if (this == o) return true;
// if (o == null || getClass() != o.getClass()) return false;
//
// Person person = (Person) o;
//
// if (age != person.age) return false;
// return name != null ? name.equals(person.name) : person.name == null;
// }
CollectionTest1类:
public class CollectionTest1 {
@Test
public void test1(){
Collection coll = new ArrayList();
coll.add(123);
coll.add(new String("tom"));
coll.add(new Person("Jerry",20));
//contains(Object obj):判断当前集合中是否包含obj
boolean contains = coll.contains(123);
System.out.println(contains);//true
System.out.println(coll.contains(new String("tom")));//true
System.out.println(coll.contains(new Person("Jerry", 20)));//false
}
}
new String 是true的原因是它是重写过equals方法了的,所以知己比较内容 而new Person 是false的原因是它没有重写equals方法,所以就调用的是Object中的equals方法,而Object中的equals方法是==所以比较的是地址,但是我们是new 的两个对象,所以返回false,而当我们打开Person类中重写Person的注释,那么此时上述结果就会变成true。
结论:向Collection接口的实现类的对象中添加数据obj时,要求所在类要重写 equals 方法.
containsAll(Conllection coll1):判断形参coll1的所有的元素是否都存在于当前集合中,返回值为boolean型
public class CollectionTest2 {
@Test
public void test2(){
Collection coll = new ArrayList();
coll.add(123);
coll.add(456);
coll.add("abc");
coll.add(false);
//containsAll(Conllection coll1):判断形参coll1
//的所有的元素是否都存在于当前集合中,返回值为boolean型
Collection coll1 = Arrays.asList(123,456);
System.out.println(coll.containsAll(coll1));//true
}
}
remove(Object obj):从当前集合中移除obj元素。返回值为boolean型
@Test
public void test21(){
//3.remove(Object obj):从当前集合中移除obj元素。返回值为boolean型
Collection coll = new ArrayList();
coll.add(123);
coll.add(456);
coll.add(new Person("Jerry",20));
coll.remove(123);
System.out.println(coll);//[456, Person{name='Jerry', age=20}]
coll.remove(new Person("Jerry",20));
//也是先要比较才能移除,所以调用了equals方法
//所以也要重写了toString方法,才能成功的移除new Person
System.out.println(coll);//[456]
}
removeAll(Collection coll1):从当前集合中移除coll1中所有的元素,返回值为boolean型,相当于得到两个集合的差集
@Test
public void test22() {
Collection coll = new ArrayList();
coll.add(123);
coll.add(456);
coll.add(new Person("Jerry", 20));
coll.remove(123);
System.out.println(coll);//[456, Person{name='Jerry', age=20}]
//4.removeAll(Collection coll1):从当前集合中移除coll1中所有的元素,返回值为boolean型
Collection coll1 = Arrays.asList(123, 456);
coll.removeAll(coll1);
System.out.println(coll);//[Person{name='Jerry', age=20}]
}
boolean removeIf(Predicate filter):按照条件删除
public static void main(String[] args) {
ArrayList arrayList = new ArrayList();
arrayList.add("aaa");
arrayList.add("bbb");
arrayList.add(111);
arrayList.add(222);
arrayList.removeIf(new Predicate() {
@Override
public boolean test(Object obj) {
if(obj instanceof String) {
return true;
} else {
return false;
}
}
});
System.out.println(arrayList);//[111, 222]
}
retainAll(Collection coll1):交集:相当于获取当前集合和coll1
@Test
public void test4(){
Collection coll = new ArrayList();
coll.add("abc");
coll.add(456);
coll.add(false);
coll.add(123);
coll.add(new String("tom"));
coll.add(new Person("Jerry",20));
//retainAll(Collection coll1):交集:相当于获取当前集合和coll1
//集合的交集
Collection coll1 = Arrays.asList(123,456,789);
coll.retainAll(coll1);
System.out.println(coll);//[456, 123]
}
equals(Object obj):
@Test
public void test5(){
Collection coll = new ArrayList();
coll.add("abc");
coll.add(456);
coll.add(false);
coll.add(123);
coll.add(new String("tom"));
coll.add(new Person("Jerry",20));
//equals(Object obj):
Collection coll1 = new ArrayList();
coll1.add("abc");
coll1.add(456);
coll1.add(false);
coll1.add(123);
coll1.add(new String("tom"));
coll1.add(new Person("Jerry",20));
System.out.println(coll.equals(coll1));//true
}
因为ArraysList是有序的所以如果我们改变add的顺序就会返回false。
hashCode:返回当前的对象的哈希值;集合 → 数组:toArray();扩展:数组 → 集合:调用Arrays类的静态方法aList()
@Test
public void test6(){
Collection coll = new ArrayList();
coll.add("abc");
coll.add(456);
coll.add(false);
coll.add(123);
coll.add(new String("tom"));
coll.add(new Person("Jerry",20));
//7.hashCode:返回当前的对象的哈希值
System.out.println(coll.hashCode());//-1201566180
//8.集合 → 数组:toArray()
Object[] arr = coll.toArray();
for(int i = 0;i < arr.length;i++){
System.out.println(arr[i]);
}
//扩展:数组 → 集合:调用Arrays类的静态方法aList()
List<String> list = Arrays.asList(new String[]{"AA","BB","CC"});
System.out.println(list);//[AA, BB, CC]
List arr1 = Arrays.asList(new int[]{123, 456});
System.out.println(arr1.size());//1 这种写法它会将括号里面的识别为一个元素
List arr2 = Arrays.asList(new Integer[]{123, 456});
System.out.println(arr2.size());//2
}
Iterator迭代器接口
主要使用在Collection接口中的遍历,Map遍历不使用迭代器
/**
* 集合元素的遍历操作,使用迭代器Iterator接口
* 内部的方法:hasNext() 和 next()
* 集合对象每次调用iterator()方法都得到一个全新的迭代器对象,默认游标都在集合 的第一个元素之前
*
* @author acoffee
* @create 2020-10-12 20:09
*/
public class IteratorTest {
@Test
public void test6(){
Collection coll = new ArrayList();
coll.add("abc");
coll.add(456);
coll.add(false);
coll.add(123);
coll.add(new String("tom"));
coll.add(new Person("Jerry",20));
Iterator iterator = coll.iterator();
//方式一:不推荐
// for(int i = 0;i < coll.size();i++){
// System.out.println(iterator.next());
// }
//方式三:推荐
//next():①指针下移 ②将下移以后集合位置上的元素返回
while(iterator.hasNext()){
System.out.println(iterator.next());
}
}
}
两种错误:
Iterator接口remove()方法
@Test
//如果还未调用next()或在上一次调用 next 方法之后已经调用了 remove 方法, 再调用remove都会报IllegalStateException。
public void test3(){
Collection coll = new ArrayList();
coll.add("abc");
coll.add(456);
coll.add(false);
coll.add(123);
coll.add(new String("tom"));
coll.add(new Person("Jerry", 20));
//删除集合中的abc
Iterator iterator = coll.iterator();
while(iterator.hasNext()){
Object obj = iterator.next();
if ("abc".equals(obj)){
iterator.remove();
}
}
iterator = coll.iterator();//需要重新生成,使指针移至集合首部的前一位
while (iterator.hasNext()){
System.out.println(iterator.next());
}
}
新特性foreach循环(增强for循环)
@Test
//新特性foreach循环
public void test1(){
Collection coll = new ArrayList();
coll.add("abc");
coll.add(456);
coll.add(false);
coll.add(123);
coll.add(new String("tom"));
coll.add(new Person("Jerry", 20));
//for(集合元素的类型 局部变量:集合对象)
//内部仍然调用了迭代器
for(Object obj : coll){
System.out.println(obj);
}
}
@Test
public void test2() {
int[] arr = new int[]{1, 2, 3, 4, 5, 6};
//for(数组元素的类型 局部变量:数组对象)
for (int i : arr) {
//i = "2";如果我们在增强for循环中通过
// 局部变量i去修改值是不能修改成功的
System.out.println(i);
}
}
Collection子接口之一: List接口
List接口:存储有序的、可重复的数据 →“动态”数组,替换原有的数组
①ArrayList: 作为List接口的主要实现类,线程不安全,效率高:底层使用Object[ ] elementData存储 (从查找的角度、尾部添加)
②LinkedList: 对于频繁的插入、删除操作,使用此类效率比ArrayList高:底层使用的是双向链表储存 (频繁的插入、删除操作)
③Vector: 作为List接口的古老实现类,线程安全的,效率低:底层使用Object[ ] elementData存储
面试题:ArrayList、LinkedList、Vector三者的异同?
同:三个类都是实现了List接口,储存数据的特点相同:储存有序的,可重复的数据
不同:见上
ArrayList的源码分析:
源码分析(难点)
jdk 7情况下
ArrayList list = new ArrayList();//底层创建了长度是10的Object[]数组elementData
list.add(123);//elementData[0] = new Integer(123);
…
list.add(11);//如果此次的添加导致底层elementData数组容量不够,则扩容。
默认情况下,扩容为原来的容量的1.5倍,同时需要将原有数组中的数据复制到新的数组中。
结论:建议开发中使用带参的构造器:ArrayList list = new ArrayList(int capacity)
jdk 8中ArrayList的变化:
ArrayList list = new ArrayList();//底层Object[] elementData初始化为{}.并没创建长度为10的数组
list.add(123);//第一次调用add()时,底层才创建了长度10的数组,并将数据123添加到elementData[0]
…
后续的添加和扩容操作与jdk 7 无异。
小结: jdk7中的ArrayList的对象的创建类似于单例的饿汉式,而jdk8中的ArrayList的对象
的创建类似于单例的懒汉式,延迟了数组的创建,节省内存。
LinkedList的源码分析:
LinkedList list = new LinkedList(); 内部声明了Node类型的first和last属性,默认值为null
list.add(123);//将123封装到Node中,创建了Node对象。
其中,Node定义为:体现了LinkedList的双向链表的说法
private static class Node<E> {
E item;
Node<E> next;
Node<E> prev;
Node(Node<E> prev, E element, Node<E> next) {
this.item = element;
this.next = next;
this.prev = prev;
}
}
LinkedList特有方法
Vector的源码分析:
jdk7和jdk8中通过Vector()构造器创建对象时,底层都创建了长度为10的数组。在扩容方面,默认扩容为原来的数组长度的2倍。
List接口中的常用方法
代码实现:
package com.acoffee.java1;
import com.acoffee.java.Person;
import org.junit.Test;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
/**
* List除了从Collection集合继承的方法外,
* List 集合里添加了一些根据索引来 操作集合元素的方法。
* void add(int index, Object ele):在index位置插入ele元素
* boolean addAll(int index, Collection eles):从index位置开始将eles中的所有元素添加进来
* Object get(int index):获取指定index位置的元素
* int indexOf(Object obj):返回obj在集合中首次出现的位置
* int lastIndexOf(Object obj):返回obj在当前集合中末次出现的位置
* Object remove(int index):移除指定index位置的元素,并返回此元素
* Object set(int index, Object ele):设置指定index位置的元素为ele
* List subList(int fromIndex, int toIndex):返回从fromIndex到toIndex位置的子集合
*
* @author acoffee
* @create 2020-10-13 10:37
*/
public class ListTest {
@Test
public void test1(){
ArrayList list = new ArrayList();
list.add("abc");
list.add(456);
list.add(false);
list.add(123);
list.add(new String("tom"));
list.add(new Person("Jerry", 20));
//void add(int index, Object ele):在index位置插入ele元素
list.add(1,"ccc");
System.out.println(list);//[abc, ccc, 456, false, 123, tom, Person{name='Jerry', age=20}]
//boolean addAll(int index, Collection eles):从index位置开始将eles中的所有元素添加进来
List list1 = Arrays.asList(1, 2, 3);
list.addAll(1,list1);
System.out.println(list.size());//10
}
@Test
public void test2(){
ArrayList list = new ArrayList();
list.add("abc");
list.add(456);
list.add(false);
list.add(123);
list.add(new String("tom"));
list.add(new Person("Jerry", 20));
//Object get(int index):获取指定index位置的元素
System.out.println(list.get(1));//456
//int indexOf(Object obj):返回obj在集合中首次出现的位置,找不到返回-1
System.out.println(list.indexOf(123));//3
//int lastIndexOf(Object obj):返回obj在当前集合中末次出现的位置。如果不存在,返回-1.
System.out.println(list.lastIndexOf(456));//1
//Object remove(int index):移除指定index位置的元素,并返回此元素
Object obj = list.remove(0);
System.out.println(obj);//abc
System.out.println(list);//[456, false, 123, tom, Person{name='Jerry', age=20}]
//Object set(int index, Object ele):设置指定index位置的元素为ele
list.set(1,"CC");
System.out.println(list);//[456, CC, 123, tom, Person{name='Jerry', age=20}]
//List subList(int fromIndex, int toIndex):返回从fromIndex到toIndex位置的左闭右开区间的子集合
List subList = list.subList(2, 4);
System.out.println(subList);//[123, tom]
System.out.println(list);//[456, CC, 123, tom, Person{name='Jerry', age=20}]
}
}
其中开发中常用方法:
增:add(Object obj)
删:remove(int index) / remove(Object obj)
改:set(int index, Object ele)
查:get(int index)
插:add(int index, Object ele)
长度:size()
遍历:
① Iterator迭代器方式
② 增强for循环
③ 普通的循环
遍历操作:
@Test
public void test3(){
ArrayList list = new ArrayList();
list.add(123);
list.add(456);
list.add("AA");
//方式一:Iterator迭代器方式
Iterator iterator = list.iterator();
while(iterator.hasNext()){
System.out.println(iterator.next());
}
System.out.println("***************");
//方式二:增强for循环
for(Object obj : list){
System.out.println(obj);
}
System.out.println("***************");
//方式三:普通for循环
for(int i = 0;i < list.size();i++){
System.out.println(list.get(i));
}
}
Collection子接口之二: Set接口
1. Set接口的框架:
Collection接口:单列集合,用来存储一个一个的对象
Set接口:存储无序的、不可重复的数据 -->高中讲的“集合”
HashSet:作为Set接口的主要实现类;线程不安全的;可以存储null值
LinkedHashSet:作为HashSet的子类;遍历其内部数据时,可以按照添加的顺序遍历 对于频繁的遍历操作,LinkedHashSet效率高于HashSet.
TreeSet:可以按照添加对象的指定属性,进行排序。
-
Set接口中没有额外定义新的方法,使用的都是Collection中声明过的方法。
-
要求:向Set(主要指:HashSet、LinkedHashSet)中添加的数据,其所在的类一定要重写hashCode()和equals()
要求:重写的hashCode()和equals()尽可能保持一致性:相等的对象必须具有相等的散列码
重写两个方法的小技巧:对象中用作 equals() 方法比较的 Field,都应该用来计算 hashCode 值。
以HashSet为例说明:
public class SetTest {
@Test
public void test1(){
Set set = new HashSet();
set.add(456);
set.add(123);
set.add(123);
set.add("AA");
set.add("CC");
set.add(new User("Tom",12));
set.add(new User("Tom",12));
set.add(129);
Iterator iterator = set.iterator();
while(iterator.hasNext()){
System.out.println(iterator.next());
}
}
//LinkedHashSet的使用
//LinkedHashSet作为HashSet的子类,在添加数据的同时,每个数据还维护了两个引用,记录此数据前一个
//数据和后一个数据。
//优点:对于频繁的遍历操作,LinkedHashSet效率高于HashSet
@Test
public void test2(){
Set set = new LinkedHashSet();
set.add(456);
set.add(123);
set.add(123);
set.add("AA");
set.add("CC");
set.add(new User("Tom",12));
set.add(new User("Tom",12));
set.add(129);
Iterator iterator = set.iterator();
while(iterator.hasNext()){
System.out.println(iterator.next());
}
}
}
TreeSet代码实现:
自然排序:
User类:
package com.acoffee.java1;
/**
* @author shkstart
* @create 2019 下午 3:56
*/
public class User implements Comparable{
private String name;
private int age;
public User() {
}
public User(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 String toString() {
return "User{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
@Override
public boolean equals(Object o) {
System.out.println("User equals()....");
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
User user = (User) o;
if (age != user.age) return false;
return name != null ? name.equals(user.name) : user.name == null;
}
@Override
public int hashCode() { //return name.hashCode() + age;
int result = name != null ? name.hashCode() : 0;
result = 31 * result + age;
return result;
}
//按照姓名从大到小排列,年龄从小到大排列
@Override
public int compareTo(Object o) {
if(o instanceof User){
User user = (User)o;
// return -this.name.compareTo(user.name);
int compare = -this.name.compareTo(user.name);
if(compare != 0){
return compare;
}else{
return Integer.compare(this.age,user.age);
}
}else{
throw new RuntimeException("输入的类型不匹配");
}
}
}
TreeSetTest类:
/*
* 1.自TreeSet中添加的数据,要求是相同类的对象
* 2.两种排序方式:自然排序(实现comparable接口)和定制排序(实现comparator接口)
* 3.自然排序中,比较两个对象是否相同的标准为:compareTo()
* 返回0,不再是equals().
* */
@Test
public void test1() {
TreeSet set = new TreeSet();
//失败不能添加不同类的对象
// set.add(123);
// set.add(456);
// set.add("ada");
// set.add(new Person("da",12));
//实例一:
// set.add(12);
// set.add(-12);
// set.add(123);
// set.add(124);
// set.add(234);//-12 12 123 124 234
//举例二:
set.add(new User("Tom",12));
set.add(new User("Jerry",32));
set.add(new User("Jim",2));
set.add(new User("Mike",65));
set.add(new User("Jack",33));
set.add(new User("Jack",56));
Iterator iterator = set.iterator();
while (iterator.hasNext()) {
System.out.println(iterator.next());
}
}
}
执行结果:
定制排序:
@Test
//定制排序中,比较连个对象是否相同的标准为:compare()返回0.不再是equals().
public void test2(){
Comparator com = new Comparator() {
@Override
public int compare(Object o1, Object o2) {
if (o1 instanceof User && o2 instanceof User){
User u1 = (User)o1;
User u2 = (User)o2;
return Integer.compare(u1.getAge(),u2.getAge());
}else{
throw new RuntimeException("输入的数据不匹配!");
}
}
};
TreeSet set = new TreeSet(com);
set.add(new User("Tom",12));
set.add(new User("Jerry",32));
set.add(new User("Jim",2));
set.add(new User("Mike",65));
set.add(new User("Jack",33));
set.add(new User("Jack",56));
Iterator iterator = set.iterator();
while (iterator.hasNext()){
System.out.println(iterator.next());
}
}
执行结果:
练习:
关于Set的面试题:
public class duplicateList1 {
@Test
public void test(){
HashSet set = new HashSet();
Person p1 = new Person(1001,"aa");
Person p2 = new Person(1002,"bb");
set.add(p1);
set.add(p2);
System.out.println(set);//[Person{age=1002, name='bb'}, Person{age=1001, name='aa'}]
p1.name = "cc";
set.remove(p1);
System.out.println(set);//[Person{age=1002, name='bb'}, Person{age=1001, name='cc'}]
set.add(new Person(1001,"cc"));
System.out.println(set);//[Person{age=1002, name='bb'}, Person{age=1001, name='cc'}, Person{age=1001, name='cc'}]
set.add(new Person(1001,"aa"));
System.out.println(set);//[Person{age=1002, name='bb'}, Person{age=1001, name='cc'}, Person{age=1001, name='cc'}, Person{age=1001, name='aa'}]
}
}
每日一练:
1. 集合Collection中存储的如果是自定义类的对象,需要自定义类重写哪个方法?为什么?
equals()方法。 contains() /remove()/retainsAll() ….
List:equals()方法
Set:(HashSet、LinkedHashSet为例):equals()、hashCode()
(TreeSet为例):Comparable:compareTo(Object obj)
Comparator:compare(Object o1,Object o2)
2. ArrayList,LinkedList,Vector三者的相同点与不同点?【面试题】
不同:
①ArrayList: 作为List接口的主要实现类,线程不安全,效率高:底层使用Object[ ] elementData存储 (从查找的角度、尾部添加)
②LinkedList: 对于频繁的插入、删除操作,使用此类效率比ArrayList高:底层使用的是双向链表储存 (频繁的插入、删除操作)
③Vector: 作为List接口的古老实现类,线程安全的,效率低:底层使用Object[ ] elementData存储
同:
三个类都是实现了List接口,储存数据的特点相同:储存有序的,可重复的数据
3. List 接口的常用方法有哪些?(增、删、改、查、插、长度、遍历)
add(Object obj)
remove(Object obj)/remove(int index)
set(int index,Object obj)
get(int index)
add(int index,Object obj)
size()
使用Iterator;foreach;普通的for
4. 如何使用Iterator和增强for循环遍历List。举例说明
Iterator iterator = coll.iterator();
while(iterator.hasNext()){
System.out.println(iterator.next());
}
}
5. Set存储数据的特点是什么?常见的实现类有什么?说明一下彼此的特点。
Map,Set和list三者的区别:
• list:其中的值允许重复,因为其为有序的数据结构 ,允许空值
三个实现类:LinkedList、ArrayList、Vector
• set :其中的值不允许重复,无序的数据结构 (Set 集合根据 hashcode 来进行数据的存储,所以位置是固定的,但是位置不是用户可以控制的,所以对于用户来说 set 中的元素还是无序的)
两个实现类:TreeSet 、HashSet
• map:成对的数据结构,健值必须具有唯一性(键不能同,否则值替换)
四个实现类:HashMap、HashTable、SortMap、 LinkedHashMap