目录
List系列集合
常见的是ArrayList、LinkedList
特点
①有序:存储和取出的元素顺序一致
②有索引:可以通过索引操作元素
③可重复:存储的元素可以重复
List集合特有方法
List集合因为支持索引,所以多了很多索引操作的独特API,其他Collection的功能List也都继承了。
常见方法如下
方法名称 | 说明 |
---|---|
void add(int index,E element) | 在此集合中的指定位置插入指定的元素 |
E remove(int index) | 删除指定索引处的元素,返回被删除的元素 |
E set(int index,E element) | 修改指定索引处的元素,返回被修改的元素 |
E get(int index) | 返回指定索引处的元素 |
(这些方法没什么好说的,还是直接调用就行)
List的实现类的底层原理
1)ArrayList底层是基于数组实现的,根据查询元素快,增删相对慢
2)LinkedList底层基于双链表实现的,查询元素慢,增删首尾元素是非常快的
List集合的遍历方式
①迭代器
②增强for循环
③Lambda表达式
④for循环(因为List集合存在索引)
ArrayList集合底层原理(简略了解一下就行)
- ArrayList底层是基于数组实现的,根据索引定位元素快,增删需做元素的移位操作。
- 第一次创建集合并添加第一个元素的时候,在底层创建一个默认长度为10的数组
LinkedList集合
LinkedList底层数据结构是双链表,查询慢,首尾操作的速度是极快的,所以它有许多首尾操作的特有API
LinkedList集合的常见特有功能
方法名称 | 功能 |
---|---|
public void addFirst(E e) | 在该列表开头插入指定的元素 |
public void addLast(E e) | 将指定的元素追加到此列表的末尾 |
public E getFirst() | 返回此列表中的第一个元素 |
public E getLast() | 返回此列表中的最后一个元素 |
public E removeFirst() | 从此列表中删除并返回第一个元素 |
public E removeLast() | 从此列表中删除并返回最后一个元素 |
这些特有功能只有LinkedList集合能用,所以要用这些功能有两种方式
① LinkedList 集合名=new LinkedList<>();
②用多态时,后面操作强转一下就行了
代码展示
①
LinkedList<String> lists=new LinkedList<>();
②
List<String> lists=new LinkedList<>();
LinkedList linkedLists= (LinkedList) lists;
LinkedList可以完成栈结构和队列结构的
栈结构的特点为先进后出,后进先出。 所以只要一直在开头加元素,再一直去掉第一个元素就可以了
从专业角度讲,我们一般用push替代addFirst,pop替代 removeFirst,效果是一样的
代码如下
LinkedList<String> lists=new LinkedList<>();
//压栈,入栈
lists.push("第一颗子弹");
lists.push("第二颗子弹");
lists.push("第三颗子弹");
lists.push("第四颗子弹");
lists.push("第五颗子弹");
//弹栈,出栈
System.out.println(lists.pop()); // 第五颗子弹
System.out.println(lists.pop()); // 第四颗子弹
System.out.println(lists.pop()); // 第三颗子弹
System.out.println(lists.pop()); // 第二颗子弹
System.out.println(lists.pop()); // 第一颗子弹
队列结构的特点为先进先出,后进后出。 所以只要一直在最后加元素,再一直去掉第一个元素就可以了
代码如下
LinkedList<String> lists=new LinkedList<>();
//入队
lists.addLast("1号");
lists.addLast("2号");
lists.addLast("3号");
lists.addLast("4号");
lists.addLast("5号");
//出队
System.out.println(lists.removeFirst()); // 1号
System.out.println(lists.removeFirst()); // 2号
System.out.println(lists.removeFirst()); // 3号
System.out.println(lists.removeFirst()); // 4号
System.out.println(lists.removeFirst()); // 5号
遍历可能存在的问题
当我们从集合中找出某个元素并删除的时候可能出现一种并发修改异常问题。
哪些遍历存在问题?
① 迭代器遍历集合且直接用集合删除元素的时候可能出现。
我前面说过了迭代器的遍历方式,我直接粘贴过来
方法名称 | 说明 |
---|---|
boolean hasNext() ; | 询问当前位置是否有元素存在,存在返回true,不存在返回false |
E next(); | 获取当前位置的元素,并同时将迭代器对象移向下一个位置,注意防止取出越界 |
这两个方法的配合使用代码如下
Iterator<String> it=Lists.iterator();
while (it.hasNext()){
System.out.println(it.next());
}
我们如果在while中删除元素,即进行下面的操作
Iterator<String> it=lists.iterator();
while (it.hasNext()){
lists.remove(it.next());
}
it.next() 这一步是为了获取当前位置的元素,并同时将迭代器对象移向下一个位置,但我们删除元素后就相当于向下移动了两个位置,就会出现越界异常
②增强for循环遍历集合且直接用集合删除元素的时候可能出现。
因为增强for循环其内部原理是一个Iterator迭代器,遍历集合相当于是迭代器的简化形式,当然不能了,而且没有解决办法。
哪种遍历且删除元素不出问题
①迭代器遍历集合但是用迭代器自己的删除方法操作可以解决。(最好加个判断条件,否则会出现IllegalStateException异常,该异常表示,当前对客户端的响应已经结束,不能在响应已经结束(或说消亡)后再向客户端(实际上是缓冲区)输出任何内容。)(了解一下就行了)
while (it.hasNext()){
it.remove();
}
② 使用for循环遍历并删除元素不会存在这个问题。(就是用普通的for循环遍历)
泛型
概述
- 是JDK5中引入的特性,可以在编译阶段约束操作的数据类型,并进行检查。
- 泛型的格式:<数据类型>。 注意:泛型只能支持引用数据类型。
- 集合体系的全部接口和实现类都是支持泛型的使用的。
好处
- 统一数据类型。
- 把运行时期的问题提前到了编译期间,避免了强制类型转换可能出现的异常,因为编译阶段类型就能确定下来。
泛型类的概述
- 定义类时同时定义了泛型的类就是泛型类。
- 泛型类的格式: 修饰符 class 类名<泛型变量>{ }
public class Demo<T>{}
- 此处泛型变量T可以随便写为任意标识,常见的如E、T、K、V等。
作用
编译阶段可以指定数据类型,类似于集合的作用。
原理
把出现泛型变量的地方全部替换成传输的真实数据类型。
泛型方法的概述
概述
- 定义方法时同时定义了泛型的方法就是泛型方法。
- 泛型方法的格式:修饰符<泛型变量>方法返回值 方法名称(形参列表){}
public static<T> void sum(){}
作用
方法中可以使用泛型接收一切实际类型的参数,方法更具备通用性。
原理(核心思想)
把出现泛型变量的地方全部替换成传输的真实数据类型。
泛型接口
概述
- 使用了泛型定义的接口就是泛型接口。
- 泛型接口的格式:修饰符 interface 接口名称<泛型变量>{ }
public interface Demo<T>{}
作用
泛型接口可以让实现类选择当前功能需要操作的数据类型
原理
实现类可以在实现接口的时候传入自己操作的数据类型,这样重写的方法都将是针对于该类型的操作。
通配符
- ?可以在“使用泛型”的时候代表一切类型。
- E T K V 是在定义泛型的时候使用的。
泛型的上下限
- ?extends Car:?必须是Car或者其子类 泛型上限。
- ?super Car: ?必须是Car或者其父类 泛型下限。
Set系列集合
特点
- 无序:存储顺序不一致。
- 不重复:可以去重复。
- 无索引:没有带索引的方法,所以不能使用普通for循环遍历,也不能通过索引获取元素。
Set集合实现类特点
- HashSet:无序、不重复、无索引。
- LinkedHashSet:有序、不重复、无索引。
- TreeSet:排序、不重复、无索引。
注意:Set集合的功能基本上与Collection的API一致
HashSet集合
底层原理
- HashSet集合底层采取哈希表存储的数据。
- 哈希表是一种对于增删改查数据性能都较好的结构
哈希表
组成
- JDK8之前,底层使用数组+链表组成。
- JDK8开始后,底层采用数组+链表+红黑树组成。
在了解哈希表之前需要先理解哈希值的概念
哈希值: JDK根据对象的地址,按照某种规则算出来的int类型的数值。
我们可以通过Object类的一个API得到对象的哈希值
方法名称 | 作用 |
---|---|
public int hashCode(); | 返回对象的哈希值 |
对象的哈希值特点
1)同一个对象多次调用hashCode()方法返回的哈希值是相同的。
2)默认情况下,不同对象的哈希值是不同的。
Set集合底层用哈希表的详细流程(包含无序和去重复的原因)
①创建一个默认长度16,默认加载因为0.75的数组,数组名table
②根据元素的哈希值跟数组的长度计算出应存入的位置
③判断当前位置是否为null,如果是null直接存入,如果位置不为null,表示有元素,则调用equals方法比较属性值,如果一样,则不存,如果不一样,则存入数组。(JDK7新元素占老元素位置,指向老元素;JDK 8中新元素挂在老元素下面)
④当数组存满到16*0.75=12时,就自动扩容,每次扩容原先的两倍
随便个一个草图
一条线的就是链表,JDK8之前的,不是一条的就是红黑树,JDK8开始后的,至于这个位置怎么排的、谁指向谁,看上面的流程自己理解。
问题:
如果我们自己创建了一个类,但创建不同对象时有可能会出现内容相同的情况,但都是new出来的对象,地址是不同的,哈希值也就不同,但我们想把内容相同的排除该怎么办呢?
解决: 重写对象的hashCode和equals方法
(ieda中有快捷键,自己看看理解一下就行了)
步骤
①鼠标右键
②
③
LinkedHashSet集合
特点:有序、不重复、无索引。
注: 这里的有序并不是位置按顺序,而是保证存储和取出的元素顺序一致,即每个元素结点存储上一个和下一个元素的地址,即双链表结构
原理
底层数据结构是依然哈希表,只是每个元素又额外的多了一个双链表的机制记录存储的顺序。
模型图
TreeSet集合
特点
- 不重复、无索引、可排序。
- 可排序:按照元素的大小默认升序(有小到大)排序。
- TreeSet集合底层是基于红黑树的数据结构实现排序的,增删改查性能都较好。
注意: TreeSet集合是一定要排序的,可以将元素按照指定的规则进行排序。
TreeSet集合默认的规则
- 对于数值类型: Integer,Double,官方默认按照大小进行升序排序。
- 对于字符串类型:默认按照首字符的编号升序排序。
- 对于自定义类型,如Student对象,TreeSet无法直接排序。
结论: 想要使用TreeSet存储自定义类型,需要制定排序规则
自定义排序规则(以Student类,比年龄为例)
方式一
- 让自定义的类(如学生类)实现Comparable接口重写里面的compareTo方法来定制比较规则。
class Student implements Comparable<Student>{
@Override
public int compareTo(Student o) {
return this.age-o.age;
}
方式二
- TreeSet集合有参数构造器,可以设置Comparator接口对应的比较器对象,来定制比较规则。
TreeSet<Student> treeSet=new TreeSet<>(new Comparator<Student>() {
@Override
public int compare(Student o1, Student o2) {
return o1.age-o2.age;
}
});
}
两种方式中,关于返回值的规则:
1)如果认为第一个元素大于第二个元素返回正整数即可。
2)如果认为第一个元素小于第二个元素返回负整数即可。
3)如果认为第一个元素等于第二个元素返回0即可,此时Treeset集合只会保留一个元素,认为两者重复。
注意: 如果TreeSet集合存储的对象有实现比较规则,集合也自带比较器,默认使用集合自带的比较器排序
Collection系列集合使用场景总结
如果希望元素可以重复,又有索引,索引查询要快
- 用ArrayList集合,基于数组的。 (用的最多)
如果希望元素可以重复,又有索引,增删首尾操作快
- 用LinkedList集合,基于链表的。
如果希望增删改查都快,但是元素不重复、无序、无索引
- 用HashSet集合,基于哈希表的。
如果希望增删改查都快,但是元素不重复、有序、无索引
- 用LinkedHashSet集合,基于哈希表和双链表。
如果要对对象进行排序
- 用TreeSet集合,基于红黑树。后续也可以用List集合实现排序