Set 接口
public interface Set<E> extends Collection<E>
0:接口说明
Set 接口是对数学中的集合的抽象,满足集合的一般性质:
- 无序性(进出顺序)
- 唯一性(不包含重复元素)
- 至多一个null值元素(可以没有)
1:功能概述
Set 接口完全继承,并根据集合的特点重写了 Collection的 15个方法。没有任何特有方法。
- 添加功能 2 add、addAll
- 删除功能 4 clear、remove、removeAll、retainAll
- 获取功能 3 size、Iterator、hashCode
- 判断功能 4 isEmpty、equals、contains、containsAll
- 转换功能 2 toArray、toArray
2、实现子类
HashSet
public class HashSet<E> extends AbstractSet<E> implements Set<E>, Cloneable, Serializable
0:类的描述:
此类实现 Set 接口,由哈希表(实际上是一个 HashMap 实例【参见 构造器的底层源码】) 支持的。不保证集合的 迭代顺序,特别是不保证此顺序恒久不变,此类允许 null 元素。
此类为基本操作提供了稳定的性能,这些基本操作是 add、remove、contains 和 size。 假设 哈希函数将这些元素正确分布在桶中。对此 set 进行迭代所需时间与 HashSet 实例的大小(元素的数量)和 底层 HashMap 实例(桶的数量)的“容量”的和成正比。因此,如果迭代器的性能很重要,则不要将初始容量设置的太高。(或将加载因子设置的太低)
此类 单线程高效,需要同步进行安全访问的时候,粗腰创建 同步的集合Collections.synchroniizedSet(new HashSet(...))
请注意,并发修改异常 ConcurrentModificationException
:创建迭代器之后,使用集合的方法进行集合元素的修改,Iterator抛出的异常,是快速失败。
1:功能方法
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-b9gNv3ZF-1651334169067)(./set.png)]
根据哈希表的结构,来实际分析 基本操作的 实现原理:
-
add:实现的过程:(1)获取需添加的元素的
hashCode()
,根据元素的 哈希值(和哈希函数) 找到 链表在数组中位置;(2)遍历链表,使用equals()
进行判断,如果已经存在 直接返回 false;否则添加在链表最后,并返回true。 -
remove:实现过程:(1)获取需删除的元素的
hashCode()
,根据元素的 哈希值(和哈希函数) 找到 链表在数组中位置;(2)遍历链表,使用equals()
进行判断,如果存在 直接删除,并返回true;否则直接返回 false。 -
contains:实现过程:(1)获取需判断的元素的
hashCode()
,根据元素的 哈希值(和哈希函数) 找到 链表在数组中位置;(2)遍历链表,使用equals()
进行判断,如果存在,则返回 true,否则返回 false -
size:实现过程:直接遍历数组中每个链表的,返回 所有链表的元素个数之和。
package com.rupeng.set;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;
public class Demo2
{
public static void main(String[] args)
{
Set set = new HashSet();
set.add("com");
set.add("Rupeng");
set.add("www");
Iterator it = set.iterator();
while (it.hasNext())
{
String str = (String) it.next();
System.out.println(str);
}
/** 虽然 进出的顺序相同,但不能说明 Set 是有序的。 */
/** 暂且将有序定义为:不论如何添加元素,在写代码的那一刻,你就知道取出时的顺序了 */
/** 比如:数组列表、队列、栈等有序集合 */
}
}
说完了 有序的问题,再来看看 Set 集合的唯一性,是如何保证的?
实际上,Set 基本操作的实现过程已经说明了,Set 元素唯一性的原因:
- 先看
hashCode()
值是否相同,不相同 直接添加到集合中(作为新链表的首元素);相同 则继续看equals()
比较的结果,返回 true ,元素存在 不添加;返回 false ,原始不存在,添加到集合中(作为原有链表的尾元素)
源码分析:
// HashMap 类中 实现添加最核心的语句
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
{
break;
}
默认情况下,hashCode()
和 equals()
都是继承自Object超类的,进行比较的是地址值,但是有些情况下,比较地址值的意义不大,相反,使用类的成员变量的比较更有意义。比如:两个学生是否相等,如果是用地址值比较的话,因为在堆内存中不可能公用一块地址,即使是同一个人实例化两次,也会得到 为 false 的判断结果,这个时候,就需要重写 这两个方法 了。
案例1:创建字符串的 HashSet 集合
package com.rupeng.set;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;
public class Demo2
{
public static void main(String[] args)
{
Set set = new HashSet();
set.add("com");
set.add("Rupeng");
set.add("www");
set.add("Rupeng");
Iterator it = set.iterator();
while (it.hasNext())
{
String str = (String) it.next();
System.out.println(str);
}
}
}
String 类 重写了来自 Object 的 hashCode()
和 equals()
,对于相等的比较,是按位比较 字符串中的每个字符是否相等。故此,可以保证元素的唯一性。
案例2:创建自定义学生对象的 HashSet 集合
package com.rupeng.set;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;
public class Demo3
{
public static void main(String[] args)
{
Student stu1 = new Student("Cathy", 18);
Student stu2 = new Student("Cathy", 18);
Student stu3 = new Student("Ian", 17);
Student stu4 = new Student("Ian", 17);
Set<Student> set = new HashSet<Student>();
set.add(stu1);
set.add(stu2);
set.add(stu3);
set.add(stu4);
for (Iterator<Student> it = set.iterator(); it.hasNext();)
{
Student st = it.next();
System.out.println(st);
}
}
}
从代码的执行结果,可能会觉得错了。实际上 计算器 又被冤枉了,它只是按照 引用对象的地址值进行比较,确定两个值是不是相等的,而我们想要的是 通过对象的成员变量的比较来确定两个 对像是不是相等,这个时候就需要 在自定义类 Student 中重写 那两个关键的方法(hashCodse()
和 equals()
)了。【为了更加方便地输出 学生信息,可以重写 toString()
方法】
// 只是简单的重写,程序的健壮性什么的没有考虑
// 更完善的代码,可以直接使用 快捷键生成 这两个方法
// 快捷键:ALT + SHIFT + S + 键盘键入 h或H
@Override
public int hashCode()
{
return name.hashCode() + age * 2015;
}
@Override
public boolean equals(Object obj)
{
Student other = (Student) obj;
if (other == this)
{
return true;
}
if (other.getName() == name)
{
if (other.getAge() == age)
{
return true;
}
}
return false;
}
综上,HashSet 如何保证 元素的唯一性
- a、底层数据结构是 哈希表【元素是链表的 数组】
- b、哈希表依赖于哈希值存储
- c、添加功能底层实现依赖两个方法 (
hashCodse()
和equals()
)
LinkedHashSet
public class LinkedHashSet<E> extends HashSet<E> implements Set<E>, Cloneable, Serializable
a、可预知迭代顺序的 Set接口,底层是哈希表和链表的实现
b、元素有序且唯一,允许 null 元素
【哈希表保证元素的唯一性,链表保证元素的有序性】,增删效率高
c、按照链表定义的迭代顺序与其插入顺序相同
d、单线程高效,安全需要时,使用同步Set集合方式定义 Collections.synchronizedSet(new LinkedHashSet(...));
,同样存在快速失败的 并发修改异常 ConcurrentModificationException
。
TreeSet
public class TreeSet<E> extends AbstractSet<E> implements NavigableSet<E>, Cloneable, Serializable
public interface NavigableSet<E> extends SortedSet<E>
0:类的描述
根据实际的继承关系,可以知道 NavigableSet 接口实现了一个排序的接口。
TreeSet 是基于 TreeMap 的 NavigableSet 实现,实际上是 TreeMap 的一个实例。使用元素的自然顺序(Comparable)对元素进行排序,或者根据创建 set 时提供的 Comparator 进行排序,具体取决于使用的构造方法。
此实现为基本操作(add、remove 和 contains)提供受保证的 log(n) 时间开销
。 (时间复杂度)
1:数据结构
TreeSet 底层数据结构是 红黑树(Red-Blank Tree),也就是自平衡的二叉查找树(元素的快速查找
)
二叉查找树:每个节点有且只有一个父节点,最多存在两个子节点,并且该节点的值大于等于左子树中的所有节点,小于等于右子树的所有节点。
简单元素的存取:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-0Vh6TtaX-1651334169068)(./treeset.png)]
2:功能方法
自然排序 构造器:public TreeSet()
比较器排序 构造器:public TreeSet(Comparator<? extends E> c)
其余功能方法和Set一致,不再赘述。
案例1:创建字符串的 TreeSet 集合
package com.rupeng.set;
import java.util.Iterator;
import java.util.Set;
import java.util.TreeSet;
public class TreeSetDemo1
{
public static void main(String[] args)
{
Set<String> set = new TreeSet<String>();
set.add("Javase");
set.add("Rupeng");
set.add("JoianSUN");
set.add("Shanhai");
set.add("20150705");
for (Iterator<String> it = set.iterator(); it.hasNext();)
{
String str = it.next();
System.out.println(str);
}
}
}
案例2:创建自定义对象的 TreeSet 集合
package com.rupeng.set;
import java.util.Iterator;
import java.util.Set;
import java.util.TreeSet;
public class TreeSetDemo2
{
public static void main(String[] args)
{
Set<Student> set = new TreeSet<Student>();
set.add(new Student("Joian", 18));
set.add(new Student("Du", 27));
set.add(new Student("Yzk", 10));
for (Iterator<Student> it = set.iterator(); it.hasNext();)
{
Student stu = it.next();
System.out.println(stu);
}
}
}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-dgaPvsnZ-1651334169069)(./1440483027918.png)]
无法将 Student 转换为 Comparable :Student 不是接口 Comparable 的实现类,所以无法装换。
在构造 TreeSet 对象的时候,使用了默认的构造器,集合中元素的排序方式是自然排序,也就是说 添加到集合的元素必须是 Comparable 接口的实现类实例。否则报错。简言之:需要的 Comparable 接口的实现类实例,实际是 未实现接口的 Student 。之所以 String 类可以正常添加,是因为 String 类实现了 Comparable 接口。
为了篇幅的整齐,直接在此处给出具体的解决方案。进行具体的问题解决之前,先去看一下 这两个 接口的相关内容吧,就在这一节的后面。
解决方案1:元素 实现 自然排序接口
// 仅仅给出了 Comparable 接口的方法实现
public class Student implements Comparable<Student>
{
/** 实现的是 先按照姓名升序,姓名相同,按照年龄升序 */
@Override
public int compareTo(Student o)
{
int result = getName().compareTo(o.getName());
if (result == 0)
{
return getAge() - o.getAge();
} else
{
return result;
}
}
}
解决方案2:TreeSet 实现 比较器接口的构造
// 使用比较器 构造 TreeSet 对象,并对代码进行了重构
/** 实现的是 先按照姓名升序,姓名相同,按照年龄升序 */
Set<Student> set = new TreeSet<Student>(new Comparator<Student>()
{
@Override
public int compare(Student o1, Student o2)
{
int result = o1.getName().compareTo(o2.getName());
if (result == 0)
{
result = o1.getAge() - o2.getAge();
}
return result;
}
});
实现的是 先按照姓名升序,姓名相同,按照年龄升序
,实际完成需求时,需要自行取分析主要条件和次要条件,譬如:给定的需求会是 请按照学生姓名对集合中的元素进行升序排序,那么 主要条件是 姓名,次要条件是 年龄(如果姓名相同的话,如何按照年龄排序,可升序 可降序)
案例:根据学生的姓名长度进行排序
package com.rupeng.set;
import java.util.Comparator;
import java.util.Iterator;
import java.util.Set;
import java.util.TreeSet;
/**
* 需求:根据学生姓名长度进行升序排序
*
* 分析:
* (1)主要条件:学生姓名长度
* (2)次要条件:学生姓名、学生年龄
*
* 编码实现:
* 因为实际给定了一个 比较,使用比较器 构造 TreeSet,
* 相对 使Student 实现自然排序,更有意义,自然排序 应该是只和对象成员变量本身有关,
* 才更有意义,而且一般不要更改已经写好的 类
*/
public class TreeSetDemo3
{
public static void main(String[] args)
{
Set<Student> set = new TreeSet<Student>(new Comparator<Student>()
{
@Override
public int compare(Student o1, Student o2)
{
int result = o1.getName().length() - o2.getName().length();
if (result == 0)
{
result = o1.getName().compareTo(o2.getName());
if (result == 0)
{
result = o1.getAge() - o2.getAge();
}
}
return result;
}
});
set.add(new Student("Joian", 18));
set.add(new Student("Cathy", 18));
set.add(new Student("Du", 27));
set.add(new Student("Du", 27));
set.add(new Student("Yzk", 10));
set.add(new Student("Yzk", 19));
Iterator<Student> it = set.iterator();
while (it.hasNext())
{
Student stu = it.next();
System.out.println(stu);
}
}
}
TreeSet 的排序方式 取决于使用的构造方法:public TreeSet()
自然排序,public TreeSet(Comparator<? extends E> c)
比较器排序
public TreeSet(Comparator<? extends E> c)
/**
* 接口 作为参数传递,实际需要的是 接口的实现类对象:
* 主要三种方式 给定 实现类对象
* (1)匿名内部类(最常用)
* (2)外部类
* (3)借口实现
*/
综合案例1:使用HashSet 重新实现:获取 10个 1~20 范围内的整数,且不可重复
package com.rupeng.set;
import java.util.HashSet;
import java.util.Random;
import java.util.Set;
public class HashSetDemo
{
public static void main(String[] args)
{
Random random = new Random();
Set set = new HashSet();
while (set.size() < 10)
{
int num = random.nextInt(20) + 1;
set.add(num);
}
System.out.println(set);
}
}
综合案例2:键盘录入五个学生的成绩信息,按照总分降序排序
package com.rupeng.set;
import java.util.Comparator;
import java.util.Scanner;
import java.util.Set;
import java.util.TreeSet;
/**
* 需求:键盘录入五个学生的成绩信息,按照总分降序排序
* 格式 为: 姓名 语文成绩 数学成绩 英语成绩。譬如:Joian 90 89 100
*/
public class TreeSetDemo
{
public static void main(String[] args)
{
Scanner sc = new Scanner(System.in);
String[] strs = new String[5];
for (int i = 0; i < strs.length; i++)
{
System.out.println("Please enter the No."+(i + 1)+" student's record:");
strs[i] = sc.nextLine();
}
sc.close();
Set<Student> set = new TreeSet<Student>(new Comparator<Student>()
{
public int compare(Student o1, Student o2)
{
/* int result = o2.getTotal() - o1.getTotal();
if (result == 0)
{
result = o1.getName().compareTo(o2.getName());
if (result == 0)
{
result = o1.getChinese() - o2.getChinese();
if (result == 0)
{
result = o1.getMath() - o2.getMath();
if (result == 0)
{
result = o1.getEnglish() - o2.getEnglish();
}
}
}
}
return result;*/
int num1 = o2.getTotal() - o1.getTotal();
int num2 = (num1 == 0) ? (o1.getName().compareTo(o2.getName()))
: num1;
int num3 = (num2 == 0) ? (o1.getChinese() - o2.getChinese())
: num2;
int num4 = (num3 == 0) ? (o1.getMath() - o2.getMath()) : num3;
int num5 = (num4 == 0) ? (o1.getEnglish() - o2.getEnglish())
: num4;
return num5;
};
});
for (String str : strs)
{
String[] info = str.split(" ");
Student stu = new Student(info[0], Integer.parseInt(info[1]),
Integer.parseInt(info[2]), Integer.parseInt(info[3]));
set.add(stu);
}
System.out.println("----------------------------");
for (Student st : set)
{
System.out.println(st.printScore());
}
}
}
Comparable 自然排序接口
底层源码:
public interface Comparable<T>
{
public int compareTo(T o);
// 实际修饰符 应该是 public abstract
}
接口强行对实现它的每一个类进行整体排序,称为类的 自然排序。类的方法 compareTo
被称为 自然排序方法。
-
实现此接口的对象集合和数组,可以通过对应工具类中的 sort 方法(
Collections.sort()
和Arrays.sort()
)进行整体排序。 -
实现此接口的对象可以作为 有序映射(SortedMap)的键和有序集合(SortedSet)的元素,无需指定比较器(Comparator) (
TreeMap 和 TreeSet 就是有序映射、集合
)
对于集合两个对象 e1 和 e2 ,当且仅当 e1.equals(e2)
和 e1.compareTo(e2)==0
返回相同的 boolean 值时,类的自然排序才叫做与 equals 一致。 现阶段 我们需要这种一致,如果不一致的话,会出现很奇怪的想象,所以注意 与equals一致
。实际上,所有实现 Comparable 接口的 Java 核心类都具有与 equals 一致的自然排序。 但是 java.util.BigDecimal
是个特例,它的自然排序 将值相同、精度不同的对象视为相等。
功能方法
public abstract int compareTo(T o);
比较当前对象与指定对象的顺序。如果当前对象小于、等于或大于指定对象,则分别返回负整数、零或正整数。
实现整体排序,实际的底层 是 常见的排序算法。
Comparator 比较器接口
底层源码:Java 1.8 之前
public interface Comparator<T>
{
// Java 1.8 以前 全部的方法
int compare(T o1, T o2);
boolean equals(Object obj);
// 实际修饰符:public abstract
}
强行对实现接口的 对象集合进行整体排序的比较器。
-
可以将 Comparator 传递给 sort 方法 (
Collections.sort()
和Arrays.sort()
) 从而实现排序顺序的精确控制。 -
可以将使用 Comparator 来控制某些数据结构的顺序 (有序映射 TreeMap 和 有序集合 TreeSet)
-
可以为没有实现自然排序的对象 的集合提供排序
对于Comparator c 的两个元素 e1 和 e2 ,当且仅当 e1.equals(e2)
和 c.compare(e1, e2)==0
返回相同的 boolean 值时,Comparator c 提供的排序才叫做与 equals 一致。
功能方法
int compare(T o1, T o2);
比较用来排序的两个参数。根据第一个参数小于、等于或大于第二个参数分别返回负整数、零或正整数。
boolean equals(Object obj);
指示某个其他对象是否“等于”此 Comparator。仅当 指定的对象也是一个 Comparator,并且强行实施与此 Comparator 相同的排序时,此方法才返回 true。
Map<K, V>
接口
0:接口说明
public interface Map<K,V>
java.util 包下。
Map 接口是 键映射到值的对象。 键具有唯一性,值可重复 。 Map 实际对应的是 数学中函数。键为自变量,值为因变量。 Map 中的每一个键值对,实际就是 函数中每一个自变量与所得因变量的对应关系,这种对应关系在 Map 中的体现是 就是键值对 public static interface Map.Entry<K,V>
,是以内部类(实际是接口)形式定义的 。
public static interface Map.Entry<K,V>
-
Map 的成员方法
entrySet
返回的就是 Map 的 内部类(接口)Entry 的对象集合 。 -
获得映射项引用的唯一方法就是通过 集合视图的 迭代器 实现(新特性 增强 for实质也是迭代器实现的)
-
Map.Entry 仅仅在迭代期间有效,更确切来讲,在迭代器返回项之后,修改底层映射,会造成映射行为的不确定性。
注意
:setValue方法例外
。
Map.Entry 接口成员方法:
boolean equals(Object e)
给定对象 为映射项且和当前对象拥有相同的映射关系,返回 true ,更确切的说,满足如下关系的 e1 和 e2 才具有相同的映射关系(e1.getKey() == null ? e2.getKey() == null : e1.getKey().equals(e2.getKey())) && (e1.getValue() == null ? e2.getValue() : e1.getValue().equals(e2.getValue()))
也就是说 当两个 映射项 对的键和值对影响等的是时候,才说 这两个对应关系是 相同对应关系,允许 null 键值,这可以确保 equals 方法在不同的 Map.Entry 接口实现间可正确地工作。(定义中之所以使用 equals 方法 而不是 == ,是因为可能存在着需要 自定义的相等关系,而不只是 地址相等,可能会是 对象的成员变量对应相等,即视为 对象相等这样的定义,这很常见)int hashCode()
返回此映射项的哈希码值。 映射项 e 的哈希码值的定义如下:(e.getKey() == null ? 0 : e.getKey().hashCode())^(e.getValue() ? 0 :e.getValue().hashCode())
这个确保e1.equals(e2)
因为着 对于任意的两个项 e1 和 e2 而言e1.hashCode() == e2.hashCode()
,这也正是Object.hashCode
常规协定所要求的。K getKey()
获取当前映射项的键V getValue()
获取当前映射项的值V setValue(V value)
设置 当前映射项的值 (注意各种异常),返回 旧的值
Map 接口提供了三个视图:
- 键的集合:键具有唯一性
public Set<K> keySet()
- 值的集合:值可重复
public Collection<V> values()
- 映射项的集合:根据键值的点,键值对(映射项)具有唯一性
public Set<Map.Entry<K,V>> entrySet()
三种视图的迭代器,支持移除操作(remove、removeAll、clear),但是不支持添加操作。
映射顺序:迭代器在视图上返回元素的顺序。TreeMap 保证这种顺序,HashMap 不保证这种顺序。(对比 HashSet、TreeSet【底层实现就是 对应的HashMap、TreeMap的键集合】)
通过映射实现类应该有两种构造器:
- 无参构造
public 实现类名()
- 根据已有映射关系复制建立的
public 实现类名(Map<? extends K,? extends V> m)
注意:equals 和 hashCode 的定义必须是明确的。
1:功能概述
-
a、添加功能
V put(K key, V value)
修改集合中的映射项,返回的是与 key 关联的旧的值,如果该映射项不存在,则执行添加 并返回 null(注意:如果集合 允许 null 值,返回的 null 包含两种可能性:(1)第一次添加;(2)修改之前的值 是 null )void putAll(Map<? extends K, ? extends V> m)
从指定映射 m 中,将其所有得映射项 复制到 当前的映射中。相当于:从 m 中 遍历每一个映射项,并对当前的 映射 进行 put 方法的调用(修改/添加)
-
b、删除功能
-
void clear()
清楚当前映射 -
V remove(Object key)
移除 key 键所在的映射项,如果不存在这样的映射项,则返回 null,如果存在的话,执行删除 并返回 与之对应的 值。 满足的映射项的 键 k 满足以下条件:key == null ? k == null : key.equals(k)
。(注意:如果集合 允许 null 值,返回的 null 包含两种可能性:(1)不能存在对应的映射项;(2)删除的映射项的值 是 null )
-
-
c、判断功能
-
boolean equals(Object o)
给定的 映射 o 和当前映射相等,返回 true,相等的定义是,具有相同的映射关系,也就是o.entrySet().equals(this.entrySet())
返回 true 。注意重写 hashCode 方法,需要满足 Java 的常规协定,hashCode()
返回此映射的哈希码值。映射的哈希码定义为此映射 entrySet() 视图中每个项的哈希码之和。这确保 m1.equals(m2) 对于任意两个映射 m1 和 m2 而言,都意味着m1.hashCode()==m2.hashCode()
,正如 Object.hashCode() 常规协定的要求。 -
boolean isEmpty()
如果 当前的映射不包含任何的键值对(映射项)的话,返回true -
boolean containsKey(Object key)
如果此映射包含指定键的映射关系,则返回 true。更确切地讲,当且仅当此映射包含针对满足(key==null ? k==null : key.equals(k))
的键 k 的映射关系时,返回 true。(最多只能有一个这样的映射关系)。 -
boolean containsValue(Object value)
如果此映射将一个或多个键映射到指定值,则返回 true。更确切地讲,当且仅当此映射至少包含一个对满足(value==null ? v==null : value.equals(v))
的值 v 的映射关系时,返回 true。对于大多数 Map 接口的实现而言,此操作需要的时间可能与映射大小呈线性关系。 (Map 的数据结构只是针对 键 有效,所以这个操作是需要 遍历映射的 所有映射项
)
-
-
d、获取功能
V get(Object key)
返回指定键所映射的值;如果此映射不包含该键的映射关系,则返回 null。 更确切地讲,如果此映射包含满足(key==null ? k==null : key.equals(k))
的键 k 到值 v 的映射关系,则此方法返回 v;否则返回 null。(最多只能有一个这样的映射关系)。(注意:如果集合 允许null值,返回 null 包含两种含义:(1)不存在该项;(2)该项的值是null)int size()
该映射中键值对(映射项) 的个数。注意 int 值的范围键的集合
:键具有唯一性public Set<K> keySet()
值的集合
:值可重复public Collection<V> values()
映射项的集合
:根据键值的点,键值对(映射项)具有唯一性public Set<Map.Entry<K,V>> entrySet()
获取映射中的项,必须使用 迭代器
- 键集合 + get 方法
- 键值集合 + Entry<K, V> 获取功能
案例:遍历映射实例
- 方式1:键集合 + get 方法
package com.rupeng.map;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
/** 遍历 映射 */
public class MapDemo1
{
public static void main(String[] args)
{
Map<Integer, String> map = new HashMap<Integer, String>();
map.put(1, "Joian");
map.put(2, "Yzk");
map.put(3, "Cathy");
map.put(4, "Apple");
Set<Integer> set = map.keySet();
for (Integer in : set)
{
System.out.println(in + " " + map.get(in));
}
}
}
- 方式2:键值集合 + Entry<K, V> 获取功能
package com.rupeng.map;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
/** 遍历 映射 */
public class MapDemo1
{
public static void main(String[] args)
{
Map<Integer, String> map = new HashMap<Integer, String>();
map.put(1, "Joian");
map.put(2, "Yzk");
map.put(3, "Cathy");
map.put(4, "Apple");
Set<Entry<Integer, String>> set = map.entrySet();
for (Iterator<Entry<Integer, String>> it = set.iterator(); it.hasNext();)
{
Map.Entry<Integer, String> me = it.next();
System.out.println(me.getKey() + " " + me.getValue());
}
}
}
2:实现子类
HashMap
基于哈希表的Map接口实现
四个特性:
Map 中键唯一,值可重复,映射项唯一
,这个是所有的 Map实现类的**共性
**- Map 的数据结构 只对 键有效,与值无关,第一点的映射项唯一就是依靠键的唯一实现
- HashMap 允许 null 键、null值(但这样的键值对,最多出现一次)
- HashMap 不保证 映射的顺序,特别是不保证其顺序很久不变。
注意
: 如果出现存入的顺序和取出的顺序一样,并不能说明 HashMap的有序性,只是 HashMap 也有其存储的顺序 而已。
使用四个案例 来说明,HashMap的特性
-
案例1:HashMap<String, String>
// 自己写去吧
-
案例2:HashMap<Integer, String>
// 自己写完吧
- 案例3:HashMap<String, Student>
package com.rupeng.map;
import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
/** c: Map<String,Student> */
public class HashMapDemo3
{
public static void main(String[] args)
{
Map<String, Student> map = new HashMap<String, Student>();
Student stu1 = new Student("Joian", 18);
Student stu2 = new Student("Cathy", 10);
map.put("2008", stu1);
map.put("2011", stu1);
map.put("2011", stu2);
/** 键相同 只添加一次,之后的操作是 进行 值的修改 */
Set<Map.Entry<String, Student>> set = map.entrySet();
for (Entry<String, Student> entry : set)
{
System.out.println(entry.getKey() + " " + entry.getValue());
}
}
}
- 案例4:HashMap<Student, String>
package com.rupeng.map;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
/** d: Map<Student,String> */
public class HashMapDemo4
{
public static void main(String[] args)
{
Map<Student, String> map = new HashMap<Student, String>();
Student stu1 = new Student("Joian", 18);
Student stu2 = new Student("Cathy", 10);
Student stu3 = new Student("Joian", 18);
map.put(stu1, "高中");
map.put(stu2, "大学");
map.put(stu3, "初中");
Set<Student> set = map.keySet();
for (Student stu : set)
{
System.out.println(stu + " " + map.get(stu));
}
}
}
重写 equals 和 hashCode 如下:
@Override
public int hashCode()
{
return age * 18 + (name == null ? 0 : name.hashCode());
}
@Override
public boolean equals(Object obj)
{
if (this == obj)
{
return true;
}
if (obj == null)
{
return false;
}
if (!(obj instanceof Student))
{
return false;
}
Student other = (Student) obj;
if (age != other.age)
{
return false;
}
if (name == null)
{
if (other.name != null)
{
return false;
} else if (!name.equals(other.name))
{
return false;
}
}
return true;
}
LinkedHashMap
基于链表和哈希表的Map接口实现
四个特性:
Map 中键唯一,值可重复,映射项唯一
,这个是所有的 Map实现类的**共性
**- Map 的数据结构 只对 键有效,与值无关
- LinkedHashMap 允许 null 键、null值(但这样的键值对,最多出现一次,继承自HashMap 的特性)
- LinkedHashMap 保证 映射的顺序,底层数据结构 链表实现,键的唯一性依赖底层数据结构 哈希表。保证 插入的顺序和取出的顺序相同
package com.rupeng.map;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Set;
/** 验证插入、取出的有序性 */
public class HashMapDemo1
{
public static void main(String[] args)
{
Map<String, String> map = new LinkedHashMap<String, String>();
map.put("2011", "软件技术 一年级");
map.put("2012", "软件技术 二年级");
map.put("2015", "软件工程 四年级");
map.put("2013", "软件技术 三年级");
map.put(null, null);
map.put(null, "我在逗你呢");
map.put("2014", "软件工程 三 年级");
Set<Map.Entry<String, String>> set = map.entrySet();
for (Map.Entry<String, String> entry : set)
{
System.out.println(entry.getKey() + " : " + entry.getValue());
}
}
}
TreeMap
基于红黑树(Red-Black tree)的 NavigableMap 实现。该映射根据其键的自然顺序 Compare 进行排序【元素具有排序性】,者根据创建映射时提供的 Comparator 进行排序【集合具有排序性】,具体取决于使用的构造方法。
此实现为 containsKey、get、put 和 remove 操作提供受保证的 log(n) 时间开销。
注意,如果要正确实现 Map 接口,则有序映射所保持的顺序(无论是否明确提供了比较器)都必须与 equals 一致。(关于与 equals 一致 的精确定义,请参阅 Comparable 或 Comparator:(1)对于集合或映射两个对象 e1 和 e2 ,当且仅当 e1.equals(e2)
和 e1.compareTo(e2)==0
返回相同的 boolean 值时,类的自然排序才叫做与 equals 一致;(2)对于Comparator c 的两个元素 e1 和 e2 ,当且仅当 e1.equals(e2)
和 c.compare(e1, e2)==0
返回相同的 boolean 值时,Comparator c 提供的排序才叫做与 equals 一致。)。这是因为 Map 接口是按照 equals 操作定义的,但有序映射使用它的 compareTo(或 compare)方法对所有键进行比较,因此从有序映射的观点来看,此方法认为相等的两个键就是相等的。即使排序与 equals 不一致,有序映射的行为仍然是 定义良好的,只不过没有遵守 Map 接口的常规协定。
TreeSet 实现 键的唯一性 和 排序性
,进而实现映射项的唯一和排序性。
构造器:
TreeMap()
使用键的自然顺序构造一个新的、空的树映射。TreeMap(Comparator<? super K> comparator)
构造一个新的、空的树映射,该映射根据给定比较器进行排序。TreeMap(Map<? extends K,? extends V> m)
构造一个与给定映射具有相同映射关系的新的树映射,该映射根据其键的自然顺序 进行排序。TreeMap(SortedMap<K,? extends V> m)
构造一个与指定有序映射具有相同映射关系和相同排序顺序的新的树映射。
分别使用 自然排序和比较器排序实现下面的案例(对比 TreeSet 案例需求 自行完成)
- 案例1:TreeMap<String, String>
package com.rupeng.map;
import java.util.Comparator;
import java.util.Map;
import java.util.TreeMap;
public class TreeMapDemo
{
public static void main(String[] args)
{
Map<String, String> map = new TreeMap<String, String>(
new Comparator<String>()
{
@Override
public int compare(String o1, String o2)
{
return 0;
}
});
map.put("a", "Aa");
map.put("v", "Vv");
map.put("d", "Dd");
map.put("l", "Ll");
map.put("m", "Mm");
System.out.println(map);
}
}
- 案例2:HashMap<Student, String>
package com.rupeng.map;
import java.util.Map;
import java.util.TreeMap;
public class TreeMapDemo2
{
public static void main(String[] args)
{
Map<Student, String> map = new TreeMap<>();
Student stu1 = new Student("Joian", 18);
Student stu2 = new Student("Cathy", 10);
Student stu3 = new Student("Joian", 18);
map.put(stu1, "高中");
map.put(stu2, "大学");
map.put(stu3, "初中");
System.out.println(map);
}
}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-51zDqOb1-1651334169076)(./1440680461951.png)]
解决方法:
- 元素具有排序性:Student 实现 自然排序接口 Comparable 。构造器 :无参构造
- 集合具有排序性:TreeMap 给定比较器实例 作为参数构造 TreeMap 实例
/** 改写Student,实现接口Comparable */
public class Student implements Comparable<Student>
{
@Override
public int compareTo(Student obj)
{
if (this == obj)
{
return 0;
}
Student other = (Student) obj;
int result0 = name.compareTo(other.name);
int result = (result0 == 0) ? age - other.age : result0;
return result;
}
}
/** 改写TreeMap的构造器,传递 Comparator实现类对象作为参数 */
Map<Student, String> map = new TreeMap<>(new Comparator<Student>()
{
@Override
public int compare(Student o1, Student o2)
{
if (o1 == o2)
{
return 0;
}
int result0 = o1.getName().compareTo(o2.getName());
int result = (result0 == 0) ? (o2.getAge() - o1.getAge())
: result0;
return result;
}
});
综合案例
案例1:统计字符串中每一种字符出现的次数,并格式化打印,比如:“a(5)b(3)”,表示a出现5次、b出现3次
- 方式1:传统统计思想
package com.rupeng.map;
/**
* 需求:统计字符串中每一种字符出现的次数,并格式化打印
* 比如:"a(5)b(3)",表示a出现5次、b出现3次 方式1:传统统计思想
*
* 分析:
* (1)定义一个字符串
* (2)获取每一个字符
* (3)统计每一个字符出现的次数
* (4)定义一个字符串生成器对象
* (5)按照格式拼接
* (6)调用字符串生成器的toString,转换为字符串并打印
*/
public class Demo1
{
public static void main(String[] args)
{
/** (1)定义一个字符串 */
String str = "aasasdsadfasdasfgasewsfd";
/** (2)获取每一中字符 */
char[] chr0 = str.toCharArray();
StringBuilder sb0 = new StringBuilder();
for (int i = 0; i < chr0.length; i++)
{
if (!sb0.toString().contains(chr0[i] + ""))
{
sb0.append(chr0[i]);
}
}
char[] chr = sb0.toString().toCharArray();
/** (3)统计每一个字符出现的次数 */
int[] nums = new int[chr.length];
for (int i = 0; i < chr.length; i++)
{
for (int j = 0; j < chr0.length; j++)
{
if (chr[i] == chr0[j])
{
nums[i]++;
}
}
}
/** (4)定义一个字符串生成器对象 */
StringBuilder sb = new StringBuilder();
/** (5)按照格式拼接 */
for (int i = 0; i < chr.length; i++)
{
sb.append(chr[i] + "(" + nums[i] + ")");
}
/** (6)调用字符串生成器的toString,转换为字符串并打印 */
String outStr = sb.toString();
System.out.println(outStr);
}
}
- 方式2:定义一个映射Map<Character,Integer>
package com.rupeng.map;
import java.util.TreeMap;
import java.util.Map;
import java.util.Set;
/**
* 需求:统计字符串中每一种字符出现的次数,并格式化打印
* 比如:"a(5)b(3)",表示a出现5次、b出现3次 方式1:传统统计思想
*
* 分析:
* (1)定义一个字符串
* (2)定义一个Map<Character,Integer>
* (3)获取字符串中的每一个字符
* (4)将获取的字符作为键在Map中获取值
* a、返回 null ,说明不存在 添加 put(c,1)
* b、返回 非null,说明已经存在,修改值 put(c,value+1)
* (5)定义一个字符串生成器
* (6)按照格式拼接
* (7)调用字符串生成器的toString,转换为字符串并打印
*/
public class Demo2
{
public static void main(String[] args)
{
/** (1)定义一个字符串*/
String str = "aasasdsadfasdasfgasewsfd";
/** (2)定义一个Map<Character,Integer>*/
Map<Character,Integer> map = new TreeMap<Character,Integer>();
/** (3)获取字符串中的每一个字符*/
char[] chr = str.toCharArray();
/** (4)将获取的字符作为键在Map中获取值*/
for (char c : chr)
{
/** a、返回 null ,说明不存在 添加 put(c,1)*/
if(map.get(c) == null)
{
map.put(c,1);
}
/** b、返回 非null,说明已经存在,修改值 put(c,value+1)*/
else {
map.put(c, map.get(c)+1);
}
}
/** (5)定义一个字符串生成器*/
StringBuilder sb = new StringBuilder();
/** (6)按照格式拼接*/
Set<Character> keyset = map.keySet();
for (Character ch : keyset)
{
sb.append(ch).append("(").append(map.get(ch)).append(")");
}
/** (7)调用字符串生成器的toString,转换为字符串并打印*/
String outStr = sb.toString();
System.out.println(outStr);
}
}
案例2:集合的嵌套使用
- HashMap嵌套HashMap
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ituFbO1v-1651334169079)(./1440683547885.png)]
- HashMap嵌套ArrayList
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-CFyQlPF6-1651334169079)(./1440684115465.png)]
- ArrayList嵌套HashMap
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-wDECwHKz-1651334169080)(./1440684677293.png)]
- HashMap自身嵌套三层
面试题
解答::不对,如果两个对象 x 和 与满足 x.equals(y) == true
,它们的哈希码值(hash code)应当相同。Java对于equals和hashCode 方法是这样规定的:(1)如果两个对象相同(equals方法返回true),那么它们的hashCode值一定相同;(2)如果两个对象的hashCode相同,它们并不一定相同。当然,你未必要按照要求去做,但是如果你违背了上述原则就会发现,在使用容器时,相同的对象可以在Set集合中,同时增加新元素的效率会大大降低(对于使用哈希存储的系统,如果哈希码频繁的冲突,会造成存取性能急剧下降)。
补充:关于 equals 和 hasnCode 方法,很多 Java 程序员都知道,但很多也就仅仅是知道而已,在 Joshua Bloch 的大作 《Effective Java》中这样介绍 equals 方法:首先equals方法必须满足自反性(x.equals(x) 必须返回 true)、对称性(x.equals(y) 和 y.equals(x) 必须相同返回)、传递性(x.equals(y) 和 x.equals(z) 返回 true,x.equals(z) 必须返回 true)、一致性(x.equals(y) 在 没有改变 x、y信息的时候,必须永远具有相同的返回),而且对于任何非 null 值的引用 x , x.equals(null) 必须返回 false。实现高质量 equals 方法的诀窍包括:
- (1) 使用 “==” 操作符检查 “参数是否为当前对象的引用”;
- (1-2)对于任何非 null 值的引用 x , x.equals(null) 必须返回 false
- (2)使用 instanceOf 操作符检查 “参数是否为正确的类型”;快捷重写中使用的是
getClass()
方法 - (3)对于类中的关键属性,检查参数传入对象的属性是否与之相等;
- (4)编写完成 equals 方法后,问问自己它是否满足 对称性、传递性、一致性、自反性;
- (5)重写 equals 总是要重写 hashCode 方法;
- (6)不要将 equals 方法总的 Object 对象替换成任何的其它类型,在重写的时候,不要忘记 @Override 注解。
快捷生成的Student类的 equals 方法
/** (5)重写 equals 时,总是需要重写 hashCode 方法 */
@Override
public int hashCode()
{
final int prime = 31;
int result = 1;
result = prime * result + age;
result = prime * result + ((name == null) ? 0 : name.hashCode());
return result;
}
@Override
public boolean equals(Object obj)
{
/** (1)使用 == 检查参数是否为当前对象的引用 */
/** 若是,直接返回 true 【自反性:x.equals(x) 为true】 */
if (this == obj)
return true;
/** (1-2)检查参数是否为 null,为null 则必须返回 false */
if (obj == null)
return false;
/** (2)使用 getClass 检查参数是否当前类的对象 */
/** 若不是,直接返回 false 【也可使用 instanceOf 符号判断】*/
if (getClass() != obj.getClass())
/** 等价于:if(!(obj instanceOf Student)) */
return false;
/** 若是,进行强制类型转化,调用方法判断对应属性是否相同 */
Student other = (Student) obj;
/** (3)检查参数的关键属性,是否和当前对象相同 */
/** 先进行基本数据类型属性的对比 */
if (age != other.age)
return false;
/** 再进行引用数据类型属性的对比 */
/** 满足 name == null ? obj.name == null : name.equals(obj.name) 时,为 true */
if (name == null)
{
if (other.name != null)
return false;
} else if (!name.equals(other.name))
return false;
return true;
}
解答: HashMap 和 Hashtable 都实现了 Map 接口,所以有所有Map接口的特性,比如:底层数据结构 针对键有效,键唯一、值可重复等。存在以下的不同点:(1)HashMap 允许 键值为 null,Hashtable 则不允许;(2)HashMap 是不同步的,单线程高效,Hashtable 是同步的,多线程安全;(3)HashMap 提供了可供应用迭代的键的集合,因此,HashMap 是快速失败的。另一方面,Hashtable 提供了对键的列举(Enumeration);【迭代器和枚举接口的不同点】(4)一般认为 Hashtable 是一个遗留的类。
**解答:**Enumeration 速度是 Iterator 的2倍,同时占用更少的内存。但是,Iterator 远远比 Enumeration 安全,因为其他线程不能够修改正在被 iterator 遍历的集合里面的对象,会导致 快速失败的。同时,Iterator 允许调用者删除底层集合里面的元素,这对 Enumeration 来说是不可能的。Iterator 提供了书写更加简单的方法名,并且额外提供了一个移除操作,Eunmeration 与 Iterator 重复功能。新的实现应该优先 考虑Iterator而不是Enumeration。
**解答:**List、Set 是,Map 不是。Map 是键值对映射容器,与 List 和 Set 有明显的区别,而 Set 存储的零散的元素且不允许有重复元素(数学中的集合也是如此),List 是线性结构的容器,适用于按数值索引访问元素的情形。
**解答:**Collection 接口 是单列集合的顶层接口,有子接口 List、Set、Queue等;Collections 工具类 提供了对集合进行操作的一系列静态方法,诸如:排序和二分查找等。
集合工具类 Collections
0:类的描述
public class Collections extends Object
此类完全由在 collection 上进行操作或返回 collection 的静态方法组成。它包含在 collection 上操作的多态算法,即“包装器”,包装器返回由指定 collection 支持的新 collection,以及少数其他内容。
如果为此类的方法所提供的 collection 或类对象为 null,则这些方法都将抛出 NullPointerException。
1:功能概述
Collections 会根据集合或元素的排序性,提供两个相同功能的方法。
-
排序功能
该排序算法是一个经过修改的合并排序算法
。此算法提供可保证的 n log(n) 性能。 此实现将指定列表转储到一个数组中,并对数组进行排序,在重置数组中对相应位置上的每个元素的列表上进行迭代。这避免了由于试图原地对链接列表进行排序而产生的 n2 log(n) 性能。public static <T extends Comparable<? super T>> void sort(List<T> list)
根据列表中元素本身的自然排序进行 升序排序。元素必须实现Comparable,否则抛出异常 ClassCastException 。public static <T> void sort(List<T> list, Comparator<? super T> c)
根据为列表提供的比较器进行 升序排序。列表中元素必须可相互比较,否则抛出异常 ClassCastException 。
-
查找功能
-
public static <T> int binarySearch(List<? extends Comparable<? super T>> list, T key)
使用二分搜索法搜索指定列表,以获得指定对象。在进行此调用之前,必须根据列表元素的自然顺序对列表进行升序排序(通过sort(List)
方法)。如果没有对列表进行排序,则结果是不确定的。如果列表包含多个等于指定对象的元素,则无法保证找到的是哪一个。 -
public static <T> int binarySearch(List<? extends T> list, T key, Comparator<? super T> c)
使用二分搜索法搜索指定列表,以获得指定对象。在进行此调用之前,必须根据指定的比较器对列表进行升序排序(通过 sort(List, Comparator) 方法)。如果没有对列表进行排序,则结果是不确定的。如果列表包含多个等于指定对象的元素,则无法保证找到的是哪一个。 -
上述方法返回值:(1)存在 key,返回索引;(2)不存在 key,将第一个大于key的元素的索引作为插入点,并返回
-(插入点)-1
、如果key大于所有的元素,则返回-list.size()-1
-
-
最值功能(同样两种方法,自然排序和比较器排序)
- 最小值
- 最大值
-
置换功能
public static void reverse(List<?> list)
逆向置换(反转)列表,线性时间运行public static void shuffle(List<?> list)
根据默认的随机源 随机置换列表,线性时间运行
案例1:ArrayList 存储自定义对象 排序查找最值
package com.rupeng.collections;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
public class Demo1
{
public static void main(String[] args)
{
List<Student> list = new ArrayList<Student>();
Student stu1 = new Student("Cathy", 21);
Student stu2 = new Student("Joian", 22);
Student stu4 = new Student("Du", 29);
Student stu3 = new Student("Yzk", 37);
list.add(stu3);
list.add(stu2);
list.add(stu1);
list.add(stu4);
System.out.println("---初始列表---");
for (Student stu : list)
{
System.out.println(stu);
}
System.out.println("---排序之后的列表---");
/** 元素的自然排序 */
System.out.println("元素的自然排序");
Collections.sort(list);
for (Student stu : list)
{
System.out.println(stu);
}
/** 二分查找之前必须先进性对应的排序 */
System.out.println("--查找 Cathy_21 的学生(自然排序)--");
int index = Collections.binarySearch(list, new Student("Cathy", 21));
if (index >= 0)
{
System.out.println("所在位置是 " + index);
}
/** 自然排序和比较器排序同时存在时,以比较器排序 */
System.out.println("列表的比较器排序");
/** 自定义的Comparator实现类,实现按照姓名长度升序 */
MyComparator mc = new MyComparator();
Collections.sort(list, mc);
for (Student stu : list)
{
System.out.println(stu);
}
/** 二分查找之前必须先进性对应的排序 */
System.out.println("--查找 Cathy_21 的学生(比较器排序)--");
index = Collections.binarySearch(list, new Student("Cathy", 21), mc);
if (index >= 0)
{
System.out.println("所在位置是 " + index);
}
System.out.println("--查找 \"最大\"的学生(自然排序)--");
Student stuMax = Collections.max(list);
System.out.println(stuMax);
System.out.println("--查找 \"最大\"的学生(比较器排序)--");
stuMax = Collections.max(list, mc);
System.out.println(stuMax);
}
}
案例2:模拟斗地主的洗牌、发牌
package com.rupeng.collections;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.TreeSet;
/*
* 思路:
* A:创建一个HashMap集合
* B:创建一个ArrayList集合
* C:创建花色数组和点数数组
* D:从0开始往HashMap里面存储编号,并存储对应的牌
* 同时往ArrayList里面存储编号即可。
* E:洗牌(洗的是编号)
* F:发牌(发的也是编号,为了保证编号是排序的,就创建TreeSet集合接收)
* G:看牌(遍历TreeSet集合,获取编号,到HashMap集合找对应的牌)
*/
public class PokerDemo
{
public static void main(String[] args)
{
// 创建一个HashMap集合
HashMap<Integer, String> hm = new HashMap<Integer, String>();
// 创建一个ArrayList集合
ArrayList<Integer> array = new ArrayList<Integer>();
// 创建花色数组和点数数组
// 定义一个花色数组
String[] colors = { "♠", "♥", "♣", "♦" };
// 定义一个点数数组
String[] numbers = { "3", "4", "5", "6", "7", "8", "9", "10", "J", "Q","K", "A", "2", };
// 从0开始往HashMap里面存储编号,并存储对应的牌,同时往ArrayList里面存储编号即可。
int index = 0;
for (String number : numbers)
{
for (String color : colors)
{
String poker = color.concat(number);
hm.put(index, poker);
array.add(index);
index++;
}
}
hm.put(index, "小王");
array.add(index);
index++;
hm.put(index, "大王");
array.add(index);
// 洗牌(洗的是编号)
Collections.shuffle(array);
// 发牌(发的也是编号,为了保证编号是排序的,就创建TreeSet集合接收)
TreeSet<Integer> player1 = new TreeSet<Integer>();
TreeSet<Integer> player2 = new TreeSet<Integer>();
TreeSet<Integer> player3 = new TreeSet<Integer>();
TreeSet<Integer> remain = new TreeSet<Integer>();
for (int x = 0; x < array.size(); x++)
{
if (x >= array.size() - 3)
{
remain.add(array.get(x));
} else if (x % 3 == 0)
{
player1.add(array.get(x));
} else if (x % 3 == 1)
{
player2.add(array.get(x));
} else if (x % 3 == 2)
{
player3.add(array.get(x));
}
}
// 看牌(遍历TreeSet集合,获取编号,到HashMap集合找对应的牌)
lookPoker("Player1", player1, hm);
lookPoker("Player2", player2, hm);
lookPoker("Player3", player3, hm);
lookPoker("Remain", remain, hm);
}
// 写看牌的功能
public static void lookPoker(String name, TreeSet<Integer> ts,
HashMap<Integer, String> hm)
{
System.out.print(name + ":");
for (Integer key : ts)
{
String value = hm.get(key);
System.out.print(value + " ");
}
System.out.println();
}
}
集合框架
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-nxKalDKI-1651334169081)(./collections.png)]
通用方法
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-hUYg6qjy-1651334169082)(./通用方法.png)]
性能测试
List性能比较
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-CRPLdOPb-1651334169082)(./1list 性能测试.png)]
Set性能比较
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-2vGK5z24-1651334169083)(./2set 性能测试.png)]
Map性能比较
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-vW01XkzC-1651334169083)(./3map 性能测试.png)]