集合类体系结构
知识体系前览
特点
- 集合只能存引用数据类型,不能存基本数据类型
- 长度不固定
Collection接口(单例集合)
Collection定义了所有集合要有的功能(抽象方法)
泛型
- 在使用集合时,在JDK5以前是没有泛型这么一说的,集合中可以存储任意类型的元素,但是,这并不是一件好事。因为在编写代码时,无法控制往集合中添加什么类型的元素。一旦有非法的数据进入集合,那么就会导致集合无法进行统一的操作
- 使用泛型,就可以在编译时让编译器来检查类型是否一致
List接口
元素要有索引,可以重复,有序(存取顺序一致)
ArrayList类
特点
底层数据结构是数组,查询快,增删慢
ArrayList集合的底层原理
- 当我们创建一个
ArrayList
集合时,底层会创建一个长度为0
的数组 - 当我们往
ArrayList
集合中添加第一个元素时,底层会将数组的长度扩容为10 - 当我们继续往
ArrayList
集合中添加更多元素时, 如果到了 10 这和容量, 底层会把这个数组扩容为原来的 1.5 倍 - 然后把原先数组中的元素复制到新数组中,老数组被回收机制回收
- 所以,可以不断地往
ArrayList
集合中添加元素,添加的元素越多,底层数组扩容就越大
LinkedList类
特点: 底层数据结构是链表,查询慢,增删快
Set接口
元素无索引,不可以重复,无序
特点
- 没有索引
- 不可重复
- 存取顺序不一致
HashSet类
JDK8
之前使用数组 + 链表
的形式,JDK8
以后使用数组 + 链表 + 红黑树
特点
- 底层数据结构是哈希表
- 不能保证存储和取出的顺序完全一致
- 没有带索引的方法,所以不能使用普通
for
遍历 - 由于是
Set
集合,所以元素唯一
底层原理
-
JDK8
之前- 创建
HashSet
集合,创建一个长度为 16 的数组,加载因子为0.75
加载因子: (当集合中元素超过容量的0.75
时进行扩容,扩容大小为原先的1
倍) - 添加元素时,底层会自动调用元素的
hashCode
方法,获取Hash
值 - 接着根据
Hash
值和数组的长度做一个类似取余的操作,计算出一个索引位置 - 判断该索引位置的元素是否为
null
,如果这个位置为null
,说明这个位置没有元素,此时直接把元素添加到这个索引为值;如果这个位置的元素不为null
,说明这个位置已经有元素存在,此时就拿着新添加的元素,和这个位置已经存在的每一个元素使用equals
进行比较,如果equals
返回false
,就说明新添加的元素和已有的元素不相同,此时以链表的形式连接在一起,如果某一次返回true,说明该位置已经有该元素,不存
- 创建
-
JDK8
之后- 大体和
JDK8
之前相同,区别是链表长度超过 8 时会转换为红黑树
- 大体和
Hash值
JDK
根据对象的地址值或者属性值,算出来的int
类型整数Object
类有hashCode()
方法可以根据对象的地址值获取Hash
值Hash
值- 如果一个类没有重写
hashCode()
方法,那么该对象的Hash
值与地址有关,此时每个对象的Hash
值都不同 - 如果一个类重写了
hashCode()
方法, 那么如果两个对象的属性值相同,则Hash
值相同
- 如果一个类没有重写
TreeSet类
特点
- 不包括重复元素的集合
- 没有带索引的方法
- 可以将元素按照规则进行排序, 如果是
API
中提供的元素类型,他们有默认的排序规则, 如果存储的是自定义元素类型,没有排序规则是不能排序的
Compareable自然排序
- 使用空参构造创建
TreeSet
集合 - 自定义的类实现
Compareable
接口 - 重写里面的
compareTo
方法
方法1
public class Student implements Comparable<Student> {
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 String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
@Override
public int compareTo(Student o) {
return this.name.compareTo(o.name);
}
}
public class Test {
public static void main(String[] args) {
Set<Student> ts = new TreeSet<>();
ts.add(new Student("abc", 12));
ts.add(new Student("abd", 10));
ts.add(new Student("abb", 14));
ts.add(new Student("abe", 10));
ts.add(new Student("abe", 9));
System.out.println(ts);
}
}
方法2
public class Test {
public static void main(String[] args) {
Set<Student> ts = new TreeSet<>(new Comparator<Student>() {
@Override
public int compare(Student o1, Student o2) {
int res = o1.getAge() - o2.getAge();
if (res == 0) {
return o1.getName().compareTo(o2.getName());
}
return res;
}
});
ts.add(new Student("abc", 12));
ts.add(new Student("aaa", 12));
ts.add(new Student("abb", 13));
ts.add(new Student("abd", 11));
System.out.println(ts);
}
}
如果重写
compareTo
和比较器
同时存在,使用比较器
来比较,所以使用比较器
的方式比较好
Map接口(双列集合)
概述
Interface Map<K, V>
(是一个接口) K: 键的数据类型 V: 值的数据类型- 键不能重复,值可以重复
- 键和值是一 一对应的,每一个键只能找到自己对应的值
- (键 + 值)这个整体我们称之为
键值对
或者键值对对象
,在Java
中叫做Entry对象
Map
集合是用来存储键值对的集合
特点
- 需要明确键和值的数据类型,以及键和值是用来表示什么的
- 如果键重复,用新的值替换旧的值
基本方法
put
: 如果重复,用新的值替换旧的值,返回被替换的值remove
: 返回被删除的值get
: 获取值clear
: 清空Map
isEmpty
: 判断是否为空containsKey
: 判断键是否存在containsValue
: 判断值是否存在size
: 获取Map
的大小
遍历方法
方法1: keySet()
Map<String, Integer> map = new HashMap<>();
map.put("Test1", 1);
map.put("Test2", 2);
map.put("Test3", 3);
for (String s : map.keySet()) {
System.out.println("key: " + s + " -- value: " + map.get(s));
}
方法2: entrySet()
Map<String, Integer> map = new HashMap<>();
map.put("Test1", 1);
map.put("Test2", 2);
map.put("Test3", 3);
Set<Map.Entry<String, Integer>> entries = map.entrySet();
for (Map.Entry<String, Integer> entry : entries) {
System.out.println("key: " + entry.getKey() + " -- value: " + entry.getValue());
}
方法3: forEach()
Map<String, Integer> map = new HashMap<>();
map.put("Test1", 1);
map.put("Test2", 2);
map.put("Test3", 3);
map.forEach((String s, Integer i) -> {
System.out.println(s + "..." + i);
});
如果想在遍历过程中结束循环
forEach
不可中途结束,反例:
HashMap<String, Integer> hashMap = new HashMap<>();
{
hashMap.put("Test1", 1);
hashMap.put("Test2", 2);
hashMap.put("Test3", 3);
hashMap.put("Test4", 4);
hashMap.put("Test5", 5);
hashMap.put("Test6", 6);
hashMap.put("Test7", 7);
hashMap.put("Test8", 8);
hashMap.put("Test9", 9);
hashMap.put("Test10", 10);
hashMap.put("Test11", 11);
hashMap.put("Test12", 12);
hashMap.put("Test13", 13);
hashMap.put("Test14", 14);
}
hashMap.forEach((key, value) -> {
if (java.util.Objects.equals("Test1", key)) {
return;
}
System.out.println(key + "..." + hashMap.get(value));
});
这样子并不能结束循环,它结束的实际上是 Lambda
内部的函数,完整程序如下:
hashMap.forEach(new BiConsumer<String, Integer>() {
@Override
public void accept(String s, Integer integer) {
if (java.util.Objects.equals("Test1", s)) {
return;
}
System.out.println(s + "..." + integer);
}
});
- 增强
for
方法:
for (String s : hashMap.keySet()) {
if (java.util.Objects.equals("Test1", s)) {
break;
}
System.out.println(s + "..." + hashMap.get(s));
}
这样子是可以结束的
HashMap
本质上和
HashSet
是一个东西,HashSet
本质上也是想链表的节点属性值中添加东西,JDK8
之前使用数组 + 链表
,JDK8
之后使用数组 + 链表 + 红黑树
TreeMap
- 排序只关心键,不关心值
- 实际上
TreeMap
和TreeSet
一样, 都是红黑树
结构,当往TreeSet
集合中添加数据时,本质上是往TreeMap
的键位置添加一个数据
HashMap和HashTable的区别
HashMap
和HashTable
使用起来一模一样,HashTable
是线程安全的,HashMap
线程不安全- 类似的还有
StringBuilder
和StringBuffer
也是使用起来一模一样,StrigBuffer
是线程安全的,StringBuilder
线程不安全
MultiValueMap
value是一个集合,put相同的key值时,原value不会被覆盖,会添加到集合中
遍历
迭代器遍历集合
Iterarot<E> iterator = ...
: 集合中的(Collection Set 通用)迭代器对象,该迭代器对象默认指向当前集合的 0 索引
常用方法
hasNext()
: 当前位置是否有元素可以取出next()
: 取出当前位置元素并且迭代器往后移动一个位置
例
Set<String> hs = new HashSet<>();
hs.add("aaa");
hs.add("bbb");
hs.add("ccc");
Iterator<String> it = hs.iterator();
while (it.hasNext()) {
System.out.println(it.next());
}
增强for
- 格式:
for (元素数据类型 变量名 : 数组或者Collection集合)
forEach
方法底层调用了增强for
, 本质上也是迭代器
注意事项
- 数据类型一定是集合或者数组中元素的类型
- 每一次循环时的变量名仅仅是一个变量名而已,在循环的过程中,依次表示集合或者数组中的每一个元素
创建不可变集合
在
JDK9
中对List、Set、Map
提供了可以快速创建集合,并添加元素的方法,但是这里创建出来的集合不可变(只能查询、不能添加、不能删除、不能修改)
特点
.of()
创建的集合是不可变的Set.of()
创建的集合不可变,所以不能重复Map.ofEntries
: 先把数据变为entry
对象,再放到Map
中
例:
List<String> testList = List.of("test1", "test2", "test3", "test4");
System.out.println(testList);
Set<String> testSet = Set.of("test1", "test2", "test3");
System.out.println(testSet);
Map.ofEntries(Map.entry("test1", 1), Map.entry("test2", 2)).forEach((key, value) -> {
System.out.println(key + "..." + value);
});
Map<String, Integer> map = Map.of("test1", 1, "test2", 2);
map.entrySet().stream().forEach(e -> {
System.out.println(e.getKey() + "..." + e.getValue());
});
注
- 可以使用
Collections.addAll()
向集合中批量添加元素
例:
ArrayList<String> list = new ArrayList<>();
Collections.addAll(list, "test1", "test2", "test3");
System.out.println(list);
HashSet<String> set = new HashSet<>();
Collections.addAll(set, "test1", "test2", "test3");
System.out.println(set);
tln(e.getKey() + "..." + e.getValue());
});