集合体系结构
Collection (单列集合)
在单列集合里,每个元素(数据)只包含一个值。
Map (双列集合)
双列集合,每个元素包含两个值(键值对)。
Collection集合体系
Collection集合特点
List系列集合:添加的元素是有序、可重复、有索引的。
ArrayList 、LinekdList:有序、可重复、有索引。
Set系列集合:添加的元素是无序、不重复、无索引。
HashSet:无序,不重复,无索引;
LinkedHashSet:有序,不重复,无索引。
TreeSet:按照大小默认升序排序、不重复、无索引。
Collection的常用方法
- Collection是单列集合的祖宗,它规定的方法(功能)是全部单列集合都会继承的。
Collection的遍历方式
迭代器
迭代器概述:迭代器是用来遍历集合的专用方式(数组没有迭代器),在Java中迭代器的代表是Iterator。
Collection集合获取迭代器的方法
Iterator迭代器中的常用方法
Collection<String> c = new ArrayList<>();
c.add("赵敏");
c.add("小昭");
c.add("素素");
// c.add("灭绝");
System.out.println(c);
// c = [赵敏, 小昭, 素素]
// it
// 使用迭代器遍历集合
// 1、从集合对象中获取迭代器对象。
Iterator<String> it = c.iterator();
// System.out.println(it.next());
// System.out.println(it.next());
// System.out.println(it.next());
// System.out.println(it.next());
// System.out.println(it.next()); // 出现异常的
// 2、我们应该使用循环结合迭代器遍历集合。
while (it.hasNext()){
String ele = it.next();
System.out.println(ele);
}
}
增强for循环
格式:
for (元素的数据类型 变量名 : 数组或者集合) {
}
- 增强for循环可以遍历集合和数组。
- 增强for循环遍历集合,本质就是迭代器遍历集合的简化写法。
Collection<String> c = new ArrayList<>();
c.add("赵敏");
c.add("小昭");
c.add("素素");
c.add("灭绝");
System.out.println(c);
// c = [赵敏, 小昭, 素素, 灭绝]
// ele
// 使用增强for遍历集合或者数组。
for (String ele : c) {
System.out.println(ele);
}
String[] names = {"迪丽热巴", "古力娜扎", "稀奇哈哈"};
for (String name : names) {
System.out.println(name);
}
}
Lambda表达式
- 得益于JDK 8开始的新技术Lambda表达式,提供了一种更简单、更直接的方式来遍历集合。
需要使用Collection的如下方法来完成
public static void main(String[] args) {
Collection<String> c = new ArrayList<>();
c.add("赵敏");
c.add("小昭");
c.add("殷素素");
c.add("周芷若");
System.out.println(c);
/* c.forEach(new Consumer<String>() {
@Override
public void accept(String s) {
System.out.println(s);
}
}) ;*/
/* c.forEach((String s)-> {
System.out.println(s);
}) ;*/
/* c.forEach( s-> {
System.out.println(s);
}) ;*/
c.forEach( System.out::println) ;
}
集合中存储的是元素对象的地址。
List集合
List系列集合的特点: 有序,可重复,有索引。
ArrayList:有序,可重复,有索引。
(底层实现不同,适合场景不同)
LinkedList:有序,可重复,有索引。
List集合的特有方法:
List集合因为支持索引,所以多了很多与索引相关的方法,当然,Collection的功能List也都继承了。
// 1.创建一个ArrayList集合对象(有序、可重复、有索引)
List<String> list = new ArrayList<>(); // 一行经典代码
list.add("蜘蛛精");
list.add("至尊宝");
list.add("至尊宝");
list.add("牛夫人");
System.out.println(list); // [蜘蛛精, 至尊宝, 至尊宝, 牛夫人]
// 2.public void add(int index, E element): 在某个索引位置插入元素。
list.add(2, "紫霞仙子");
System.out.println(list);
// 3.public E remove(int index): 根据索引删除元素,返回被删除元素
System.out.println(list.remove(2));
System.out.println(list);
// 4.public E get(int index): 返回集合中指定位置的元素。
System.out.println(list.get(3));
// 5.public E set(int index, E element): 修改索引位置处的元素,修改成功后,会返回原来的数据
System.out.println(list.set(3, "牛魔王"));
System.out.println(list);
}
List集合支持的遍历方式
- for循环(因为List集合有索引)
- 迭代器
- 增强for循环
- Lambda表达式
ArrayList集合的底层原理
- 基于数组实现的
数组的特点 :查询快,增删慢
查询速度快(注意:是根据索引查询数据快):查询数据通过地址值和索引定位,查询任意数据耗时相同。
删除效率低:可能需要把后面很多的数据进行前移。
添加效率极低:可能需要把后面很多的数据后移,再添加元素;或者也可能需要进行数组的扩容。
- 利用无参构造器创建的集合,会在底层创建一个默认长度为0的数组
- 添加第一个元素时,底层会创建一个新的长度为10的数组
- 存满时,会扩容1.5倍
- 如果一次添加多个元素,1.5倍还放不下,则新创建数组的长度以实际为准
ArrayList集合与数组一样都是查询快,增删慢。
ArrayList集合适合的应用场景:
1.ArrayList适合:根据索引查询数据,比如根据随机索引取数据(高效)!或者数据量不是很大时!
2.ArrayList不适合:数据量大的同时,又要频繁的进行增删操作。
LinkedList集合的底层原理
- 基于双链表实现。
什么是链表,链表的特点是什么?
- 链表中的结点是独立的对象,在内存中是不连续的,每个结点包含数据值和下一个结点的地址。
- 链表的特点1:查询慢,无论查询哪个数据都要从头开始找。
- 链表的特点2:链表增删相对快
双向链表的特点:查询慢,增删相对较快,但对首尾元素进行增删改查的速度是极快的。
LinkedList新增了:很多首尾操作的特有方法。
LinkedList的应用场景之一:可以用来设计队列
// 1、创建一个队列。
LinkedList<String> queue = new LinkedList<>();
// 入队
queue.addLast("第1号人");
queue.addLast("第2号人");
queue.addLast("第3号人");
queue.addLast("第4号人");
System.out.println(queue);
// 出队
System.out.println(queue.removeFirst());
System.out.println(queue.removeFirst());
System.out.println(queue.removeFirst());
System.out.println(queue);
LinkedList的应用场景之一:可以用来设计栈
栈的特点:后进先出,先进后出。
数据进入栈模型的过程称为:压/进栈(push)
数据离开栈模型的过程称为:弹/出栈(pop)
// 2、创建一个栈对象。
LinkedList<String> stack = new LinkedList<>();
// 压栈(push)
stack.push("第1颗子弹");
stack.push("第2颗子弹");
stack.push("第3颗子弹");
stack.push("第4颗子弹");
System.out.println(stack);
// 出栈(pop)
System.out.println(stack.pop());
System.out.println(stack.pop());
System.out.println(stack);
Set集合
特点 :
Set系列集合的特点:无序:添加数据的顺序和获取出的数据顺序不一致; 不重复; 无索引;
HashSet:无序,不重复,无索引。
LinkedHashSet:有序,不重复,无索引。
TreeSet:可排序(按照从小到大),不重复,无索引。
注意:
- Set要用到的常用方法,基本上就是Collection提供的!!
- 自己几乎没有额外新增的一些常用功能!
HashSet集合的底层原理
注意:在正式了解HashSet集合的底层原理前,我们需要先搞清楚一个前置知识:哈希值!
哈希值
- 就是一个int类型的数值,Java中每个对象都有一个哈希值。
- Java中的所有对象,都可以调用Obejct类提供的hashCode方法,返回该对象自己的哈希值。
- public int hashCode():返回对象的哈希码值。
对象哈希值的特点
- 同一个对象多次调用hashCode()方法返回的哈希值是相同的。
- 不同的对象,它们的哈希值一般不相同,但也有可能会相同(哈希碰撞)。
int (-21亿多~21亿多)
HashSet集合的底层原理
- 基于哈希表实现。
- 哈希表是一种增删改查数据,性能都较好的数据结构。
哈希表
- JDK8之前,哈希表 = 数组+链表
- JDK8开始,哈希表 = 数组+链表+红黑树
JDK8之前HashSet集合的底层原理,基于哈希表:数组+链表
- 创建一个默认长度16的数组,默认加载因子为0.75,数组名table
- 使用元素的哈希值对数组的长度求余计算出应存入的位置
- 判断当前位置是否为null,表示有元素,则调用equals方法比较。相等,则不存;不相等,则存入数组。
- JDK8之前,新元素存入数组,占老元素位置,老元素挂下面。
- JDK8开始之后,新元素直接挂老元素下面。
1.为什么添加的元素无序,不重复,无索引?
- 哈希表是一种增删改查数据性能都较好的结构。
2.如果数组快占满了,会出现什么问题?该咋办?
链表会过长,导致查询性能降低;扩容。
JDK8开始,当链表长度超过8,且数组长度>=64时,自动将链表转成红黑树。
引入红黑树后进一步提高了操作数据的性能。
HashSet集合默认不能对内容一样的两个不同对象去重复!
如何让HashSet集合能够实现对内容一样的两个不同对象也能去重复?
如果希望Set集合认为2个内容一样的对象是重复的, 必须重写对象的hashCode()和equals()方法
LinkedHashSet底层原理
依然是基于哈希表(数组、链表、红黑树)实现的。
但是,它的每个元素都额外的多了一个双链表的机制记录它前后元素的位置。
TreeSet集合
特点:不重复、无索引、可排序(默认升序排序 ,按照元素的大小,由小到大排序)
底层是基于红黑树实现的排序。
注意:
对于数值类型:Integer , Double,默认按照数值本身的大小进行升序排序。
对于字符串类型:默认按照首字符的编号升序排序。
对于自定义类型如Student对象,TreeSet默认是无法直接排序的。
自定义排序规则:
TreeSet集合存储自定义类型的对象时,必须指定排序规则,支持如下两种方式来指定比较规则。
方式一:
让自定义的类(如学生类)实现Comparable接口,重写里面的compareTo方法来指定比较规则。
方式二:
通过调用TreeSet集合有参数构造器,可以设置Comparator对象(比较器对象,用于指定比较规则。
两种方式中关于返回值的规则:
如果认为第一个元素 > 第二个元素 返回正整数即可。
如果认为第一个元素 < 第二个元素返回负整数即可。
如果认为第一个元素 = 第二个元素返回0即可,此时Treeset集合只会保留一个元素,认为两者重复。
注意:如果类本身有实现Comparable接口,TreeSet集合同时也自带比较器,默认使用集合自带的比较器排序。
public static void main(String[] args) {
//TreeSet就近选择自带的比较器对象进行比较
/* Set <Student> students=new TreeSet<>(new Comparator<Student>() {
@Override
public int compare(Student o1, Student o2) {
//身高进行升序排列
return Double.compare(o1.getHeight(),o2.getHeight());
}
});*/
Set <Student> students=new TreeSet<>(( o1,o2)-> Double.compare(o1.getHeight(),o2.getHeight()));
students.add(new Student("赵露思",75,16));
students.add(new Student("张三",130,17));
students.add(new Student("赵五",120,14));
students.add(new Student("李四",100,20));
System.out.println(students);
}
public class Student implements Comparable<Student> {
private String name;
private double height;
private int age;
@Override
public int compareTo(Student o) {
//如果认为左边对象大于右边对象,则返回正整数
//如果认为左边对象小于右边对象,则返回负整数
//如果认为左边对象等于右边对象,则返回0
return this.age-o.age;//升序
}
集合的并发修改异常
1.使用迭代器遍历集合时,又同时在删除集合中的数据,程序就会出现并发修改异常的错误。
2.由于增强for循环遍历集合就是迭代器遍历集合的简化写法,因此,使用增强for循环遍历集合,又在同时删除集合中的数据时,程序也会出现并发修改异常的错误 。
怎么保证遍历集合同时删除数据时不出bug?
1.使用迭代器遍历集合,但用迭代器自己的删除方法删除数据即可。
2.如果能用for循环遍历时:可以倒着遍历并删除;或者从前往后遍历,但删除元素后做i --操作。