(一)、Set系列集合
Set系列集合特点
- 无序:存取顺序不一致
- 不重复:可以去除重复
- 无索引:没有带索引的方法,所以不能使用普通for循环遍历,也不能通过索引来获取元素。
Set集合实现类特点
- HashSet : 无序、不重复、无索引。//get()取不到值,第一次运行顺序统一
- LinkedHashSet:有序、不重复、无索引。
- TreeSet:排序、不重复、无索引。
Set集合的功能上基本上与Collection的API一致。
实现类:HashSet集合元素无序的底层原理:哈希表
- HashSet集合底层采取哈希表存储的数据。
- 哈希表是一种对于增删改查数据性能都较好的结构。
哈希表的组成
- JDK8之前的,底层使用数组+链表组成
- JDK8开始后,底层采用数组+链表+红黑树组成。
哈希值
- 是JDK根据对象的地址,按照某种规则算出来的int类型的数值。
Object类的API
- public int hashCode():返回对象的哈希值
对象的哈希值特点
- 同一个对象多次调用hashCode()方法返回的哈希值是相同的
- 默认情况下,不同对象的哈希值是不同的。//因为地址不同
public class SetDemo2 {
public static void main(String[] args) {
//目标:学会获取对象的哈希值,并确认
String name = "Java";
System.out.println(name.hashCode());
System.out.println(name.hashCode());
String name1 = "java";
System.out.println(name1.hashCode());
System.out.println(name1.hashCode());
}
}
HashSet1.7版本原理解析:数组 + 链表 +(结合哈希算法)
- 创建一个默认长度16的数组,数组名table
- 根据元素的哈希值跟数组的长度求余计算出应存入的位置(哈希算法)
- 判断当前位置是否为null,如果是null直接存入
- 如果位置不为null,表示有元素,则调用equals方法比较
- 如果一样,则不存,如果不一样,则存入数组,
- JDK 7新元素占老元素位置,指向老元素
- JDK 8中新元素挂在老元素下面
结论:哈希表是一种对于增删改查数据性能都较好的结构。
JDK1.8版本开始HashSet原理解析
- 底层结构:哈希表(数组、链表、红黑树的结合体)
- 当挂在元素下面的数据过多时,查询性能降低,从JDK8开始后,当链表长度超过8的时候,自动转换为红黑树。
import java.util.HashSet;
import java.util.Set;
public class SetDemo3 {
public static void main(String[] args) {
Set<Student> sets = new HashSet<>();
Student s1 = new Student("aaa",12,'a');
Student s2 = new Student("aaa",12,'a');
Student s3 = new Student("bbb",12,'a');
System.out.println(s1.hashCode());
System.out.println(s2.hashCode());
System.out.println(s3.hashCode());
sets.add(s1);
sets.add(s2);
sets.add(s3);
System.out.println(sets);
}
}
比较哈希值,对象元素相等哈希值也相等,比较然后剔除
如果希望Set集合认为2个内容相同的对象,就重写对象的hashCode和equals方法。
不重写,重复值就依然重复!
LinkedHashSet集合概述和特点
- 有序、不重复、无索引。
- 这里的有序指的是保证存储和取出的元素顺序一致
- 原理:底层数据结构是依然哈希表,只是每个元素又额外的多了一个双链表的机制记录存储的顺序。
LinkedHashSet集合的特点和原理
- 有序、不重复、无索引
- 底层基于哈希表,使用双链表记录添加顺序。
TreeSet集合概述和特点
- 不重复、无索引、可排序
- 可排序:按照元素的大小默认升序(有小到大)排序。
- TreeSet集合底层是基于红黑树的数据结构实现排序的,增删改查性能都较好。
- 注意:TreeSet集合是一定要排序的,可以将元素按照指定的规则进行排序。
TreeSet集合默认的规则
- 对于数值类型:Integer , Double,官方默认按照大小进行升序排序。
- 对于字符串类型:默认按照首字符的编号升序排序。
- 对于自定义类型如Student对象,TreeSet无法直接排序。
结论:想要使用TreeSet存储自定义类型,需要制定排序规则
自定义排序规则:TreeSet集合存储对象的的时候有2种方式可以设计自定义比较规则
方式一 :让自定义的类(如学生类)实现Comparable接口重写里面的compareTo方法来定制比较规则。
方式二:TreeSet集合有参数构造器,可以设置Comparator接口对应的比较器对象,来定制比较规则。
两种方式中,关于返回值的规则:
- 如果认为第一个元素大于第二个元素返回正整数即可。
- 如果认为第一个元素小于第二个元素返回负整数即可。
- 如果认为第一个元素等于第二个元素返回0即可,此时Treeset集合只会保留一个元素,认为两者重复。
注意:如果TreeSet集合存储的对象有实现比较规则,集合也自带比较器,默认使用集合自带的比较器排序
import java.util.Comparator;
import java.util.Set;
import java.util.TreeSet;
public class SetDemo5 {
/*目标:观察TreeSet对于有值特征的数据如何排序
* 学会自定义类型的对象进行指定规则排序*/
public static void main(String[] args) {
Set<Integer> sets = new TreeSet<>();
sets.add(23);
sets.add(65);
sets.add(11);
sets.add(45);
System.out.println(sets);
Set<String> set1 = new TreeSet<>();
set1.add("Java");
set1.add("Jcva");
set1.add("Jdva");
set1.add("Java1");
System.out.println(set1);
System.out.println("--------------");
Set<Student1> xs = new TreeSet<>((Student1 o1, Student1 o2)-> {
return o1.age - o2.age >= 0 ? 1:-1;
//注意:浮点型建议使用Double.compare()
});
xs.add(new Student1("aaa",12,'a'));
xs.add(new Student1("dxdx",31,'h'));
xs.add(new Student1("fsaf",63,'t'));
xs.add(new Student1("aaa",2,'l'));
System.out.println(xs);
}
}
//implements Comparable<Student1>
/*public int compareTo(Student1 o) {
* return this.age - o.age >= 0 ? 1:-1;//可保留
* //return this.age - o.age; 会去掉重量重复的元素
* }
*/
(二)、补充知识:可变参数
可变参数的特点
- 可变参数用在形参中可以接收多个数据。
- 可变参数的格式:数据类型...参数名称
可变参数的作用
- 接收参数非常灵活方便。可以不接收参数,可以接收1个或者多个参数,也可以接收一个数组
- 可变参数在方法内部本质上就是一个数组。
public class MethodDemo {
public static void main(String[] args) {
sun();//不传参数
sun(2);
sun(new int[] {10,20,10,29});//也可传数组
}
public static void sun(int...nums){
//注意:可变参数在方法内部就是一个数组,nums
System.out.println("元素个数:" + nums.length);
System.out.println("元素内容:" + Arrays.toString(nums));
}
}
可变参数的注意事项:(防止冲突)
- 一个形参列表中可变参数只能有一个//(int...age,int...ages)
- 可变参数必须放在形参列表的最后面// (int age,int...ages)正确,(int...age,int ages)
(三)、斗地主游戏案例分析
需求: 在启动游戏房间的时候,应该提前准备好54张牌,完成洗牌、发牌、牌排序、逻辑。
分析:
- 当系统启动的同时需要准备好数据的时候,就可以用静态代码块了。
- 洗牌就是打乱牌的顺序。
- 定义三个玩家、依次发出51张牌
- 给玩家的牌进行排序(拓展)
- 输出每个玩家的牌数据。
public class Card {
private String size;
private String color;
private int index;//牌的真正大小
public int getIndex() {
return index;
}
public void setIndex(int index) {
this.index = index;
}
public String getSize() {
return size;
}
public void setSize(String size) {
this.size = size;
}
public String getColor() {
return color;
}
public void setColor(String color) {
this.color = color;
}
public Card(String size, String color,int index) {
this.size = size;
this.color = color;
this.index = index;
}
public Card() {
}
@Override
public String toString() {
return size + color;
}
}
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
public class GameDemo {
//定义一个静态的集合存储54张牌对象
public static List<Card> allCards = new ArrayList<>();
//做牌,定义静态代码块初始化派数据
static{
//定义点数:个数确定,类型数据,使用数组
String[] sizes = {"3","4",
"5","6","7","8","9","10","J","Q","K","A","2"};
String[] colors = {"ht","hot","hm","hom"};
int index = 0;
for (String s : sizes) {
index++;
for (String s1 : colors) {
Card c = new Card(s,s1,index);
allCards.add(c);
}
}
Card c1 = new Card("","xking",++index);
Card c2 = new Card("","xking",++index);
Collections.addAll(allCards,c1,c2);
System.out.println("新增:" + allCards);
}
public static void main(String[] args) {
//洗牌
Collections.shuffle(allCards);
System.out.println("洗牌过后:" + allCards);
//发牌
List<Card> l1 = new ArrayList<>();
List<Card> l2 = new ArrayList<>();
List<Card> l3 = new ArrayList<>();
//开始发牌
for (int i = 0; i < allCards.size()-3; i++) {
Card c = allCards.get(i);
if(i%3==0){
l1.add(c);
}else if(i%3==1){
l2.add(c);
}else if (i%3==2){
l3.add(c);
}
}
//最后拿,组成子集合
List<Card> lastThreeCards = allCards.subList(allCards.size()-3,allCards.size());
sortCards(l1);
sortCards(l2);
sortCards(l3);
//输出牌
System.out.println("l1: " + l1);
System.out.println("l2: " + l2);
System.out.println("l3: " + l3);
System.out.println("最后三张牌:" + lastThreeCards);
}
private static void sortCards(List<Card> cards){
Collections.sort(cards, new Comparator<Card>() {
@Override
public int compare(Card o1, Card o2) {
return o2.getIndex() - o1.getIndex();
}
});
}
}
(四)、Map集合体系
Map集合概述和使用
- Map集合是一种双列集合,每个元素包含两个数据。
- Map集合的每个元素的格式:key=value(键值对元素)。
- Map集合也被称为“键值对集合”。
Map集合整体格式
- Collection集合的格式: [元素1,元素2,元素3..]
- Map集合的完整格式:{key1=value1 , key2=value2 , key3=value3 , ...}
Map集合的使用场景之一:购物车系统 -> {商品1=2 , 商品2=3 , 商品3 = 2 , 商品4= 3}
- 购物车提供的四个商品和购买的数量在后台需要容器存储。
- 每个商品对象都一一对应一个购买数量。
- 把商品对象看成是Map集合的建,购买数量看成Map集合的值。
Map集合体系特点
- Map集合的特点都是由键决定的。
- Map集合的键是无序,不重复的,无索引的,值不做要求(可以重复)。
- Map集合后面重复的键对应的值会覆盖前面重复键的值。
- Map集合的键值对都可以为null。
Map集合实现类特点
- HashMap:元素按照键是无序,不重复,无索引,值不做要求。(与Map体系一致)
- LinkedHashMap:元素按照键是有序,不重复,无索引,值不做要求。
- TreeMap:元素按照建是排序,不重复,无索引的,值不做要求。
import java.util.HashMap;
import java.util.Map;
//目标:认识Map体系
public class MapDemo1 {
public static void main(String[] args) {
//创建一个Map集合对象
Map<String,Integer> maps = new HashMap<>();
maps.put("java" ,3);
maps.put("python" ,7);
maps.put("c语言" ,null);
maps.put("go语言" ,4);
maps.put(null ,null);
System.out.println(maps);
}
}
Map集合API
public class MapDemo1 { public static void main(String[] args) { //创建一个Map集合对象 Map<String,Integer> maps = new HashMap<>(); //Map集合的API接口 //添加元素 maps.put("java" ,3); maps.put("python" ,7); maps.put("python" ,10);//相同键,对应的值会被后者覆盖 maps.put("c语言" ,null); maps.put("go语言" ,4); maps.put(null ,null); System.out.println(maps); //清空集合 //maps.clear(); //判断集合是否为空 System.out.println(maps.isEmpty()); //获取键后面对应的值 Integer key = maps.get("python"); System.out.println(key); //根据键删除整个元素 System.out.println(maps.remove("go语言")); System.out.println(maps); //是否包含某个键 System.out.println(maps.containsKey("c语言")); System.out.println(maps.containsKey("b语言")); //是否包含某个值 System.out.println(maps.containsValue(100)); System.out.println(maps.containsValue(12)); //获取全部键,public Set<K> keySet(); Set<String> key1 = maps.keySet(); System.out.println(maps.keySet()); //获取全部值的集合 Collection<V> value(); Collection<Integer> value = maps.values(); System.out.println(maps.values()); //集合大小 System.out.println(maps.size()); //合并其他的Map集合 Map<String,Integer> nap1 = new HashMap<>(); nap1.put("java",1); nap1.put("java1",5); Map<String,Integer> nap2 = new HashMap<>(); nap1.put("java2",2); nap1.put("java3",4); nap1.putAll(nap2);//将nap2拷贝到nap1里面 } }
Map集合遍历
import java.util.HashMap; import java.util.Map; import java.util.Set; public class MapDemo2 { public static void main(String[] args) { Map<String,Integer> nap1 = new HashMap<>(); nap1.put("java" ,3); nap1.put("python" ,7); nap1.put("go语言" ,4); System.out.println(nap1); /linkedHash,输入有序,重复替换原处 //键找值,方法一 //先拿到集合的全部键 Set<String> key = nap1.keySet(); for (String s : key) { int value = nap1.get(s); System.out.println(s + "值" + value); } //键值对 方法二 //可以调用Map方法 entrySet把Map集合换成Set集合(Map集合键值对元素没有类型) //赋予类型,Map.Entry<String,Integer> //Set<Map.Entry<String,Integer>> entries //小技巧,ctrl+alt+v补全代码 Set<Map.Entry<String, Integer>> entries = nap1.entrySet(); System.out.println(entries); //开始遍历 for (Map.Entry<String, Integer> entry : entries) { String key1 = entry.getKey(); int value = entry.getValue(); System.out.println(key1 + "值" + value); } //lambda表达式遍历 nap1.forEach((key1, value) -> { System.out.println(key1 + "值" + value); }); } }
Map集合案例分析
import java.util.HashMap; import java.util.Map; import java.util.Random; //案例:80人去A\B\C\D景点,只选择一个,那个景点去的人多 public class MapDemo3 { public static void main(String[] args) { //把80个学生选择的数据拿过来 String[] selects = {"A","B","C","D"}; StringBuilder sb = new StringBuilder(); Random r = new Random(); for (int i = 0; i < 80; i++) { sb.append(selects[r.nextInt(selects.length)]); } System.out.println(sb); //map集合记录选择的结果 Map<Character,Integer> infe = new HashMap<>(); //遍历数据 for (int i = 0; i < sb.length(); i++) { //获取选取字符 char ch = sb.charAt(i); //判断是否存在此键 if(infe.containsKey(ch)){ //其值计数加一,put()键相同,取最新更新的值 infe.put(ch,infe.get(ch)+1); }else { //刚开始计数 infe.put(ch,1); } System.out.println(infe); } } }
TreeMap<>();与set方法一致
Map<Student,String> map = new TreeMap<>();
有this中比较对象重复就覆盖
(五)、集合嵌套
import java.util.*; public class MapTest { public static void main(String[] args) { //一个学生选择多个 Map<String, List<String>> data = new HashMap<>(); //选择的存进去 List<String> selects = new ArrayList<>(); Collections.addAll(selects,"A","C"); data.put("aaa",selects); List<String> selects1 = new ArrayList<>(); Collections.addAll(selects1,"A","C","D"); data.put("bbb",selects1); System.out.println(data); //统计选择人数 Map<String,Integer> infe = new HashMap<>();//只是为了存东西进去 //提取信息 Collection<List<String>> value = data.values(); for (List<String> values : value) {//数组 for (String s : values) {//数组里面的数据 if(infe.containsKey(s)){ //其值计数加一,put()键相同,取最新更新的值 infe.put(s,infe.get(s)+1); }else { //刚开始计数 infe.put(s,1); } } } System.out.println(infe); } }