1.集合
集合和数组都是容器
数组的特点
数组定义完成并启动后,类型确定、长度固定
在进行增删数据操作的时候,数组是不太合适的,增删数据都需要放弃原有数组或者移位
数组适合的场景
当业务数据的个数是固定的,且都是同一批数据类型的时候,可以采取定义数组存储
集合是Java中存储对象数据的容器
集合的特点
集合的大小不固定,启动后可以动态变化,类型也可以选择不固定
集合非常适合做元素的增删操作
注意:集合中只能存储引用类型数据,如果要存储基本类型数据可以使用包装类
集合适合的场景
数据的个数不稳定,需要进行增删改元素的时候
集合的体系结构
Collection 单列
Map 双列
Collection单列集合,每个元素只包含一个值
Map 双列集合,每个元素包含了两个值(键值对)
Collection集合体系
List系列集合:添加的元素是有序、可查的、有索引
ArrayList、LinkedList:有序、可重复、有索引
Set系列集合:添加的元素是无序、不重复、无索引
HashSet:无序、不重复、无索引;LinkedHashSet:有序、不重复、无索引
TreeSet: 按照大小默认升序排序、不重复、无索引
public static void main(String[] args) {
Collection list = new ArrayList();
list.add("23");
list.add("Java");
list.add(11);
list.add(false);
list.add(false);
System.out.println(list); // [23, Java, 11, false, false]
Collection list2 = new HashSet();
list2.add("23");
list2.add("Java");
list2.add(11);
list2.add(false);
list2.add(false);
System.out.println(list2); // [23, Java, false, 11]
}
集合对于泛型的支持
集合都是支持泛型的,可以在编译阶段约束集合只能操作某种数据类类型
Collection<String> lists = new ArrayList<String>()
Collection<String> lists = new ArrayList<>() JDK1.7开始后面的泛型类型可以省略不写
2.Collection集合
Collection是单列集合的祖宗接口,它的功能是全部单列集合都可以继承使用的
Collection API
方法名 | 说明 |
---|---|
public boolean add(E e) | 把给定的对象添加到当前集合中 |
public void clear() | 清空集合中所有的元素 |
public boolean remove(E e) | 把给定对象在当前集合中删除 |
public boolean contains(Object obj) | 判断当前集合中是否包含给定的的对象 |
public boolean isEmyty() | 判断当前集合是否为空 |
public int size() | 返回集合中元素的个数 |
public Object[] toArray() | 把集合中的元素,存储到数组中 |
public static void main(String[] args) {
Collection<String> list = new ArrayList<>();
// 添加元素,添加成功返回true
list.add("Java");
list.add("C++");
list.add("Mysql");
list.add("Paython");
list.add("JavaScript");
System.out.println(list);
// 清空集合的元素
// list.clear();
// System.out.println(list);
// 判断集合是否为空
System.out.println(list.isEmpty());
// 获取集合的大小
System.out.println(list.size());
// 判断集合中是否包含某个元素
System.out.println(list.contains("Java"));
System.out.println(list.contains("HTML"));
// 删除某个元素:颗有东哥重复元素默认删除前面的第一个
System.out.println(list.remove("Java"));
System.out.println(list);
// 把集合转换成数组
Object[] arr = list.toArray();
System.out.println(Arrays.toString(arr));
Collection<String> list2 = new ArrayList<>();
list2.add("CSS");
list2.add("Echats");
list.addAll(list2);
System.out.println(list);
System.out.println(list2);
}
迭代器遍历
遍历就是一个一个把容器中元素访问一遍
迭代器在Java中的代表是Iterator,迭代器是集合的专用遍历方式
Collection集合获取迭代器
Iterator<E> iterator() 返回集合中的迭代器对象,迭代器对象默认指向当前集合的0索引
Iterator中的常用方法
boolean hasNext() 访问当前位置是否有元素存在,存在返回true,不存哎返回false
E next() 获取当前位置的元素,并同时将迭代器对象移向下一个位置,注意防止取出越界
public static void main(String[] args) {
Collection<String> lists = new ArrayList<>();
lists.add("Java");
lists.add("C++");
lists.add("Mysql");
lists.add("Paython");
lists.add("JavaScript");
// 得到当前的迭代器对象
Iterator<String> it = lists.iterator();
// String ele = it.next();
// System.out.println(ele);
// System.out.println(it.next());
// System.out.println(it.next());
// System.out.println(it.next());
while (it.hasNext()) {
String s = it.next();
System.out.println(s);
}
}
增强for循环
既可以遍历集合,也可以遍历数组
JDK5之后出现的,内部原理是一个Iterator迭代器,遍历集合先当于迭代器的简化写法
实现iterable接口的类才可以使用迭代器和增强for,Collection接口已经实现了Iterator接口
for(元素数据类型 变量名:数组或者Collection集合){
}
public static void main(String[] args) {
Collection<String> list = new ArrayList();
list.add("Java");
list.add("C++");
list.add("Mysql");
list.add("Paython");
list.add("JavaScript");
for (String ele : list) {
System.out.println(ele);
// 修改元素不会改变值
}
}
Lambda表达式
JDK8开始提供了一种更简单,更直接的遍历集合的方式
default void forEach(Consumer<? super T> action) 结合lambda遍历集合
public static void main(String[] args) {
Collection<String> list = new ArrayList<>();
list.add("Java");
list.add("C++");
list.add("Mysql");
list.add("Paython");
list.add("JavaScript");
list.forEach(new Consumer<String>() {
@Override
public void accept(String s) {
System.out.println(s);
}
});
// list.forEach(s->{
// System.out.println(s);
// });
}
案例:影片信息在程序中展示
public class Video {
private String name;
private double price;
private String actor;
public Video(String name, double price, String actor) {
this.name = name;
this.price = price;
this.actor = actor;
}
public Video() {
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public double getPrice() {
return price;
}
public void setPrice(double price) {
this.price = price;
}
public String getActor() {
return actor;
}
public void setActor(String actor) {
this.actor = actor;
}
@Override
public String toString() {
return "Video{" +
"name='" + name + '\'' +
", price=" + price +
", actor='" + actor + '\'' +
'}';
}
}
public static void main(String[] args) {
Collection<Video> video = new ArrayList<>();
video.add(new Video("《我不是妖神》", 9.9, "徐峥"));
video.add(new Video("《捉妖记》", 9.3, "白百合"));
video.add(new Video("《你的名字》", 9.9, "日本"));
for (Video v: video) {
System.out.println(v.getActor());
System.out.println(v.getName());
System.out.println(v.getPrice());
}
}
3.常见数据结构
数据结构是计算机底层存储、组织数据的方式。是指数据相互之间是以什么方式排列在一起的
栈数据结构的执行特点
后进先出,先进后出
数据进入栈模型的过程称为:压/进栈
数据离开栈模型的过程称为: 弹/出栈
队列
先进先出,后进后出
数据从后端进入队列模型的过程称为:入队列
数据从前端离开队列模型的过程称为:出队列
数组
查询速度快:查询数据通过地址值和索引定位,查询任意数据耗时相同。(元素在内存中是连续存储的)
删除效率低: 要将原始数据删除,同时后面每个数据前移
添加效率极低:添加位置后的每隔数据后移,再添加元素
链表
链表中的元素实在内存中不连续存储的,每个元素节点包含数据值和下一个元素的地址
链表查询慢。无论查询哪个数据都要从头开始找
链表增删相对快
二叉树、二叉查找树
只能有一个根节点,每个节点最多支持2个直接子节点
节点的度:节点拥有的子树的个数,二叉树的度不大于2,叶子节点度为0的节点,也称之为终端节点
高度:叶子节点的高度为1,叶子节点的父节点高度为2,以此类推,根节点的高度最高
层:根节点在第一层,一次类推
兄弟节点:拥有共同父节点的节点
二叉查找树又称为二叉排序树或者二叉搜索树
特点
每个节点上最多有两个子节点
左子树上所有节点的值都小于根节点的值
右子树上所有节点的值都大于根节点的值
目的:提高检索数据的性能
二叉查找树添加节点
规则:小的存左边,大的存右边,一样的不存
平衡二叉树
二叉树查找存在的问题:如果数据是排序好的,导致查询的性能和单链表一样,查询速度变慢
平衡二叉树实在满足二叉树的大小规则下,让树尽可能矮小,以此提高查数据的性能
要求
任意节点的左右两个子树的高度差不超过1,任意节点的左右两个子树都是一颗平衡二叉树
平衡二叉树在添加元素后可能导致不平衡
基本策略是进行左旋,或者右旋保证平衡
红黑树
红黑树是一种自平衡二叉树,是计算机科学中用到的一种数据结构
每一个节点可以是红或者黑,红黑树不是通过高度平衡的,它的平衡是通过“红黑规则”进行实现的
红黑规则
每一个节点是红色或者黑色的,根节点必须是黑色
如果一个节点没有子节点或者父节点,则该节点相应的指针属性值为Nil,这些Nil视为叶节点,叶节点是黑色的
如果某一个节点是红色,那么它的子节点必须是黑色(不能出现两个红色节点相连的情况)
对每一个节点,从该节点到其他所有后代节点的简单路径上,均包含相同数目的黑色节点
添加节点
添加的节点可以是红色的,也可以是黑色的
默认用红色效率高
红黑树增删改查的性能都很好
4.List系列集合
ArrayList,LinekdList:有序,可重复,有索引
有序:存储和去除的元素顺序一致
有索引:可以通过索引操作元素
可重复:存储的元素可以重复
List集合特有方法
List集合因为支持索引,所以多了很多索引操作的独特API,其他Colllection的功能List也都继承了
方法名称 | 说明 |
---|---|
void add(int index,E element) | 在此集合中的指定位置插入指定的元素 |
E remove(int index) | 删除指定索引处的元素,返回被删除的元素 |
E set(int index,E element) | 修改指定索引处的元素,返回被修改的元素 |
E get(int index) | 返回指定索引处的元素 |
public static void main(String[] args) {
List<String> list = new ArrayList<>();
list.add("Java");
list.add("Java");
list.add("JavaScript");
list.add("Mongo");
// 在某个索引位置插入元素
list.add(2, "CSS");
System.out.println(list);
// 根据索引删除元素,返回删除的元素
System.out.println(list.remove(1));
System.out.println(list);
// 根据索引获取元素
System.out.println(list.get(2));
// 修改某个索引位置的元素
System.out.println(list.set(2, "C++"));
System.out.println(list);
}
ArrayList底层是基于数组实现的,根据查询元素快,增删相对慢
LinkedList底层基于双链表实现的,查询元素慢,增删首位元素快
List集合遍历方式
1.for循环
2.迭代器
3.增强for循环
4.lembda表达式
public static void main(String[] args) {
List<String> list = new ArrayList<>();
list.add("Java");
list.add("Java");
list.add("JavaScript");
list.add("Mongo");
// for循环
for (int i = 0; i < list.size(); i++) {
String s = list.get(i);
System.out.println(s);
}
Iterator<String> t = list.iterator();
while (t.hasNext()) {
String ele = t.next();
System.out.println(ele);
}
for (String e:list) {
System.out.println(e);
}
list.forEach(s->{
System.out.println(s);
});
}
ArrayList集合底层原理
ArrayList底层是基于数组实现的:根据索引定位元素快,增删需要做元素的移位操作
第一次创建集合并添加一个元素的时候,在底层创建一个默认长度为10的数组
LinkedList的特点
底层数据结构是双链表,查询慢,首尾操作的速度是极快的
LinkedList集合的特有功能
方法名称 | 说明 |
---|---|
public void addFirst(E e) | 在列表开头插入指定的元素 |
public void addLast(E e) | 将指定的元素追加到此列表的末尾 |
pulic E getFirst() | 返回此列表中的第一个元素 |
public E getLast() | 返回此列表的最后一个元素 |
public E removeFirst() | 从此列表中删除并返回第一个元素 |
punlic E removeLast() | 从此列表中删除并返回最后一个元素 |
public static void main(String[] args) {
LinkedList<String> stack = new LinkedList<>();
// 压栈,入栈
stack.addFirst("第一个"); // stack.push("第一个")
stack.addFirst("第二个");
stack.addFirst("第三个");
stack.addFirst("第四个");
// 出栈,弹栈
System.out.println(stack.removeFirst()); // stack.pop()
System.out.println(stack.removeFirst());
System.out.println(stack.removeFirst());
// 队列
LinkedList<String> queue = new LinkedList<>();
queue.addLast("第一个");
queue.addLast("第二个");
queue.addLast("第三个");
queue.addLast("第四个");
// 出队
System.out.println(queue.removeFirst());
System.out.println(queue.removeFirst());
System.out.println(queue.removeFirst());
System.out.println(queue.removeFirst());
}
5.集合的并发修改异常问题
从集合中找出某个元素并删除的时候可能出现一种并发修改异常问题
public static void main(String[] args) {
List<String> list = new ArrayList<>();
list.add("Java");
list.add("HTML");
list.add("HTML");
list.add("JS");
list.add("MongoDB");
list.add("CSS");
System.out.println(list);
Iterator<String> t = list.iterator();
// while (t.hasNext()) {
// String ele = t.next();
// if ("HTML".equals(ele)) {
// // list.remove("HTML");
// // 使用迭代器删除当前位置的元素,保证不后移,成功遍历到所有元素
// t.remove();
// }
// }
// for (String s: list) {
// if("HTML".equals(s)) {
// list.remove("HTML");
// }
// }
// list.forEach(s->{
// if ("HTML".equals(s)) {
// list.remove("HTML");
// }
// });
// 不会出现异常错误,但是数据删除出现了问题
// i--
for (int i = 0; i < list.size(); i++) {
String ele = list.get(i);
if("HTML".equals(ele)) {
list.remove("HTML");
i--;
}
}
// for (int i = list.size()-1; i >= 0; i--) {
// String ele = list.get(i);
// if("HTML".equals(ele)) {
// list.remove("HTML");
// }
// }
System.out.println(list);
}
哪种遍且删除元素不出问题
迭代器遍历集合但是用迭代器自己的删除方法操作可以解决
使用for循环遍历且删除元素不会存在这个问题
6.泛型深入
泛型概述
JDK5中引入的特性,可以在编译阶段约束操作的数据类型,并进行检查
泛型的格式 <数据类型> 注意:泛型只能支持引用数据类型
集合体系的全部接口和实现类都是支持泛型的使用的
**泛型的好处 **
统一数据类型
把运行时期的问题提前到了编译阶段
泛型可以在很多地方进行定义
类后面 —> 泛型类
方法声明上 —> 泛型方法
接口后面 —> 泛型接口
泛型类
定义类时同时定义了泛型的类就是泛型类
泛型类的格式:修饰符class类名<泛型变量>{}
public class MyArrayList<T> {}
此处泛型变量T可以随便写为任意标识,常见的如E、T、K、V等
作用:编译阶段可以指定数据类型,类似于集合的作用
public class MyArrayList<E> {
private ArrayList lists = new ArrayList();
public void add(E e) {
lists.add(e);
}
public void remove(E e) {
lists.remove(e);
}
@Override
public String toString() {
return "MyArrayList{" +
"lists=" + lists +
'}';
}
}
public static void main(String[] args) {
MyArrayList<String> list = new MyArrayList<String>();
list.add("Java");
list.add("MySQL");
}
泛型类的原理
把出现泛型变量的地方全部替换成传输的真实数据类型
泛型方法
定义方法的同时定义了泛型的方法就是泛型方法
泛型方法的格式:修饰符<泛型变量>方法返回值 方法名称(形参列表){}
public void show(T t){}
作用:方法中可以使用泛型接受一切实际类型的参数,方法更具备通用性
public static void main(String[] args) {
String[] names = {"张三", "李四", "王五"};
Integer[] ages = {20, 30, 40};
printArray(names);
printArray(ages);
}
public static <T> void printArray(T[] arr) {
StringBuilder s = new StringBuilder("[");
if(arr!= null) {
for (int i=0;i<arr.length;i++) {
s.append(arr[i]).append(arr.length -1 == i ? "" :",");
}
s.append("]");
System.out.println(s);
}else {
System.out.println(arr);
}
}
泛型方法的原理
把出现泛型变量的地方全部替换成传输的真是数据类型
自定义泛型接口
使用了泛型定义的接口就是泛型接口
public interface Data<E>{}
作用:泛型接口可以让实现类选择当前功能需要操作的数据类型,这样重写的方法都是针对于该类型的操作
泛型通配符
?可以在“使用泛型”的时候代表一切类型
E T K V是在定义泛型的时候使用的
泛型的上下限
?extends Car: ?必须是Car或其子类 泛型上限
? super Car: ?必须是Car或者其父类 泛型下限
public class GenericityDemo {
public static void main(String[] args) {
ArrayList<BAOMA> b = new ArrayList<>();
b.add(new BAOMA());
b.add(new BAOMA());
b.add(new BAOMA());
go(b);
ArrayList<BENCHI> c = new ArrayList<>();
c.add(new BENCHI());
c.add(new BENCHI());
c.add(new BENCHI());
go(c);
ArrayList<Dog> d = new ArrayList<>();
d.add(new Dog());
d.add(new Dog());
d.add(new Dog());
// go(d);
}
public static void go(ArrayList<? extends Car> car){
}
}
class BENCHI extends Car {
}
class BAOMA extends Car{
}
class Car{
}
class Dog{
}