文章目录
- 补充知识-泛型
- 版本信息
- 泛型类
- 常见泛型标识符
- 泛型应用实例
- 注意事项
- 泛型方法
- 非静态的泛型方法
- 静态的泛型方法:必须声明出自己独立的泛型
- 泛型接口
- 泛型通配符
- 补充知识-树
- 基本概念
- 二叉树-普通二叉树
- 二叉树-二叉查找树
- 添加节点流程
- 优点、不足
- 二叉树-平衡二叉树
- 旋转机制
- 二叉树-红黑树
- 红黑规则
- 补充知识-常见数据结构
- 栈
- 队列
- 链表
- 单项链表和双向链表
- 集合底层数据结构
- Collection-List集合
- Collection-Set集合
- Map集合
- 集合
- 集合分类
- 大分类
- 小分类
- Collection接口的子类
- Map接口的子类
- 集合的遍历方式
- 通用遍历方式
- 迭代器
- 增强for循环
- forEach方法
- 额外遍历方式-List集合
- 普通for循环
- ListIterator(List集合特有的迭代器)
- Collection接口--通用函数
- Collection接口--List接口-ArrayList
- ArrayList长度可变原理
- Collection接口--List接口-LinkedList
- Collection接口--Set接口-TreeSet
- 两种排序规则-自然排序
- 常见类排序:String,默认增序排序
- 自定义类排序
- 两种排序规则-比较器排序
- 两种排序规则优先级
- Collection接口--Set接口-HashSet
- 版本信息
- 数据存储过程
- 数组扩容
- 链表转红黑树
- 特点
- Collection接口--Set接口-LinkedHashSet
- Collection接口--总结
- 类型选用
- Collections工具类
- Map接口
- 基础介绍
- Map类别
- 通用方法
- 底层原理-HashMap举例
- 遍历方式
- 通过forEach方法遍历
- 通过键值对对象获取键和值
- 通过键找值
- Map集合选用
补充知识-泛型
版本信息
JDK5引入, 可以在编译阶段约束操作的数据类型, 并进行检查
- 即统一数据类型,将运行时期的错误提升到编译期
泛型类
不知道使用者想要添加什么类型的数据,就给出一个泛型类
常见泛型标识符
E | T | K | V |
---|---|---|---|
Element | Type | Key | Value |
表示集合的元素类型 | 表示任意类型 | 表示关键字类型 | 表示值类型 |
泛型应用实例
package com.itheima.day10.generics;
import java.util.ArrayList;
public class GenericsDemo2 {
public static void main(String[] args) {
Student<Integer> stu = new Student<>(); // 正确
Student<int> stu = new Student<>(); // 错误,泛型类只能是引用数据类型
}
}
class Student<E> {
private E e;
public E getE() {
return e;
}
public void setE(E e) {
this.e = e;
}
}
注意事项
-
当创建对象的时候,才能确定泛型类具体的类别
-
!!!泛型类只能写引用数据类型
泛型方法
非静态的泛型方法
-
根据类的泛型去匹配
-
上述 getE方法、setE方法
静态的泛型方法:必须声明出自己独立的泛型
- 静态方法随着类的加载而加载,类还没创建,就没有具体类型,静态方法就有问题了。所以要声明自己独立的类型
- 在调用方法,传入具体参数的时候,确定到静态方法具体的泛型类型
package com.itheima.day10.generics;
public class GenericsDemo3 {
public static void main(String[] args) {
String[] arr1 = {"张三", "李四", "王五"};
Integer[] arr2 = {11, 22, 33};
Double[] arr3 = {11.1, 22.2, 33.3};
printArray(arr1);
printArray(arr2);
printArray(arr3);
}
// static后边的T
public static <T> void printArray(T[] arr) {
System.out.print("[");
for (int i = 0; i < arr.length - 1; i++) {
System.out.print(arr[i] + ", ");
}
System.out.println(arr[arr.length - 1] + "]");
}
}
泛型接口
类实现接口的时候,接口带有泛型
- 类实现接口的时候,直接确定类型
- 延续接口的泛型,等创建对象的时候确定
package com.itheima.day10.generics;
import java.util.ArrayList;
import java.util.List;
public class GenericsDemo4 {
public static void main(String[] args) {
InterBImpl<String> i = new InterBImpl<>();
}
}
interface Inter<E> {
void show(E e);
}
// 类实现接口的时候,直接确定类型
class InterAImpl implements Inter<String> {
@Override
public void show(String s) {
}
}
// 延续接口的泛型,等创建对象的时候确定
class InterBImpl<E> implements Inter<E>{
@Override
public void show(E e) {
}
}
泛型通配符
想要一个方法可以传入不同的对象类型
? | ? extends E | ? super E |
---|---|---|
任意类型 | E及E的子类 | E及E的父类 |
package com.itheima.day10.generics;
import java.util.ArrayList;
public class GenericsDemo5 {
/*
泛型通配符
? : 任意类型
? extends E : 可以传入的是E, 或者是E的子类
? super E : 可以传入的是E, 或者是E的父类
*/
public static void main(String[] args) {
ArrayList<Coder> list1 = new ArrayList<>();
list1.add(new Coder());
ArrayList<Manager> list2 = new ArrayList<>();
list2.add(new Manager());
method(list1);
method(list2);
}
public static void method(ArrayList<? extends Employee> list){
for (Employee o : list) {
o.work();
}
}
public static void method1(ArrayList<? super Employee> list){
for (Object A : list) {
Employee o = (Employee)A;
o.work();
}
}
}
abstract class Employee {
private String name;
private double salary;
public Employee() {
}
public Employee(String name, double salary) {
this.name = name;
this.salary = salary;
}
public abstract void work();
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public double getSalary() {
return salary;
}
public void setSalary(double salary) {
this.salary = salary;
}
public String toString() {
return "Employee{name = " + name + ", salary = " + salary + "}";
}
}
class Coder extends Employee {
@Override
public void work() {
System.out.println("程序员写代码...");
}
}
class Manager extends Employee {
@Override
public void work() {
System.out.println("项目经理分配任务...");
}
}
补充知识-树
基本概念
概念 | 理解 |
---|---|
节点(其他叫法:结点、Node) | 上边的每一个圈圈都是一个节点; (节点内部存储:父节点地址、节点值、左子节点地址、右子节点地址) |
度 | 每一个节点的子节点数量 (二叉数中,任意节点的度<=2) |
树高 | 整棵树的层数 (上边数的树高=4) |
根节点 | 最顶层的节点(22) |
左子节点 | 22的左子节点是18 |
右子节点 | 22的右子节点是26 |
根节点的左子树 | 18节点及其所有子节点 |
根节点的右子树 | 26节点及其所有子节点 |
二叉树-普通二叉树
无排序
二叉树-二叉查找树
有排序
又名:二叉排序树、二叉搜索树
-
每个节点最多两个子节点
-
任意节点左子树上的值都小于当前节点
-
任意节点右子树的值都大于当前节点
添加节点流程
-
小的节点存左边
-
大的节点存右边
-
一样的节点不存
优点、不足
-
优点:元素查找速度快,每一次查找,筛选掉剩余元素的一半
-
不足:特殊二叉查找树(所有节点仅有右节点或左节点),每次查找并不能过滤掉一半元素,查找速度跟数组一样
二叉树-平衡二叉树
任意节点左右子树高度差不超过1
旋转机制
- 挺巧妙地,用到了再说
二叉树-红黑树
- 不是通过高度来平衡的,而是通过红黑规则来平衡的
红黑规则
- 用到再说
补充知识-常见数据结构
数据结构 | 结构 | 操作 | 特点 | 补充 |
---|---|---|---|---|
栈 | 一端开口(栈顶)、一端封闭(栈底) | 进栈/压栈、出栈/弹栈 | 后进先出,先进后出 | |
队列 | 一端开口(后端)、一端开口(前端) | 入队列(后端)、出队列(前端) | 先进先出,后进后出 | |
数组 | 地址值、索引 | 根据地址值和索引定位数据 | 查询速度快(且一致):索引+地址值定位; 增、删效率低:增删过程大概率伴随大量数据移动 | |
链表 | 见下图 | 查询慢:查询任何数据都要从头开始查 增删相对快:查到对应元素,更改节点存储内容 | 存储内存不连续 | |
双向链表 | 见下图 | 同【链表】 | 存储内存不连续 | |
树 | 见【补充知识-树】 |
栈
队列
链表
单项链表和双向链表
集合底层数据结构
Collection-List集合
ArrayList | LinkedList |
---|---|
数组 | 双向链表 |
Collection-Set集合
Set的底层就是Map
TreeSet | HashSet | LinkedHashSet |
---|---|---|
红黑树 | 哈希表(数组+链表+红黑树) | 哈希表+双向链表 |
Map集合
TreeMap | HashMap | LinkedHashMap |
---|---|---|
红黑树 | 哈希表(数组+链表+红黑树) | 哈希表+双向链表 |
集合
是一种容器,用来装数据,类似数组,但长度可变
集合分类
大分类
单列集合 | 双列集合 | |
---|---|---|
区别 | 一次添加一个元素 | 一次添加两个元素 |
成员 | ArrayList、LinkedList、TreeSet、HashSet、LinkedHashSet | TreeMap、HashMap、LinkedHashMap |
接口实现 | 实现Collection接口 | 实现Map接口 |
小分类
Collection接口的子类
List接口 | Set接口 | |
---|---|---|
成员 | ArrayList、LinkedList | TreeSet、HashSet、LinkedHashSet |
特点 | 存取有序、有索引、可以重复存储 | 存取无序、没有索引、不可以重复存储 |
- TreeSet可以实现排序,按照排序规则输出元素
- HashSet保证元素唯一
- LinkedHashSet可以保证存储顺序
Map接口的子类
Map接口 | |
---|---|
成员 | TreeMap、HashMap、LinkedHashMap |
特点 | 同Set接口 |
集合的遍历方式
通用遍历方式
迭代器
增强for循环
forEach方法
package com.itheima.collection;
import com.itheima.domain.Student;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
public class CollectionTest2 {
public static void main(String[] args) {
Collection<Student> c = new ArrayList<>();
c.add(new Student("张三", 23));
c.add(new Student("李四", 24));
c.add(new Student("王五", 25));
// 1. 获取迭代器
Iterator<Student> it = c.iterator();
// 2. 循环判断, 集合中是否还有元素
while (it.hasNext()) {
// 3. 调用next方法, 将元素取出
Student stu = it.next();
System.out.println(stu.getName() + "---" + stu.getAge());
}
System.out.println("--------------------");
// 使用增强for循环遍历集合:内部还是迭代器,通过.class文件可以看出来
for (Student stu : c) {
System.out.println(stu);
}
System.out.println("--------------------");
// foreach方法遍历集合:匿名内部类
c.forEach(stu -> System.out.println(stu));
}
}
额外遍历方式-List集合
普通for循环
ListIterator(List集合特有的迭代器)
package com.itheima.collection.list;
import java.util.ArrayList;
import java.util.List;
import java.util.ListIterator;
public class ListDemo2 {
public static void main(String[] args) {
List<String> list = new ArrayList<>();
list.add("abc");
list.add("bbb");
list.add("ccc");
list.add("abc");
// 普通for循环
for (int i = 0; i < list.size(); i++) {
String s = list.get(i);
System.out.println(s);
}
System.out.println("---------------");
// 迭代器
// 先调用hasPrevious会打印不出来数据
/// 需要先调用hasNext,再掉用hasPrevious,才能保证一次正向打印,一次反向打印
ListIterator<String> it = list.listIterator();
while(it.hasPrevious()){
String s = it.previous();
System.out.println(s);
}
System.out.println("---------------");
while (it.hasNext()) {
String s = it.next();
System.out.println(s);
}
}
}
ListIterator(List集合特有的迭代器)遍历过程中的添加、删除元素注意事项
用迭代器遍历过程中,调用【集合对象】的添加、删除操作,就会出现异常
用【迭代器】对象的添加或删除方法,即可避免异常
- 普通迭代器:Iterator,仅支持删除操作,不支持添加操作
- List迭代器:ListIterator,既支持删除操作,也支持添加操作
package com.itheima.collection.list;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.ListIterator;
public class ListDemo3 {
public static void main(String[] args) {
List<String> list = new ArrayList<>();
list.add("眼瞅着你不是真正的高兴");
list.add("温油");
list.add("离开俺们这旮表面");
list.add("伤心的人别扭秧歌");
list.add("私奔到东北");
ListIterator<String> it = list.listIterator();
while (it.hasNext()) {
String s = it.next();
if ("温油".equals(s)) {
it.add("哈哈"); // 没问题
list.add("哈哈"); // 报错
list.remove("温油"); // 报错
}
}
System.out.println(list);
}
}
Collection接口–通用函数
-
remove()、contains()两个函数依赖对象底层的equals()方法
-
若对象没有重写equals()方法,比较的是地址值,无法按照要求删除
-
上述方法均以对象为参数,不以索引为参数(因为Collection接口里面的Set接口没有索引)
Collection接口–List接口-ArrayList
支持索引
增、删、改、查(索引)
package com.itheima.collection.list;
import java.util.ArrayList;
import java.util.List;
public class ListDemo1 {
/*
List接口的特点 : 存取有序, 有索引, 可以存储重复的
和“索引”有关的API :
public void add(int index, E element) : 在指定的索引位置, 添加元素
public E remove(int index) : 根据索引删除集合中的元素
public E set(int index, E element) : 根据索引修改集合中的元素
public E get(int index) : 返回指定索引处的元素
*/
public static void main(String[] args) {
List<String> list = new ArrayList<>();
list.add("张三");
list.add("李四");
list.add("王五");
list.set(0, "赵六");
list.remove(1);
System.out.println(list.get(0));
System.out.println(list);
System.out.println("-------------------------");
List<Integer> list2 = new ArrayList<>();
list2.add(111); // Integer e = 111;
list2.add(222);
list2.add(333);
list2.remove(Integer.valueOf(222));
System.out.println(list2);
}
}
-
根据索引删除元素是List接口(子)的特点
-
根据元素删除元素是Collection接口(父)的特点
ArrayList长度可变原理
- 空参构造时,底层创建长度为0的数组(非空参构造,创建长度为10的数组)
- 添加第一个元素时,底层会创建一个新的长度为10的数组
- 当数组存满时,会扩容1.5倍
Collection接口–List接口-LinkedList
支持索引
增、删、改、查、查首个、查尾部(非索引)
package com.itheima.collection.list;
import java.util.LinkedList;
public class LinkedListDemo {
/*
LinkedList 特有方法 :
public void addFirst(E e) : 头部添加
public void addLast(E e) : 尾部添加
public E getFirst() : 获取第一个
public E getLast() : 获取最后一个
public E removeFirst() : 删除第一个
public E removeLast() : 删除最后一个
*/
public static void main(String[] args) {
LinkedList<String> list = new LinkedList<>();
list.add("张三");
list.add("李四");
list.add("王五");
String s = list.get(1);
System.out.println(s);
}
private static void method2() {
LinkedList<String> list = new LinkedList<>();
list.add("张三");
list.add("李四");
list.add("王五");
list.add("赵六");
System.out.println(list.getFirst());
System.out.println(list.getLast());
list.removeFirst();
list.removeLast();
System.out.println(list);
}
private static void method1() {
LinkedList<String> list = new LinkedList<>();
list.addFirst("张三");
list.addFirst("李四");
list.addFirst("王五");
list.addLast("赵六");
// 王五 李四 张三 赵六
System.out.println(list);
}
}
LinkedList也归属与List接口,具有get(int index)方法,但是其底层是双向链表,get方法怎么实现?
- 将索引和集合长度比较
- 靠近头部:从头找数据,一个一个找
- 靠近尾部:从尾找数据,一个一个找
不同于ArrayList的查询(地址值+索引)。LinkedList的存储内存不连续,是一个一个元素查找,速度慢
Collection接口–Set接口-TreeSet
对集合中的元素进行排序操作(底层红黑树实现)
两种排序规则-自然排序
常见类排序:String,默认增序排序
package com.itheima.day10.set;
import java.util.TreeSet;
public class TreeSetDemo1 {
public static void main(String[] args) {
TreeSet<String> ts = new TreeSet<>();
ts.add("a");
ts.add("d");
ts.add("e");
ts.add("c");
ts.add("b");
ts.add("b");
ts.add("b");
ts.add("b");
ts.add("b");
System.out.println(ts); // [a, b, c, d, e]
}
}
自定义类排序
-
类实现Comparable接口
-
重写compareTo方法
-
根据返回值,组织排序规则
-
返回负数:往树左边走
-
返回正数:往树右边走
-
返回0:不存(对于根节点除外,即第一个元素除外)
-
package com.itheima.day10.set;
import com.itheima.day10.domain.Student;
import java.util.TreeSet;
public class TreeSetDemo2 {
public static void main(String[] args) {
TreeSet<Student> ts = new TreeSet<>();
ts.add(new Student("A", 23));
ts.add(new Student("B", 26));
ts.add(new Student("C", 27));
ts.add(new Student("D", 20));
// [Student{name = C, age = 27}, Student{name = B, age = 26}, Student{name = A, age = 23}, Student{name = D, age = 20}]
System.out.println(ts);
}
}
比较过程:
23+A—23+A
26+B—23+A
27+C—23+A
27+C—26+B
20+D—26+B
20+D—23+A
// Student类
package com.itheima.day10.domain;
public class Student implements Comparable<Student>{
// this.xxx - o.xxx 正序
// o.xxx - this.xxx 降序
@Override
public int compareTo(Student o) {
System.out.println(this.getAge() + "+" + this.name + "---" + o.age + "+" + o.name);
// 根据年龄做主要排序条件
int ageResult = o.age - this.age;
// 根据姓名做次要排序条件
int nameResult = ageResult == 0 ? o.name.compareTo(this.name) : ageResult;
// 判断姓名是否相同
int result = nameResult == 0 ? 1 : nameResult;
return result;
}
private String name;
private int age;
public Student() {
}
public Student(String name, int age) {
this.name = name;
this.age = age;
}
/**
* 获取
* @return name
*/
public String getName() {
return name;
}
/**
* 设置
* @param name
*/
public void setName(String name) {
this.name = name;
}
/**
* 获取
* @return age
*/
public int getAge() {
return age;
}
/**
* 设置
* @param age
*/
public void setAge(int age) {
this.age = age;
}
public String toString() {
return "Student{name = " + name + ", age = " + age + "}";
}
}
两种排序规则-比较器排序
-
在 TreeSet 的构造方法中, 传入 Compartor 接口的实现类对象
-
重写 compare 方法
-
根据方法的返回值, 来组织排序规则
-
负数 : 左边走
-
正数 : 右边走
-
0 : 不存
-
该接口也是函数式接口,可以写为Lambda表达式
package com.itheima.day10.set;
import com.itheima.day10.domain.Student;
import java.util.Comparator;
import java.util.TreeSet;
public class TreeSetDemo3 {
public static void main(String[] args) {
TreeSet<Student> ts = new TreeSet<>(new Comparator<Student>() {
@Override
public int compare(Student o1, Student o2) {
int ageResult = o1.getAge() - o2.getAge();
return ageResult == 0 ? o1.getName().compareTo(o2.getName()) : ageResult;
}
});
ts.add(new Student("赵六", 26));
ts.add(new Student("李四", 24));
ts.add(new Student("张三", 23));
ts.add(new Student("王五", 25));
// [Student{name = 张三, age = 23}, Student{name = 李四, age = 24}, Student{name = 王五, age = 25}, Student{name = 赵六, age = 26}]
System.out.println(ts);
}
}
两种排序规则优先级
如果同时具备比较器和自然排序, 会优先按照比较器的规则, 进行排序操作
- 有些类给出默认的排序规则,不好重写其底层代码,就用比较器
Collection接口–Set接口-HashSet
可以保证数据唯一性
版本信息
JDK7 | JDK8 |
---|---|
数组+链表 | 数组+链表+红黑树 |
数据存储过程
- 每个数据的存储都需要进行比较,根据比较结果来判断是否存
- 需要重写存入对象的hashCode方法、equals方法
- 创建一个默认长度16的数组,数组名table
- 根据元素的哈希值跟数组的长度求余计算出应存入的位置(调用hashCode方法)
- 判断当前位置是否为null,如果是null直接存入
- 如果位置不为null,表示有元素,则调用equals方法比较
- 如果一样,则不存,如果不一样,则存入数组
- JDK 7新元素占老元素位置,指向老元素 (头插法)
- JDK 8中新元素挂在老元素下面(尾插法)
数组扩容
- 条件1:数组存满到16*0.75(加载因子)=12时,就自动扩容,每次扩容为原先的两倍
- 条件2:链表挂载元素超过了8个 (阈值) ,但数组长度没有到达64
链表转红黑树
- 条件:链表挂载元素超过了8个 (阈值),且数组长度到达64
特点
-
底层是哈希表存储数据,对增删改查数据性能都比较好
-
存储的数据具有唯一性
-
不对数据进行排序
Collection接口–Set接口-LinkedHashSet
Collection接口–总结
类型选用
Case | Choice |
---|---|
如果想要集合中的元素可重复 | ArrayList |
如果想要集合中的元素可重复,而且当前的增删操作明显多于查询 | LinkedList |
如果想对集合中的元素去重 | HashSet |
如果想对集合中的元素去重,而且保证存取顺序 | LinkedHashSet |
如果想对集合中的元素进行排序 | TreeSet |
Collections工具类
package com.itheima.day11.tools;
import com.itheima.day11.domain.Student;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
public class CollectionsDemo {
public static void main(String[] args) {
// 批量添加
ArrayList<String> list = new ArrayList<>();
Collections.addAll(list, "a", "b", "c", "d");
System.out.println(list);
// 二分查找 (前提: 必须是排好序的数据)
System.out.println(Collections.binarySearch(list, "b"));
// 洗牌
Collections.shuffle(list);
System.out.println(list);
ArrayList<Student> nums = new ArrayList<>();
Collections.addAll(nums, new Student("张三", 23), new Student("王五", 25), new Student("李四", 24));
// 从集合中找最值
System.out.println(Collections.max(nums));
System.out.println(Collections.min(nums));
// 对集合中的元素进行交换
Collections.swap(nums, 0, 2);
System.out.println(nums);
// sort : 对集合进行排序
ArrayList<Integer> box = new ArrayList<>();
Collections.addAll(box, 1, 3, 5, 2, 4);
Collections.sort(box, (o1, o2) -> o2 - o1);
System.out.println(box);
}
}
Map接口
基础介绍
- Map是双列集合,每个元素包含两个数据
- Map元素格式:key=value
- key:不允许重复
- value:允许重复
- key-value一一对应
- key-value整体,称为【键值对】或【键值对对象】,用Entry表示
Map类别
TreeMap、HashMap、LinkedHashMap
通用方法
Map是双列集合的顶层接口
底层原理-HashMap举例
- 用键值对中的键来计算哈希值,与值无关
- 后续原理同HashSet
遍历方式
通过forEach方法遍历
通过键值对对象获取键和值
通过键找值
package com.itheima.day11.map;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import java.util.function.BiConsumer;
public class MapDemo3 {
public static void main(String[] args) {
HashMap<String, String> hm = new HashMap<>();
hm.put("张三", "北京");
hm.put("李四", "上海");
hm.put("王五", "成都");
hm.forEach((key, value) -> System.out.println(key + "---" + value));
}
private static void method2(HashMap<String, String> hm) {
// 1. 获取到所有的键值对对象
Set<Map.Entry<String, String>> entrySet = hm.entrySet();
// 2. 遍历set集合获取每一个键值对对象
for (Map.Entry<String, String> entry : entrySet) {
// 3. 通过键值对对象, 获取键和值
System.out.println(entry.getKey() + "---" + entry.getValue());
}
}
private static void method1(HashMap<String, String> hm) {
// 1. 获取到所有的键
Set<String> keySet = hm.keySet();
// 2. 遍历set集合, 获取每一个键
for (String key : keySet) {
// 3. 调用map集合的get方法, 根据键查找对应的值
String value = hm.get(key);
System.out.println(key + "---" + value);
}
}
}
Map集合选用
Case | Choice |
---|---|
如果想根据集合中的键进行去重 | HashMap |
如果想根据键对集合中的元素去重,而且保证存取顺序 | LinkedHashMap |
如果想根据集合中的键进行排序 | TreeMap |