集合概念
在以往的编程过程中 , 使用数组来存储一系列 相同类型的数据
数组的弊端
- 数组长度固定不变, 不能够很好地适应元素数量动态变化 (新闻每天数量不等)
- 可以通过 .length 获取数组长度, 不能获取实际元素的个数
概念
是一种工具类,可以存储任意数量、任意类型的对象
集合与数组的差别
1、数组长度固定,集合长度可变
2、数组只能通过下标访问具体元素,集合则可通过任意类型查找所映射的具体对象
集合体系结构
Collection 单列集合
list 存储 可重复, 有序的一组数据
set 存储 不重复(唯一) , 无序的一组数据
Map 双列集合
存储的数据, 是成对出现的
List
list 特点 可重复, 有序
ArrayList
方法名 | 说明 |
---|---|
boolean add(Object o) | |
void add(int index , Object o) | |
int size() | |
Object get(int index) | |
boolean contains(Object o) | |
boolean remove(Object o) | |
Object remove(int index) |
// 使用ArrayList 保存每天的新闻信息
public class NewsTitle {
private int id;
private String title;
private String author;
}
NewsTitle nt1 = new NewsTitle(1,"XX又出车祸了","某某强");
NewsTitle nt2 = new NewsTitle(2,"上海垃圾分类","某某强");
// 创建一个集合类
ArrayList list = new ArrayList();
// 添加元素
list.add(nt1);
list.add(nt2);
collection 接口中常用的方法
clear() | |
isEmpty() | size() == 0 |
toArray() | |
iterator(); | 迭代器 |
LinkedList 和 ArrayList 区别
数据结构
数组 ( 排队 军训 报数 有下标 )
链表 ( 没有下标 必须摆好一个积木之后才能开始摆下一个 )
数组
数组是数据结构中很基本的结构,很多编程语言都内置数组。
在java中当创建数组时会在内存中划分出一块连续的内存,然后当有数据进入的时候会将数据按顺序的存储在这块连续的内存中。当需要读取数组中的数据时,需要提供数组中的索引,然后数组根据索引将内存中的数据取出来,返回给读取程序。在Java中并不是所有的数据都能存储到数组中,只有相同类型的数据才可以一起存储到数组中。
所有的数据结构都支持几个基本操作:读取、插入、删除。
因为数组在存储数据时是按顺序存储的,存储数据的内存也是连续的,所以他的特点就是寻址读取数据比较容易,插入和删除比较困难。简单解释一下为什么,在读取数据时,只需要告诉数组要从哪个位置(索引)取数据就可以了,数组会直接把你想要的位置的数据取出来给你。插入和删除比较困难是因为这些存储数据的内存是连续的,要插入和删除就需要变更整个数组中的数据的位置。举个例子:一个数组中编号0->1->2->3->4这五个内存地址中都存了数组的数据,但现在你需要往4中插入一个数据,那就代表着从4开始,后面的所有内存中的数据都要往后移一个位置。这可是很耗时的。
链表
在java中创建链表的过程和创建数组的过程不同,不会先划出一块连续的内存。因为链表中的数据并不是连续的,链表在存储数据的内存中有两块区域,一块区域用来存储数据,一块区域用来记录下一个数据保存在哪里(指向下一个数据的指针)。当有数据进入链表时候,会根据指针找到下一个存储数据的位置,然后把数据保存起来,然后再指向下一个存储数据的位置。这样链表就把一些碎片空间利用起来了,虽然链表是线性表,但是并不会按线性的顺序存储数据。
由于链表是以这种方式保存数据,所以链表在插入和删除时比较容易,读取数据时比较麻烦。举个例子:一个链表中0->1->2->3->4这五个内存地址中都存了数据,现在需要往2中插入一条数据,那么只需要更改1号和2号中记录下一个数据的位置就行了,对其他数据没有影响。删除一条数据与插入类似,很高效。但是如果是想要在链表其中取出一条数据,就需要从0号开始一个一个的找,直到找到想要的那条数据为止。
链表中插入
链表中删除
对比
ArrayList 底层数据结构为数组, 查询数据效率高, 插入数据, 删除数据效率低
LinkedList 底层数据结构为链表, 查询数据效率低, 插入,删除数据效率高
LinkedList 特有方法
特有方法 | |
---|---|
void addFirst(Object o) | |
void addLast(Object o) | |
Object getFirst() | |
Object getLast() | |
Object removeFirst() | |
Object removeLast() |
Set 接口
hashSet
无序 , 不重复
// 多态 父类引用指向子类对象
Set set = new HashSet();
set.add(3);
set.add(1);
set.add(2);
set.add(2);
// 无序, 是和程序存放的顺序无关
System.out.println(set.toString());
// set 集合 没有下标 所以 不能通过普通的for循环获取数据
/*for(int i= 0 ; i< set.size(); i++){
System.out.println(set.get(i));
}*/
回顾Object类中的equals() 方法
// Object 源码 , 一个类如果不重写 , 就使用父类的, 还是比较地址
public boolean equals(Object obj) {
return (this == obj);
// s1 == s2
}
子类重写之后
// 一般由子类重写, 用于比较内容否一致
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Student student = (Student) o;
return sId == student.sId &&
Objects.equals(name, student.name);
}
set 接口 通过equals() 方法判断两个类是否相同
TreeSet
具有排序功能的 类
二叉树数据结构
树结构
二叉树结构
自平衡二叉树
自动排序案例
TreeSet set = new TreeSet();
set.add(8);
set.add(6);
set.add(1);
set.add(9);
set.add(3);
set.add(7);
set.add(4);
for(Object obj : set){
System.out.println(obj);
}
学生按照成绩排序
环境准备
public class Student {
private int sid; // 学号
private String name;// 姓名
private double score; // 得分
}
接口和抽象类的区别
抽象类 is a 子类是一个父类 猫是一个动物
接口 has a 子类有父接口的功能
排序 就是一个接口
使用TreeSet 添加自定义类型数据, 抛出异常分析
Student s1 = new Student(10086, "煎饼果子", 100);
Student s2 = new Student(10011, "鸡蛋灌饼", 80);
Student s3 = new Student(12306, "手抓饼", 90);
Student s4 = new Student(12315, "武大郎烧饼", 100);
TreeSet<Student> set = new TreeSet<Student>();
set.add(s1);
set.add(s2);
set.add(s3);
set.add(s4);
// ClassCastException: date0719.Student cannot be cast to java.lang.Comparable
for(Student s : set){
System.out.println(s);
}
Comparable 就是一个排序接口, TreeSet 可以识别 Comparable接口的类型的排序功能
public class Student implements Comparable{
private int sid;
private String name;
private double score;
@Override
// 返回值 0 说明不添加
// 返回值为 -1 说明添加到左侧
// 返回值为 +1 添加到右侧
public int compareTo(Object o) { // 参数就是根节点
System.out.println("执行了排序算法!");
Student stu = (Student)o;
/* if(this.score < stu.score){
return -1;
}else{
return 1;
}*/
// 如果 我的成绩比较小 我就向左放
// return (int)(this.score - stu.score);
// 指定 按照学号排序
return this.sid - stu.sid;
}
}
String Integer 都默认实现了 排序接口
public final class String
implements java.io.Serializable, Comparable<String>{}
Iterator
Collection 接口中 一个 Iterator 接口
Iterator 接口 专门用于 集合遍历的接口
每一个 Collection 系列中的集合 都有一个方法 iterator() 用来获取本身的迭代器对象
通过该对象 可以遍历集合中的所有元素
iterator.hasNext()
iterator.next()
Iterator iterator = set.iterator();
// iterator.hasNext() 还有没有元素
// iterator.next() 获取下一个元素
while(iterator.hasNext()){
// 集合可以存放任意类型的元素
Object o = iterator.next();
System.out.println(o);
}
增强 for 循环
底层是 使用Iterator 实现的
// 获取 set集合中的每一个 元素 , 使用Object 接收
for(Object o : set ){
System.out.println(o);
}
泛型
为什么要是用泛型
Student s1 = new Student(10086,"张三");
Student s2 = new Student(10010,"李四");
Student s3 = new Student(12306,"王二麻子");
set.add(s1);
set.add(s2);
set.add(s3);
// 可以存放任意类型
// set.add(123456);
// 要求打印每一个学生的名字
for(Object o : set ){
// 把Object 类 强转为 Student
// 123456 强转失败, 类型转换异常
Student stu = (Student) o;
System.out.println(stu.name);
}
定义
泛型,即“参数化类型”。一提到参数,最熟悉的就是定义方法时的 形参,然后调用此方法时传递实参。那么参数化类型怎么理解呢?顾名思义,就是将类型由原来的具体的类型参数化,类似于方法中的变量参数,此时类型也定义成参数形式(可以称之为类型形参),然后在使用/调用时传入具体的类型(类型实参)。
定义一个方法 传入参数
public static void main(String[] args) {
m1(5); // 5 实际参数 实参
}
// 方法的参数列表 (形参)
public static void m1(int i){
System.out.println(i*i);
}
给集合添加参数化类型
List<Student> list = new ArrayList<Student>();
// 泛型集合 , 能够约束 该集合中只能存放哪种类型的元素
List<Student> list = new ArrayList<Student>();
Student s1 = new Student(10086,"张三");
Student s2 = new Student(10010,"李四");
Student s3 = new Student(12306,"王二麻子");
list.add(s1);
list.add(s2);
list.add(s3);
// list.add(789); // 非 Student 类型 不能存储到集合中
// 不需要 强制类型转换
for(Student stu : list){
System.out.println(stu.name);
}
泛型的好处
- 可以把 运行时的异常 提升到编译期 (征兵 )
- 更加方便的使用增强型 for 循环
泛型方法
需求引入 // 输出的值 就是输出的值
public static void main(String[] args) {
int i = get(3);
String abc = get("abc");
Student stu = new Student(9527, "华安", 100);
Object o = get(stu);
}
public static Object get(Object obj){
return obj;
}
// 输出的值 就是输出的值
public static int get(int i){
return i;
}
// 输出的值 就是输出的值
public static String get(String s){
return s;
}
泛型方法
public static void main(String[] args) {
int i = get(3);
String abc = get("abc");
Student stu = new Student(9527, "华安", 100);
Student student = get(stu);
}
/* public static Object get(Object obj){
return obj;
}*/
// java 不允许出现 未定义的变量 E 需要定义<E>
// E 参数化类型 , 可以传递任意数据类型, 相当于Object
// 假设用户传递的是一个 E , 就返回一个E类型
public static<E> E get(E e){
return e;
}
泛型类
/**
* 泛型类 , 把泛型定义到类上, 所有方法都可以使用该泛型
* @param <E>
*/
public class Demo<E>{
public E get(E e){
return e;
}
public E put(E e){
return e;
}
}
public class Demo<E>{
public E get(E e){
return e;
}
public E put(E e){
return e;
}
public static void main(String[] args) {
Demo<String> demo = new Demo<String>;
demo.get("abc");
Demo<Student> stuDemo = new Demo<Student>;
stuDemo.get(new Student());
// List 本质上就是一个泛型类
ArrayList<String> list = new ArrayList<String>();
list.add("abc");
}
}
泛型接口
public class ArrayList<E> extends AbstractList<E>
implements List<E>
Map
向集合中存储一个一个的学生
需求 : 存储一对一对的夫妻
List<String> list = new ArrayList<String>();
list.add("张三");
list.add("李四");
list.add("诸葛亮");
list.add("卓文君");
list.add("黄月英");
list.add("司马相如");
// list.add("诸葛亮","黄月英"); // 永生不分离
语法
// 定义map 集合
Map<String,String> map = new HashMap<String,String>();
map.put("诸葛亮","黄月英");
map.put("司马相如","卓文君");
map.put("薛平贵","王宝钏");
常用方法
方法名 | 解释 |
---|---|
Object put(key 键, value 值) | 存储键值对 |
Object get(key) | 根据键, 获取相对应的值, 如果不存在,返回 null |
Object remove(key) | 根据键 删除相应的键值对 |
int size() | 获取 元素个数 |
Set keySet() | 获取 map 中 所有的键 |
Collection values() | 获取 map 中 所有的值 |
boolean containsKey(key) | 判断 map 中 是否包含 指定的键 |
Set entrySet() | 获取 所有键值对的集合 |
put()
map.put("王悉丞","高翠莲");
map.put("王悉丞","嫦娥");
map.put("猪八戒","嫦娥");
System.out.println(map);
- 键是唯一的, 值是可以重复的
- 使用put 添加内容的时候, 如果键相同, 会覆盖掉之前的键值对
遍历 map 集合
广场上 一些老大爷和老大娘 跳交际舞
召集一些成员组成一支队伍 (map)
如何召集他们
第一种方式 keySet()
先找到 老大爷, 让他们去自己的舞伴
// 获取所有的键
Set<String> keySet = map.keySet();
// 根据键获取值
for(String key : keySet){
System.out.print(key+"\t");
System.out.println(map.get(key));
}
第二种遍历方式
Entry 类 (键值对类) entrySet
每一对舞伴, 都起一个组合的名称 , 组合名称报上来
// Entry<String, String>
Set<Map.Entry<String, String>> entrySet = map.entrySet();
for(Map.Entry<String, String> entry : entrySet ){
System.out.println(entry.getKey()+"\t"+entry.getValue());
}
Collections
Array 是数组 Arrays 是数组工具类
Colection 集合总接口 Collections 就是集合的工具类
常用方法
方法名 | |
---|---|
sort() | 排序 |
binarySearch() | 查找 |
max()/min() | 查找最大值/最小值 |
shuffle(List list) | 使用默认的随机源随机排列指定的列表。 |
斗地主案例
需求分析
斗地主 54张
四种花色 + 大小王
使用一个集合来制作一副扑克牌
初级版本的案例
List<String> huaSe = new ArrayList<String>();
huaSe.add("♥");
huaSe.add("♠");
huaSe.add("♣");
huaSe.add("♦");
List<String> shuZi = new ArrayList<String>();
shuZi.add("A");
shuZi.add("2");
shuZi.add("3");
shuZi.add("4");
shuZi.add("5");
shuZi.add("6");
shuZi.add("7");
shuZi.add("8");
shuZi.add("9");
shuZi.add("10");
shuZi.add("J");
shuZi.add("Q");
shuZi.add("K");
//把两个集合的 内容拼装组合成为一副牌
List<String> PK = new ArrayList<String>();
// 花色
for(String hua : huaSe){
// 数字
for(String num : shuZi){
PK.add(hua+num);
}
}
PK.add("BJoker");
PK.add("SJoker");
// 洗牌
Collections.shuffle(PK);
// 定义三位游戏玩家
List<String> wu = new ArrayList<String>();
List<String> yu = new ArrayList<String>();
List<String> li = new ArrayList<String>();
// 定义底牌
List<String> di = new ArrayList<String>();
// 开始发牌
for(int i = 0 ; i < PK.size() ; i++){
if(i > 50){
di.add(PK.get(i));
continue;
}
if(i % 3 == 0){
wu.add(PK.get(i));
}else if(i % 3 == 1){
yu.add(PK.get(i));
}else{
li.add(PK.get(i));
}
}
// 看牌
System.out.println(li);
有序版本的优化
添加排序功能
集合中 数据全部都是使用特殊符号组成的 字符串, 排序的结果无非是按照花色排序, 不是我们需要的
集合本身没有排序功能
手动给所有的牌添加一个序号, 按照牌面大小分配编号
牌面 和 对应的编号 是成对出现的
考虑使用map 集合
// 序号 和 牌面是一个一一对应的关系
洗牌的时候 和 发给用户的时候 , 都是编号
只有在最后看牌的时候, 才是使用 map 获取真正的牌面
public class DouPlus {
public static void main(String[] args) {
// 新建两个集合
// 保存花色
// 保存数字
List<String> huaSe = new ArrayList<String>();
huaSe.add("♥");
huaSe.add("♠");
huaSe.add("♣");
huaSe.add("♦");
List<String> shuZi = new ArrayList<String>();
shuZi.add("3");
shuZi.add("4");
shuZi.add("5");
shuZi.add("6");
shuZi.add("7");
shuZi.add("8");
shuZi.add("9");
shuZi.add("10");
shuZi.add("J");
shuZi.add("Q");
shuZi.add("K");
shuZi.add("A");
shuZi.add("2");
//把两个集合的 内容拼装组合成为一副牌
Map<Integer,String> PK = new HashMap<Integer,String>();
// 花色
int count = 1;
for(String num : shuZi){
// 数字
for(String hua : huaSe){
PK.put(count,hua+num);
count++;
}
}
PK.put(count++,"SJoker");
PK.put(count,"BJoker");
// System.out.println(PK);
// 洗牌, map的键的集合 牌的编号
Set<Integer> keySet = PK.keySet(); // (1 - 54 )
List<Integer> keys = new ArrayList<Integer>();
for(Integer key : keySet){
keys.add(key);
}
// 洗牌
Collections.shuffle(keys);
// 定义三位游戏玩家 不保存牌面 只保存 每张牌的编号
List<Integer> caocao = new ArrayList<>();
List<Integer> liubei = new ArrayList<>();
List<Integer> sunquan = new ArrayList<>();
List<Integer> dipai = new ArrayList<>();
// 开始发牌
for(int i = 0 ; i < keys.size() ; i++){
if(i > 50){
dipai.add(keys.get(i));
continue;
}
if(i % 3 == 0){
caocao.add(keys.get(i));
}else if(i % 3 == 1){
liubei.add(keys.get(i));
}else{
sunquan.add(keys.get(i));
}
}
Collections.sort(caocao);
// 看牌
for(Integer i : caocao){
System.out.print(PK.get(i));
}
}
}
集合案例
使用集合优化点名程序
使用map集合 存储多个班级信息
字符串每个字符出现的次数
使用map集合统计从键盘上输入的字符串每个字符出现的次数
// 使用map集合统计 从键盘上输入的字符串每个字符 出现的次数
public class Demo {
public static void main(String[] args) {
Scanner input = new Scanner(System.in);
System.out.println("请输入一个字符串: ");
String str = input.next();
/**
* 1- 把字符串 变为一个 字符数组
* 2- 向 map集合中存储字符和该字符出现的次数
*/
Map<Character,Integer> map = new HashMap<Character,Integer>();
char[] chars = str.toCharArray();
for (char ch: chars ) {
// 如果集合中没有出现过 该字符 说明 第一次存储, 数量为1
// 如果 不是第一次存储 在原有的数量上 添加 1
if(map.containsKey(ch)){
map.put(ch,map.get(ch)+1);
}else{
map.put(ch,1);
}
}
// 让所有的字母按照顺序出现
Set<Character> set = map.keySet();
List<Character> list = new ArrayList<>();
for(Character ch : set){
list.add(ch);
}
Collections.sort(list);
for (Character ch:list ) {
System.out.print(ch+"\t");
System.out.println(map.get(ch));
}
}
}