(1)集合
-
集合体系
|---Collection 单列集合
|---List 有序 可重复
|---ArrayList
|---LinkedList
|---Set 无序 不可重复
|---HashSet
|---LinkedHashSet 存取有序
|---TreeSet 默认升序
|---Map 双列集合
-
Collection<E> 这是单列集合的根接口
boolean add(E e) 添加元素
boolean remove(E e) 删除指定的元素 (如有重复删除第一个)
boolean contains(Object obj) 判断集合中是否包含指定元素
int size() 返回集合中元素的个数
boolean isEmpty() 判断集合是否为空
Object[] toArray() 将集合中元素存入一个对象数组并返回
T[] toArray(T[]a) 将集合中元素存入一个指定类型的数组并返回(指定数组长度)
void clear() 清空集合
void addAll(集合) 添加另外一个集合中的元素
public class Demo2 {
public static void main(String[] args) {
//多态创建单列集合
Collection<String> coll = new ArrayList();
//boolean add(E e) 添加元素
coll.add("关羽");
coll.add("刘备");
coll.add("赵云");
System.out.println(coll);
//boolean remove(E e) 删除指定的元素 (如有重复删除第一个)
coll.remove("赵云");
System.out.println(coll);
//boolean contains(Object obj) 判断集合中是否包含指定元素
boolean contains = coll.contains("刘备1");
System.out.println("contains = " + contains);
//int size() 返回集合中元素的个数
int size = coll.size();
System.out.println("集合长度:" + size);
//boolean isEmpty() 判断集合是否为空
boolean empty = coll.isEmpty();
System.out.println("empty="+empty); //true-集合空。false-非空
//Object[] toArray() 将集合中元素存入一个对象数组并返回
//Object[] objects = coll.toArray();
//T[] toArray(T[]a) 将集合中元素存入一个指定类型的数组并返回(指定数组长度)
String[] toArray = coll.toArray(new String[coll.size()]);
System.out.println(Arrays.toString(toArray));
//void clear() 清空集合
coll.clear();
System.out.println(coll.size());
}
}
-
遍历1: 送代器Iterator
单列集合专用遍历方式
-
Iterator相关方法
Iterator<E> iterator() 获取迭代器对象,默认指向第一个元素
boolean hasNext() 断当前位置是否有元素可以取出 (有返回true,没有返回false)
E next() 返回当前位置的元素,并将送代器后移一位(如果没有元素可以取出了还继续取,会报NoSuchElementException)
-
固定格式
Iterator<String> iterator = list.iterator();
while (iterator.hasNext()) {
String s = iterator.next();
}
public class Demo3 {
public static void main(String[] args) {
//1. 准备一个集合
Collection<String> collection = new ArrayList<>();
collection.add("java");
collection.add("python");
collection.add("c++");
collection.add("c#");
//2. 获取迭代器对象
Iterator<String> iterator = collection.iterator();
//3. 使用迭代器遍历
//3.1 使用while,判断是否有下个元素
while(iterator.hasNext()) {
//3.2 获取下个元素
String str = iterator.next();
System.out.println(str);
}
}
}
-
遍历2: 增强for循环
数组和集合都可以使用
-
相关格式
for(元素数据类型 变量名 : 数组或者集合){
操作变量
}
-
注意
1. 在增强for循环中修改数据, 是不会影响数据源的(底层会创建临时变量,来记录容器中的数据)
2. 增强for遍历集合,底层是迭代器遍历集合的逻辑
3. 增强for遍历数组,底层是普通for循环的逻辑
public class Demo4 {
public static void main(String[] args) {
//1. 准备一个集合
Collection<String> collection = new ArrayList<>();
collection.add("java");
collection.add("python");
collection.add("c++");
collection.add("c#");
//2. 使用增强for循环遍历
for (String s : collection) {
System.out.println(s);
}
//增强for循环也可以遍历数组
int[] arr = {1,2,3};
for (int i : arr) {
System.out.println(i);
}
}
}
public class Demo5 {
public static void main(String[] args) {
//1. 准备一个集合
Collection<String> collection = new ArrayList<>();
collection.add("java");
collection.add("python");
collection.add("c++");
collection.add("c#");
//2. Lambda表达式方式遍历集合
collection.forEach((e) -> {
System.out.println(e);
});
}
}
/*
展示多部电影信息
每部电影都是一个对象,多部电影要使用集合装起来。
遍历集合中的3个电影对象,输出每部电影的详情信息。
*/
public class Demo6 {
public static void main(String[] args) {
//1. 创建集合
Collection<Movie> coll = new ArrayList<Movie>();
coll.add(new Movie("肖申克的救赎", 9.7, "弗兰克"));
coll.add(new Movie("霸王别姬", 9.9, "陈凯歌"));
coll.add(new Movie("阿甘正传", 9.7, "罗伯特"));
//Movie movie1 = new Movie("热辣滚烫", 9.7, "贾玲");
//coll.add(movie1);
//2. 遍历输出
//2-1 迭代器
//获取迭代器
Iterator<Movie> iterator = coll.iterator();
//while循环
while (iterator.hasNext()) {
//获取下一个
Movie movie = iterator.next();
System.out.println(movie);
}
System.out.println("================");
//2-2 增强for
for (Movie movie : coll) {
System.out.println(movie);
}
System.out.println("================");
//2-3 lambda
coll.forEach((e) -> {
System.out.println(e);
});
}
}
/**
* 影视类
*/
public class Movie {
//名称
private String name;
//评分
private double score;
//导演
private String director;
public Movie(String name, double score, String director) {
this.name = name;
this.score = score;
this.director = director;
}
public Movie() { }
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public double getScore() {
return score;
}
public void setScore(double score) {
this.score = score;
}
public String getDirector() {
return director;
}
public void setDirector(String director) {
this.director = director;
}
@Override
public String toString() {
return "Movie{" +
"name='" + name + '\'' +
", score=" + score +
", director='" + director + '\'' +
'}';
}
}
-
问题
使用迭代器遍历集合时,又同时在删除集合中的数据,程序就会出现并发修改异常的错误。
由于增强for循环遍历集合就是迭代器遍历集合的简化写法,因此,使用增强for循环遍历集合,又在同时删除集合中的数据时,程序也会出现并发修改异常的错误
-
解决方法
迭代器: 用迭代器自己的删除方法删除数据即可
增强for循环: 暂时无法解决
普通for循环:可以倒着遍历并删除;或者从前往后遍历,但删除元素后做i --操作。
public class Demo7 {
public static void main(String[] args) {
//1. 准备一个集合
ArrayList<String> arrayList = new ArrayList<>();
arrayList.add("Java入门");
arrayList.add("宁夏枸杞");
arrayList.add("黑枸杞");
arrayList.add("人字拖");
arrayList.add("特级枸杞");
arrayList.add("枸杞子");
//使用迭代器方式:删除所有带枸杞的
Iterator<String> iterator = arrayList.iterator();
//循环遍历
while (iterator.hasNext()) {
//每个元素
String str = iterator.next();
if (str.contains("枸杞")) {
//通过迭代器删除
iterator.remove();
}
}
System.out.println(arrayList);
}
}
(2)list
public class Demo1 {
public static void main(String[] args) {
//多态方式创建list集合
List<String> list = new ArrayList<String>();
list.add("关羽"); //0
list.add("张飞"); //1
list.add("赵云"); //3
list.add("刘备"); //4
//void add(int index,E e) 在此集合中的指定位置插入指定的元素
list.add(2,"曹操"); //2
System.out.println(list);
//E remove(int index) 删除指定索引处的元素,返回被删除的元素 (一般不接收)
list.remove(4);
System.out.println(list);
//E set(int index,E e) 修改指定索引处的元素,返回被修改的元素(一般不接收)
list.set(1,"张飞飞");
System.out.println(list);
//E get(int index) 返回指定索引处的元素
System.out.println(list.get(1));
//System.out.println(list);
}
}
public class Demo2 {
public static void main(String[] args) {
List<String> list = new ArrayList<>();
list.add("张三");
list.add("李四");
list.add("王五");
list.add("王五");
//1. 迭代器
Iterator<String> iterator = list.iterator();
while (iterator.hasNext()) {
String s = iterator.next();
System.out.println(s);
}
System.out.println("---------------");
//2. 增强for循环
for (String s : list) {
System.out.println(s);
}
System.out.println("---------------");
//3. Lambda表达式
list.forEach(e ->{
System.out.println(e);
});
System.out.println("---------------");
//4. for循环(因为List集合有索引)
for (int i = 0; i < list.size(); i++) {
System.out.println(list.get(i));
}
}
}
-
ArrayList
底层数据结构:
基于数组实现
特点:
1. 查询速度快(通过索引直接定位)
2. 增删效率低(增删的时候,需要移动增删元素后面的元素, 有时还需要进行扩容)
适用场景:
1. ArrayList适合于根据索引查询数据, 或者数据量不大的场景
2. ArrayList不适合于数据量大, 同时又要频繁进行增删操作的场景
底层原理:
1. 利用无参构造器创建的集合,会在底层创建一个默认长度为0的数组
ArrayList<String> list = new ArrayList();
2. 添加第一个元素时,底层会创建一个新的长度为10的数组
list.add("a");
3. 存满时,会扩容1.5倍
比如存入第11个元素的时候, 长度会扩容到15
4. 如果一次添加多个元素, 1.5倍还放不下, 则新创建数组的长度以实际为准
比如原来是10个元素,现在又要存入10个, 则长度会扩容到20
-
LinkedList
底层数据结构:
基于双向链表实现(内存地址不连续,每个元素记录自己的前后元素)
特点:
1. 查询速度慢
2. 增删效率高
3. 对于首尾元素进行增删改查的速度都是极快的
应用场景:
1. 用来设计队列(两端开口,类似于一个管道,先进先出)
只操作首尾元素, 尾部添加, 首部删除
2. 用来设计栈(一段开口,类似于弹夹,先进后出)
public class Demo4 {
public static void main(String[] args) {
//makeQueue();
makeStack();
}
/*
队列: 两端开口,特点是先进先出(排队)
从队列后端入队列: addLast 方法
从队列前端出队列: removeFirst方法
*/
public static void makeQueue() {
LinkedList<String> queue = new LinkedList<>();
//排队买票 (入队)
queue.addLast("1号顾客"); //1号顾客
queue.addLast("2号顾客"); //1号顾客,2号顾客
queue.addLast("3号顾客"); //1号顾客,2号顾客,3号顾客
queue.addLast("4号顾客"); //1号顾客,2号顾客,3号顾客,4号顾客
queue.addLast("5号顾客"); //1号顾客,2号顾客,3号顾客,4号顾客,5号顾客
System.out.println(queue);
System.out.println("--------------");
//放票-出对
String str1 = queue.removeFirst();
System.out.println(str1);
String str2 = queue.removeFirst();
System.out.println(str2);
String str3 = queue.removeFirst();
System.out.println(str3);
System.out.println(queue);
}
/*
栈: 顶端开口的结构,特点是先进后出
进栈/压栈: push方法(底层封装了addFirst 方法)
出栈/弹栈: pop方法底 (底层封装了removeFirst方法)
*/
public static void makeStack() {
//创建LinkedList
LinkedList<String> stack = new LinkedList<>();
//压栈(push):将数据存入栈中
stack.push("关羽");
stack.push("张飞");
stack.push("赵云");
System.out.println(stack);
System.out.println("-------------------");
//弹栈(pop):将栈顶数据移除
String str1 = stack.pop();
System.out.println(str1); //赵云
System.out.println(stack);
}
}
(3)set
public class Demo1 {
public static void main(String[] args) {
//testHashSet();
//testLinkedHashSet();
testTreeSet();
}
//HashSet: 无序、没有索引、不可重复
private static void testHashSet() {
HashSet<Integer> set = new HashSet();
set.add(44);
set.add(25);
set.add(6);
set.add(29);
set.add(44);
System.out.println(set);
}
//LinkedHashSet: 存取有序、没有索引、不可重复
private static void testLinkedHashSet() {
LinkedHashSet<Integer> linkedHashSet = new LinkedHashSet<>();
linkedHashSet.add(44);
linkedHashSet.add(33);
linkedHashSet.add(11);
linkedHashSet.add(22);
linkedHashSet.add(22);
System.out.println(linkedHashSet);
}
//TressSet: 排序、没有索引、不可重复
private static void testTreeSet() {
TreeSet<String> treeSet = new TreeSet<>();
treeSet.add("book");
treeSet.add("dog");
treeSet.add("cat");
treeSet.add("apple");
// treeSet.add(44);
// treeSet.add(33);
// treeSet.add(11);
// treeSet.add(22);
// treeSet.add(22);
System.out.println(treeSet);
}
}
使用场景
HashSet集合判定两个对象的标准就是两个对象的hash值是否一致, 因此我们经常重写hashcode实现集合中对象去重
public class Demo2 {
public static void main(String[] args) {
// //1、创建Student对象,获取对象的hash值
// Student student = new Student("张三", 18);
// int code1 = student.hashCode();
// int code2 = student.hashCode();
// //同一个对象多次调用hashCode方法,返回的哈希值是相同的;
// System.out.println(code1);
// System.out.println(code2);
// //2、不同的对象,他们哈希值大几率不相同,但是也有可能会相同(哈希碰撞)
// Student student2 = new Student("张三", 18);
// int code3 = student2.hashCode();
// System.out.println(code3);
//通过HashSet集合,存储学生数据(去重)
HashSet<Student> set = new HashSet();
//构造几个学生对象,存入到set
Student student1 = new Student("张三", 18);
Student student2 = new Student("李四", 28);
Student student3 = new Student("张三", 18);
System.out.println(student1.hashCode());
System.out.println(student3.hashCode());
System.out.println(student1.toString());
System.out.println(student3.toString());
set.add(student1);
set.add(student2);
set.add(student3);
//打印set中的数据
System.out.println(set);
}
}
class Student {
@Override
public int hashCode() {
return Objects.hash(name, age);
}
private String name;
private int age;
public Student() {
}
public Student(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Student student = (Student) o;
return age == student.age && Objects.equals(name, student.name);
}
}
-
HashSet集合的底层原理
JDK8之前: 数组+链表
JDK8开始: 数组+链表+红黑树
-
树结构
二叉树: 基本模型
每一个根节点最多只能有两个子节点,子节点数量称为度,树的总层数为树高
左子节点
右子节点
左子树
右子树
-
二叉查找/二又搜索树: BST (Binary Search Tree)
小的存左边,大的存右边,相同则不存
如果数据已经排好序,那么存入二又树查找树,会形成类似链表结构,查询效率还是低
如果左、右子树的树高差比较大,那么导致左右子树不平衡,影响查询效率
-
平衡二叉树: AVL (Balanced Binary Tree)
有规律且相对平衡的二叉树
当插入一个元素,导致左右子树的树高差大于1。那么就会触发旋转
旋转分为左旋和右旋,用来保证左右子树的相对平衡
-
红黑: RBT (Red-Black Tree)
特殊的/自平衡的二又查找树,是计算机科学中用到的一种数据结构
1972年出现时被称为平衡二叉树,后来1978年被修改为如今的红黑树
红黑树不是高度平衡的,有自己保证平衡的规则(红黑规则),性能较好,红黑规则如下:
每一个节点都是红色或者黑色的
根节点必须是黑色的
两个红色节点不能相连
如果一个节点没有子节点,则该节点相应的指针属性为Nil (称为叶子结点),叶子结点是黑色的
对于每一个节点,到其所有后代叶节点的简单路径上,包含的黑色 节点数量相同
-
TreeSet 可排序、不重复、无索引
底层基于红黑树实现排序,排序规则认为属性是相同的对象则不存
-
TreeSet的排序
对于数值型Integer、Double,默认按照数值升序排列;
对于String类型数据,默认按照字典排序
对于自定义类,默认是无法排序的,需要我们指定排序规则
自然排序:自定义类实现Comparable接口,重写compareTo方法,指定排序规则
比较器排序:写在TreeSet构造参数中传递Comparator比较器对象,重写compare方法,指定排序规则
-
需求
使用TreeSet存储教师对象,重复对象不存,并且用两种方式按照年龄升序排列
public class Demo4 {
public static void main(String[] args) {
//创建TreeSet
Set<Teacher> treeSet = new TreeSet<>(new Comparator<Teacher>() {
@Override
public int compare(Teacher o1, Teacher o2) {
return o1.getAge() - o2.getAge();
}
});
//添加学生
treeSet.add(new Teacher("张三", 19));
treeSet.add(new Teacher("李四", 18));
treeSet.add(new Teacher("王五", 20));
treeSet.add(new Teacher("赵六", 17));
treeSet.add(new Teacher("赵六", 17));
//打印
for (Teacher teacher : treeSet) {
System.out.println(teacher);
}
}
}
class Teacher {
private String name;
private int age;
public Teacher() {
}
public Teacher(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public String toString() {
return "Teacher{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Teacher teacher = (Teacher) o;
return age == teacher.age && Objects.equals(name, teacher.name);
}
@Override
public int hashCode() {
return Objects.hash(name, age);
}
}