Java集合进阶
文章目录
集合体系结构
单列集合(Collection)
在添加数据的时候,每次只能添加一个元素。
双列集合(Map)
特点
- 双列集合一次需要存一对数据,分别为键和值
- 键不能重复,值可以重复
- 键和值是一一对应的,每一个键只能找到自己对应的值
- 键和值这个整体,我们称之为“键值对”或者“键值对象”,在Java中叫做“Entry对象”
Map的常见API
Map是双列集合的顶层接口,他的功能是全部双列集合都可以继承使用
Map的遍历方式
键找值
public class MapDemo2{
public static void main(String[] args){
Map<String,String> map = new HashMap<>();
map.put("嘻嘻","呼呼");
map.put("哈哈","嘿嘿");
map.put("芜湖","嗯哼");
Set<String> keys = map.keySet();
for(String key:keys){
String value = map.get(key);
System.out.println(key + "=" + value);
}
}
}
键值对
package com.example.demo;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
public class Text {
public static void main(String[] args) {
//1.创建Map集合的对象
Map<String,String> map = new HashMap<>();
//2.添加元素
map.put("花园宝宝1","玛卡巴卡");
map.put("花园宝宝2","唔西迪西");
map.put("花园宝宝3","汤伯利伯");
//3.Map集合的第二种遍历方式
//通过键值对对象进行遍历
//3.1通过一个方法获取所有的键值对对象,返回一个Set集合
Set<Map.Entry<String, String>> entries = map.entrySet();
//3.2 遍历entries这个集合去得到里面的每个键值对对象
for (Map.Entry<String,String> entry:entries){
// 3.3利用Entry调用get方法用来获取每一个键和每一个值
String key = entry.getKey();
String value = entry.getValue();
System.out.println(key + "=" + value);
}
}
}
Lambda表达式
package com.example.demo;
import java.util.HashMap;
import java.util.Map;
public class Text {
public static void main(String[] args) {
//1.创建Map集合的对象
Map<String,String> map = new HashMap<>();
//2.添加元素
map.put("花园宝宝1","玛卡巴卡");
map.put("花园宝宝2","唔西迪西");
map.put("花园宝宝3","汤伯利伯");
//3.利用Lambda表达式进行遍历
//底层:
//forEach其实就是利用第二种方式进行遍历,依次得到每一个键和值
//再调用accept方法
map.forEach((key, value)-> System.out.println(key + "=" + value));
}
}
HashMap的特点
-
HashMap是Map里面的一个实现类
-
特点都是由键决定的:无序、不重复、无索引
-
HashMap跟HashSet底层原理是一模一样的,都是哈希表结构
HashMap底层是哈希表结构的
依赖hashCode方法和equals方法保证键的唯一
如果键存储的是自定义对象,需要重写hashCode和equals方法。如果值存储自定义对象,不需要重写hashCode和equals方法。
++modCount:跟并发修改异常有关
LinkedHashMap的特点
由键决定:有序、不重复、无索引
有序:保证存取的元素顺序一致
原理:底层数据结构依然是哈希表,只是每个键值对元素又额外的多了一个双链表的机制记录存储的顺序
TreeMap的特点
TreeMap跟TreeSet底层原理一样,都是红黑树结构的
由键决定特性:不重复、无索引、可排序
可排序:对键进行排序
注意:默认按照键的从小到大进行排序,也可以自己规定键的排序规则
代码书写两种排序规则
- 实现Comparable接口,指定比较规则
- 创建集合时传递Comparator比较器对象,指定比较规则
可变参数
本质上是一个数组
小细节:
- 在方法的形参中最多只能写一个可变参数
- 在方法当中,如果除了可变参数以外,还有其他的形参,那么可变参数要写在最后
Collections
- java.util.Collections:是集合工具类
- 作用:Collections不是集合,而是集合的工具类
常用的API
方法名称 | 说明 |
---|---|
public static boolean addAll(Collection c,T…elements) | 批量添加元素 |
public static void shuffle(List<?> list) | 打乱List集合元素的顺序 |
不可变集合
不能被修改的集合
应用场景
- 如果某个数据不能被修改,把它防御性地拷贝到不可变集合中是个很好的实践。
- 当集合对象被不可信的库调用时,不可变形式是安全的。
- 简单理解:不想让别个修改集合中的内容
书写格式
方法名称 | 说明 |
---|---|
static List of(E…elements) | 创建一个具有指定元素的List集合对象 |
static Set of(E…elements) | 创建一个具有指定元素的Set集合对象(创建时要确保元素的唯一性) |
static<K,V> Map<K,V> of(E…elements) | 创建一个具有指定元素的Map集合对象(创建时键是不能重复的,Map里面的of方法参数有上限,最多只能传递20个参数。) |
注意:这个集合不能添加,不能删除,不能修改。
Collection集合
Collection的遍历方式
迭代器遍历
迭代器
在Java中的类时Iterator,迭代器是集合专用的遍历方式。
特点:
不依赖索引
增强for遍历
-
增强for的底层就是迭代器,为了简化迭代器的代码书写的。
-
它是JDK5之后出现的,其内部原理就是一个Iterator迭代器。
-
所有的单列集合和数组才能用增强for进行遍历。
for(元素的数据类型 变量名 : 数组或者集合){ }
细节:
- 修改增强for中的变量,不会改变集合中原本的数据
Lambda表达式遍历
- 得益于JDK8开始的新技术Lambda表达式,提供了一种更简单、更直接的遍历集合的方式
方法名称 | 说明 |
---|---|
default void forEach(Consumer<? super T> action): | 结合Lambda遍历集合 |
package com.example.demo;
import java.util.ArrayList;
import java.util.Collection;
public class Text {
public static void main(String[] args) {
Collection<String> coll = new ArrayList<>();
coll.add("aaa");
coll.add("aaa");
coll.add("aaa");
coll.add("aaa");
// 底层原理:其实也会自己遍历,
// 依次得到每一个元素。把得到的每一个元素,传递给下面accept方法
//s表示集合中的每一个数据
coll.forEach( s -> System.out.println(s));
}
}
List集合
- 在调用remove方法的时候,如果方法出现重载现象:优先调用,实参跟形参类型一致的那个方法。
- 调用set方法修改制定索引上的元素,会把被修改的元素进行返回。
List集合的遍历方式
迭代器遍历
package com.example.demo;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
public class Text {
public static void main(String[] args) {
// 利用多态的方式创建集合对象并添加元素
List<String> list = new ArrayList<>();
list.add("aaa");
list.add("bbb");
list.add("ccc");
list.add("ddd");
// 迭代器遍历
Iterator<String> it = list.iterator();
while (it.hasNext()){
String str = it.next();
System.out.println(str);
}
}
}
迭代器底层原理
列表迭代器遍历
package com.example.demo;
import java.util.ArrayList;
import java.util.List;
import java.util.ListIterator;
public class Text {
public static void main(String[] args) {
// 利用多态的方式创建集合对象并添加元素
List<String> list = new ArrayList<>();
list.add("aaa");
list.add("bbb");
list.add("ccc");
list.add("ddd");
// 列表迭代器
// 额外增加了一个方法:在遍历过程中,可以添加元素
ListIterator<String> it = list.listIterator();
while (it.hasNext()){
String str = it.next();
if("bbb".equals(str)){
it.add("qqq");
}
}
System.out.println(list);
}
}
增强for遍历
package com.example.demo;
import java.util.ArrayList;
import java.util.List;
public class Text {
public static void main(String[] args) {
// 利用多态的方式创建集合对象并添加元素
List<String> list = new ArrayList<>();
list.add("aaa");
list.add("bbb");
list.add("ccc");
list.add("ddd");
// 增强for遍历
// s就是一个第三方的变量
// 在循环的过程中,依次表示集合中的每一个元素
for (String s : list) {
System.out.println(s);
}
}
}
Lambda表达式遍历
package com.example.demo;
import java.util.ArrayList;
import java.util.List;
public class Text {
public static void main(String[] args) {
// 利用多态的方式创建集合对象并添加元素
List<String> list = new ArrayList<>();
list.add("aaa");
list.add("bbb");
list.add("ccc");
list.add("ddd");
// Lambda表示式遍历
// forEach方法的底层其实就是一个循环遍历,依次得到集合中的每一个元素
// 并把每一个元素传递给下面的accept方法
// accept方法的形参s,依次表示集合中的每一个元素
list.forEach( s-> System.out.println(s));
}
}
普通for循环(因为List集合存在索引)
package com.example.demo;
import java.util.ArrayList;
import java.util.List;
public class Text {
public static void main(String[] args) {
// 利用多态的方式创建集合对象并添加元素
List<String> list = new ArrayList<>();
list.add("aaa");
list.add("bbb");
list.add("ccc");
list.add("ddd");
// 普通for循环进行遍历
for (int i = 0; i < list.size(); i++) {
System.out.println(list.get(i));
}
}
}
数据结构
- 计算机存储、组织数据的方式。
- 数据相互之间是以什么方式排列在一起的。
- 为了更加方便的管理和使用数据,需要结合具体业务场景进行选择。
- 一般情况下,精心选择的数据结构可以带来更高的运行或者存储效率。
栈
队列
数组
链表
添加一个元素
删除一个元素
树
从小到大的顺序进行排列的
ArrayList集合
底层原理
LinkedList集合
泛型
泛型类
泛型方法
//格式:
修饰符<类型> 返回值类型 方法名(类型 变量名){
}
//举例:
public <T> void show(T t){
//调用该方法时,T就确定类型
}
//此处T可以理解为变量,但是不是用来记录数据的,而是记录类型的,可以写成:T E K V等
泛型接口
//格式
修饰符 interface 接口名<类型>{
}
//举例
public interface List<E>{
}
使用方式
- 实现类给出具体类型
- 实现类延续泛型,创建对象时在确定
泛型的继承和通配符
泛型不具备继承性,但是数据具备继承性
Set系列集合
- 无序:存取的顺序不一致
- 不重复:可以去除重复
- 无索引:没有带索引的方法,所以不能使用普通for循环遍历,也不能通过索引来获取元素
Set接口中的方法基本与Collection的API一致。
Set集合的实现类
HashSet:无序、不重复、无索引
底层原理
-
HashSet集合底层采取哈希表存储数据
-
哈希表是一种对于增删改查数据性能都较好的结构
哈比表组成
1.JDK8之前:数组+链表
2.JDK8之后:数组+链表+红黑树
哈希值:对象的整数表现形式
1.根据hashCode方法算出来的int类型的整数
2.该方法定义在Object类中,所有对象都可以调用,默认使用地址值进行计算
3.一般情况下,会重写hashCode方法,利用对象内部的属性值计算哈希值
对象的哈希值特点
1.如果没有重写hashCode方法,不同对象计算出的哈希值是不同的
2.如果已经重写hashCode方法,不同的对象只要属性值相同,计算出的哈希值就是一样的
3.在小部分情况下,不同的属性值或者不同的地址值计算出的哈希值也有可能一样(哈希碰撞)
JDK8以后,当链表长度超过8,而且数组长度大于等于64时,自动转换为红黑树
当集合中存储的是自定义对象,必须重写hashCode和equals方法
LinkedHashSet:有序、不重复、无索引
底层原理
这里的有序指的是保证存取元素的顺序一致
原理:底层数据结构依然是哈希表,只是每个元素又额外的多了一个双链表的机制记录存储的顺序
TreeSet:可排序、不重复、无索引
特点:
- 不重复、无索引、可排序
- 可排序:按照元素的默认规则(有大有小)排序
- TreeSet集合底层是基于红黑树的数据结构实现排序的,增删改查性能都较好。
TreeSet集合默认的规则
对于数值类型:Integer,Double,默认按照从小到大的顺序进行排序
对于字符、字符串类型:按照字符在ASCII码表中的数字升序进行排序
TreeSet的两种比较方式
方式一:
默认排序/自然排序:JavaBean类实现Comparable接口制定比较规则
方式二:
比较器排序:创建TreeSet对象的时候,传递比较器Comparable制定规则
使用原则:默认使用第一种没如果第一种不能满足当前需求,就使用第二种。
方法返回值的特点:
负数:表示当前要添加的元素是小的,存左边
正数:表示当前要添加的元素是大的,存右边
0:表示当前要添加的元素已经存在,舍弃
使用场景
如果想要集合中的元素可重复
- 用ArrayList集合,基于数组的。(用的最多)
如果想要集合中的元素可重复,而且当前的增删操作明显多于查询
- 用的LinkedList集合,基于链表的。
如果想对集合中的元素去重
- 用HashSet集合,基于哈希表。(用的最多)
如果想对集合中的元素去重,而且保证存取顺序
- 用LinkedHashSet集合,基于哈希表和双链表,效率低于HashSet
如果想对集合中的元素进行排序
Set:可排序、不重复、无索引
特点:
- 不重复、无索引、可排序
- 可排序:按照元素的默认规则(有大有小)排序
- TreeSet集合底层是基于红黑树的数据结构实现排序的,增删改查性能都较好。
TreeSet集合默认的规则
对于数值类型:Integer,Double,默认按照从小到大的顺序进行排序
对于字符、字符串类型:按照字符在ASCII码表中的数字升序进行排序
TreeSet的两种比较方式
方式一:
默认排序/自然排序:JavaBean类实现Comparable接口制定比较规则
方式二:
比较器排序:创建TreeSet对象的时候,传递比较器Comparable制定规则
使用原则:默认使用第一种没如果第一种不能满足当前需求,就使用第二种。
方法返回值的特点:
负数:表示当前要添加的元素是小的,存左边
正数:表示当前要添加的元素是大的,存右边
0:表示当前要添加的元素已经存在,舍弃
使用场景
如果想要集合中的元素可重复
- 用ArrayList集合,基于数组的。(用的最多)
如果想要集合中的元素可重复,而且当前的增删操作明显多于查询
- 用的LinkedList集合,基于链表的。
如果想对集合中的元素去重
- 用HashSet集合,基于哈希表。(用的最多)
如果想对集合中的元素去重,而且保证存取顺序
- 用LinkedHashSet集合,基于哈希表和双链表,效率低于HashSet
如果想对集合中的元素进行排序
用TreeSet集合,基于红黑树。后续也可以用List集合实现排序