0. Java 集合
也称容器,主要由两大接口派生而来:
Collection接口:主要用于存放单一元素,又有三个主要的子接口:List、Set、Queue
Map接口,主要用于存放键值对。
1. Collection
1.1 List
1.1.1 ArrayList:object[]数组,频繁访问和只在末尾增删时使用
底层是数组队列,相当于动态数组(容量动态增长)。在添加大量元素前,可以使用ensureCapacity操作来增加ArrayList实例的容量。
重要方法:
添加:add(obj)
删除:remove(obj)、remove(index)、clear()
是否存在:contains(obj)
获取索引元素:get(index)
替换索引元素:set(int index, E element)
大小:size()
截取部分:subList(int fromIndex, int toIndex)
删除指定索引之间存在的元素:removeRange(int fromIndex, int toIndex)
排序:sort()
转数组:toArray()、转字符串:toString()
指定容量大小:ensureCapacity(int minCapacity)
返回指定元素最后一次出现的位置:lastIndexOf(Object obj)
继承于AbstractList,实现了
List
,RandomAccess
,Cloneable
,java.io.Serializable
这些接口。
List
: 表明它是一个列表,支持添加、删除、查找等操作,并且可以通过下标进行访问。
RandomAccess
:这是一个标志接口,表明实现这个接口的List
集合是支持 快速随机访问 的。在ArrayList
中,我们即可以通过元素的序号快速获取元素对象,这就是快速随机访问。
Cloneable
:表明它具有拷贝能力,可以进行深拷贝或浅拷贝操作。单纯拷贝引用地址的动作就是浅拷贝
如果拷贝一个对象时不是简单的将地址引用拷贝出来,而是新建了一个对象,这种方式就是深拷贝,不会共享内存
Serializable
: 表明它可以进行序列化操作,也就是可以将对象转换为字节流进行持久化存储或网络传输,非常方便。
ArrayList
中可以存储任何类型的对象,包括 null
值。不过,不建议向ArrayList
中添加 null
值, null
值无意义,会让代码难以维护比如忘记做判空处理就会导致空指针异常。
ArrayList与LinkedList区别:
- 底层数据结构:
ArrayList
底层使用的是Object
数组;LinkedList
底层使用的是 双向链表 数据结构- 插入和删除是否受元素位置的影响:即数组与链表的区别
ArrayList
采用数组存储,默认在列表末尾添加,O(1)。但在指定位置 i 插入和删除元素为 O(n)。因为要移动元素。
LinkedList
采用链表存储,头尾插入或者删除元素不受元素位置的影响,O(1),但在指定位置i
插入和删除元素为 O(n) ,因为需要先移动到指定位置。
- 是否支持快速随机访问:
LinkedList
不支持高效的随机元素访问,而ArrayList
(实现了RandomAccess
接口) 支持。快速随机访问就是通过元素的序号快速获取元素对象(对应于get(int index)
方法)。- 内存空间占用:
ArrayList
的空间浪费主要体现在在 list 列表的结尾会预留一定的容量空间,而 LinkedList 的空间花费则体现在它的每一个元素都需要消耗比 ArrayList 更多的空间(因为要存放直接后继和直接前驱以及数据)。
ArrayList 和 Vector 的区别?
ArrayList
是List
的主要实现类,底层使用Object[]
存储,适用于频繁的查找工作,线程不安全 。Vector
是List
的古老实现类,底层使用Object[]
存储,线程安全。
ArrayList初始化的三种方法:
1. 默认容量,无参构造函数:实际上初始化赋值的是一个空数组。当真正对数组进行添加元素操作时,才真正分配容量。即向数组中添加第一个元素时,数组容量扩为默认值
2. 用户指定容量,有参构造函数
3. 构造包含指定collection元素的列表
1.1.2 LinkedList:双向链表,需迭代访问和头尾增删时使用
需要用到 LinkedList
的场景几乎都可以使用 ArrayList
来代替,并且,性能通常会更好!
为什么不能实现
RandomAccess接口:
底层数据结构是链表,内存地址不连续,只能通过指针来定位,不支持随机快速访问。
LinkedList
实现了以下接口:
List
: 表明它是一个列表,支持添加、删除、查找等操作,并且可以通过下标进行访问。Deque
:继承自Queue
接口,具有双端队列的特性,支持从两端插入和删除元素,方便实现栈和队列等数据结构。Cloneable
:表明它具有拷贝能力,可以进行深拷贝或浅拷贝操作。Serializable
: 表明它可以进行序列化操作,也就是可以将对象转换为字节流进行持久化存储或网络传输,非常方便。
插入元素:
add(E element):末尾插入
add(int index, E element) :指定位置
获取元素:
getFirst()
:第一个。
getLast()
:最后一个。
get(int index)
:指定位置。
删除元素:
removeFirst()
:删除并返回第一个。removeLast()
:删除并返回最后一个。remove(E e)
:删除链表中首次出现的指定元素,如果不存在则返回 false。remove(int index)
:删除并返回指定位置。void clear()
:移除所有。
遍历:
for (String fruit : list)
1.1.3 Vector:object[]数组
1.2 Set
1.2.1 HashSet:无序,唯一。
实现了HashMap和Set接口,底层采用HashMap保存元素。
允许有 null 值
不是线程安全的, 如果多个线程尝试同时修改 HashSet,则最终结果是不确定的。 您必须在多线程访问时显式同步对 HashSet 的并发访问。
1.2.2 LinkedHashSet:HashSet
的子类,内部通过 LinkedHashMap
实现
1.2.3 TreeSet:有序,唯一,红黑树(自平衡的排序二叉树)
三者异同:
同:都是
Set
接口的实现类,都能保证元素唯一,并且都不是线程安全的。异:底层数据结构不同
主要区别在于底层数据结构不同。
HashSet
的底层数据结构是哈希表(基于HashMap
实现)。LinkedHashSet
的底层数据结构是链表和哈希表,元素的插入和取出顺序满足 FIFO。TreeSet
底层数据结构是红黑树,元素是有序的,排序的方式有自然排序和定制排序。底层数据结构不同又导致这三者的应用场景不同。
HashSet
用于不需要保证元素插入和取出顺序的场景,LinkedHashSet
用于保证元素的插入和取出顺序满足 FIFO 的场景,TreeSet
用于支持对元素自定义排序规则的场景。
无序性和不可重复性的含义是什么
- 无序性不等于随机性 ,无序性是指存储的数据在底层数组中并非按照数组索引的顺序添加 ,而是根据数据的哈希值决定的。
- 不可重复性是指添加的元素按照
equals()
判断时 ,返回 false,需要同时重写equals()
方法和hashCode()
方法。
Set和List的区别
Set 接口实例存储的是无序的,不重复的数据。List 接口实例存储的是有序的,可以重复的元素。
Set 检索效率低下,删除和插入效率高,插入和删除不会引起元素位置改变。
List 和数组类似,可以动态增长,根据实际存储的数据的长度自动增长 List 的长度。查找元素效率高,插入删除效率低,因为会引起其他元素位置改变。
1.3 Queue
1.3.1 PriorityQueue:
1.3.2 DelayQueue:延迟队列,用于实现延时任务,按照到期时间升序编排任务
1.3.3 ArrayDeque:可扩容动态双向数组
Queue与Deque的区别:
Queue
是单端队列,只能从一端插入元素,另一端删除元素,遵循 先进先出 规则。Queue
扩展了Collection
的接口。
Deque
是双端队列,在队列的两端均可以插入或删除元素。Deque
扩展了Queue
的接口。
2. Map
2.1 HashMap:
是一个散列表,主要用来存放键值对,键和值类型可不同。
继承于AbstractMap,实现了 Map、Cloneable、java.io.Serializable 接口。
是非线程安全的,不支持线程同步。
HashMap 是无序的,即不会记录插入的顺序。
由 table数组+链表/红黑树 组成,当链表长度大于等于阈值(默认为 8)(将链表转换成红黑树前会判断,如果当前数组的长度小于 64,那么会选择先进行数组扩容,而不是转换为红黑树)时,将链表转化为红黑树,以减少搜索时间。
红黑树:避免二叉查找树退化成单链表(根和叶子是黑的,从任一节点到其每个叶子的所有简单路径都包含相同数目的黑色节点)
重要参数:
DEFAULT_INITIAL_CAPACITY
Table数组的初始化长度:1 << 4
2^4=16
(为什么要是 2的n次方?)MAXIMUM_CAPACITY
Table数组的最大长度:1<<30
2^30=1073741824
DEFAULT_LOAD_FACTOR
负载因子:默认值为0.75
。 当元素的总个数>当前数组的长度 * 负载因子。数组会进行扩容,扩容为原来的两倍(todo:为什么是两倍?)TREEIFY_THRESHOLD
链表树化阙值: 默认值为8
。表示在一个node(Table)节点下的值的个数大于8时候,会将链表转换成为红黑树。UNTREEIFY_THRESHOLD
红黑树链化阙值: 默认值为6
。 表示在进行扩容期间,单个Node节点下的红黑树节点的个数小于6时候,会将红黑树转化成为链表。MIN_TREEIFY_CAPACITY = 64
最小树化阈值,当Table所有元素超过该值,才会进行树化(为了防止前期阶段频繁扩容和树化过程冲突)。
重要方法:
- 添加键值对:put(key, value) 、putAll(Map m) 添加所有,若重复会替换、putifAbsent(key, value) 不存在则添加
- 获取 key 对应的 value:get(key)
- 删除 key 对应的键值对(key-value):remove(key)
- 删除所有键值对(key-value):clear()
- 计算元素数量:size()
- 迭代元素:for-each
- 检查是否存在指定的 key/value 对应的映射关系:containsKey(key)、containsValue(value)
- 判空:isEmpty()
- 替换指定的 key 对应的 value:replace(key, newValue)
- 返回所有 key :keySet()
- 返回所有value:values()
// 创建 HashMap 对象 Sites
HashMap<Integer, String> Sites = new HashMap<Integer, String>();
// 添加键值对
Sites.put(1, "Google");
Sites.put(2, "Runoob");
// 大小
System.out.println(Sites.size()); // 2
// 输出
System.out.println(Sites); // {1=Google, 2=Runoob}
System.out.println(Sites.get(1)); // Google
// 迭代
// 输出 key 和 value
for (Integer i : Sites.keySet()) {
System.out.println("key: " + i + " value: " + Sites.get(i));
}
// key: 1 value: Google
// key: 2 value: Runoob
// 输出每一个value:Google, Runoob,
for(String value: Sites.values()) {
System.out.print(value + ", ");
}
// 删除
Sites.remove(1);
System.out.println(Sites); // {2=Runoob}
Sites.clear();
System.out.println(Sites); // {}