JavaSE -- 集合详细介绍(中篇)
JavaSE – 集合详细介绍(上篇)
LinkedList
链表
介绍
相较于数组,链表的物理上的空间时非连续的。逻辑上的顺序是由指针链接次序实现的。链表是由一系列节点组成,节点则可以动态生成。
链表分类
链表可以分为单项链表,双向链表,循环链表
- 单项链表:每个节点有两部分组成,一部分是数据域用于存储数据,一部分是指针域用于存储下一个节点的地址
- 双向链表:每个节点由三部分组成,一部分是数据域用于存储数据,一部分是真指针域用于指向下一个节点的地址,一个也是指针域用于指向上一个节点的地址
- 循环链表:就是多了一个,链表尾节点指向链表的头
链表优缺点
链表克服了数组需要预先知道数据大小的缺点。插入删除快,查询慢
LinkedList 常用方法
这里介绍比较常用的几个方法
方法 | 描述 |
---|---|
public boolean add(E e) | 链表末尾添加元素,返回是否成功,成功为 true,失败为 false。 |
public void add(int index, E element) | 向指定位置插入元素。 |
public boolean addAll(Collection c) | 将一个集合的所有元素添加到链表后面,返回是否成功,成功为 true,失败为 false。 |
public boolean addAll(int index, Collection c) | 将一个集合的所有元素添加到链表的指定位置后面,返回是否成功,成功为 true,失败为 false。 |
public void addFirst(E e) | 元素添加到头部。 |
public void addLast(E e) | 元素添加到尾部。 |
public boolean offer(E e) | 向链表末尾添加元素,返回是否成功,成功为 true,失败为 false。 |
public boolean offerFirst(E e) | 头部插入元素,返回是否成功,成功为 true,失败为 false。 |
[public boolean offerLast(E e) | 尾部插入元素,返回是否成功,成功为 true,失败为 false。 |
public void clear() | 清空链表。 |
public E removeFirst() | 删除并返回第一个元素。 |
public E removeLast() | 删除并返回最后一个元素。 |
public boolean remove(Object o) | 删除某一元素,返回是否成功,成功为 true,失败为 false。 |
public E remove(int index) | 删除指定位置的元素。 |
public E poll() | 删除并返回第一个元素。 |
public E remove() | 删除并返回第一个元素。 |
public boolean contains(Object o) | 判断是否含有某一元素。 |
public E get(int index) | 返回指定位置的元素。 |
public E getFirst() | 返回第一个元素。 |
public E getLast() | 返回最后一个元素。 |
public int indexOf(Object o) | 查找指定元素从前往后第一次出现的索引。 |
public int lastIndexOf(Object o) | 查找指定元素最后一次出现的索引。 |
public E peek() | 返回第一个元素。 |
public E element() | 返回第一个元素。 |
public E peekFirst() | 返回头部元素。 |
public E peekLast() | 返回尾部元素。 |
public E set(int index, E element) | 设置指定位置的元素。 |
public Object clone() | 克隆该列表。 |
public Iterator descendingIterator() | 返回倒序迭代器。 |
public int size() | 返回链表元素个数。 |
public ListIterator listIterator(int index) | 返回从指定位置开始到末尾的迭代器。 |
public Object[] toArray() | 返回一个由链表元素组成的数组。 |
public T[] toArray(T[] a) | 返回一个由链表元素转换类型而成的数组。 |
LinkedList 和 ArrayList 的区别
存储结构:
- ArrayList: 底层是数组,线性顺序存储。
- LinkedList:底层是链表结构,非连续,非顺序的存储,对象间是通过指针域链接起来的
操作性能:
- ArrayList: 适合随机查询数据的操作
- LinkedList:适合元素的删除和插入
Vector 和 ArrayList 的区别
底层实现是一样的都是利用数组
比较:
- ArrayList 在创建的时候初始大小设置为 0,第一次加入元素的时候就进行扩容。Vector 在创建的时候初始大小为 10
- ArrayList 每次扩容时都是原来的 1.5 倍,Vector 扩容时,可以给定阔容大小 capacityIncrement ,如果给定扩容大小大于 0 则在原来容量基础上加上 capacityIncrement,否则扩大为原来的两倍。
- Vector 是线程安全的,ArrayList 线程不安全
哈希表
回想一下数组,当我们知道我们要查找的元素在数组的中的下标索引时查询是非常快的,但是如果不知道他的下标,那么我们只能通过循环遍历的方式进行查找,效率非常慢。那么哈希表刚好解决这一痛点。
简介
也叫散列表,是根据和值而直接进行访问的数据结构。通过一种函数使得元素存储的位置和他的键值建立一个映射关系,加快查询速度,这个函数叫做哈希函数/散列表函数,存放数据的数组叫做哈希表
哈希函数
这个简单了解即可,不需要知道它的具体实现,我简单介绍几个,哈希函数设计的好坏可以减少哈希冲突,但是无法避免(哈希冲突就是两个值通过哈希函数,处理后的值一样)
- 除留余数法:这个是比较常用的,就是将数值除以数组大小得到的余数就是元素在数组中的位置
- 直接定址法
- 随机数法
- 折叠发
解决哈希冲突
要解决哈希冲突有两种常用的方法:闭散列,开散列
闭散列:也叫开放寻址法,当发生哈希冲突的时候,如果哈希表还有空位置,这回通过线性探测(比如从左往右一个一个遍历的找)的方式,扫描过去直到找到那个空位置。
开散列:也叫链地址法或开链法,当发生哈希冲突时,不会找空位置,而是建立一个链表,将冲突的元素通过链表链接起来,然后链表的头节点存入哈希表中。但是出现极端情况,所有元素都在一个链上,此时就会退化成一个链表,我们可以将链表换成红黑树,加快查询效率
HashSet
特点
HashSet是基于HashMap实现的。具有无序,唯一性。
存储数据
之所以 HashSet 具有去重功能,是因为当发生哈希冲突的时候,会调用 equals 方法判断是否相同,如果相同就不会加入。这是对于非自定义数据来说的(String,Interge…),对于自定义数据类型,需要我们重写equals 和 hashCode 方法。但是这个可以通过 Idea 帮我们自动生成
常用方法
方法 | 返回值 | 说明 | 示例 |
---|---|---|---|
add(E e) | boolean | 添加元素到集合,成功返回 true ,重复元素返回 false 。 | set.add("Java"); |
remove(Object o) | boolean | 删除指定元素,成功返回 true ,元素不存在返回 false 。 | set.remove("Python"); |
contains(Object o) | boolean | 检查集合是否包含指定元素。 | if (set.contains("Java")) { ... } |
size() | int | 返回集合中的元素数量。 | int count = set.size(); |
isEmpty() | boolean | 判断集合是否为空。 | if (set.isEmpty()) { ... } |
clear() | void | 清空集合中的所有元素。 | set.clear(); |
iterator() | Iterator<E> | 返回集合的迭代器,用于遍历元素。 | for (String s : set) { ... } |
toArray() | Object[] | 将集合转换为数组。 | Object[] arr = set.toArray(); |
toArray(T[] a) | T[] | 将集合转换为指定类型的数组。 | String[] arr = set.toArray(new String[0]); |
addAll(Collection<? extends E> c) | boolean | 添加另一个集合的所有元素(并集操作)。 | set.addAll(Arrays.asList("A", "B")); |
retainAll(Collection<?> c) | boolean | 仅保留与指定集合共有的元素(交集操作)。 | set.retainAll(otherSet); |
removeAll(Collection<?> c) | boolean | 删除与指定集合共有的元素(差集操作)。 | set.removeAll(otherSet); |
LinkedHashSet
底层是通过 LinkedhashMap 实现的, LinkedHashSet 是其中的一个特殊类型,它结合了哈希表和链表的特性,适用于需要保持元素插入顺序并确保唯一性的情况。
实现原理
底层通过维护一个双向链表来保证有序性(插入顺序),当一个元素插入时,会先计算 HashCode,将元素存入哈希表中,然后再将它放入双向链表当中,当遍历时会按照双向链表的链接顺序一次读出。
TreeSet
底层的数据结构时红黑树(一种特殊的平衡树),我们知道对于平衡树我们采用中序遍历读取的元素时有序的(数值大小),这就说明存入 TreeSet 中的元素必须时可排序的(Comparable)。
排序接口
要存储自定义数据有两种方法:
- 实现 Comparable 接口,重写 CompareTo() 方法,对其中的某个属性进行大小比较,小于返回负数,大于返回正数,相等返回 0。
- 创建一个 Comparator 接口的实现类,重写 Compara() 方法(传入两个对象),根据业务需求对两个对象的属性进行比较。然后将该实现类放入 TreeSet 的构造方法中,相当于一个外部的比较器,比较灵活。更多的情况是通过匿名内部类或者 Lambda 表达式的方式实现。
如果业务比较逻辑固定,则可以通过实现 Comparable 接口的方式实现,如果业务比较逻辑不是固定的,则使用 Comparator 接口。
Collections 工具类
Java 提供了一个操作 Set、List 和 Map 等集合的工具类 Collections,该工具类提供了大量方法对集合进行排序、查询和修改等操作,还提供了将集合对象置为不可变、对集合对象实现同步控制等方法。这个类不需要创建对象,内部提供的都是静态方法(通过类名直接调用)。
常用方法
方法 | 解释 |
---|---|
shuffle(List list) | 打乱指定 List 集合元素的顺序 |
reverse(List list) | 反转指定 List 集合 |
sort(List list) | 对指定 List 集合升序排序 |
sort(List list, Comparator c) | 对指定 List 集合,按比较器指定循序排序 |
swap(List list, int i, int j) | 对指定 List 集合,交换指定位置 i, j 处元素 |
rotate(List list, int distance) | 进行循环移动,正顺时针,负逆时针 |
binarySearch(List list, T key) | 二分搜索指定元素 |
max(Collection coll) | 查找指定集合中的最大值 |
max(Collection coll, Comparator comp) | 按指定比较逻辑,查找集合最大值 |
min(Collection coll) | 查找指定集合中的最小值 |
min(Collection coll, Comparator comp) | 按指定比较逻辑,查找集合最小值 |
fill(List list, T obj) | 使用指定元素替换指定列表中的所有元素 |
frequency(Collection c, Object o) | 返回指定 collection 中等于指定对象的出现次数 |
replaceAll(List list, T oldVal, T newVal) | 使用一个新值 newVal 替换 List 对象的所有旧值 oldVal |
synchronizedCollection(Collection c) | 返回指定 collection 支持的同步(线程安全的)collection。 |
synchronizedList(List list) | 返回指定列表支持的同步(线程安全的)列表 |
synchronizedMap(Map m) | 返回由指定映射支持的同步(线程安全的)映射 |
synchronizedSet(Set s) | 返回指定 set 支持的同步(线程安全的)set |