这部分Java中的基础内容,集合,也叫做Java容器,用在很多的地方。
集合是用来存储数据的,简称为容器,其中这里的存储指内存层面的存储,不是持久化存储。
1. 数组的特点:
- 指定长度后,长度不可以更改
- 声明了类型后,数组只能存放这个类型的数据。
- 数组的查询效率高,删除、增加元素的效率低
- 数组中实际元素的数量无法获取,没有提供具体的方法来获取
- 数组存储数据是有序可重复的,这里的有序不是指实际数据排序,指是线性排列的意思
综上所述,数组的灵活性低,引入了集合,有Set、List、Map等等,不同集合底层数据结构不一样,所以每个集合都有各自的特点,适用于不同的场景。
2. 集合体系结构图
3. 集合的应用场合:
集合是用来存储数据的,在整个开发过程中从始到终一直在用到,根据各个集合的特点选用对应合适的集合使用。其中集合只能存放引用数据类型的数据,不能是基本数据类型,基本数据类型自动装箱。当使用泛型时候,为了方便统一管理,一般只会存入一种数据类型。
- 大致总结一下如:
- 如果需要频繁访问元素,使用ArrayList
- 如果需要频繁插入和删除元素,使用LinkedList
- 如果不允许重复元素且无序,使用HashSet
- 如果需要自动排序,使用TreeSet
- 如果需要快速查找和存储键值对,使用HashMap
- 如果需要按键自动排序,使用TreeMap
区别:
List接口特点:不唯一,有序的
Set接口特点:唯一,无序的(无序不等于随机,是相对于List接口部分来说的)
4. ArrayList
ArrayList是一个可变大小的数组实现,适用于需要频繁访问元素的场景,查询快,增删慢,是多线程不安全的。
在提前知道list长度的时候,可以提前指定集合的容量,这样可以减少扩容的次数,提高性能(数据量越大,效果越明显)
ArrayList源码类似StringBuilder,其中jdk1.7 中和jdk1.8版本中ArrayList实现有所不同(以下使用jdk1.8版本做说明)。
新建一个ArrayList对象,未指定大小时候,使用的数组为空。调用add后,才会确定数组长度,存实际数据
新增到空间不够了,再进行扩容
ArrayList底层的数据结构分为物理结构和逻辑结构,物理结构对应紧密结构,在内存中是连续着的,形成的逻辑结构是线性表中的数组。
public static void main(String[] args) {
List<Object> arrayList = new ArrayList<>();
arrayList.add(10);
arrayList.add(8);
arrayList.add(13);
System.out.println("arrayList中数据:" + arrayList);
arrayList.add(1, 24);
System.out.println("arrayList中数据2:" + arrayList);
arrayList.set(3, 12);
System.out.println("arrayList中数据3:" + arrayList);
arrayList.add(2);
System.out.println("arrayList中数据4:" + arrayList);
arrayList.remove(2);
System.out.println("arrayList中数据5:" + arrayList);
}
输出
arrayList中数据:[10, 8, 13]
arrayList中数据2:[10, 24, 8, 13]
arrayList中数据3:[10, 24, 8, 12]
arrayList中数据4:[10, 24, 8, 12, 2]
arrayList中数据5:[10, 24, 12, 2]
5. LinkedList
底层使用的双向链表,使用链表查找元素,所以查询慢,增删只需要替换掉指针的指向即可,所以增删快,元素按照插入的顺序排列,元素可以重复。
新增数据时候,创建新Node节点信息,指向上一个元素的地址
Node对象:存前一个元素的地址,当前存入的元素,下一个元素的地址,整个对象是链表中的一个节点。
遍历LinkList的方式:
for (Object node : linkedList) {
System.out.println(node);
}
Iterator<Object> iterator = linkedList.iterator();
while (iterator.hasNext()) {
System.out.println(iterator.next());
}
// 这种方式,it在for循环这个作用域内有效,for循环结束时候,it这种对象的生命周期结束,就自动消失了
for (Iterator<Object> it = linkedList.iterator(); it.hasNext(); ) {
System.out.println(it.next());
}
- LinkedList的数据结构:
- 物理结构:内存中是跳转结构,不连续的
- 逻辑结构:是线性表中的链表
6. HashSet
Set接口里没有跟索引相关的方法,意味着不能用普通for循环遍历
- 遍历方式:
- 迭代器
- 增强for循环(底层还是迭代器来的)
存放基本数据类型的数据时候,都满足唯一,无序的特点。
存放自定义类型的数据时候,没有满足唯一的特点。
HashSet集合存入的数据(以Integer类型为例):
调用对应的hashCode方法计算哈希值,哈希值是int类型的数据,再通过哈希值和一个表达式计算再数组中存放的位置。如位置冲突,则维护一个链表来存储数据,如果存放的数据重复了,不会再次放入(使用equals方法比较)
HashSet底层原理:数组+链表,即哈希表
所以,放入HashSet中的数据,一定要重写两个方法:hashCode和equals
其中底层用到了HashMap的结构
7. TreeSet
待补充
8. HashMap
待补充
9. TreeMap
待补充
10. LinkedHashMap
待补充
11. Hashtable
待补充
12. Vector
实际上已经很少用了,底层是Object数组,底层数组长度默认为10(当未指定长度时),底层扩容数组长度为2倍,是线程安全的
Vector和ArrayList的区别:
ArrayList底层扩容长度为原数组的1.5倍,Vector底层扩容长度为原数组的2倍。
ArrayList线程不安全,Vector是线程安全的